Compare commits

..

1 Commits

Author SHA1 Message Date
35C4n0r 7b84d916e1 feat: add opencode module (#515)
## Description

This PR adds the opencode module to the registry.

## Type of Change

- [x] New module
- [ ] New template
- [ ] Bug fix
- [ ] Feature/enhancement
- [ ] Documentation
- [ ] Other

## Module Information

<!-- Delete this section if not applicable -->

**Path:** `registry/coder-labs/modules/opencode`  
**New version:** `v0.1.0`  
**Breaking change:** [ ] Yes [x] No

## Testing & Validation

- [x] Tests pass (`bun test`)
- [x] Code formatted (`bun fmt`)
- [x] Changes tested locally

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-11-26 10:31:58 -06:00
8 changed files with 1275 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
<svg width='240' height='300' viewBox='0 0 240 300' fill='none' xmlns='http://www.w3.org/2000/svg'><g clip-path='url(#clip0_1401_86283)'><mask id='mask0_1401_86283' style='mask-type:luminance' maskUnits='userSpaceOnUse' x='0' y='0' width='240' height='300'><path d='M240 0H0V300H240V0Z' fill='white'/></mask><g mask='url(#mask0_1401_86283)'><path d='M180 240H60V120H180V240Z' fill='#4B4646'/><path d='M180 60H60V240H180V60ZM240 300H0V0H240V300Z' fill='#F1ECEC'/></g></g><defs><clipPath id='clip0_1401_86283'><rect width='240' height='300' fill='white'/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 577 B

@@ -0,0 +1,108 @@
---
display_name: OpenCode
icon: ../../../../.icons/opencode.svg
description: Run OpenCode AI coding assistant for AI-powered terminal assistance
verified: false
tags: [agent, opencode, ai, tasks]
---
# OpenCode
Run [OpenCode](https://opencode.ai) AI coding assistant in your workspace for intelligent code generation, analysis, and development assistance. This module integrates with [AgentAPI](https://github.com/coder/agentapi) for seamless task reporting in the Coder UI.
```tf
module "opencode" {
source = "registry.coder.com/coder-labs/opencode/coder"
version = "0.1.0"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
}
```
## Prerequisites
- **Authentication credentials** - OpenCode auth.json file is required for non-interactive authentication, you can find this file on your system: `$HOME/.local/share/opencode/auth.json`
## Examples
### Basic Usage with Tasks
```tf
resource "coder_ai_task" "task" {
app_id = module.opencode.task_app_id
}
module "opencode" {
source = "registry.coder.com/coder-labs/opencode/coder"
version = "0.1.0"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
ai_prompt = coder_ai_task.task.prompt
auth_json = <<-EOT
{
"google": {
"type": "api",
"key": "gem-xxx-xxxx"
},
"anthropic": {
"type": "api",
"key": "sk-ant-api03-xxx-xxxxxxx"
}
}
EOT
config_json = jsonencode({
"$schema" = "https://opencode.ai/config.json"
mcp = {
filesystem = {
command = ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/home/coder/projects"]
enabled = true
type = "local"
environment = {
SOME_VARIABLE_X = "value"
}
}
playwright = {
command = ["npx", "-y", "@playwright/mcp@latest", "--headless", "--isolated"]
enabled = true
type = "local"
}
}
model = "anthropic/claude-sonnet-4-20250514"
})
pre_install_script = <<-EOT
#!/bin/bash
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
EOT
}
```
### Standalone CLI Mode
Run OpenCode as a command-line tool without web interface or task reporting:
```tf
module "opencode" {
source = "registry.coder.com/coder-labs/opencode/coder"
version = "0.1.0"
agent_id = coder_agent.example.id
workdir = "/home/coder"
report_tasks = false
cli_app = true
}
```
## Troubleshooting
If you encounter any issues, check the log files in the `~/.opencode-module` directory within your workspace for detailed information.
## References
- [Opencode JSON Config](https://opencode.ai/docs/config/)
- [OpenCode Documentation](https://opencode.ai/docs)
- [AgentAPI Documentation](https://github.com/coder/agentapi)
- [Coder AI Agents Guide](https://coder.com/docs/tutorials/ai-agents)
@@ -0,0 +1,362 @@
import {
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";
import dedent from "dedent";
let cleanupFunctions: (() => Promise<void>)[] = [];
const registerCleanup = (cleanup: () => Promise<void>) => {
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);
}
}
});
interface SetupProps {
skipAgentAPIMock?: boolean;
skipOpencodeMock?: boolean;
moduleVariables?: Record<string, string>;
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_opencode: props?.skipOpencodeMock ? "true" : "false",
install_agentapi: props?.skipAgentAPIMock ? "true" : "false",
workdir: projectDir,
...props?.moduleVariables,
},
registerCleanup,
projectDir,
skipAgentAPIMock: props?.skipAgentAPIMock,
agentapiMockScript: props?.agentapiMockScript,
});
if (!props?.skipOpencodeMock) {
await writeExecutable({
containerId: id,
filePath: "/usr/bin/opencode",
content: await loadTestFile(import.meta.dir, "opencode-mock.sh"),
});
}
return { id };
};
setDefaultTimeout(60 * 1000);
describe("opencode", async () => {
beforeAll(async () => {
await runTerraformInit(import.meta.dir);
});
test("happy-path", async () => {
const { id } = await setup();
await execModuleScript(id);
await expectAgentAPIStarted(id);
});
test("install-opencode-version", async () => {
const version_to_install = "0.1.0";
const { id } = await setup({
skipOpencodeMock: true,
moduleVariables: {
install_opencode: "true",
opencode_version: version_to_install,
pre_install_script: dedent`
#!/usr/bin/env bash
set -euo pipefail
# Mock the opencode install for testing
mkdir -p /home/coder/.opencode/bin
echo '#!/bin/bash\necho "opencode mock version ${version_to_install}"' > /home/coder/.opencode/bin/opencode
chmod +x /home/coder/.opencode/bin/opencode
`,
},
});
await execModuleScript(id);
const resp = await execContainer(id, [
"bash",
"-c",
`cat /home/coder/.opencode-module/install.log`,
]);
expect(resp.stdout).toContain(version_to_install);
});
test("check-latest-opencode-version-works", async () => {
const { id } = await setup({
skipOpencodeMock: true,
skipAgentAPIMock: true,
moduleVariables: {
install_opencode: "true",
pre_install_script: dedent`
#!/usr/bin/env bash
set -euo pipefail
# Mock the opencode install for testing
mkdir -p /home/coder/.opencode/bin
echo '#!/bin/bash\necho "opencode mock latest version"' > /home/coder/.opencode/bin/opencode
chmod +x /home/coder/.opencode/bin/opencode
`,
},
});
await execModuleScript(id);
await expectAgentAPIStarted(id);
});
test("opencode-auth-json", async () => {
const authJson = JSON.stringify({
token: "test-auth-token-123",
user: "test-user",
});
const { id } = await setup({
moduleVariables: {
auth_json: authJson,
},
});
await execModuleScript(id);
const authFile = await readFileContainer(
id,
"/home/coder/.local/share/opencode/auth.json",
);
expect(authFile).toContain("test-auth-token-123");
expect(authFile).toContain("test-user");
});
test("opencode-config-json", async () => {
const configJson = JSON.stringify({
$schema: "https://opencode.ai/config.json",
mcp: {
test: {
command: ["test-cmd"],
type: "local",
},
},
model: "anthropic/claude-sonnet-4-20250514",
});
const { id } = await setup({
moduleVariables: {
config_json: configJson,
},
});
await execModuleScript(id);
const configFile = await readFileContainer(
id,
"/home/coder/.config/opencode/opencode.json",
);
expect(configFile).toContain("test-cmd");
expect(configFile).toContain("anthropic/claude-sonnet-4-20250514");
});
test("opencode-ai-prompt", async () => {
const prompt = "This is a task prompt for OpenCode.";
const { id } = await setup({
moduleVariables: {
ai_prompt: prompt,
},
});
await execModuleScript(id);
const resp = await execContainer(id, [
"bash",
"-c",
`cat /home/coder/.opencode-module/agentapi-start.log`,
]);
expect(resp.stdout).toContain(prompt);
});
test("opencode-continue-flag", async () => {
const { id } = await setup({
moduleVariables: {
continue: "true",
ai_prompt: "test prompt",
},
});
await execModuleScript(id);
const startLog = await execContainer(id, [
"bash",
"-c",
"cat /home/coder/.opencode-module/agentapi-start.log",
]);
expect(startLog.stdout).toContain("--continue");
});
test("opencode-continue-with-session-id", async () => {
const sessionId = "session-123";
const { id } = await setup({
moduleVariables: {
continue: "true",
session_id: sessionId,
ai_prompt: "test prompt",
},
});
await execModuleScript(id);
const startLog = await execContainer(id, [
"bash",
"-c",
"cat /home/coder/.opencode-module/agentapi-start.log",
]);
expect(startLog.stdout).toContain("--continue");
expect(startLog.stdout).toContain(`--session ${sessionId}`);
});
test("opencode-session-id", async () => {
const sessionId = "session-123";
const { id } = await setup({
moduleVariables: {
session_id: sessionId,
ai_prompt: "test prompt",
},
});
await execModuleScript(id);
const startLog = await execContainer(id, [
"bash",
"-c",
"cat /home/coder/.opencode-module/agentapi-start.log",
]);
expect(startLog.stdout).toContain(`--session ${sessionId}`);
});
test("opencode-report-tasks-enabled", async () => {
const { id } = await setup({
moduleVariables: {
report_tasks: "true",
ai_prompt: "test prompt",
},
});
await execModuleScript(id);
const startLog = await execContainer(id, [
"bash",
"-c",
"cat /home/coder/.opencode-module/agentapi-start.log",
]);
expect(startLog.stdout).toContain(
"report your progress using coder_report_task",
);
});
test("opencode-report-tasks-disabled", async () => {
const { id } = await setup({
moduleVariables: {
report_tasks: "false",
ai_prompt: "test prompt",
},
});
await execModuleScript(id);
const startLog = await execContainer(id, [
"bash",
"-c",
"cat /home/coder/.opencode-module/agentapi-start.log",
]);
expect(startLog.stdout).not.toContain(
"report your progress using coder_report_task",
);
});
test("cli-app-creation", async () => {
const { id } = await setup({
moduleVariables: {
cli_app: "true",
cli_app_display_name: "OpenCode Terminal",
},
});
await execModuleScript(id);
// CLI app creation is handled by the agentapi module
// We just verify the setup completed successfully
await expectAgentAPIStarted(id);
});
test("pre-post-install-scripts", async () => {
const { id } = await setup({
moduleVariables: {
pre_install_script: "#!/bin/bash\necho 'opencode-pre-install-script'",
post_install_script: "#!/bin/bash\necho 'opencode-post-install-script'",
},
});
await execModuleScript(id);
const preInstallLog = await readFileContainer(
id,
"/home/coder/.opencode-module/pre_install.log",
);
expect(preInstallLog).toContain("opencode-pre-install-script");
const postInstallLog = await readFileContainer(
id,
"/home/coder/.opencode-module/post_install.log",
);
expect(postInstallLog).toContain("opencode-post-install-script");
});
test("workdir-variable", async () => {
const workdir = "/home/coder/opencode-test-folder";
const { id } = await setup({
skipOpencodeMock: false,
moduleVariables: {
workdir,
},
});
await execModuleScript(id);
const resp = await readFileContainer(
id,
"/home/coder/.opencode-module/agentapi-start.log",
);
expect(resp).toContain(workdir);
});
test("subdomain-enabled", async () => {
const { id } = await setup({
moduleVariables: {
subdomain: "true",
},
});
await execModuleScript(id);
// Subdomain configuration is handled by the agentapi module
// We just verify the setup completed successfully
await expectAgentAPIStarted(id);
});
test("custom-display-names", async () => {
const { id } = await setup({
moduleVariables: {
web_app_display_name: "Custom OpenCode Web",
cli_app_display_name: "Custom OpenCode CLI",
cli_app: "true",
},
});
await execModuleScript(id);
// Display names are handled by the agentapi module
// We just verify the setup completed successfully
await expectAgentAPIStarted(id);
});
});
@@ -0,0 +1,203 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.12"
}
}
}
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
variable "order" {
type = number
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
default = null
}
variable "group" {
type = string
description = "The name of a group that this app belongs to."
default = null
}
variable "icon" {
type = string
description = "The icon to use for the app."
default = "/icon/opencode.svg"
}
variable "workdir" {
type = string
description = "The folder to run OpenCode in."
}
variable "report_tasks" {
type = bool
description = "Whether to enable task reporting to Coder UI via AgentAPI"
default = true
}
variable "cli_app" {
type = bool
description = "Whether to create a CLI app for OpenCode"
default = false
}
variable "web_app_display_name" {
type = string
description = "Display name for the web app"
default = "OpenCode"
}
variable "cli_app_display_name" {
type = string
description = "Display name for the CLI app"
default = "OpenCode CLI"
}
variable "pre_install_script" {
type = string
description = "Custom script to run before installing OpenCode."
default = null
}
variable "post_install_script" {
type = string
description = "Custom script to run after installing OpenCode."
default = null
}
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.11.2"
}
variable "ai_prompt" {
type = string
description = "Initial task prompt for OpenCode."
default = ""
}
variable "subdomain" {
type = bool
description = "Whether to use a subdomain for AgentAPI."
default = false
}
variable "install_opencode" {
type = bool
description = "Whether to install OpenCode."
default = true
}
variable "opencode_version" {
type = string
description = "The version of OpenCode to install."
default = "latest"
}
variable "continue" {
type = bool
description = "continue the last session. Uses the --continue flag"
default = false
}
variable "session_id" {
type = string
description = "Session id to continue. Passed via --session"
default = ""
}
variable "auth_json" {
type = string
description = "Your auth.json from $HOME/.local/share/opencode/auth.json, Required for non-interactive authentication"
default = ""
}
variable "config_json" {
type = string
description = "OpenCode JSON config. https://opencode.ai/docs/config/"
default = ""
}
locals {
workdir = trimsuffix(var.workdir, "/")
app_slug = "opencode"
install_script = file("${path.module}/scripts/install.sh")
start_script = file("${path.module}/scripts/start.sh")
module_dir_name = ".opencode-module"
}
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "2.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 = var.web_app_display_name
cli_app = var.cli_app
cli_app_slug = var.cli_app ? "${local.app_slug}-cli" : null
cli_app_display_name = var.cli_app ? var.cli_app_display_name : null
agentapi_subdomain = var.subdomain
folder = local.workdir
module_dir_name = local.module_dir_name
install_agentapi = var.install_agentapi
agentapi_version = var.agentapi_version
pre_install_script = var.pre_install_script
post_install_script = var.post_install_script
start_script = <<-EOT
#!/bin/bash
set -o errexit
set -o pipefail
echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh
chmod +x /tmp/start.sh
ARG_WORKDIR='${local.workdir}' \
ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \
ARG_SESSION_ID='${var.session_id}' \
ARG_REPORT_TASKS='${var.report_tasks}' \
ARG_CONTINUE='${var.continue}' \
/tmp/start.sh
EOT
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_OPENCODE_VERSION='${var.opencode_version}' \
ARG_MCP_APP_STATUS_SLUG='${local.app_slug}' \
ARG_INSTALL_OPENCODE='${var.install_opencode}' \
ARG_REPORT_TASKS='${var.report_tasks}' \
ARG_WORKDIR='${local.workdir}' \
ARG_AUTH_JSON='${var.auth_json != null ? base64encode(replace(var.auth_json, "'", "'\\''")) : ""}' \
ARG_OPENCODE_CONFIG='${var.config_json != null ? base64encode(replace(var.config_json, "'", "'\\''")) : ""}' \
/tmp/install.sh
EOT
}
output "task_app_id" {
value = module.agentapi.task_app_id
}
@@ -0,0 +1,374 @@
run "defaults_are_correct" {
command = plan
variables {
agent_id = "test-agent"
workdir = "/home/coder/project"
}
assert {
condition = var.install_opencode == true
error_message = "OpenCode installation should be enabled by default"
}
assert {
condition = var.install_agentapi == true
error_message = "AgentAPI installation should be enabled by default"
}
assert {
condition = var.agentapi_version == "v0.11.2"
error_message = "Default AgentAPI version should be 'v0.11.2'"
}
assert {
condition = var.opencode_version == "latest"
error_message = "Default OpenCode version should be 'latest'"
}
assert {
condition = var.report_tasks == true
error_message = "Task reporting should be enabled by default"
}
assert {
condition = var.cli_app == false
error_message = "CLI app should be disabled by default"
}
assert {
condition = var.subdomain == false
error_message = "Subdomain should be disabled by default"
}
assert {
condition = var.web_app_display_name == "OpenCode"
error_message = "Default web app display name should be 'OpenCode'"
}
assert {
condition = var.cli_app_display_name == "OpenCode CLI"
error_message = "Default CLI app display name should be 'OpenCode CLI'"
}
assert {
condition = local.app_slug == "opencode"
error_message = "App slug should be 'opencode'"
}
assert {
condition = local.module_dir_name == ".opencode-module"
error_message = "Module dir name should be '.opencode-module'"
}
assert {
condition = local.workdir == "/home/coder/project"
error_message = "Workdir should be trimmed of trailing slash"
}
assert {
condition = var.continue == false
error_message = "Continue flag should be disabled by default"
}
}
run "workdir_trailing_slash_trimmed" {
command = plan
variables {
agent_id = "test-agent"
workdir = "/home/coder/project/"
}
assert {
condition = local.workdir == "/home/coder/project"
error_message = "Workdir should be trimmed of trailing slash"
}
}
run "opencode_version_configuration" {
command = plan
variables {
agent_id = "test-agent"
workdir = "/home/coder/project"
opencode_version = "v1.0.0"
}
assert {
condition = var.opencode_version == "v1.0.0"
error_message = "OpenCode version should be set correctly"
}
}
run "agentapi_version_configuration" {
command = plan
variables {
agent_id = "test-agent"
workdir = "/home/coder/project"
agentapi_version = "v0.9.0"
}
assert {
condition = var.agentapi_version == "v0.9.0"
error_message = "AgentAPI version should be set correctly"
}
}
run "cli_app_configuration" {
command = plan
variables {
agent_id = "test-agent"
workdir = "/home/coder/project"
cli_app = true
cli_app_display_name = "Custom OpenCode CLI"
}
assert {
condition = var.cli_app == true
error_message = "CLI app should be enabled when specified"
}
assert {
condition = var.cli_app_display_name == "Custom OpenCode CLI"
error_message = "Custom CLI app display name should be set"
}
}
run "web_app_configuration" {
command = plan
variables {
agent_id = "test-agent"
workdir = "/home/coder/project"
web_app_display_name = "Custom OpenCode Web"
order = 5
group = "AI Tools"
icon = "/custom/icon.svg"
}
assert {
condition = var.web_app_display_name == "Custom OpenCode Web"
error_message = "Custom web app display name should be set"
}
assert {
condition = var.order == 5
error_message = "Custom order should be set"
}
assert {
condition = var.group == "AI Tools"
error_message = "Custom group should be set"
}
assert {
condition = var.icon == "/custom/icon.svg"
error_message = "Custom icon should be set"
}
}
run "ai_configuration_variables" {
command = plan
variables {
agent_id = "test-agent"
workdir = "/home/coder/project"
ai_prompt = "This is a test prompt"
session_id = "session-123"
continue = true
}
assert {
condition = var.ai_prompt == "This is a test prompt"
error_message = "AI prompt should be set correctly"
}
assert {
condition = var.session_id == "session-123"
error_message = "Session ID should be set correctly"
}
assert {
condition = var.continue == true
error_message = "Continue flag should be set correctly"
}
}
run "auth_json_configuration" {
command = plan
variables {
agent_id = "test-agent"
workdir = "/home/coder/project"
auth_json = "{\"token\": \"test-token\", \"user\": \"test-user\"}"
}
assert {
condition = var.auth_json != ""
error_message = "Auth JSON should be set"
}
assert {
condition = can(jsondecode(var.auth_json))
error_message = "Auth JSON should be valid JSON"
}
}
run "config_json_configuration" {
command = plan
variables {
agent_id = "test-agent"
workdir = "/home/coder/project"
config_json = "{\"$schema\": \"https://opencode.ai/config.json\", \"mcp\": {\"test\": {\"command\": [\"test-cmd\"], \"type\": \"local\"}}, \"model\": \"anthropic/claude-sonnet-4-20250514\"}"
}
assert {
condition = var.config_json != ""
error_message = "OpenCode JSON configuration should be set"
}
assert {
condition = can(jsondecode(var.config_json))
error_message = "OpenCode JSON configuration should be valid JSON"
}
}
run "task_reporting_configuration" {
command = plan
variables {
agent_id = "test-agent"
workdir = "/home/coder/project"
report_tasks = false
}
assert {
condition = var.report_tasks == false
error_message = "Task reporting should be disabled when specified"
}
}
run "subdomain_configuration" {
command = plan
variables {
agent_id = "test-agent"
workdir = "/home/coder/project"
subdomain = true
}
assert {
condition = var.subdomain == true
error_message = "Subdomain should be enabled when specified"
}
}
run "install_flags_configuration" {
command = plan
variables {
agent_id = "test-agent"
workdir = "/home/coder/project"
install_opencode = false
install_agentapi = false
}
assert {
condition = var.install_opencode == false
error_message = "OpenCode installation should be disabled when specified"
}
assert {
condition = var.install_agentapi == false
error_message = "AgentAPI installation should be disabled when specified"
}
}
run "custom_scripts_configuration" {
command = plan
variables {
agent_id = "test-agent"
workdir = "/home/coder/project"
pre_install_script = "#!/bin/bash\necho 'pre-install'"
post_install_script = "#!/bin/bash\necho 'post-install'"
}
assert {
condition = var.pre_install_script != null
error_message = "Pre-install script should be set"
}
assert {
condition = var.post_install_script != null
error_message = "Post-install script should be set"
}
assert {
condition = can(regex("pre-install", var.pre_install_script))
error_message = "Pre-install script should contain expected content"
}
assert {
condition = can(regex("post-install", var.post_install_script))
error_message = "Post-install script should contain expected content"
}
}
run "empty_variables_handled_correctly" {
command = plan
variables {
agent_id = "test-agent"
workdir = "/home/coder/project"
ai_prompt = ""
session_id = ""
auth_json = ""
config_json = ""
continue = false
}
assert {
condition = var.ai_prompt == ""
error_message = "Empty AI prompt should be handled correctly"
}
assert {
condition = var.session_id == ""
error_message = "Empty session ID should be handled correctly"
}
assert {
condition = var.auth_json == ""
error_message = "Empty auth JSON should be handled correctly"
}
assert {
condition = var.config_json == ""
error_message = "Empty config JSON should be handled correctly"
}
assert {
condition = var.continue == false
error_message = "Continue flag default should be handled correctly"
}
}
run "continue_flag_configuration" {
command = plan
variables {
agent_id = "test-agent"
workdir = "/home/coder/project"
continue = true
}
assert {
condition = var.continue == true
error_message = "Continue flag should be enabled when specified"
}
}
+131
View File
@@ -0,0 +1,131 @@
#!/bin/bash
set -euo pipefail
command_exists() {
command -v "$1" > /dev/null 2>&1
}
ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"}
ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true}
ARG_MCP_APP_STATUS_SLUG=${ARG_MCP_APP_STATUS_SLUG:-}
ARG_OPENCODE_VERSION=${ARG_OPENCODE_VERSION:-latest}
ARG_INSTALL_OPENCODE=${ARG_INSTALL_OPENCODE:-true}
ARG_AUTH_JSON=$(echo -n "$ARG_AUTH_JSON" | base64 -d 2> /dev/null || echo "")
ARG_OPENCODE_CONFIG=$(echo -n "$ARG_OPENCODE_CONFIG" | base64 -d 2> /dev/null || echo "")
# Print all received environment variables
printf "=== INSTALL CONFIG ===\n"
printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR"
printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS"
printf "ARG_MCP_APP_STATUS_SLUG: %s\n" "$ARG_MCP_APP_STATUS_SLUG"
printf "ARG_OPENCODE_VERSION: %s\n" "$ARG_OPENCODE_VERSION"
printf "ARG_INSTALL_OPENCODE: %s\n" "$ARG_INSTALL_OPENCODE"
if [ -n "$ARG_AUTH_JSON" ]; then
printf "ARG_AUTH_JSON: [AUTH DATA RECEIVED]\n"
else
printf "ARG_AUTH_JSON: [NOT PROVIDED]\n"
fi
if [ -n "$ARG_OPENCODE_CONFIG" ]; then
printf "ARG_OPENCODE_CONFIG: [RECEIVED]\n"
else
printf "ARG_OPENCODE_CONFIG: [NOT PROVIDED]\n"
fi
printf "==================================\n"
install_opencode() {
if [ "$ARG_INSTALL_OPENCODE" = "true" ]; then
if ! command_exists opencode; then
echo "Installing OpenCode (version: ${ARG_OPENCODE_VERSION})..."
if [ "$ARG_OPENCODE_VERSION" = "latest" ]; then
curl -fsSL https://opencode.ai/install | bash
else
VERSION=$ARG_OPENCODE_VERSION curl -fsSL https://opencode.ai/install | bash
fi
export PATH=/home/coder/.opencode/bin:$PATH
printf "Opencode location: %s\n" "$(which opencode)"
if ! command_exists opencode; then
echo "ERROR: Failed to install OpenCode"
exit 1
fi
echo "OpenCode installed successfully"
else
echo "OpenCode already installed"
fi
else
echo "OpenCode installation skipped (ARG_INSTALL_OPENCODE=false)"
fi
}
setup_opencode_config() {
local opencode_config_file="$HOME/.config/opencode/opencode.json"
local auth_json_file="$HOME/.local/share/opencode/auth.json"
mkdir -p "$(dirname "$auth_json_file")"
mkdir -p "$(dirname "$opencode_config_file")"
setup_opencode_auth "$auth_json_file"
if [ -n "$ARG_OPENCODE_CONFIG" ]; then
echo "Writing to the config file"
echo "$ARG_OPENCODE_CONFIG" > "$opencode_config_file"
fi
if [ "$ARG_REPORT_TASKS" = "true" ]; then
setup_coder_mcp_server "$opencode_config_file"
fi
echo "MCP configuration completed: $opencode_config_file"
}
setup_opencode_auth() {
local auth_json_file="$1"
if [ -n "$ARG_AUTH_JSON" ]; then
echo "$ARG_AUTH_JSON" > "$auth_json_file"
printf "added auth json to %s" "$auth_json_file"
else
printf "auth json not provided"
fi
}
setup_coder_mcp_server() {
local opencode_config_file="$1"
# Set environment variables based on task reporting setting
echo "Configuring OpenCode task reporting"
export CODER_MCP_APP_STATUS_SLUG="$ARG_MCP_APP_STATUS_SLUG"
export CODER_MCP_AI_AGENTAPI_URL="http://localhost:3284"
echo "Coder integration configured for task reporting"
# Add coder MCP server configuration to the JSON file
echo "Adding Coder MCP server configuration"
# Create the coder server configuration JSON
coder_config=$(
cat << EOF
{
"type": "local",
"command": ["coder", "exp", "mcp", "server"],
"enabled": true,
"environment": {
"CODER_MCP_APP_STATUS_SLUG": "${CODER_MCP_APP_STATUS_SLUG:-}",
"CODER_MCP_AI_AGENTAPI_URL": "${CODER_MCP_AI_AGENTAPI_URL:-}",
"CODER_AGENT_URL": "${CODER_AGENT_URL:-}",
"CODER_AGENT_TOKEN": "${CODER_AGENT_TOKEN:-}",
"CODER_MCP_ALLOWED_TOOLS": "coder_report_task"
}
}
EOF
)
temp_file=$(mktemp)
jq --argjson coder_config "$coder_config" '.mcp.coder = $coder_config' "$opencode_config_file" > "$temp_file"
mv "$temp_file" "$opencode_config_file"
echo "Coder MCP server configuration added"
}
install_opencode
setup_opencode_config
echo "OpenCode module setup completed."
+71
View File
@@ -0,0 +1,71 @@
#!/bin/bash
set -euo pipefail
export PATH=/home/coder/.opencode/bin:$PATH
command_exists() {
command -v "$1" > /dev/null 2>&1
}
ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"}
ARG_AI_PROMPT=$(echo -n "${ARG_AI_PROMPT:-}" | base64 -d 2> /dev/null || echo "")
ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true}
ARG_SESSION_ID=${ARG_SESSION_ID:-}
ARG_CONTINUE=${ARG_CONTINUE:-false}
# Print all received environment variables
printf "=== START CONFIG ===\n"
printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR"
printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS"
printf "ARG_CONTINUE: %s\n" "$ARG_CONTINUE"
printf "ARG_SESSION_ID: %s\n" "$ARG_SESSION_ID"
if [ -n "$ARG_AI_PROMPT" ]; then
printf "ARG_AI_PROMPT: [AI PROMPT RECEIVED]\n"
else
printf "ARG_AI_PROMPT: [NOT PROVIDED]\n"
fi
printf "==================================\n"
OPENCODE_ARGS=()
AGENTAPI_ARGS=()
validate_opencode_installation() {
if ! command_exists opencode; then
printf "ERROR: OpenCode not installed. Set install_opencode to true\n"
exit 1
fi
}
build_opencode_args() {
if [ -n "$ARG_SESSION_ID" ]; then
OPENCODE_ARGS+=(--session "$ARG_SESSION_ID")
fi
if [ "$ARG_CONTINUE" = "true" ]; then
OPENCODE_ARGS+=(--continue)
fi
if [ -n "$ARG_AI_PROMPT" ]; then
if [ "$ARG_REPORT_TASKS" = "true" ]; then
PROMPT="Every step of the way, report your progress using coder_report_task tool with proper summary and statuses. Your task at hand: $ARG_AI_PROMPT"
else
PROMPT="$ARG_AI_PROMPT"
fi
AGENTAPI_ARGS+=(-I "$PROMPT")
fi
}
start_agentapi() {
printf "Starting in directory: %s\n" "$ARG_WORKDIR"
cd "$ARG_WORKDIR"
build_opencode_args
printf "Running OpenCode with args: %s\n" "${OPENCODE_ARGS[*]}"
echo agentapi server "${AGENTAPI_ARGS[@]}" --type opencode --term-width 67 --term-height 1190 -- opencode "${OPENCODE_ARGS[@]}"
agentapi server "${AGENTAPI_ARGS[@]}" --type opencode --term-width 67 --term-height 1190 -- opencode "${OPENCODE_ARGS[@]}"
}
validate_opencode_installation
start_agentapi
@@ -0,0 +1,25 @@
#!/bin/bash
# Mock OpenCode CLI for testing purposes
# This script simulates the OpenCode command-line interface
echo "OpenCode Mock CLI - Test Version"
echo "Args received: $*"
# Simulate opencode behavior based on arguments
case "$1" in
--version | -v)
echo "opencode mock version 0.1.0-test"
;;
--help | -h)
echo "OpenCode Mock Help"
echo "Usage: opencode [options] [command]"
echo "This is a mock version for testing"
;;
*)
echo "Running OpenCode mock with arguments: $*"
echo "Mock execution completed successfully"
;;
esac
exit 0