@@ -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,75 @@ 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
+ # we have to trim the slash because otherwise coder exp mcp will
113
+ # set up an invalid claude config
114
+ workdir = trimsuffix (var. folder , " /" )
115
+ encoded_pre_install_script = var. experiment_pre_install_script != null ? base64encode (var. experiment_pre_install_script ) : " "
116
+ encoded_post_install_script = var. experiment_post_install_script != null ? base64encode (var. experiment_post_install_script ) : " "
117
+ agentapi_start_command = <<- EOT
118
+ #!/bin/bash
119
+ set -e
120
+
121
+ # if the first argument is not empty, start claude with the prompt
122
+ if [ -n "$1" ]; then
123
+ prompt="$(cat ~/.claude-code-prompt)"
124
+ cp ~/.claude-code-prompt /tmp/claude-code-prompt
125
+ else
126
+ rm -f /tmp/claude-code-prompt
127
+ fi
128
+
129
+ # We need to check if there's a session to use --continue. If there's no session,
130
+ # using this flag would cause claude to exit with an error.
131
+ # warning: this is a hack and will break if claude changes the format of the .claude.json file.
132
+ # Also, this solution is not ideal: a user has to quit claude in order for the session id to appear
133
+ # in .claude.json. If they just restart the workspace, the session id will not be available.
134
+ continue_flag=""
135
+ if grep -q '"lastSessionId":' ~/.claude.json; then
136
+ echo "Found a Claude Code session to continue."
137
+ continue_flag="--continue"
138
+ else
139
+ echo "No Claude Code session to continue."
140
+ fi
141
+
142
+ # use low width to fit in the tasks UI sidebar. height is adjusted so that width x height ~= 80x1000 characters
143
+ # visible in the terminal screen by default.
144
+ prompt_subshell='"$(cat /tmp/claude-code-prompt)"'
145
+ agentapi server --term-width 67 --term-height 1190 -- bash -c "claude $continue_flag --dangerously-skip-permissions $prompt_subshell"
146
+ EOT
147
+ agentapi_wait_for_start_command = <<- EOT
148
+ #!/bin/bash
149
+ set -o errexit
150
+ set -o pipefail
151
+
152
+ echo "Waiting for agentapi server to start on port 3284..."
153
+ for i in $(seq 1 15); do
154
+ if lsof -i :3284 | grep -q 'LISTEN'; then
155
+ echo "agentapi server started on port 3284."
156
+ break
157
+ fi
158
+ echo "Waiting... ($i/15)"
159
+ sleep 1
160
+ done
161
+ if ! lsof -i :3284 | grep -q 'LISTEN'; then
162
+ echo "Error: agentapi server did not start on port 3284 after 15 seconds."
163
+ exit 1
164
+ fi
165
+ EOT
166
+ agentapi_start_command_base64 = base64encode (local. agentapi_start_command )
167
+ agentapi_wait_for_start_command_base64 = base64encode (local. agentapi_wait_for_start_command )
102
168
}
103
169
104
170
# Install and Initialize Claude Code
@@ -132,12 +198,12 @@ resource "coder_script" "claude_code" {
132
198
fi
133
199
}
134
200
135
- if [ ! -d "${ var . folder } " ]; then
136
- echo "Warning: The specified folder '${ var . folder } ' does not exist."
201
+ if [ ! -d "${ local . workdir } " ]; then
202
+ echo "Warning: The specified folder '${ local . workdir } ' does not exist."
137
203
echo "Creating the folder..."
138
204
# The folder must exist before tmux is started or else claude will start
139
205
# in the home directory.
140
- mkdir -p "${ var . folder } "
206
+ mkdir -p "${ local . workdir } "
141
207
echo "Folder created successfully."
142
208
fi
143
209
if [ -n "${ local . encoded_pre_install_script } " ]; then
@@ -176,9 +242,38 @@ resource "coder_script" "claude_code" {
176
242
npm install -g @anthropic-ai/claude-code@${ var . claude_code_version }
177
243
fi
178
244
245
+ # Install AgentAPI if enabled
246
+ if [ "${ var . install_agentapi } " = "true" ]; then
247
+ echo "Installing AgentAPI..."
248
+ arch=$(uname -m)
249
+ if [ "$arch" = "x86_64" ]; then
250
+ binary_name="agentapi-linux-amd64"
251
+ elif [ "$arch" = "aarch64" ]; then
252
+ binary_name="agentapi-linux-arm64"
253
+ else
254
+ echo "Error: Unsupported architecture: $arch"
255
+ exit 1
256
+ fi
257
+ wget "https://github.com/coder/agentapi/releases/download/${ var . agentapi_version } /$binary_name"
258
+ chmod +x "$binary_name"
259
+ sudo mv "$binary_name" /usr/local/bin/agentapi
260
+ fi
261
+ if ! command_exists agentapi; then
262
+ echo "Error: AgentAPI is not installed. Please enable install_agentapi or install it manually."
263
+ exit 1
264
+ fi
265
+
266
+ # save the prompt for the agentapi start command
267
+ echo -n "$CODER_MCP_CLAUDE_TASK_PROMPT" > ~/.claude-code-prompt
268
+
269
+ echo -n "${ local . agentapi_start_command_base64 } " | base64 -d > ~/.agentapi-start-command
270
+ chmod +x ~/.agentapi-start-command
271
+ echo -n "${ local . agentapi_wait_for_start_command_base64 } " | base64 -d > ~/.agentapi-wait-for-start-command
272
+ chmod +x ~/.agentapi-wait-for-start-command
273
+
179
274
if [ "${ var . experiment_report_tasks } " = "true" ]; then
180
275
echo "Configuring Claude Code to report tasks via Coder MCP..."
181
- coder exp mcp configure claude-code ${ var . folder }
276
+ coder exp mcp configure claude-code ${ local . workdir } --ai-agentapi-url http://localhost:3284
182
277
fi
183
278
184
279
if [ -n "${ local . encoded_post_install_script } " ]; then
@@ -257,17 +352,16 @@ EOF
257
352
export LANG=en_US.UTF-8
258
353
export LC_ALL=en_US.UTF-8
259
354
355
+ tmux new-session -d -s agentapi-cc -c ${ local . workdir } '~/.agentapi-start-command true; exec bash'
356
+ ~/.agentapi-wait-for-start-command
357
+
260
358
if [ "${ var . experiment_tmux_session_persistence } " = "true" ]; then
261
359
sleep 3
360
+ fi
262
361
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
362
+ if ! tmux has-session -t claude-code 2>/dev/null; then
363
+ # Only create a new session if one doesn't exist
364
+ tmux new-session -d -s claude-code -c ${ local . workdir } "agentapi attach; exec bash"
271
365
fi
272
366
fi
273
367
297
391
export LANG=en_US.UTF-8
298
392
export LC_ALL=en_US.UTF-8
299
393
394
+ screen -U -dmS agentapi-cc bash -c '
395
+ cd ${ local . workdir }
396
+ # setting the first argument will make claude use the prompt
397
+ ~/.agentapi-start-command true
398
+ exec bash
399
+ '
400
+ ~/.agentapi-wait-for-start-command
401
+
300
402
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"
403
+ cd ${ local . workdir }
404
+ agentapi attach
303
405
exec bash
304
406
'
305
407
else
312
414
run_on_start = true
313
415
}
314
416
417
+ resource "coder_app" "claude_code_web" {
418
+ # use a short slug to mitigate https://github.com/coder/coder/issues/15178
419
+ slug = " ccw"
420
+ display_name = " Claude Code Web"
421
+ agent_id = var. agent_id
422
+ url = " http://localhost:3284/"
423
+ icon = var. icon
424
+ subdomain = true
425
+ healthcheck {
426
+ url = " http://localhost:3284/status"
427
+ interval = 5
428
+ threshold = 3
429
+ }
430
+ }
431
+
315
432
resource "coder_app" "claude_code" {
316
433
slug = " claude-code"
317
434
display_name = " Claude Code"
@@ -324,31 +441,47 @@ resource "coder_app" "claude_code" {
324
441
export LC_ALL=en_US.UTF-8
<
F987
tr class="diff-line-row">325
442
326
443
if [ "${ var . experiment_use_tmux } " = "true" ]; then
444
+ if ! tmux has-session -t agentapi-cc 2>/dev/null; then
445
+ # start agentapi without claude using the prompt (no argument)
446
+ tmux new-session -d -s agentapi-cc -c ${ local . workdir } '~/.agentapi-start-command; exec bash'
447
+ ~/.agentapi-wait-for-start-command
448
+ fi
449
+
327
450
if tmux has-session -t claude-code 2>/dev/null; then
328
451
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
452
tmux attach-session -t claude-code
334
453
else
335
454
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"
455
+ tmux new-session -s claude-code -c ${ local . workdir } "agentapi attach ; exec bash"
337
456
fi
338
457
elif [ "${ var . experiment_use_screen } " = "true" ]; then
458
+ if ! screen -list | grep -q "agentapi-cc"; then
459
+ screen -S agentapi-cc bash -c '
460
+ cd ${ local . workdir }
461
+ # start agentapi without claude using the prompt (no argument)
462
+ ~/.agentapi-start-command
463
+ exec bash
464
+ '
465
+ fi
339
466
if screen -list | grep -q "claude-code"; then
340
467
echo "Attaching to existing Claude Code screen session." | tee -a "$HOME/.claude-code.log"
341
468
screen -xRR claude-code
342
469
else
343
470
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'
471
+ screen -S claude-code bash -c 'agentapi attach ; exec bash'
345
472
fi
346
473
else
347
- cd ${ var . folder }
348
- claude
474
+ cd ${ local . workdir }
475
+ agentapi attach
349
476
fi
350
477
EOT
351
478
icon = var. icon
352
479
order = var. order
353
480
group = var. group
354
481
}
482
+
483
+ resource "coder_ai_task" "claude_code" {
484
+ sidebar_app {
485
+ id = coder_app. claude_code . id
486
+ }
487
+ }
0 commit comments