feat(coder/modules/claude-code): add support for aibridge (#657)
## Description - Add support for AI Bridge ## Type of Change - [ ] New module - [ ] New template - [ ] Bug fix - [x] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information <!-- Delete this section if not applicable --> **Path:** `registry/coder/modules/claude-code` **New version:** `v4.5.0` **Breaking change:** [ ] Yes [x] No ## Testing & Validation - [x] Tests pass (`bun test`) - [x] Code formatted (`bun fmt`) - [x] Changes tested locally ## Related Issues Closes: #649 --------- Co-authored-by: Atif Ali <atif@coder.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
51676b6e62
commit
2e8870bcee
@ -3,7 +3,7 @@ display_name: Claude Code
|
|||||||
description: Run the Claude Code agent in your workspace.
|
description: Run the Claude Code agent in your workspace.
|
||||||
icon: ../../../../.icons/claude.svg
|
icon: ../../../../.icons/claude.svg
|
||||||
verified: true
|
verified: true
|
||||||
tags: [agent, claude-code, ai, tasks, anthropic]
|
tags: [agent, claude-code, ai, tasks, anthropic, aibridge]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Claude Code
|
# Claude Code
|
||||||
@ -13,7 +13,7 @@ Run the [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude
|
|||||||
```tf
|
```tf
|
||||||
module "claude-code" {
|
module "claude-code" {
|
||||||
source = "registry.coder.com/coder/claude-code/coder"
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
version = "4.4.2"
|
version = "4.5.0"
|
||||||
agent_id = coder_agent.main.id
|
agent_id = coder_agent.main.id
|
||||||
workdir = "/home/coder/project"
|
workdir = "/home/coder/project"
|
||||||
claude_api_key = "xxxx-xxxxx-xxxx"
|
claude_api_key = "xxxx-xxxxx-xxxx"
|
||||||
@ -45,7 +45,7 @@ This example shows how to configure the Claude Code module to run the agent behi
|
|||||||
```tf
|
```tf
|
||||||
module "claude-code" {
|
module "claude-code" {
|
||||||
source = "registry.coder.com/coder/claude-code/coder"
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
version = "4.4.2"
|
version = "4.5.0"
|
||||||
agent_id = coder_agent.main.id
|
agent_id = coder_agent.main.id
|
||||||
workdir = "/home/coder/project"
|
workdir = "/home/coder/project"
|
||||||
enable_boundary = true
|
enable_boundary = true
|
||||||
@ -53,25 +53,68 @@ module "claude-code" {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Usage with Tasks and Advanced Configuration
|
### Usage with AI Bridge
|
||||||
|
|
||||||
This example shows how to configure the Claude Code module with an AI prompt, API key shared by all users of the template, and other custom settings.
|
[AI Bridge](https://coder.com/docs/ai-coder/ai-bridge) is a Premium Coder feature that provides centralized LLM proxy management. To use AI Bridge, set `enable_aibridge = true`.
|
||||||
|
|
||||||
|
For tasks integration with AI Bridge, add `enable_aibridge = true` to the [Usage with Tasks](#usage-with-tasks) example below.
|
||||||
|
|
||||||
|
#### Standalone usage with AI Bridge
|
||||||
|
|
||||||
|
```tf
|
||||||
|
module "claude-code" {
|
||||||
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
|
version = "4.5.0"
|
||||||
|
agent_id = coder_agent.main.id
|
||||||
|
workdir = "/home/coder/project"
|
||||||
|
enable_aibridge = true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When `enable_aibridge = true`, the module automatically sets:
|
||||||
|
|
||||||
|
- `ANTHROPIC_BASE_URL` to `${data.coder_workspace.me.access_url}/api/v2/aibridge/anthropic`
|
||||||
|
- `CLAUDE_API_KEY` to the workspace owner's session token
|
||||||
|
|
||||||
|
This allows Claude Code to route API requests through Coder's AI Bridge instead of directly to Anthropic's API.
|
||||||
|
Template build will fail if either `claude_api_key` or `claude_code_oauth_token` is provided alongside `enable_aibridge = true`.
|
||||||
|
|
||||||
|
### Usage with Tasks
|
||||||
|
|
||||||
|
This example shows how to configure Claude Code with Coder tasks.
|
||||||
|
|
||||||
|
```tf
|
||||||
|
resource "coder_ai_task" "task" {
|
||||||
|
count = data.coder_workspace.me.start_count
|
||||||
|
app_id = module.claude-code.task_app_id
|
||||||
|
}
|
||||||
|
|
||||||
|
data "coder_task" "me" {}
|
||||||
|
|
||||||
|
module "claude-code" {
|
||||||
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
|
version = "4.5.0"
|
||||||
|
agent_id = coder_agent.main.id
|
||||||
|
workdir = "/home/coder/project"
|
||||||
|
claude_api_key = "xxxx-xxxxx-xxxx"
|
||||||
|
ai_prompt = data.coder_task.me.prompt
|
||||||
|
|
||||||
|
# Optional: route through AI Bridge (Premium feature)
|
||||||
|
# enable_aibridge = true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Configuration
|
||||||
|
|
||||||
|
This example shows additional configuration options for version pinning, custom models, and MCP servers.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> When a specific `claude_code_version` (other than "latest") is provided, the module will install Claude Code via npm instead of the official installer. This allows for version pinning. The `claude_binary_path` variable can be used to specify where a pre-installed Claude binary is located.
|
> When a specific `claude_code_version` (other than "latest") is provided, the module will install Claude Code via npm instead of the official installer. This allows for version pinning. The `claude_binary_path` variable can be used to specify where a pre-installed Claude binary is located.
|
||||||
|
|
||||||
```tf
|
```tf
|
||||||
data "coder_parameter" "ai_prompt" {
|
|
||||||
type = "string"
|
|
||||||
name = "AI Prompt"
|
|
||||||
default = ""
|
|
||||||
description = "Initial task prompt for Claude Code."
|
|
||||||
mutable = true
|
|
||||||
}
|
|
||||||
|
|
||||||
module "claude-code" {
|
module "claude-code" {
|
||||||
source = "registry.coder.com/coder/claude-code/coder"
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
version = "4.4.2"
|
version = "4.5.0"
|
||||||
agent_id = coder_agent.main.id
|
agent_id = coder_agent.main.id
|
||||||
workdir = "/home/coder/project"
|
workdir = "/home/coder/project"
|
||||||
|
|
||||||
@ -83,9 +126,7 @@ module "claude-code" {
|
|||||||
claude_binary_path = "/opt/claude/bin" # Path to pre-installed Claude binary
|
claude_binary_path = "/opt/claude/bin" # Path to pre-installed Claude binary
|
||||||
agentapi_version = "0.11.4"
|
agentapi_version = "0.11.4"
|
||||||
|
|
||||||
ai_prompt = data.coder_parameter.ai_prompt.value
|
model = "sonnet"
|
||||||
model = "sonnet"
|
|
||||||
|
|
||||||
permission_mode = "plan"
|
permission_mode = "plan"
|
||||||
|
|
||||||
mcp = <<-EOF
|
mcp = <<-EOF
|
||||||
@ -108,7 +149,7 @@ Run and configure Claude Code as a standalone CLI in your workspace.
|
|||||||
```tf
|
```tf
|
||||||
module "claude-code" {
|
module "claude-code" {
|
||||||
source = "registry.coder.com/coder/claude-code/coder"
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
version = "4.4.2"
|
version = "4.5.0"
|
||||||
agent_id = coder_agent.main.id
|
agent_id = coder_agent.main.id
|
||||||
workdir = "/home/coder/project"
|
workdir = "/home/coder/project"
|
||||||
install_claude_code = true
|
install_claude_code = true
|
||||||
@ -130,7 +171,7 @@ variable "claude_code_oauth_token" {
|
|||||||
|
|
||||||
module "claude-code" {
|
module "claude-code" {
|
||||||
source = "registry.coder.com/coder/claude-code/coder"
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
version = "4.4.2"
|
version = "4.5.0"
|
||||||
agent_id = coder_agent.main.id
|
agent_id = coder_agent.main.id
|
||||||
workdir = "/home/coder/project"
|
workdir = "/home/coder/project"
|
||||||
claude_code_oauth_token = var.claude_code_oauth_token
|
claude_code_oauth_token = var.claude_code_oauth_token
|
||||||
@ -203,7 +244,7 @@ resource "coder_env" "bedrock_api_key" {
|
|||||||
|
|
||||||
module "claude-code" {
|
module "claude-code" {
|
||||||
source = "registry.coder.com/coder/claude-code/coder"
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
version = "4.4.2"
|
version = "4.5.0"
|
||||||
agent_id = coder_agent.main.id
|
agent_id = coder_agent.main.id
|
||||||
workdir = "/home/coder/project"
|
workdir = "/home/coder/project"
|
||||||
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
||||||
@ -260,7 +301,7 @@ resource "coder_env" "google_application_credentials" {
|
|||||||
|
|
||||||
module "claude-code" {
|
module "claude-code" {
|
||||||
source = "registry.coder.com/coder/claude-code/coder"
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
version = "4.4.2"
|
version = "4.5.0"
|
||||||
agent_id = coder_agent.main.id
|
agent_id = coder_agent.main.id
|
||||||
workdir = "/home/coder/project"
|
workdir = "/home/coder/project"
|
||||||
model = "claude-sonnet-4@20250514"
|
model = "claude-sonnet-4@20250514"
|
||||||
|
|||||||
@ -228,6 +228,22 @@ variable "compile_boundary_from_source" {
|
|||||||
default = false
|
default = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "enable_aibridge" {
|
||||||
|
type = bool
|
||||||
|
description = "Use AI Bridge for Claude Code. https://coder.com/docs/ai-coder/ai-bridge"
|
||||||
|
default = false
|
||||||
|
|
||||||
|
validation {
|
||||||
|
condition = !(var.enable_aibridge && length(var.claude_api_key) > 0)
|
||||||
|
error_message = "claude_api_key cannot be provided when enable_aibridge is true. AI Bridge automatically authenticates the client using Coder credentials."
|
||||||
|
}
|
||||||
|
|
||||||
|
validation {
|
||||||
|
condition = !(var.enable_aibridge && length(var.claude_code_oauth_token) > 0)
|
||||||
|
error_message = "claude_code_oauth_token cannot be provided when enable_aibridge is true. AI Bridge automatically authenticates the client using Coder credentials."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resource "coder_env" "claude_code_md_path" {
|
resource "coder_env" "claude_code_md_path" {
|
||||||
count = var.claude_md_path == "" ? 0 : 1
|
count = var.claude_md_path == "" ? 0 : 1
|
||||||
agent_id = var.agent_id
|
agent_id = var.agent_id
|
||||||
@ -248,10 +264,9 @@ resource "coder_env" "claude_code_oauth_token" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "coder_env" "claude_api_key" {
|
resource "coder_env" "claude_api_key" {
|
||||||
count = length(var.claude_api_key) > 0 ? 1 : 0
|
|
||||||
agent_id = var.agent_id
|
agent_id = var.agent_id
|
||||||
name = "CLAUDE_API_KEY"
|
name = "CLAUDE_API_KEY"
|
||||||
value = var.claude_api_key
|
value = var.enable_aibridge ? data.coder_workspace_owner.me.session_token : var.claude_api_key
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "coder_env" "disable_autoupdater" {
|
resource "coder_env" "disable_autoupdater" {
|
||||||
@ -281,6 +296,13 @@ resource "coder_env" "anthropic_model" {
|
|||||||
value = var.model
|
value = var.model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "coder_env" "anthropic_base_url" {
|
||||||
|
count = var.enable_aibridge ? 1 : 0
|
||||||
|
agent_id = var.agent_id
|
||||||
|
name = "ANTHROPIC_BASE_URL"
|
||||||
|
value = "${data.coder_workspace.me.access_url}/api/v2/aibridge/anthropic"
|
||||||
|
}
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
# we have to trim the slash because otherwise coder exp mcp will
|
# we have to trim the slash because otherwise coder exp mcp will
|
||||||
# set up an invalid claude config
|
# set up an invalid claude config
|
||||||
@ -382,6 +404,7 @@ module "agentapi" {
|
|||||||
ARG_ALLOWED_TOOLS='${var.allowed_tools}' \
|
ARG_ALLOWED_TOOLS='${var.allowed_tools}' \
|
||||||
ARG_DISALLOWED_TOOLS='${var.disallowed_tools}' \
|
ARG_DISALLOWED_TOOLS='${var.disallowed_tools}' \
|
||||||
ARG_MCP='${var.mcp != null ? base64encode(replace(var.mcp, "'", "'\\''")) : ""}' \
|
ARG_MCP='${var.mcp != null ? base64encode(replace(var.mcp, "'", "'\\''")) : ""}' \
|
||||||
|
ARG_ENABLE_AIBRIDGE='${var.enable_aibridge}' \
|
||||||
/tmp/install.sh
|
/tmp/install.sh
|
||||||
EOT
|
EOT
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,7 +42,7 @@ run "test_claude_code_with_api_key" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert {
|
assert {
|
||||||
condition = coder_env.claude_api_key[0].value == "test-api-key-123"
|
condition = coder_env.claude_api_key.value == "test-api-key-123"
|
||||||
error_message = "Claude API key value should match the input"
|
error_message = "Claude API key value should match the input"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -288,3 +288,94 @@ run "test_claude_report_tasks_disabled" {
|
|||||||
error_message = "System prompt should end with </system>"
|
error_message = "System prompt should end with </system>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run "test_aibridge_enabled" {
|
||||||
|
command = plan
|
||||||
|
|
||||||
|
variables {
|
||||||
|
agent_id = "test-agent-aibridge"
|
||||||
|
workdir = "/home/coder/aibridge"
|
||||||
|
enable_aibridge = true
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = var.enable_aibridge == true
|
||||||
|
error_message = "AI Bridge should be enabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = coder_env.anthropic_base_url[0].name == "ANTHROPIC_BASE_URL"
|
||||||
|
error_message = "ANTHROPIC_BASE_URL environment variable should be set"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = length(regexall("/api/v2/aibridge/anthropic", coder_env.anthropic_base_url[0].value)) > 0
|
||||||
|
error_message = "ANTHROPIC_BASE_URL should point to AI Bridge endpoint"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = coder_env.claude_api_key.name == "CLAUDE_API_KEY"
|
||||||
|
error_message = "CLAUDE_API_KEY environment variable should be set"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = coder_env.claude_api_key.value == data.coder_workspace_owner.me.session_token
|
||||||
|
error_message = "CLAUDE_API_KEY should use workspace owner's session token when aibridge is enabled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run "test_aibridge_validation_with_api_key" {
|
||||||
|
command = plan
|
||||||
|
|
||||||
|
variables {
|
||||||
|
agent_id = "test-agent-validation"
|
||||||
|
workdir = "/home/coder/test"
|
||||||
|
enable_aibridge = true
|
||||||
|
claude_api_key = "test-api-key"
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_failures = [
|
||||||
|
var.enable_aibridge,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
run "test_aibridge_validation_with_oauth_token" {
|
||||||
|
command = plan
|
||||||
|
|
||||||
|
variables {
|
||||||
|
agent_id = "test-agent-validation"
|
||||||
|
workdir = "/home/coder/test"
|
||||||
|
enable_aibridge = true
|
||||||
|
claude_code_oauth_token = "test-oauth-token"
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_failures = [
|
||||||
|
var.enable_aibridge,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
run "test_aibridge_disabled_with_api_key" {
|
||||||
|
command = plan
|
||||||
|
|
||||||
|
variables {
|
||||||
|
agent_id = "test-agent-no-aibridge"
|
||||||
|
workdir = "/home/coder/test"
|
||||||
|
enable_aibridge = false
|
||||||
|
claude_api_key = "test-api-key-xyz"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = var.enable_aibridge == false
|
||||||
|
error_message = "AI Bridge should be disabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = coder_env.claude_api_key.value == "test-api-key-xyz"
|
||||||
|
error_message = "CLAUDE_API_KEY should use the provided API key when aibridge is disabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = length(coder_env.anthropic_base_url) == 0
|
||||||
|
error_message = "ANTHROPIC_BASE_URL should not be set when aibridge is disabled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ ARG_MCP_APP_STATUS_SLUG=${ARG_MCP_APP_STATUS_SLUG:-}
|
|||||||
ARG_MCP=$(echo -n "${ARG_MCP:-}" | base64 -d)
|
ARG_MCP=$(echo -n "${ARG_MCP:-}" | base64 -d)
|
||||||
ARG_ALLOWED_TOOLS=${ARG_ALLOWED_TOOLS:-}
|
ARG_ALLOWED_TOOLS=${ARG_ALLOWED_TOOLS:-}
|
||||||
ARG_DISALLOWED_TOOLS=${ARG_DISALLOWED_TOOLS:-}
|
ARG_DISALLOWED_TOOLS=${ARG_DISALLOWED_TOOLS:-}
|
||||||
|
ARG_ENABLE_AIBRIDGE=${ARG_ENABLE_AIBRIDGE:-false}
|
||||||
|
|
||||||
echo "--------------------------------"
|
echo "--------------------------------"
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ printf "ARG_MCP_APP_STATUS_SLUG: %s\n" "$ARG_MCP_APP_STATUS_SLUG"
|
|||||||
printf "ARG_MCP: %s\n" "$ARG_MCP"
|
printf "ARG_MCP: %s\n" "$ARG_MCP"
|
||||||
printf "ARG_ALLOWED_TOOLS: %s\n" "$ARG_ALLOWED_TOOLS"
|
printf "ARG_ALLOWED_TOOLS: %s\n" "$ARG_ALLOWED_TOOLS"
|
||||||
printf "ARG_DISALLOWED_TOOLS: %s\n" "$ARG_DISALLOWED_TOOLS"
|
printf "ARG_DISALLOWED_TOOLS: %s\n" "$ARG_DISALLOWED_TOOLS"
|
||||||
|
printf "ARG_ENABLE_AIBRIDGE: %s\n" "$ARG_ENABLE_AIBRIDGE"
|
||||||
|
|
||||||
echo "--------------------------------"
|
echo "--------------------------------"
|
||||||
|
|
||||||
@ -133,8 +135,8 @@ function setup_claude_configurations() {
|
|||||||
function configure_standalone_mode() {
|
function configure_standalone_mode() {
|
||||||
echo "Configuring Claude Code for standalone mode..."
|
echo "Configuring Claude Code for standalone mode..."
|
||||||
|
|
||||||
if [ -z "${CLAUDE_API_KEY:-}" ]; then
|
if [ -z "${CLAUDE_API_KEY:-}" ] && [ "$ARG_ENABLE_AIBRIDGE" = "false" ]; then
|
||||||
echo "Note: CLAUDE_API_KEY not set, skipping authentication setup"
|
echo "Note: Neither claude_api_key nor enable_aibridge is set, skipping authentication setup"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -147,8 +149,7 @@ function configure_standalone_mode() {
|
|||||||
if [ -f "$claude_config" ]; then
|
if [ -f "$claude_config" ]; then
|
||||||
echo "Updating existing Claude configuration at $claude_config"
|
echo "Updating existing Claude configuration at $claude_config"
|
||||||
|
|
||||||
jq --arg apikey "${CLAUDE_API_KEY:-}" \
|
jq --arg workdir "$ARG_WORKDIR" --arg apikey "${CLAUDE_API_KEY:-}" \
|
||||||
--arg workdir "$ARG_WORKDIR" \
|
|
||||||
'.autoUpdaterStatus = "disabled" |
|
'.autoUpdaterStatus = "disabled" |
|
||||||
.bypassPermissionsModeAccepted = true |
|
.bypassPermissionsModeAccepted = true |
|
||||||
.hasAcknowledgedCostThreshold = true |
|
.hasAcknowledgedCostThreshold = true |
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user