@@ -4,7 +4,7 @@ terraform {
4
4
required_providers {
5
5
coder = {
6
6
source = " coder/coder"
7
- version = " >= 2.5 "
7
+ version = " >= 2.7 "
8
8
}
9
9
}
10
10
}
@@ -96,9 +96,73 @@ variable "experiment_tmux_session_save_interval" {
96
96
default = " 15"
97
97
}
98
98
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
+
99
111
locals {
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 )
102
166
}
103
167
104
168
# Install and Initialize Claude Code
@@ -132,12 +196,12 @@ resource "coder_script" "claude_code" {
132
196
fi
133
197
}
134
198
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."
137
201
echo "Creating the folder..."
138
202
# The folder must exist before tmux is started or else claude will start
139
203
# in the home directory.
140
- mkdir -p "${ var . folder } "
204
+ mkdir -p "${ local . workdir } "
141
205
echo "Folder created successfully."
142
206
fi
143
207
if [ -n "${ local . encoded_pre_install_script } " ]; then
@@ -176,9 +240,38 @@ resource "coder_script" "claude_code" {
176
240
npm install -g @anthropic-ai/claude-code@${ var . claude_code_version }
177
241
fi
178
242
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
+
179
272
if [ "${ var . experiment_report_tasks } " = "true" ]; then
180
273
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
182
275
fi
183
276
184
277
if [ -n "${ local . encoded_post_install_script } " ]; then
@@ -257,17 +350,16 @@ EOF
257
350
export LANG=en_US.UTF-8
258
351
export LC_ALL=en_US.UTF-8
259
352
353
+ tmux new-session -d -s agentapi-cc -c ${ local . workdir } '~/.agentapi-start-command true; exec bash'
354
+ ~/.agentapi-wait-for-start-command
355
+
260
356
if [ "${ var . experiment_tmux_session_persistence } " = "true" ]; then
261
357
sleep 3
358
+ fi
262
359
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"
271
363
fi
272
364
fi
273
365
297
389
export LANG=en_US.UTF-8
298
390
export LC_ALL=en_US.UTF-8
299
391
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
+
300
400
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
303
403
exec bash
304
404
'
305
405
else
312
412
run_on_start = true
313
413
}
314
414
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
+
315
430
resource "coder_app" "claude_code" {
316
431
slug = " claude-code"
317
432
display_name = " Claude Code"
@@ -324,31 +439,47 @@ resource "coder_app" "claude_code" {
324
439
export LC_ALL=en_US.UTF-8
325
440
326
441
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
+
327
448
if tmux has-session -t claude-code 2>/dev/null; then
328
449
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
333
450
tmux attach-session -t claude-code
334
451
else
335
452
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"
337
454
fi
338
455
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
339
464
if screen -list | grep -q "claude-code"; then
340
465
echo "Attaching to existing Claude Code screen session." | tee -a "$HOME/.claude-code.log"
341
466
screen -xRR claude-code
342
467
else
343
468
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'
345
470
fi
346
471
else
347
- cd ${ var . folder }
348
- claude
472
+ cd ${ local . workdir }
473
+ agentapi attach
349
474
fi
350
475
EOT
351
476
icon = var. icon
352
477
order = var. order
353
478
group = var. group
354
479
}
480
+
481
+ resource "coder_ai_task" "claude_code" {
482
+ sidebar_app {
483
+ id = coder_app. claude_code . id
484
+ }
485
+ }
0 commit comments