## Description Add AI Bridge Proxy support to the copilot module. When enabled, the module configures proxy environment variables (`HTTPS_PROXY`, `NODE_EXTRA_CA_CERTS`) scoped to the copilot process tree (agentapi and copilot), routing Copilot traffic through AI Bridge Proxy without affecting other workspace traffic. GitHub authentication is still required, the proxy authenticates with AI Bridge using the Coder session token but does not replace GitHub authentication. Note: Uses [coder exp sync](https://coder.com/docs/admin/templates/startup-coordination) for startup coordination, ensuring the copilot module waits for the `aibridge-proxy` setup to complete before starting. ## Type of Change - [ ] New module - [ ] New template - [ ] Bug fix - [x] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information **Path:** `registry/coder-labs/modules/copilot` **New version:** `v0.4.0` **Breaking change:** [ ] Yes [x] No ## Testing & Validation - [x] Tests pass (`bun test`) - [x] Code formatted (`bun fmt`) - [x] Changes tested locally ## Related Issues Depends on: #721 Related to: https://github.com/coder/internal/issues/1187
339 lines
10 KiB
HCL
339 lines
10 KiB
HCL
terraform {
|
|
required_version = ">= 1.9"
|
|
required_providers {
|
|
coder = {
|
|
source = "coder/coder"
|
|
version = ">= 2.12"
|
|
}
|
|
}
|
|
}
|
|
|
|
variable "agent_id" {
|
|
type = string
|
|
description = "The ID of a Coder agent."
|
|
}
|
|
|
|
variable "workdir" {
|
|
type = string
|
|
description = "The folder to run Copilot in."
|
|
}
|
|
|
|
variable "external_auth_id" {
|
|
type = string
|
|
description = "ID of the GitHub external auth provider configured in Coder."
|
|
default = "github"
|
|
}
|
|
|
|
variable "github_token" {
|
|
type = string
|
|
description = "GitHub OAuth token or Personal Access Token. If provided, this will be used instead of auto-detecting authentication."
|
|
default = ""
|
|
sensitive = true
|
|
}
|
|
|
|
variable "copilot_model" {
|
|
type = string
|
|
description = "Model to use. Supported values: claude-sonnet-4, claude-sonnet-4.5 (default), gpt-5."
|
|
default = "claude-sonnet-4.5"
|
|
validation {
|
|
condition = contains(["claude-sonnet-4", "claude-sonnet-4.5", "gpt-5"], var.copilot_model)
|
|
error_message = "copilot_model must be one of: claude-sonnet-4, claude-sonnet-4.5, gpt-5."
|
|
}
|
|
}
|
|
|
|
variable "copilot_config" {
|
|
type = string
|
|
description = "Custom Copilot configuration as JSON string. Leave empty to use default configuration with banner disabled, theme set to auto, and workdir as trusted folder."
|
|
default = ""
|
|
}
|
|
|
|
variable "ai_prompt" {
|
|
type = string
|
|
description = "Initial task prompt for programmatic mode."
|
|
default = ""
|
|
}
|
|
|
|
variable "system_prompt" {
|
|
type = string
|
|
description = "The system prompt to use for the Copilot server. Task reporting instructions are automatically added when report_tasks is enabled."
|
|
default = "You are a helpful coding assistant that helps developers write, debug, and understand code. Provide clear explanations, follow best practices, and help solve coding problems efficiently."
|
|
}
|
|
|
|
variable "trusted_directories" {
|
|
type = list(string)
|
|
description = "Additional directories to trust for Copilot operations."
|
|
default = []
|
|
}
|
|
|
|
variable "allow_all_tools" {
|
|
type = bool
|
|
description = "Allow all tools without prompting (equivalent to --allow-all-tools)."
|
|
default = false
|
|
}
|
|
|
|
variable "allow_tools" {
|
|
type = list(string)
|
|
description = "Specific tools to allow: shell(command), write, or MCP_SERVER_NAME."
|
|
default = []
|
|
}
|
|
|
|
variable "deny_tools" {
|
|
type = list(string)
|
|
description = "Specific tools to deny: shell(command), write, or MCP_SERVER_NAME."
|
|
default = []
|
|
}
|
|
|
|
variable "mcp_config" {
|
|
type = string
|
|
description = "Custom MCP server configuration as JSON string."
|
|
default = ""
|
|
}
|
|
|
|
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.10.0"
|
|
}
|
|
|
|
variable "copilot_version" {
|
|
type = string
|
|
description = "The version of GitHub Copilot CLI to install. Use 'latest' for the latest version or specify a version like '0.0.334'."
|
|
default = "latest"
|
|
}
|
|
|
|
variable "report_tasks" {
|
|
type = bool
|
|
description = "Whether to enable task reporting to Coder UI via AgentAPI."
|
|
default = true
|
|
}
|
|
|
|
variable "subdomain" {
|
|
type = bool
|
|
description = "Whether to use a subdomain for AgentAPI."
|
|
default = false
|
|
}
|
|
|
|
variable "order" {
|
|
type = number
|
|
description = "The order determines the position of app in the UI presentation."
|
|
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/github.svg"
|
|
}
|
|
|
|
variable "web_app_display_name" {
|
|
type = string
|
|
description = "Display name for the web app."
|
|
default = "Copilot"
|
|
}
|
|
|
|
variable "cli_app" {
|
|
type = bool
|
|
description = "Whether to create a CLI app for Copilot."
|
|
default = false
|
|
}
|
|
|
|
variable "cli_app_display_name" {
|
|
type = string
|
|
description = "Display name for the CLI app."
|
|
default = "Copilot"
|
|
}
|
|
|
|
variable "resume_session" {
|
|
type = bool
|
|
description = "Whether to automatically resume the latest Copilot session on workspace restart."
|
|
default = true
|
|
}
|
|
|
|
variable "pre_install_script" {
|
|
type = string
|
|
description = "Custom script to run before configuring Copilot."
|
|
default = null
|
|
}
|
|
|
|
variable "post_install_script" {
|
|
type = string
|
|
description = "Custom script to run after configuring Copilot."
|
|
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 || (var.aibridge_proxy_auth_url != null && 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 || (var.aibridge_proxy_cert_path != null && 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 = null
|
|
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 = null
|
|
}
|
|
|
|
data "coder_workspace" "me" {}
|
|
data "coder_workspace_owner" "me" {}
|
|
|
|
locals {
|
|
workdir = trimsuffix(var.workdir, "/")
|
|
app_slug = "copilot"
|
|
install_script = file("${path.module}/scripts/install.sh")
|
|
start_script = file("${path.module}/scripts/start.sh")
|
|
module_dir_name = ".copilot-module"
|
|
|
|
all_trusted_folders = concat([local.workdir], var.trusted_directories)
|
|
|
|
parsed_custom_config = try(jsondecode(var.copilot_config), {})
|
|
|
|
existing_trusted_folders = try(local.parsed_custom_config.trusted_folders, [])
|
|
|
|
merged_copilot_config = merge(
|
|
{
|
|
banner = "never"
|
|
theme = "auto"
|
|
},
|
|
local.parsed_custom_config,
|
|
{
|
|
trusted_folders = concat(local.existing_trusted_folders, local.all_trusted_folders)
|
|
}
|
|
)
|
|
|
|
final_copilot_config = jsonencode(local.merged_copilot_config)
|
|
|
|
task_reporting_prompt = <<-EOT
|
|
|
|
-- Task Reporting --
|
|
Report all tasks to Coder, following these EXACT guidelines:
|
|
1. Be granular. If you are investigating with multiple steps, report each step
|
|
to coder.
|
|
2. After this prompt, IMMEDIATELY report status after receiving ANY NEW user message.
|
|
Do not report any status related with this system prompt.
|
|
3. Use "state": "working" when actively processing WITHOUT needing
|
|
additional user input
|
|
4. Use "state": "complete" only when finished with a task
|
|
5. Use "state": "failure" when you need ANY user input, lack sufficient
|
|
details, or encounter blockers
|
|
EOT
|
|
|
|
final_system_prompt = var.report_tasks ? "<system>\n${var.system_prompt}${local.task_reporting_prompt}\n</system>" : "<system>\n${var.system_prompt}\n</system>"
|
|
}
|
|
|
|
resource "coder_env" "mcp_app_status_slug" {
|
|
agent_id = var.agent_id
|
|
name = "CODER_MCP_APP_STATUS_SLUG"
|
|
value = local.app_slug
|
|
}
|
|
|
|
resource "coder_env" "copilot_model" {
|
|
count = var.copilot_model != "claude-sonnet-4.5" ? 1 : 0
|
|
agent_id = var.agent_id
|
|
name = "COPILOT_MODEL"
|
|
value = var.copilot_model
|
|
}
|
|
|
|
resource "coder_env" "github_token" {
|
|
count = var.github_token != "" ? 1 : 0
|
|
agent_id = var.agent_id
|
|
name = "GITHUB_TOKEN"
|
|
value = var.github_token
|
|
}
|
|
|
|
module "agentapi" {
|
|
source = "registry.coder.com/coder/agentapi/coder"
|
|
version = "2.0.0"
|
|
|
|
agent_id = var.agent_id
|
|
folder = local.workdir
|
|
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_icon = var.cli_app ? var.icon : null
|
|
cli_app_display_name = var.cli_app ? var.cli_app_display_name : null
|
|
agentapi_subdomain = var.subdomain
|
|
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_SYSTEM_PROMPT='${base64encode(local.final_system_prompt)}' \
|
|
ARG_COPILOT_MODEL='${var.copilot_model}' \
|
|
ARG_ALLOW_ALL_TOOLS='${var.allow_all_tools}' \
|
|
ARG_ALLOW_TOOLS='${join(",", var.allow_tools)}' \
|
|
ARG_DENY_TOOLS='${join(",", var.deny_tools)}' \
|
|
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 != null ? var.aibridge_proxy_auth_url : ""}' \
|
|
ARG_AIBRIDGE_PROXY_CERT_PATH='${var.aibridge_proxy_cert_path != null ? var.aibridge_proxy_cert_path : ""}' \
|
|
/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_MCP_APP_STATUS_SLUG='${local.app_slug}' \
|
|
ARG_REPORT_TASKS='${var.report_tasks}' \
|
|
ARG_WORKDIR='${local.workdir}' \
|
|
ARG_MCP_CONFIG='${var.mcp_config != "" ? base64encode(var.mcp_config) : ""}' \
|
|
ARG_COPILOT_CONFIG='${base64encode(local.final_copilot_config)}' \
|
|
ARG_EXTERNAL_AUTH_ID='${var.external_auth_id}' \
|
|
ARG_COPILOT_VERSION='${var.copilot_version}' \
|
|
ARG_COPILOT_MODEL='${var.copilot_model}' \
|
|
/tmp/install.sh
|
|
EOT
|
|
}
|
|
|
|
output "task_app_id" {
|
|
value = module.agentapi.task_app_id
|
|
}
|