diff --git a/registry/coder-labs/modules/gemini/README.md b/registry/coder-labs/modules/gemini/README.md index d0a113a02..e31028c75 100644 --- a/registry/coder-labs/modules/gemini/README.md +++ b/registry/coder-labs/modules/gemini/README.md @@ -111,6 +111,19 @@ 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. +## 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: diff --git a/registry/coder-labs/modules/gemini/main.tf b/registry/coder-labs/modules/gemini/main.tf index dbc81bc79..61d33c96e 100644 --- a/registry/coder-labs/modules/gemini/main.tf +++ b/registry/coder-labs/modules/gemini/main.tf @@ -39,7 +39,7 @@ variable "icon" { variable "folder" { type = string description = "The folder to run Gemini in." - default = "/home/coder" + default = "/home/coder/project" } variable "install_gemini" { @@ -126,6 +126,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" @@ -177,23 +183,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 +216,20 @@ 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)}' \ /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..96b664322 --- /dev/null +++ b/registry/coder-labs/modules/gemini/main.tftest.hcl @@ -0,0 +1,82 @@ +run "test_gemini_basic" { + command = plan + + variables { + agent_id = "test-agent-123" + folder = "/home/coder/projects" + } + + assert { + condition = var.agent_id == "test-agent-123" + error_message = "Agent ID variable should be set correctly" + } + + assert { + condition = var.folder == "/home/coder/projects" + 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_enable_state_persistence_default" { + command = plan + + variables { + agent_id = "test-agent" + folder = "/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" + folder = "/home/coder" + enable_state_persistence = false + } + + assert { + condition = var.enable_state_persistence == false + error_message = "enable_state_persistence should be false when explicitly disabled" + } +} \ No newline at end of file diff --git a/registry/coder-labs/modules/gemini/scripts/start.sh b/registry/coder-labs/modules/gemini/scripts/start.sh index eed550907..e60e18f20 100644 --- a/registry/coder-labs/modules/gemini/scripts/start.sh +++ b/registry/coder-labs/modules/gemini/scripts/start.sh @@ -4,6 +4,18 @@ 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) + +set +o nounset + command_exists() { command -v "$1" > /dev/null 2>&1 } @@ -44,22 +56,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" @@ -68,7 +64,32 @@ if [ -n "$GEMINI_API_KEY" ] || [ -n "$GOOGLE_API_KEY" ]; then fi else printf "No API key provided (neither GEMINI_API_KEY nor GOOGLE_API_KEY)\n" + exit 1 +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 -agentapi server --term-width 67 --term-height 1190 -- \ - bash -c "$(printf '%q ' gemini "${GEMINI_ARGS[@]}")" +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) + agentapi server --type gemini --term-width 67 --term-height 1190 -- \ + bash -c "$(printf '%q ' gemini "${GEMINI_ARGS[@]}")" +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 + agentapi server --type gemini --term-width 67 --term-height 1190 -- \ + bash -c "$(printf '%q ' gemini "${GEMINI_ARGS[@]}")" +fi