Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions registry/coder/modules/agentapi/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,71 @@ describe("agentapi", async () => {
]);
expect(respAgentAPI.exitCode).toBe(0);
});

test("no-subdomain-base-path", async () => {
const { id } = await setup({
moduleVariables: {
agentapi_subdomain: "false",
},
});

const respModuleScript = await execModuleScript(id);
expect(respModuleScript.exitCode).toBe(0);

const agentApiProcessOutput = await execContainer(id, [
"bash",
"-c",
"ps -eo command | grep [a]gentapi",
]);
expect(agentApiProcessOutput.exitCode).toBe(0);
expect(agentApiProcessOutput.stdout).toContain(
`--chat-base-path /@default/default.foo/apps/agentapi-web/chat`,
);
});

test("validate-agentapi-version", async () => {
const cases = [
{
moduleVariables: {
agentapi_version: "v0.3.1",
agentapi_subdomain: "false",
},
shouldThrow: "",
},
{
moduleVariables: {
agentapi_version: "v0.3.1",
agentapi_subdomain: "true",
},
shouldThrow: "",
},
{
moduleVariables: {
agentapi_version: "v0.3.0",
agentapi_subdomain: "false",
},
shouldThrow: "Running with subdomain = false is only supported by agentapi >= v0.3.1.",
},
{
moduleVariables: {
agentapi_version: "v0.3.0",
agentapi_subdomain: "true",
},
shouldThrow: "",
},
{
moduleVariables: {
agentapi_version: "arbitrary-string-bypasses-validation",
},
shouldThrow: "",
}
];
for (const { moduleVariables, shouldThrow } of cases) {
if (shouldThrow) {
expect(setup({ moduleVariables: moduleVariables as Record<string, string> })).rejects.toThrow(shouldThrow);
} else {
expect(setup({ moduleVariables: moduleVariables as Record<string, string> })).resolves.toBeDefined();
}
}
});
});
34 changes: 31 additions & 3 deletions registry/coder/modules/agentapi/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ variable "install_agentapi" {
variable "agentapi_version" {
type = string
description = "The version of AgentAPI to install."
default = "v0.2.3"
default = "v0.3.1"
}

variable "agentapi_port" {
Expand All @@ -126,6 +126,27 @@ variable "agentapi_port" {
default = 3284
}

locals {
# agentapi_subdomain_false_min_version_expr matches a semantic version >= v0.3.1.
agentapi_subdomain_false_min_version_expr = "^v(0\\.(3\\.[1-9]+|[4-9]+\\.\\d+)|[1-9]\\d*\\.\\d+\\.\\d+)$"
}

variable "agentapi_subdomain" {
type = bool
description = "Whether to use a subdomain for AgentAPI."
default = true
validation {
condition = var.agentapi_subdomain || (
# If version doesn't look like a valid semantic version, just allow it.
# Note that boolean operators do not short-circuit in Terraform.
can(regex("^v\\d+\\.\\d+\\.\\d+$", var.agentapi_version)) ?
can(regex(local.agentapi_subdomain_false_min_version_expr, var.agentapi_version)) :
true
)
error_message = "Running with subdomain = false is only supported by agentapi >= v0.3.1."
}
}

variable "module_dir_name" {
type = string
description = "Name of the subdirectory in the home directory for module files."
Expand All @@ -140,7 +161,13 @@ locals {
encoded_post_install_script = var.post_install_script != null ? base64encode(var.post_install_script) : ""
agentapi_start_script_b64 = base64encode(var.start_script)
agentapi_wait_for_start_script_b64 = base64encode(file("${path.module}/scripts/agentapi-wait-for-start.sh"))
main_script = file("${path.module}/scripts/main.sh")
// Chat base path is only set if not using a subdomain.
// NOTE:
// - This requires agentapi version >= v0.3.1.
// - As CODER_WORKSPACE_AGENT_NAME is a recent addition we use agent ID
// for backward compatibility.
agentapi_chat_base_path = var.agentapi_subdomain ? "" : "/@${data.coder_workspace_owner.me.name}/${data.coder_workspace.me.name}.${var.agent_id}/apps/${var.web_app_slug}/chat"
main_script = file("${path.module}/scripts/main.sh")
}

resource "coder_script" "agentapi" {
Expand All @@ -165,6 +192,7 @@ resource "coder_script" "agentapi" {
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_CHAT_BASE_PATH='${local.agentapi_chat_base_path}' \
/tmp/main.sh
EOT
run_on_start = true
Expand All @@ -178,7 +206,7 @@ resource "coder_app" "agentapi_web" {
icon = var.web_app_icon
order = var.web_app_order
group = var.web_app_group
subdomain = true
subdomain = var.agentapi_subdomain
healthcheck {
url = "http://localhost:${var.agentapi_port}/status"
interval = 3
Expand Down
3 changes: 3 additions & 0 deletions registry/coder/modules/agentapi/scripts/main.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ 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_CHAT_BASE_PATH="${ARG_AGENTAPI_CHAT_BASE_PATH:-}"
set +o nounset

command_exists() {
Expand Down Expand Up @@ -92,5 +93,7 @@ export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8

cd "${WORKDIR}"

export AGENTAPI_CHAT_BASE_PATH="${AGENTAPI_CHAT_BASE_PATH:-}"
nohup "$module_path/scripts/agentapi-start.sh" true "${AGENTAPI_PORT}" &>"$module_path/agentapi-start.log" &
"$module_path/scripts/agentapi-wait-for-start.sh" "${AGENTAPI_PORT}"
12 changes: 11 additions & 1 deletion registry/coder/modules/agentapi/test-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,17 @@ export const setupContainer = async ({
});
const coderScript = findResourceInstance(state, "coder_script");
const id = await runContainer(image ?? "codercom/enterprise-node:latest");
return { id, coderScript, cleanup: () => removeContainer(id) };
return {
id, coderScript, cleanup: () => {
if (process.env["DEBUG"] === "true" || process.env["DEBUG"] === "1" || process.env["DEBUG"] === "yes") {
console.log(`Not removing container ${id} in debug mode`);
console.log(`Run "docker rm -f ${id}" to remove it manually.`);
} else {
removeContainer(id);
}
return Promise.resolve();
}
};
};

export const loadTestFile = async (
Expand Down
15 changes: 12 additions & 3 deletions registry/coder/modules/agentapi/testdata/agentapi-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ log_file_path="$module_path/agentapi.log"
echo "using prompt: $use_prompt" >>/home/coder/test-agentapi-start.log
echo "using port: $port" >>/home/coder/test-agentapi-start.log

agentapi server --port "$port" --term-width 67 --term-height 1190 -- \
bash -c aiagent \
>"$log_file_path" 2>&1
AGENTAPI_CHAT_BASE_PATH="${AGENTAPI_CHAT_BASE_PATH:-}"
if [ -n "$AGENTAPI_CHAT_BASE_PATH" ]; then
echo "Using AGENTAPI_CHAT_BASE_PATH: $AGENTAPI_CHAT_BASE_PATH" >>/home/coder/test-agentapi-start.log
fi

cmd=(agentapi server)
if [ -n "$AGENTAPI_CHAT_BASE_PATH" ]; then
cmd+=(--chat-base-path "$AGENTAPI_CHAT_BASE_PATH")
fi
cmd+=(--port "$port" --term-width 67 --term-height 1190 -- bash -c aiagent)

"${cmd[@]}" >"$log_file_path" 2>&1
Loading