diff --git a/registry/coder-labs/modules/copilot/README.md b/registry/coder-labs/modules/copilot/README.md index 76b8f025c..212cc40db 100644 --- a/registry/coder-labs/modules/copilot/README.md +++ b/registry/coder-labs/modules/copilot/README.md @@ -3,7 +3,7 @@ display_name: Copilot CLI description: GitHub Copilot CLI agent for AI-powered terminal assistance icon: ../../../../.icons/github.svg verified: false -tags: [agent, copilot, ai, github, tasks] +tags: [agent, copilot, ai, github, tasks, aibridge] --- # Copilot @@ -164,6 +164,39 @@ module "copilot" { } ``` +### Usage with AI Bridge Proxy + +[AI Bridge Proxy](https://coder.com/docs/ai-coder/ai-bridge/ai-bridge-proxy) routes Copilot traffic through [AI Bridge](https://coder.com/docs/ai-coder/ai-bridge) for centralized LLM management and governance. +The proxy environment variables are scoped to the Copilot process only and do not affect other workspace traffic. + +```tf +module "aibridge-proxy" { + source = "registry.coder.com/coder/aibridge-proxy/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + proxy_url = "https://aiproxy.example.com" +} + +module "copilot" { + source = "registry.coder.com/coder-labs/copilot/coder" + version = "0.4.0" + agent_id = coder_agent.main.id + workdir = "/home/coder/projects" + enable_aibridge_proxy = true + aibridge_proxy_auth_url = module.aibridge-proxy.proxy_auth_url + aibridge_proxy_cert_path = module.aibridge-proxy.cert_path +} +``` + +> [!NOTE] +> AI Bridge Proxy is a Premium Coder feature that requires [AI Bridge](https://coder.com/docs/ai-coder/ai-bridge) to be enabled. +> See the [AI Bridge Proxy setup guide](https://coder.com/docs/ai-coder/ai-bridge/ai-bridge-proxy/setup) for details on configuring the proxy on your Coder deployment. +> GitHub authentication is still required for Copilot as the proxy authenticates with AI Bridge using the Coder session token, but does not replace GitHub authentication. + +> [!IMPORTANT] +> When using AI Bridge Proxy, enable [startup coordination](https://coder.com/docs/admin/templates/startup-coordination) by setting `CODER_AGENT_SOCKET_SERVER_ENABLED=true` in the workspace container environment. +> This ensures the Copilot module waits for the `aibridge-proxy` module to complete before starting. Without it, the Copilot start script may fail if the certificate is not yet available. + ## Authentication The module supports multiple authentication methods (in priority order): diff --git a/registry/coder-labs/modules/copilot/copilot.tftest.hcl b/registry/coder-labs/modules/copilot/copilot.tftest.hcl index 185c019ba..8ef7148d4 100644 --- a/registry/coder-labs/modules/copilot/copilot.tftest.hcl +++ b/registry/coder-labs/modules/copilot/copilot.tftest.hcl @@ -234,3 +234,116 @@ run "app_slug_is_consistent" { error_message = "module_dir_name should be '.copilot-module'" } } + +run "aibridge_proxy_defaults" { + command = plan + + variables { + agent_id = "test-agent" + workdir = "/home/coder" + } + + assert { + condition = var.enable_aibridge_proxy == false + error_message = "enable_aibridge_proxy should default to false" + } + + assert { + condition = var.aibridge_proxy_auth_url == "" + error_message = "aibridge_proxy_auth_url should default to empty" + } + + assert { + condition = var.aibridge_proxy_cert_path == "" + error_message = "aibridge_proxy_cert_path should default to empty" + } +} + +run "aibridge_proxy_enabled" { + command = plan + + variables { + agent_id = "test-agent-aibridge-proxy" + workdir = "/home/coder" + enable_aibridge_proxy = true + aibridge_proxy_auth_url = "https://coder:mock-token@aiproxy.example.com" + aibridge_proxy_cert_path = "/tmp/aibridge-proxy/ca-cert.pem" + } + + assert { + condition = var.enable_aibridge_proxy == true + error_message = "AI Bridge Proxy should be enabled" + } + + assert { + condition = var.aibridge_proxy_auth_url == "https://coder:mock-token@aiproxy.example.com" + error_message = "AI Bridge Proxy auth URL should match the input variable" + } + + assert { + condition = var.aibridge_proxy_cert_path == "/tmp/aibridge-proxy/ca-cert.pem" + error_message = "AI Bridge Proxy cert path should match the input variable" + } +} + +run "aibridge_proxy_validation_missing_proxy_auth_url" { + command = plan + + variables { + agent_id = "test-agent-validation" + workdir = "/home/coder" + enable_aibridge_proxy = true + aibridge_proxy_auth_url = "" + aibridge_proxy_cert_path = "/tmp/aibridge-proxy/ca-cert.pem" + } + + expect_failures = [ + var.enable_aibridge_proxy, + ] +} + +run "aibridge_proxy_validation_missing_cert_path" { + command = plan + + variables { + agent_id = "test-agent-validation" + workdir = "/home/coder" + enable_aibridge_proxy = true + aibridge_proxy_auth_url = "https://coder:mock-token@aiproxy.example.com" + aibridge_proxy_cert_path = "" + } + + expect_failures = [ + var.enable_aibridge_proxy, + ] +} + +run "aibridge_proxy_with_copilot_config" { + command = plan + + variables { + agent_id = "test-agent" + workdir = "/home/coder" + copilot_model = "gpt-5" + github_token = "ghp_test123" + allow_all_tools = true + enable_aibridge_proxy = true + aibridge_proxy_auth_url = "https://coder:mock-token@aiproxy.example.com" + aibridge_proxy_cert_path = "/tmp/aibridge-proxy/ca-cert.pem" + } + + assert { + condition = var.enable_aibridge_proxy == true + error_message = "AI Bridge Proxy should be enabled" + } + + assert { + condition = length(resource.coder_env.github_token) == 1 + error_message = "github_token environment variable should be set alongside proxy" + } + + assert { + condition = length(resource.coder_env.copilot_model) == 1 + error_message = "copilot_model environment variable should be set alongside proxy" + } +} diff --git a/registry/coder-labs/modules/copilot/main.tf b/registry/coder-labs/modules/copilot/main.tf index 218184d75..78e836496 100644 --- a/registry/coder-labs/modules/copilot/main.tf +++ b/registry/coder-labs/modules/copilot/main.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.9" required_providers { coder = { source = "coder/coder" @@ -173,6 +173,35 @@ variable "post_install_script" { default = null } +variable "enable_aibridge_proxy" { + type = bool + description = "Route Copilot traffic through AI Bridge Proxy. See https://coder.com/docs/ai-coder/ai-bridge/ai-bridge-proxy" + default = false + + validation { + condition = !var.enable_aibridge_proxy || length(var.aibridge_proxy_auth_url) > 0 + error_message = "aibridge_proxy_auth_url is required when enable_aibridge_proxy is true." + } + + validation { + condition = !var.enable_aibridge_proxy || length(var.aibridge_proxy_cert_path) > 0 + error_message = "aibridge_proxy_cert_path is required when enable_aibridge_proxy is true." + } +} + +variable "aibridge_proxy_auth_url" { + type = string + description = "AI Bridge Proxy URL with authentication. Use the proxy_auth_url output from the aibridge-proxy module." + default = "" + sensitive = true +} + +variable "aibridge_proxy_cert_path" { + type = string + description = "Path to the AI Bridge Proxy CA certificate. Use the cert_path output from the aibridge-proxy module." + default = "" +} + data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} @@ -279,6 +308,9 @@ module "agentapi" { ARG_TRUSTED_DIRECTORIES='${join(",", var.trusted_directories)}' \ ARG_EXTERNAL_AUTH_ID='${var.external_auth_id}' \ ARG_RESUME_SESSION='${var.resume_session}' \ + ARG_ENABLE_AIBRIDGE_PROXY='${var.enable_aibridge_proxy}' \ + ARG_AIBRIDGE_PROXY_AUTH_URL='${var.aibridge_proxy_auth_url}' \ + ARG_AIBRIDGE_PROXY_CERT_PATH='${var.aibridge_proxy_cert_path}' \ /tmp/start.sh EOT diff --git a/registry/coder-labs/modules/copilot/scripts/start.sh b/registry/coder-labs/modules/copilot/scripts/start.sh index 98341e9bc..836603ac3 100644 --- a/registry/coder-labs/modules/copilot/scripts/start.sh +++ b/registry/coder-labs/modules/copilot/scripts/start.sh @@ -22,6 +22,9 @@ ARG_DENY_TOOLS=${ARG_DENY_TOOLS:-} ARG_TRUSTED_DIRECTORIES=${ARG_TRUSTED_DIRECTORIES:-} ARG_EXTERNAL_AUTH_ID=${ARG_EXTERNAL_AUTH_ID:-github} ARG_RESUME_SESSION=${ARG_RESUME_SESSION:-true} +ARG_ENABLE_AIBRIDGE_PROXY=${ARG_ENABLE_AIBRIDGE_PROXY:-false} +ARG_AIBRIDGE_PROXY_AUTH_URL=${ARG_AIBRIDGE_PROXY_AUTH_URL:-} +ARG_AIBRIDGE_PROXY_CERT_PATH=${ARG_AIBRIDGE_PROXY_CERT_PATH:-} validate_copilot_installation() { if ! command_exists copilot; then @@ -118,6 +121,48 @@ setup_github_authentication() { return 0 } +setup_aibridge_proxy() { + if [ "$ARG_ENABLE_AIBRIDGE_PROXY" != "true" ]; then + return 0 + fi + + echo "Setting up AI Bridge Proxy..." + + # Wait for the aibridge-proxy module to finish. + # Uses startup coordination to block until aibridge-proxy-setup signals completion. + if command -v coder > /dev/null 2>&1; then + coder exp sync want "copilot-aibridge" "aibridge-proxy-setup" || true + coder exp sync start "copilot-aibridge" || true + trap 'coder exp sync complete "copilot-aibridge" > /dev/null 2>&1 || true' EXIT + fi + + if [ -z "$ARG_AIBRIDGE_PROXY_AUTH_URL" ]; then + echo "ERROR: AI Bridge Proxy is enabled but no proxy auth URL provided." + exit 1 + fi + + if [ -z "$ARG_AIBRIDGE_PROXY_CERT_PATH" ]; then + echo "ERROR: AI Bridge Proxy is enabled but no certificate path provided." + exit 1 + fi + + if [ ! -f "$ARG_AIBRIDGE_PROXY_CERT_PATH" ]; then + echo "ERROR: AI Bridge Proxy certificate not found at $ARG_AIBRIDGE_PROXY_CERT_PATH." + echo " Ensure the aibridge-proxy module has completed setup." + exit 1 + fi + + # Set proxy environment variables scoped to this process tree only. + # These are inherited by the agentapi/copilot process below, + # but do not affect other workspace processes, avoiding routing + # unnecessary traffic through the proxy. + export HTTPS_PROXY="$ARG_AIBRIDGE_PROXY_AUTH_URL" + export NODE_EXTRA_CA_CERTS="$ARG_AIBRIDGE_PROXY_CERT_PATH" + + echo "✓ AI Bridge Proxy configured" + echo " CA certificate: $ARG_AIBRIDGE_PROXY_CERT_PATH" +} + start_agentapi() { echo "Starting in directory: $ARG_WORKDIR" cd "$ARG_WORKDIR" @@ -157,5 +202,6 @@ start_agentapi() { } setup_github_authentication +setup_aibridge_proxy validate_copilot_installation start_agentapi