@@ -4,7 +4,7 @@ terraform {
44 required_providers {
55 coder = {
66 source = " coder/coder"
7- version = " >= 2.5 "
7+ version = " >= 2.7 "
88 }
99 }
1010}
@@ -96,9 +96,69 @@ variable "experiment_tmux_session_save_interval" {
9696 default = " 15"
9797}
9898
99+ variable "install_agentapi" {
100+ type = bool
101+ description = " Whether to install AgentAPI."
102+ default = true
103+ }
104+
105+ variable "agentapi_version" {
106+ type = string
107+ description = " The version of AgentAPI to install."
108+ default = " v0.2.2"
109+ }
110+
99111locals {
100- encoded_pre_install_script = var. experiment_pre_install_script != null ? base64encode (var. experiment_pre_install_script ) : " "
101- encoded_post_install_script = var. experiment_post_install_script != null ? base64encode (var. experiment_post_install_script ) : " "
112+ workdir = trimsuffix (var. folder , " /" )
113+ encoded_pre_install_script = var. experiment_pre_install_script != null ? base64encode (var. experiment_pre_install_script ) : " "
114+ encoded_post_install_script = var. experiment_post_install_script != null ? base64encode (var. experiment_post_install_script ) : " "
115+ agentapi_start_command = <<- EOT
116+ #!/bin/bash
117+ set -e
118+
119+ # if the first argument is not empty, start claude with the prompt
120+ if [ -n "$1" ]; then
121+ prompt="$(cat ~/.claude-code-prompt)"
122+ cp ~/.claude-code-prompt /tmp/claude-code-prompt
123+ else
124+ rm -f /tmp/claude-code-prompt
125+ fi
126+
127+ agentapi_started_marker_file=~/.agentapi-started
128+
129+ # if we haven't started agentapi before, then there's no session to continue
130+ # using the --continue flag would fail
131+ continue_flag="--continue"
132+ if [ -f "$agentapi_started_marker_file" ]; then
133+ continue_flag=""
134+ fi
135+ touch "$agentapi_started_marker_file"
136+
137+ # use low width to fit in the tasks UI sidebar. height is adjusted to ~match the default 80k (80x1000) characters
138+ # visible in the terminal screen.
139+ agentapi server --term-width 67 --term-height 1190 -- bash -c "claude $continue_flag --dangerously-skip-permissions \"$(cat /tmp/claude-code-prompt)\""
140+ EOT
141+ agentapi_wait_for_start_command = <<- EOT
142+ #!/bin/bash
143+ set -o errexit
144+ set -o pipefail
145+
146+ echo "Waiting for agentapi server to start on port 3284..."
147+ for i in $(seq 1 15); do
148+ if lsof -i :3284 | grep -q 'LISTEN'; then
149+ echo "agentapi server started on port 3284."
150+ break
151+ fi
152+ echo "Waiting... ($i/15)"
153+ sleep 1
154+ done
155+ if ! lsof -i :3284 | grep -q 'LISTEN'; then
156+ echo "Error: agentapi server did not start on port 3284 after 15 seconds."
157+ exit 1
158+ fi
159+ EOT
160+ agentapi_start_command_base64 = base64encode (local. agentapi_start_command )
161+ agentapi_wait_for_start_command_base64 = base64encode (local. agentapi_wait_for_start_command )
102162}
103163
104164# Install and Initialize Claude Code
@@ -132,12 +192,12 @@ resource "coder_script" "claude_code" {
132192 fi
133193 }
134194
135- if [ ! -d "${ var . folder } " ]; then
136- echo "Warning: The specified folder '${ var . folder } ' does not exist."
195+ if [ ! -d "${ local . workdir } " ]; then
196+ echo "Warning: The specified folder '${ local . workdir } ' does not exist."
137197 echo "Creating the folder..."
138198 # The folder must exist before tmux is started or else claude will start
139199 # in the home directory.
140- mkdir -p "${ var . folder } "
200+ mkdir -p "${ local . workdir } "
141201 echo "Folder created successfully."
142202 fi
143203 if [ -n "${ local . encoded_pre_install_script } " ]; then
@@ -176,9 +236,38 @@ resource "coder_script" "claude_code" {
176236 npm install -g @anthropic-ai/claude-code@${ var . claude_code_version }
177237 fi
178238
239+ # Install AgentAPI if enabled
240+ if [ "${ var . install_agentapi } " = "true" ]; then
241+ echo "Installing AgentAPI..."
242+ arch=$(uname -m)
243+ if [ "$arch" = "x86_64" ]; then
244+ binary_name="agentapi-linux-amd64"
245+ elif [ "$arch" = "aarch64" ]; then
246+ binary_name="agentapi-linux-arm64"
247+ else
248+ echo "Error: Unsupported architecture: $arch"
249+ exit 1
250+ fi
251+ wget "https://github.com/coder/agentapi/releases/download/${ var . agentapi_version } /$binary_name"
252+ chmod +x "$binary_name"
253+ sudo mv "$binary_name" /usr/local/bin/agentapi
254+ fi
255+ if ! command_exists agentapi; then
256+ echo "Error: AgentAPI is not installed. Please enable install_agentapi or install it manually."
257+ exit 1
258+ fi
259+
260+ # save the prompt for the agentapi start command
261+ echo -n "$CODER_MCP_CLAUDE_TASK_PROMPT" > ~/.claude-code-prompt
262+
263+ echo -n "${ local . agentapi_start_command_base64 } " | base64 -d > ~/.agentapi-start-command
264+ chmod +x ~/.agentapi-start-command
265+ echo -n "${ local . agentapi_wait_for_start_command_base64 } " | base64 -d > ~/.agentapi-wait-for-start-command
266+ chmod +x ~/.agentapi-wait-for-start-command
267+
179268 if [ "${ var . experiment_report_tasks } " = "true" ]; then
180269 echo "Configuring Claude Code to report tasks via Coder MCP..."
181- coder exp mcp configure claude-code ${ var . folder }
270+ coder exp mcp configure claude-code ${ local . workdir } --ai-agentapi-url http://localhost:3284
182271 fi
183272
184273 if [ -n "${ local . encoded_post_install_script } " ]; then
@@ -257,17 +346,16 @@ EOF
257346 export LANG=en_US.UTF-8
258347 export LC_ALL=en_US.UTF-8
259348
349+ tmux new-session -d -s claude-code-agentapi -c ${ local . workdir } '~/.agentapi-start-command true; exec bash'
350+ ~/.agentapi-wait-for-start-command
351+
260352 if [ "${ var . experiment_tmux_session_persistence } " = "true" ]; then
261353 sleep 3
354+ fi
262355
263- if ! tmux has-session -t claude-code 2>/dev/null; then
264- # Only create a new session if one doesn't exist
265- tmux new-session -d -s claude-code -c ${ var . folder } "claude --dangerously-skip-permissions \"$CODER_MCP_CLAUDE_TASK_PROMPT\""
266- fi
267- else
268- if ! tmux has-session -t claude-code 2>/dev/null; then
269- tmux new-session -d -s claude-code -c ${ var . folder } "claude --dangerously-skip-permissions \"$CODER_MCP_CLAUDE_TASK_PROMPT\""
270- fi
356+ if ! tmux has-session -t claude-code 2>/dev/null; then
357+ # Only create a new session if one doesn't exist
358+ tmux new-session -d -s claude-code -c ${ local . workdir } "agentapi attach; exec bash"
271359 fi
272360 fi
273361
297385 export LANG=en_US.UTF-8
298386 export LC_ALL=en_US.UTF-8
299387
388+ screen -U -dmS claude-code-agentapi bash -c '
389+ cd ${ local . workdir }
390+ # setting the first argument will make claude use the prompt
391+ ~/.agentapi-start-command true
392+ exec bash
393+ '
394+ ~/.agentapi-wait-for-start-command
395+
300396 screen -U -dmS claude-code bash -c '
301- cd ${ var . folder }
302- claude --dangerously-skip-permissions "$CODER_MCP_CLAUDE_TASK_PROMPT" | tee -a "$HOME/.claude-code.log"
397+ cd ${ local . workdir }
398+ agentapi attach
303399 exec bash
304400 '
305401 else
312408 run_on_start = true
313409}
314410
411+ resource "coder_app" "claude_code_web" {
412+ # use a short slug to mitigate https://github.com/coder/coder/issues/15178
413+ slug = " ccw"
414+ display_name = " Claude Code Web"
415+ agent_id = var. agent_id
416+ url = " http://localhost:3284/"
417+ icon = var. icon
418+ subdomain = true
419+ healthcheck {
420+ url = " http://localhost:3284/status"
421+ interval = 5
422+ threshold = 3
423+ }
424+ }
425+
315426resource "coder_app" "claude_code" {
316427 slug = " claude-code"
317428 display_name = " Claude Code"
@@ -324,31 +435,51 @@ resource "coder_app" "claude_code" {
324435 export LC_ALL=en_US.UTF-8
325436
326437 if [ "${ var . experiment_use_tmux } " = "true" ]; then
438+ if ! tmux has-session -t claude-code-agentapi 2>/dev/null; then
439+ # start agentapi without claude using the prompt (no argument)
440+ tmux new-session -d -s claude-code-agentapi -c ${ local . workdir } '~/.agentapi-start-command; exec bash'
441+ ~/.agentapi-wait-for-start-command
442+ fi
443+
327444 if tmux has-session -t claude-code 2>/dev/null; then
328445 echo "Attaching to existing Claude Code tmux session." | tee -a "$HOME/.claude-code.log"
329- # If Claude isn't running in the session, start it without the prompt
330- if ! tmux list-panes -t claude-code -F '#{pane_current_command}' | grep -q "claude "; then
331- tmux send-keys -t claude-code "cd ${ var . folder } && claude -c --dangerously-skip-permissions " C-m
446+ # If agentapi attach isn't running in the session, start it
447+ if ! tmux list-panes -t claude-code -F '#{pane_current_command}' | grep -q "agentapi "; then
448+ tmux send-keys -t claude-code "cd ${ local . workdir } && agentapi attach " C-m
332449 fi
333450 tmux attach-session -t claude-code
334451 else
335452 echo "Starting a new Claude Code tmux session." | tee -a "$HOME/.claude-code.log"
336- tmux new-session -s claude-code -c ${ var . folder } "claude --dangerously-skip-permissions | tee -a \"$HOME/.claude-code.log\" ; exec bash"
453+ tmux new-session -s claude-code -c ${ local . workdir } "agentapi attach ; exec bash"
337454 fi
338455 elif [ "${ var . experiment_use_screen } " = "true" ]; then
456+ if ! screen -list | grep -q "claude-code-agentapi"; then
457+ screen -S claude-code-agentapi bash -c '
458+ cd ${ local . workdir }
459+ # start agentapi without claude using the prompt (no argument)
460+ ~/.agentapi-start-command
461+ exec bash
462+ '
463+ fi
339464 if screen -list | grep -q "claude-code"; then
340465 echo "Attaching to existing Claude Code screen session." | tee -a "$HOME/.claude-code.log"
341466 screen -xRR claude-code
342467 else
343468 echo "Starting a new Claude Code screen session." | tee -a "$HOME/.claude-code.log"
344- screen -S claude-code bash -c 'claude --dangerously-skip-permissions | tee -a "$HOME/.claude-code.log" ; exec bash'
469+ screen -S claude-code bash -c 'agentapi attach ; exec bash'
345470 fi
346471 else
347- cd ${ var . folder }
348- claude
472+ cd ${ local . workdir }
473+ agentapi attach
349474 fi
350475 EOT
351476 icon = var. icon
352477 order = var. order
353478 group = var. group
354479}
480+
481+ resource "coder_ai_task" "claude_code" {
482+ sidebar_app {
483+ id = coder_app. claude_code . id
484+ }
485+ }
0 commit comments