Skip to content
35 changes: 26 additions & 9 deletions registry/coder-labs/modules/gemini/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ Run [Gemini CLI](https://github.com/google-gemini/gemini-cli) in your workspace
```tf
module "gemini" {
source = "registry.coder.com/coder-labs/gemini/coder"
version = "3.0.0"
version = "3.0.1"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
folder = "/home/coder"
}
```

Expand Down Expand Up @@ -46,18 +46,18 @@ variable "gemini_api_key" {

module "gemini" {
source = "registry.coder.com/coder-labs/gemini/coder"
version = "3.0.0"
version = "3.0.1"
agent_id = coder_agent.main.id
gemini_api_key = var.gemini_api_key
folder = "/home/coder/project"
folder = "/home/coder"
}
```

This basic setup will:

- Install Gemini CLI in the workspace
- Configure authentication with your API key
- Set Gemini to run in `/home/coder/project` directory
- Set Gemini to run in `/home/coder` directory
- Enable interactive use from the terminal
- Set up MCP server integration for task reporting

Expand Down Expand Up @@ -94,11 +94,11 @@ data "coder_parameter" "ai_prompt" {
module "gemini" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/gemini/coder"
version = "3.0.0"
version = "3.0.1"
agent_id = coder_agent.main.id
gemini_api_key = var.gemini_api_key
gemini_model = "gemini-2.5-flash"
folder = "/home/coder/project"
folder = "/home/coder"
task_prompt = data.coder_parameter.ai_prompt.value
enable_yolo_mode = true # Auto-approve all tool calls for automation
gemini_system_prompt = <<-EOT
Expand All @@ -111,17 +111,34 @@ module "gemini" {
> [!WARNING]
> YOLO mode automatically approves all tool calls without user confirmation. The agent has access to your machine's file system and terminal. Only enable in trusted, isolated environments.

### Session Resumption Behavior

By default, Gemini CLI automatically resumes existing conversations when your workspace restarts. Sessions are tracked per workspace directory, so conversations continue where you left off. If no session exists (first start), your `ai_prompt` will run normally. To disable this behavior and always start fresh, set `continue = false`

## State Persistence

AgentAPI can save and restore its conversation state to disk across workspace restarts. This complements `continue` (which resumes the Gemini CLI session) by also preserving the AgentAPI-level context. Enabled by default, requires agentapi >= v0.12.0 (older versions skip it with a warning).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 continue in backticks implies a command or flag, but it doesn't exist in this module. The script uses --resume. (Hisoka P2, Leorio Nit, Pen Botter Nit)

grep -n "continue" registry/coder-labs/modules/gemini/main.tf returns no results. This text appears to be copied from the codex module's README, which does have a continue variable. A reader searching for continue in this codebase will find nothing.

If this refers to the --resume flag, call it --resume. If it's a Gemini CLI concept, link or explain it.

Nit The ## State Persistence heading breaks the heading hierarchy. ### Using Vertex AI (Enterprise) was previously a child of ## Examples. Now it appears to be a child of ## State Persistence, which has nothing to do with Vertex AI. Move this section after the Vertex AI subsection, or place it before ## Troubleshooting. (Leorio Nit, Pen Botter Nit)

🤖

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 continue in backticks implies a command or flag, but it doesn't exist in this module. The script uses --resume. (Hisoka P2, Leorio Nit, Pen Botter Nit)

grep -n "continue" registry/coder-labs/modules/gemini/main.tf returns no results. This text appears to be copied from the codex module's README, which does have a continue variable. A reader searching for continue in this codebase will find nothing.

If this refers to the --resume flag, call it --resume. If it's a Gemini CLI concept, link or explain it.

Nit The ## State Persistence heading breaks the heading hierarchy. ### Using Vertex AI (Enterprise) was previously a child of ## Examples. Now it appears to be a child of ## State Persistence, which has nothing to do with Vertex AI. Move this section after the Vertex AI subsection, or place it before ## Troubleshooting. (Leorio Nit, Pen Botter Nit)

🤖

To disable:

```tf
module "gemini" {
# ... other config
enable_state_persistence = false
}
```

### Using Vertex AI (Enterprise)

For enterprise users who prefer Google's Vertex AI platform:

```tf
module "gemini" {
source = "registry.coder.com/coder-labs/gemini/coder"
version = "3.0.0"
version = "3.0.1"
agent_id = coder_agent.main.id
gemini_api_key = var.gemini_api_key
folder = "/home/coder/project"
folder = "/home/coder"
use_vertexai = true
}
```
Expand Down
66 changes: 40 additions & 26 deletions registry/coder-labs/modules/gemini/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,16 @@ variable "install_agentapi" {
default = true
}

variable "continue" {
type = bool
description = "Automatically continue existing sessions on workspace restart. When true, resumes existing conversation if found, otherwise runs prompt or starts new session. When false, always starts fresh (ignores existing sessions)."
default = true
}

variable "agentapi_version" {
type = string
description = "The version of AgentAPI to install."
default = "v0.10.0"
default = "v0.12.0"
}

variable "gemini_model" {
Expand Down Expand Up @@ -126,6 +132,12 @@ variable "enable_yolo_mode" {
default = false
}

variable "enable_state_persistence" {
type = bool
description = "Enable AgentAPI conversation state persistence across restarts."
default = true
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 enable_state_persistence defaults to true, but agentapi_version defaults to v0.10.0, which is below the >= v0.12.0 requirement stated in the README. (Mafuuu P1, Mafu-san P1, Meruem P1, Hisoka P2, Kurapika P2, Luffy P2)

A user who accepts all defaults gets enable_state_persistence = true with an agentapi binary that can't support it. The agentapi module gracefully skips persistence with a warning for old versions, but the default configuration enables a feature that cannot work. The codex module gets this right by defaulting agentapi_version to v0.12.1 when enabling state persistence.

Either bump the default agentapi_version to at least v0.12.0, or default enable_state_persistence to false.

🤖

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 enable_state_persistence defaults to true, but agentapi_version defaults to v0.10.0, which is below the >= v0.12.0 requirement stated in the README. (Mafuuu P1, Mafu-san P1, Meruem P1, Hisoka P2, Kurapika P2, Luffy P2)

A user who accepts all defaults gets enable_state_persistence = true with an agentapi binary that can't support it. The agentapi module gracefully skips persistence with a warning for old versions, but the default configuration enables a feature that cannot work. The codex module gets this right by defaulting agentapi_version to v0.12.1 when enabling state persistence.

Either bump the default agentapi_version to at least v0.12.0, or default enable_state_persistence to false.

🤖

}

resource "coder_env" "gemini_api_key" {
agent_id = var.agent_id
name = "GEMINI_API_KEY"
Expand Down Expand Up @@ -177,23 +189,24 @@ EOT

module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "2.0.0"

agent_id = var.agent_id
folder = local.folder
web_app_slug = local.app_slug
web_app_order = var.order
web_app_group = var.group
web_app_icon = var.icon
web_app_display_name = "Gemini"
cli_app_slug = "${local.app_slug}-cli"
cli_app_display_name = "Gemini CLI"
module_dir_name = local.module_dir_name
install_agentapi = var.install_agentapi
agentapi_version = var.agentapi_version
pre_install_script = var.pre_install_script
post_install_script = var.post_install_script
install_script = <<-EOT
version = "2.2.0"

agent_id = var.agent_id
folder = local.folder
web_app_slug = local.app_slug
web_app_order = var.order
web_app_group = var.group
web_app_icon = var.icon
web_app_display_name = "Gemini"
cli_app_slug = "${local.app_slug}-cli"
cli_app_display_name = "Gemini CLI"
module_dir_name = local.module_dir_name
install_agentapi = var.install_agentapi
agentapi_version = var.agentapi_version
enable_state_persistence = var.enable_state_persistence
pre_install_script = var.pre_install_script
post_install_script = var.post_install_script
install_script = <<-EOT
#!/bin/bash
set -o errexit
set -o pipefail
Expand All @@ -209,20 +222,21 @@ module "agentapi" {
GEMINI_SYSTEM_PROMPT='${base64encode(var.gemini_system_prompt)}' \
/tmp/install.sh
EOT
start_script = <<-EOT
start_script = <<-EOT
#!/bin/bash
set -o errexit
set -o pipefail

echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh
chmod +x /tmp/start.sh
GEMINI_API_KEY='${var.gemini_api_key}' \
GOOGLE_API_KEY='${var.gemini_api_key}' \
GOOGLE_GENAI_USE_VERTEXAI='${var.use_vertexai}' \
GEMINI_YOLO_MODE='${var.enable_yolo_mode}' \
GEMINI_MODEL='${var.gemini_model}' \
GEMINI_START_DIRECTORY='${var.folder}' \
GEMINI_TASK_PROMPT='${var.task_prompt}' \
GEMINI_API_KEY='${base64encode(var.gemini_api_key)}' \
GOOGLE_API_KEY='${base64encode(var.gemini_api_key)}' \
GOOGLE_GENAI_USE_VERTEXAI='${base64encode(var.use_vertexai)}' \
GEMINI_YOLO_MODE='${base64encode(var.enable_yolo_mode)}' \
GEMINI_MODEL='${base64encode(var.gemini_model)}' \
GEMINI_START_DIRECTORY='${base64encode(var.folder)}' \
GEMINI_TASK_PROMPT='${base64encode(var.task_prompt)}' \
ARG_CONTINUE='${base64encode(var.continue)}' \
/tmp/start.sh
EOT
}
Expand Down
202 changes: 202 additions & 0 deletions registry/coder-labs/modules/gemini/main.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
run "test_gemini_basic" {
command = plan
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Every test assertion checks that a variable equals what it was just set to, or that a default equals its declared default. This proves Terraform can assign variables, not that the module works. (Bisky P1, Meruem P3, Hisoka Obs)

test_enable_state_persistence_default asserts var.enable_state_persistence == true, which is tautological. None of the tests verify that enable_state_persistence actually flows through to the agentapi module, that the start_script contains expected content, or that the base64 encoding works. The only slightly real assertion is test_gemini_with_api_key checking coder_env.gemini_api_key[0].value.

Plan-level assertions on module outputs or resource attributes would catch wiring errors. Having a test file is better than not, but these particular tests create a false sense of coverage.

PS. Missing newline at end of file (line 82).

🤖

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Every test assertion checks that a variable equals what it was just set to, or that a default equals its declared default. This proves Terraform can assign variables, not that the module works. (Bisky P1, Meruem P3, Hisoka Obs)

test_enable_state_persistence_default asserts var.enable_state_persistence == true, which is tautological. None of the tests verify that enable_state_persistence actually flows through to the agentapi module, that the start_script contains expected content, or that the base64 encoding works. The only slightly real assertion is test_gemini_with_api_key checking coder_env.gemini_api_key[0].value.

Plan-level assertions on module outputs or resource attributes would catch wiring errors. Having a test file is better than not, but these particular tests create a false sense of coverage.

PS. Missing newline at end of file (line 82).

🤖


variables {
agent_id = "test-agent-123"
folder = "/home/coder"
}

assert {
condition = var.agent_id == "test-agent-123"
error_message = "Agent ID variable should be set correctly"
}

assert {
condition = var.folder == "/home/coder"
error_message = "Folder variable should be set correctly"
}

assert {
condition = var.install_gemini == true
error_message = "install_gemini should default to true"
}

assert {
condition = var.install_agentapi == true
error_message = "install_agentapi should default to true"
}

assert {
condition = var.use_vertexai == false
error_message = "use_vertexai should default to false"
}

assert {
condition = var.enable_yolo_mode == false
error_message = "enable_yolo_mode should default to false"
}
}

run "test_gemini_with_api_key" {
command = plan

variables {
agent_id = "test-agent-456"
folder = "/home/coder"
gemini_api_key = "test-api-key-123"
}

assert {
condition = coder_env.gemini_api_key[0].value == "test-api-key-123"
error_message = "Gemini API key value should match the input"
}
}

run "test_gemini_with_custom_options" {
command = plan

variables {
agent_id = "test-agent-789"
folder = "/home/coder/custom"
order = 5
group = "development"
icon = "/icon/custom.svg"
gemini_version = "1.0.0"
gemini_model = "gemini-pro"
agentapi_version = "v0.13.0"
continue = false
pre_install_script = "echo 'Pre-install script'"
post_install_script = "echo 'Post-install script'"
task_prompt = "Automate this task"
additional_extensions = "{ \"my-extension\": {} }"
gemini_system_prompt = "Custom system prompt"
}

assert {
condition = var.order == 5
error_message = "Order variable should be set to 5"
}

assert {
condition = var.group == "development"
error_message = "Group variable should be set to 'development'"
}

assert {
condition = var.icon == "/icon/custom.svg"
error_message = "Icon variable should be set to custom icon"
}

assert {
condition = var.gemini_version == "1.0.0"
error_message = "Gemini version should be set to '1.0.0'"
}

assert {
condition = var.gemini_model == "gemini-pro"
error_message = "Gemini model variable should be set to 'gemini-pro'"
}

assert {
condition = var.agentapi_version == "v0.13.0"
error_message = "AgentAPI version should be set to 'v0.13.0'"
}

assert {
condition = var.continue == false
error_message = "Continue should be set to false"
}

assert {
condition = var.pre_install_script == "echo 'Pre-install script'"
error_message = "Pre-install script should be set correctly"
}

assert {
condition = var.post_install_script == "echo 'Post-install script'"
error_message = "Post-install script should be set correctly"
}

assert {
condition = var.task_prompt == "Automate this task"
error_message = "Task prompt should be set correctly"
}

assert {
condition = var.additional_extensions == "{ \"my-extension\": {} }"
error_message = "Additional extensions should be set correctly"
}

assert {
condition = var.gemini_system_prompt == "Custom system prompt"
error_message = "Gemini system prompt should be set correctly"
}
}

run "test_gemini_system_prompt" {
command = plan

variables {
agent_id = "test-agent-system-prompt"
folder = "/home/coder/test"
gemini_system_prompt = "Custom addition"
}

assert {
condition = trimspace(coder_env.gemini_system_prompt.value) != ""
error_message = "System prompt should not be empty"
}

assert {
condition = length(regexall("Custom addition", coder_env.gemini_system_prompt.value)) > 0
error_message = "System prompt should have system_prompt variable value"
}
}

run "test_no_api_key_no_env" {
command = plan

variables {
agent_id = "test-agent-no-key"
folder = "/home/coder/test"
}

assert {
condition = length(coder_env.gemini_api_key) == 0
error_message = "GEMINI_API_KEY should not be created when no API key is provided"
}

assert {
condition = length(coder_env.google_api_key) == 0
error_message = "GOOGLE_API_KEY should not be created when no API key is provided"
}
}

run "test_enable_state_persistence_default" {
command = plan

variables {
agent_id = "test-agent"
workdir = "/home/coder"
}

assert {
condition = var.enable_state_persistence == true
error_message = "enable_state_persistence should default to true"
}
}

run "test_disable_state_persistence" {
command = plan

variables {
agent_id = "test-agent"
workdir = "/home/coder"
enable_state_persistence = false
}

assert {
condition = var.enable_state_persistence == false
error_message = "enable_state_persistence should be false when explicitly disabled"
}
}
Loading
Loading