@@ -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,73 @@ 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+ # We need to check if there's a session to use --continue. If there's no session,
128+ # using this flag would cause claude to exit with an error.
129+ # warning: this is a hack and will break if claude changes the format of the .claude.json file.
130+ # Also, this solution is not ideal: a user has to quit claude in order for the session id to appear
131+ # in .claude.json. If they just restart the workspace, the session id will not be available.
132+ continue_flag=""
133+ if grep -q '"lastSessionId":' ~/.claude.json; then
134+ echo "Found a Claude Code session to continue."
135+ continue_flag="--continue"
136+ else
137+ echo "No Claude Code session to continue."
138+ fi
139+
140+ # use low width to fit in the tasks UI sidebar. height is adjusted so that width x height ~= 80x1000 characters
141+ # visible in the terminal screen by default.
142+ prompt_subshell='"$(cat /tmp/claude-code-prompt)"'
143+ agentapi server --term-width 67 --term-height 1190 -- bash -c "claude $continue_flag --dangerously-skip-permissions $prompt_subshell"
144+ EOT
145+ agentapi_wait_for_start_command = <<- EOT
146+ #!/bin/bash
147+ set -o errexit
148+ set -o pipefail
149+
150+ echo "Waiting for agentapi server to start on port 3284..."
151+ for i in $(seq 1 15); do
152+ if lsof -i :3284 | grep -q 'LISTEN'; then
153+ echo "agentapi server started on port 3284."
154+ break
155+ fi
156+ echo "Waiting... ($i/15)"
157+ sleep 1
158+ done
159+ if ! lsof -i :3284 | grep -q 'LISTEN'; then
160+ echo "Error: agentapi server did not start on port 3284 after 15 seconds."
161+ exit 1
162+ fi
163+ EOT
164+ agentapi_start_command_base64 = base64encode (local. agentapi_start_command )
165+ agentapi_wait_for_start_command_base64 = base64encode (local. agentapi_wait_for_start_command )
102166}
103167
104168# Install and Initialize Claude Code
@@ -132,12 +196,12 @@ resource "coder_script" "claude_code" {
132196 fi
133197 }
134198
135- if [ ! -d "${ var . folder } " ]; then
136- echo "Warning: The specified folder '${ var . folder } ' does not exist."
199+ if [ ! -d "${ local . workdir } " ]; then
200+ echo "Warning: The specified folder '${ local . workdir } ' does not exist."
137201 echo "Creating the folder..."
138202 # The folder must exist before tmux is started or else claude will start
139203 # in the home directory.
140- mkdir -p "${ var . folder } "
204+ mkdir -p "${ local . workdir } "
141205 echo "Folder created successfully."
142206 fi
143207 if [ -n "${ local . encoded_pre_install_script } " ]; then
@@ -176,9 +240,38 @@ resource "coder_script" "claude_code" {
176240 npm install -g @anthropic-ai/claude-code@${ var . claude_code_version }
177241 fi
178242
243+ # Install AgentAPI if enabled
244+ if [ "${ var . install_agentapi } " = "true" ]; then
245+ echo "Installing AgentAPI..."
246+ arch=$(uname -m)
247+ if [ "$arch" = "x86_64" ]; then
248+ binary_name="agentapi-linux-amd64"
249+ elif [ "$arch" = "aarch64" ]; then
250+ binary_name="agentapi-linux-arm64"
251+ else
252+ echo "Error: Unsupported architecture: $arch"
253+ exit 1
254+ fi
255+ wget "https://github.com/coder/agentapi/releases/download/${ var . agentapi_version } /$binary_name"
256+ chmod +x "$binary_name"
257+ sudo mv "$binary_name" /usr/local/bin/agentapi
258+ fi
259+ if ! command_exists agentapi; then
260+ echo "Error: AgentAPI is not installed. Please enable install_agentapi or install it manually."
261+ exit 1
262+ fi
263+
264+ # save the prompt for the agentapi start command
265+ echo -n "$CODER_MCP_CLAUDE_TASK_PROMPT" > ~/.claude-code-prompt
266+
267+ echo -n "${ local . agentapi_start_command_base64 } " | base64 -d > ~/.agentapi-start-command
268+ chmod +x ~/.agentapi-start-command
269+ echo -n "${ local . agentapi_wait_for_start_command_base64 } " | base64 -d > ~/.agentapi-wait-for-start-command
270+ chmod +x ~/.agentapi-wait-for-start-command
271+
179272 if [ "${ var . experiment_report_tasks } " = "true" ]; then
180273 echo "Configuring Claude Code to report tasks via Coder MCP..."
181- coder exp mcp configure claude-code ${ var . folder }
274+ coder exp mcp configure claude-code ${ local . workdir } --ai-agentapi-url http://localhost:3284
182275 fi
183276
184277 if [ -n "${ local . encoded_post_install_script } " ]; then
@@ -257,17 +350,16 @@ EOF
257350 export LANG=en_US.UTF-8
258351 export LC_ALL=en_US.UTF-8
259352
353+ tmux new-session -d -s agentapi-cc -c ${ local . workdir } '~/.agentapi-start-command true; exec bash'
354+ ~/.agentapi-wait-for-start-command
355+
260356 if [ "${ var . experiment_tmux_session_persistence } " = "true" ]; then
261357 sleep 3
358+ fi
262359
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
360+ if ! tmux has-session -t claude-code 2>/dev/null; then
361+ # Only create a new session if one doesn't exist
362+ tmux new-session -d -s claude-code -c ${ local . workdir } "agentapi attach; exec bash"
271363 fi
272364 fi
273365
297389 export LANG=en_US.UTF-8
298390 export LC_ALL=en_US.UTF-8
299391
392+ screen -U -dmS agentapi-cc bash -c '
393+ cd ${ local . workdir }
394+ # setting the first argument will make claude use the prompt
395+ ~/.agentapi-start-command true
396+ exec bash
397+ '
398+ ~/.agentapi-wait-for-start-command
399+
300400 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"
401+ cd ${ local . workdir }
402+ agentapi attach
303403 exec bash
304404 '
305405 else
312412 run_on_start = true
313413}
314414
415+ resource "coder_app" "claude_code_web" {
416+ # use a short slug to mitigate https://github.com/coder/coder/issues/15178
417+ slug = " ccw"
418+ display_name = " Claude Code Web"
419+ agent_id = var. agent_id
420+ url = " http://localhost:3284/"
421+ icon = var. icon
422+ subdomain = true
423+ healthcheck {
424+ url = " http://localhost:3284/status"
425+ interval = 5
426+ threshold = 3
427+ }
428+ }
429+
315430resource "coder_app" "claude_code" {
316431 slug = " claude-code"
317432 display_name = " Claude Code"
@@ -324,31 +439,47 @@ resource "coder_app" "claude_code" {
324439 export LC_ALL=en_US.UTF-8
325440
326441 if [ "${ var . experiment_use_tmux } " = "true" ]; then
442+ if ! tmux has-session -t agentapi-cc 2>/dev/null; then
443+ # start agentapi without claude using the prompt (no argument)
444+ tmux new-session -d -s agentapi-cc -c ${ local . workdir } '~/.agentapi-start-command; exec bash'
445+ ~/.agentapi-wait-for-start-command
446+ fi
447+
327448 if tmux has-session -t claude-code 2>/dev/null; then
328449 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
332- 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 "agentapi-cc"; then
457+ screen -S agentapi-cc 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