diff --git a/registry/coder-labs/modules/gemini/README.md b/registry/coder-labs/modules/gemini/README.md index d0a113a02..7244eb10b 100644 --- a/registry/coder-labs/modules/gemini/README.md +++ b/registry/coder-labs/modules/gemini/README.md @@ -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" } ``` @@ -46,10 +46,10 @@ 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" } ``` @@ -57,7 +57,7 @@ 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 @@ -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 @@ -111,6 +111,23 @@ 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). + +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: @@ -118,10 +135,10 @@ 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 } ``` diff --git a/registry/coder-labs/modules/gemini/main.tf b/registry/coder-labs/modules/gemini/main.tf index dbc81bc79..7e29bd559 100644 --- a/registry/coder-labs/modules/gemini/main.tf +++ b/registry/coder-labs/modules/gemini/main.tf @@ -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" { @@ -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 +} + resource "coder_env" "gemini_api_key" { agent_id = var.agent_id name = "GEMINI_API_KEY" @@ -148,21 +160,17 @@ locals { base_extensions = <<-EOT { "coder": { + "command": "coder", "args": [ "exp", "mcp", "server" ], - "command": "coder", - "description": "Report ALL tasks and statuses (in progress, done, failed) you are working on.", - "enabled": true, "env": { "CODER_MCP_APP_STATUS_SLUG": "${local.app_slug}", "CODER_MCP_AI_AGENTAPI_URL": "http://localhost:3284" }, - "name": "Coder", "timeout": 3000, - "type": "stdio", "trust": true } } @@ -177,23 +185,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 @@ -209,20 +218,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 } diff --git a/registry/coder-labs/modules/gemini/main.tftest.hcl b/registry/coder-labs/modules/gemini/main.tftest.hcl new file mode 100644 index 000000000..7bef07221 --- /dev/null +++ b/registry/coder-labs/modules/gemini/main.tftest.hcl @@ -0,0 +1,202 @@ +run "test_gemini_basic" { + command = plan + + 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" + } +} diff --git a/registry/coder-labs/modules/gemini/scripts/install.sh b/registry/coder-labs/modules/gemini/scripts/install.sh index 7b70a6af4..c62f48181 100644 --- a/registry/coder-labs/modules/gemini/scripts/install.sh +++ b/registry/coder-labs/modules/gemini/scripts/install.sh @@ -1,6 +1,5 @@ #!/bin/bash -BOLD='\033[0;1m' source "$HOME"/.bashrc command_exists() { command -v "$1" > /dev/null 2>&1 @@ -40,7 +39,7 @@ function install_gemini() { if [ "${ARG_INSTALL}" = "true" ]; then check_dependencies - printf "%s Installing Gemini CLI\n" "${BOLD}" + printf "Installing Gemini CLI\n" NPM_GLOBAL_PREFIX="${HOME}/.npm-global" if [ ! -d "$NPM_GLOBAL_PREFIX" ]; then @@ -61,7 +60,7 @@ function install_gemini() { echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> "$HOME/.bashrc" fi - printf "%s Successfully installed Gemini CLI. Version: %s\n" "${BOLD}" "$(gemini --version)" + printf "Successfully installed Gemini CLI. Version: %s\n" "$(gemini --version)" fi } @@ -140,13 +139,8 @@ function add_system_prompt_if_exists() { fi } -function configure_mcp() { - export CODER_MCP_APP_STATUS_SLUG="gemini" - export CODER_MCP_AI_AGENTAPI_URL="http://localhost:3284" - coder exp mcp configure gemini "${GEMINI_START_DIRECTORY}" -} + install_gemini populate_settings_json add_system_prompt_if_exists -configure_mcp diff --git a/registry/coder-labs/modules/gemini/scripts/start.sh b/registry/coder-labs/modules/gemini/scripts/start.sh index eed550907..067a3e17c 100644 --- a/registry/coder-labs/modules/gemini/scripts/start.sh +++ b/registry/coder-labs/modules/gemini/scripts/start.sh @@ -4,6 +4,19 @@ set -o pipefail source "$HOME"/.bashrc +set -o nounset + +GEMINI_API_KEY=$(echo -n "$GEMINI_API_KEY" | base64 -d) +GOOGLE_API_KEY=$(echo -n "$GOOGLE_API_KEY" | base64 -d) +GOOGLE_GENAI_USE_VERTEXAI=$(echo -n "$GOOGLE_GENAI_USE_VERTEXAI" | base64 -d) +GEMINI_YOLO_MODE=$(echo -n "$GEMINI_YOLO_MODE" | base64 -d) +GEMINI_MODEL=$(echo -n "$GEMINI_MODEL" | base64 -d) +GEMINI_START_DIRECTORY=$(echo -n "$GEMINI_START_DIRECTORY" | base64 -d) +GEMINI_TASK_PROMPT=$(echo -n "$GEMINI_TASK_PROMPT" | base64 -d) +ARG_CONTINUE=$(echo -n "$ARG_CONTINUE" | base64 -d) + +set +o nounset + command_exists() { command -v "$1" > /dev/null 2>&1 } @@ -14,7 +27,7 @@ else export PATH="$HOME/.npm-global/bin:$PATH" fi -printf "Version: %s\n" "$(gemini --version)" +printf "Gemini CLI Version: %s\n" "$(gemini --version)" MODULE_DIR="$HOME/.gemini-module" mkdir -p "$MODULE_DIR" @@ -44,22 +57,6 @@ else } fi -if [ -n "$GEMINI_TASK_PROMPT" ]; then - printf "Running automated task: %s\n" "$GEMINI_TASK_PROMPT" - PROMPT="Every step of the way, report tasks to Coder with proper descriptions and statuses. Your task at hand: $GEMINI_TASK_PROMPT" - PROMPT_FILE="$MODULE_DIR/prompt.txt" - echo -n "$PROMPT" > "$PROMPT_FILE" - GEMINI_ARGS=(--prompt-interactive "$PROMPT") -else - printf "Starting Gemini CLI in interactive mode.\n" - GEMINI_ARGS=() -fi - -if [ -n "$GEMINI_YOLO_MODE" ] && [ "$GEMINI_YOLO_MODE" = "true" ]; then - printf "YOLO mode enabled - will auto-approve all tool calls\n" - GEMINI_ARGS+=(--yolo) -fi - if [ -n "$GEMINI_API_KEY" ] || [ -n "$GOOGLE_API_KEY" ]; then if [ -n "$GOOGLE_GENAI_USE_VERTEXAI" ] && [ "$GOOGLE_GENAI_USE_VERTEXAI" = "true" ]; then printf "Using Vertex AI with API key\n" @@ -70,5 +67,51 @@ else printf "No API key provided (neither GEMINI_API_KEY nor GOOGLE_API_KEY)\n" fi -agentapi server --term-width 67 --term-height 1190 -- \ +GEMINI_ARGS=() +configure_gemini() { + if [ "$ARG_CONTINUE" = "true" ]; then + SESSION_FOLDER_NAME=$(basename "${GEMINI_START_DIRECTORY}") + if [ -d "$GEMINI_START_DIRECTORY/.gemini/tmp/$SESSION_FOLDER_NAME/chats/" ]; then + printf "Existing Gemini chats detected. Starting Gemini CLI in interactive mode with existing chats.\n" + GEMINI_ARGS+=(--resume) + else + printf "No existing Gemini chats found. Starting Gemini CLI in interactive mode.\n" + if [ -n "$GEMINI_TASK_PROMPT" ]; then + printf "Running automated task: %s\n" "$GEMINI_TASK_PROMPT" + PROMPT="Every step of the way, report tasks to Coder with proper descriptions and statuses. Your task at hand: $GEMINI_TASK_PROMPT" + PROMPT_FILE="$MODULE_DIR/prompt.txt" + echo -n "$PROMPT" > "$PROMPT_FILE" + GEMINI_ARGS+=(--prompt-interactive "$PROMPT") + else + printf "Starting Gemini CLI in interactive mode.\n" + GEMINI_ARGS+=() + fi + fi + else + printf "Continue disabled, starting fresh Gemini CLI session\n" + if [ -n "$GEMINI_TASK_PROMPT" ]; then + printf "Running automated task: %s\n" "$GEMINI_TASK_PROMPT" + PROMPT="Every step of the way, report tasks to Coder with proper descriptions and statuses. Your task at hand: $GEMINI_TASK_PROMPT" + PROMPT_FILE="$MODULE_DIR/prompt.txt" + echo -n "$PROMPT" > "$PROMPT_FILE" + GEMINI_ARGS+=(--prompt-interactive "$PROMPT") + else + printf "Starting Gemini CLI in interactive mode.\n" + GEMINI_ARGS+=() + fi + fi + + if [ -n "$GEMINI_MODEL" ]; then + GEMINI_ARGS+=(--model "$GEMINI_MODEL") + fi + + if [ -n "$GEMINI_YOLO_MODE" ] && [ "$GEMINI_YOLO_MODE" = "true" ]; then + printf "YOLO mode enabled - will auto-approve all tool calls\n" + GEMINI_ARGS+=(--approval-mode=yolo) + fi +} + +configure_gemini + +agentapi server --type gemini --term-width 67 --term-height 1190 -- \ bash -c "$(printf '%q ' gemini "${GEMINI_ARGS[@]}")"