feat(agentapi): Add support for running under a subdomain (#297)
Updates https://github.com/coder/coder/issues/18779 A separate PR will update dependant modules `goose` and `aider`. ## Description * Adds `subdomain` argument to `agentapi` module * Updates `agentapi` module to set `AGENTAPI_CHAT_BASE_PATH` to an autogenerated path if `var.subdomain = false` * Updates default `agentapi` version to `v0.3.3` to support running without subdomain ## Type of Change - [ ] New module - [ ] Bug fix - [x] Feature/enhancement - [ ] Documentation - [ ] Other ## Testing & Validation - [X] Tests pass (`bun test`) - [X] Code formatted (`bun run fmt`) - [x] Changes tested locally ## Related Issues - https://github.com/coder/coder/issues/18779
This commit is contained in:
parent
0ccee61192
commit
57c900b2c9
@ -16,7 +16,7 @@ The AgentAPI module is a building block for modules that need to run an AgentAPI
|
|||||||
```tf
|
```tf
|
||||||
module "agentapi" {
|
module "agentapi" {
|
||||||
source = "registry.coder.com/coder/agentapi/coder"
|
source = "registry.coder.com/coder/agentapi/coder"
|
||||||
version = "1.0.2"
|
version = "1.1.0"
|
||||||
|
|
||||||
agent_id = var.agent_id
|
agent_id = var.agent_id
|
||||||
web_app_slug = local.app_slug
|
web_app_slug = local.app_slug
|
||||||
|
|||||||
@ -148,4 +148,92 @@ describe("agentapi", async () => {
|
|||||||
]);
|
]);
|
||||||
expect(respAgentAPI.exitCode).toBe(0);
|
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);
|
||||||
|
|
||||||
|
await expectAgentAPIStarted(id);
|
||||||
|
const agentApiStartLog = await readFileContainer(
|
||||||
|
id,
|
||||||
|
"/home/coder/test-agentapi-start.log",
|
||||||
|
);
|
||||||
|
expect(agentApiStartLog).toContain("Using AGENTAPI_CHAT_BASE_PATH: /@default/default.foo/apps/agentapi-web/chat");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("validate-agentapi-version", async () => {
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
moduleVariables: {
|
||||||
|
agentapi_version: "v0.3.2",
|
||||||
|
},
|
||||||
|
shouldThrow: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
moduleVariables: {
|
||||||
|
agentapi_version: "v0.3.3",
|
||||||
|
},
|
||||||
|
shouldThrow: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
moduleVariables: {
|
||||||
|
agentapi_version: "v0.0.1",
|
||||||
|
agentapi_subdomain: "false",
|
||||||
|
},
|
||||||
|
shouldThrow: "Running with subdomain = false is only supported by agentapi >= v0.3.3.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
moduleVariables: {
|
||||||
|
agentapi_version: "v0.3.2",
|
||||||
|
agentapi_subdomain: "false",
|
||||||
|
},
|
||||||
|
shouldThrow: "Running with subdomain = false is only supported by agentapi >= v0.3.3.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
moduleVariables: {
|
||||||
|
agentapi_version: "v0.3.3",
|
||||||
|
agentapi_subdomain: "false",
|
||||||
|
},
|
||||||
|
shouldThrow: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
moduleVariables: {
|
||||||
|
agentapi_version: "v0.3.999",
|
||||||
|
agentapi_subdomain: "false",
|
||||||
|
},
|
||||||
|
shouldThrow: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
moduleVariables: {
|
||||||
|
agentapi_version: "v0.999.999",
|
||||||
|
agentapi_subdomain: "false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
moduleVariables: {
|
||||||
|
agentapi_version: "v999.999.999",
|
||||||
|
agentapi_subdomain: "false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -117,7 +117,7 @@ variable "install_agentapi" {
|
|||||||
variable "agentapi_version" {
|
variable "agentapi_version" {
|
||||||
type = string
|
type = string
|
||||||
description = "The version of AgentAPI to install."
|
description = "The version of AgentAPI to install."
|
||||||
default = "v0.2.3"
|
default = "v0.3.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "agentapi_port" {
|
variable "agentapi_port" {
|
||||||
@ -126,6 +126,31 @@ variable "agentapi_port" {
|
|||||||
default = 3284
|
default = 3284
|
||||||
}
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
# agentapi_subdomain_false_min_version_expr matches a semantic version >= v0.3.3.
|
||||||
|
# Initial support was added in v0.3.1 but configuration via environment variable
|
||||||
|
# was added in v0.3.3.
|
||||||
|
# This is unfortunately a regex because there is no builtin way to compare semantic versions in Terraform.
|
||||||
|
# See: https://regex101.com/r/oHPyRa/1
|
||||||
|
agentapi_subdomain_false_min_version_expr = "^v(0\\.(3\\.[3-9]|3.[1-9]\\d+|[4-9]\\.\\d+|[1-9]\\d+\\.\\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.3."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
variable "module_dir_name" {
|
variable "module_dir_name" {
|
||||||
type = string
|
type = string
|
||||||
description = "Name of the subdirectory in the home directory for module files."
|
description = "Name of the subdirectory in the home directory for module files."
|
||||||
@ -140,7 +165,14 @@ locals {
|
|||||||
encoded_post_install_script = var.post_install_script != null ? base64encode(var.post_install_script) : ""
|
encoded_post_install_script = var.post_install_script != null ? base64encode(var.post_install_script) : ""
|
||||||
agentapi_start_script_b64 = base64encode(var.start_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"))
|
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:
|
||||||
|
// - Initial support for --chat-base-path was added in v0.3.1 but configuration
|
||||||
|
// via environment variable AGENTAPI_CHAT_BASE_PATH was added in v0.3.3.
|
||||||
|
// - 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" {
|
resource "coder_script" "agentapi" {
|
||||||
@ -165,6 +197,7 @@ resource "coder_script" "agentapi" {
|
|||||||
ARG_WAIT_FOR_START_SCRIPT="$(echo -n '${local.agentapi_wait_for_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_POST_INSTALL_SCRIPT="$(echo -n '${local.encoded_post_install_script}' | base64 -d)" \
|
||||||
ARG_AGENTAPI_PORT='${var.agentapi_port}' \
|
ARG_AGENTAPI_PORT='${var.agentapi_port}' \
|
||||||
|
ARG_AGENTAPI_CHAT_BASE_PATH='${local.agentapi_chat_base_path}' \
|
||||||
/tmp/main.sh
|
/tmp/main.sh
|
||||||
EOT
|
EOT
|
||||||
run_on_start = true
|
run_on_start = true
|
||||||
@ -178,7 +211,7 @@ resource "coder_app" "agentapi_web" {
|
|||||||
icon = var.web_app_icon
|
icon = var.web_app_icon
|
||||||
order = var.web_app_order
|
order = var.web_app_order
|
||||||
group = var.web_app_group
|
group = var.web_app_group
|
||||||
subdomain = true
|
subdomain = var.agentapi_subdomain
|
||||||
healthcheck {
|
healthcheck {
|
||||||
url = "http://localhost:${var.agentapi_port}/status"
|
url = "http://localhost:${var.agentapi_port}/status"
|
||||||
interval = 3
|
interval = 3
|
||||||
|
|||||||
@ -13,6 +13,7 @@ START_SCRIPT="$ARG_START_SCRIPT"
|
|||||||
WAIT_FOR_START_SCRIPT="$ARG_WAIT_FOR_START_SCRIPT"
|
WAIT_FOR_START_SCRIPT="$ARG_WAIT_FOR_START_SCRIPT"
|
||||||
POST_INSTALL_SCRIPT="$ARG_POST_INSTALL_SCRIPT"
|
POST_INSTALL_SCRIPT="$ARG_POST_INSTALL_SCRIPT"
|
||||||
AGENTAPI_PORT="$ARG_AGENTAPI_PORT"
|
AGENTAPI_PORT="$ARG_AGENTAPI_PORT"
|
||||||
|
AGENTAPI_CHAT_BASE_PATH="${ARG_AGENTAPI_CHAT_BASE_PATH:-}"
|
||||||
set +o nounset
|
set +o nounset
|
||||||
|
|
||||||
command_exists() {
|
command_exists() {
|
||||||
@ -92,5 +93,7 @@ export LANG=en_US.UTF-8
|
|||||||
export LC_ALL=en_US.UTF-8
|
export LC_ALL=en_US.UTF-8
|
||||||
|
|
||||||
cd "${WORKDIR}"
|
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" &
|
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}"
|
"$module_path/scripts/agentapi-wait-for-start.sh" "${AGENTAPI_PORT}"
|
||||||
|
|||||||
@ -24,7 +24,16 @@ export const setupContainer = async ({
|
|||||||
});
|
});
|
||||||
const coderScript = findResourceInstance(state, "coder_script");
|
const coderScript = findResourceInstance(state, "coder_script");
|
||||||
const id = await runContainer(image ?? "codercom/enterprise-node:latest");
|
const id = await runContainer(image ?? "codercom/enterprise-node:latest");
|
||||||
return { id, coderScript, cleanup: () => removeContainer(id) };
|
return {
|
||||||
|
id, coderScript, cleanup: async () => {
|
||||||
|
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 {
|
||||||
|
await removeContainer(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadTestFile = async (
|
export const loadTestFile = async (
|
||||||
|
|||||||
@ -11,6 +11,12 @@ log_file_path="$module_path/agentapi.log"
|
|||||||
echo "using prompt: $use_prompt" >>/home/coder/test-agentapi-start.log
|
echo "using prompt: $use_prompt" >>/home/coder/test-agentapi-start.log
|
||||||
echo "using port: $port" >>/home/coder/test-agentapi-start.log
|
echo "using port: $port" >>/home/coder/test-agentapi-start.log
|
||||||
|
|
||||||
|
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
|
||||||
|
export AGENTAPI_CHAT_BASE_PATH
|
||||||
|
fi
|
||||||
|
|
||||||
agentapi server --port "$port" --term-width 67 --term-height 1190 -- \
|
agentapi server --port "$port" --term-width 67 --term-height 1190 -- \
|
||||||
bash -c aiagent \
|
bash -c aiagent \
|
||||||
>"$log_file_path" 2>&1
|
>"$log_file_path" 2>&1
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user