From 680f098499f478ead9b81a8eb3c25cf43dcfbb52 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:33:46 +0000 Subject: [PATCH 1/7] Initial plan From 898ad5b17b18c771e44c66acde722eb05de52907 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:50:20 +0000 Subject: [PATCH 2/7] Enable --enable-api-proxy flag for Gemini engine and add parse_gemini_log.cjs Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-gemini.lock.yml | 2 +- actions/setup/js/parse_gemini_log.cjs | 98 +++++++++++++++++++++++++ pkg/workflow/enable_api_proxy_test.go | 27 +++++++ pkg/workflow/gemini_engine.go | 2 +- 4 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 actions/setup/js/parse_gemini_log.cjs diff --git a/.github/workflows/smoke-gemini.lock.yml b/.github/workflows/smoke-gemini.lock.yml index b05b3d0d377..9f9b2748568 100644 --- a/.github/workflows/smoke-gemini.lock.yml +++ b/.github/workflows/smoke-gemini.lock.yml @@ -900,7 +900,7 @@ jobs: id: agentic_execution run: | set -o pipefail - sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains '*.githubusercontent.com,*.googleapis.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,generativelanguage.googleapis.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.20.2 --skip-pull \ + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains '*.githubusercontent.com,*.googleapis.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,generativelanguage.googleapis.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.20.2 --skip-pull --enable-api-proxy \ -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && gemini --yolo --output-format json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} diff --git a/actions/setup/js/parse_gemini_log.cjs b/actions/setup/js/parse_gemini_log.cjs new file mode 100644 index 00000000000..683782d89d5 --- /dev/null +++ b/actions/setup/js/parse_gemini_log.cjs @@ -0,0 +1,98 @@ +// @ts-check +/// + +const { createEngineLogParser } = require("./log_parser_shared.cjs"); + +const main = createEngineLogParser({ + parserName: "Gemini", + parseFunction: parseGeminiLog, + supportsDirectories: false, +}); + +/** + * Parse Gemini CLI JSON log output and format as markdown. + * Gemini CLI outputs a single JSON object per line when using --output-format json. + * @param {string} logContent - The raw log content to parse + * @returns {{markdown: string, logEntries: Array, mcpFailures: Array, maxTurnsHit: boolean}} Parsed log data + */ +function parseGeminiLog(logContent) { + if (!logContent) { + return { + markdown: "## 🤖 Gemini\n\nNo log content provided.\n\n", + logEntries: [], + mcpFailures: [], + maxTurnsHit: false, + }; + } + + let markdown = ""; + let totalInputTokens = 0; + let totalOutputTokens = 0; + let lastResponse = ""; + + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) { + continue; + } + + // Try to parse each line as a JSON object (Gemini --output-format json output) + try { + const parsed = JSON.parse(trimmed); + + if (parsed.response) { + lastResponse = parsed.response; + } + + // Aggregate token usage from stats + if (parsed.stats && parsed.stats.models) { + for (const modelStats of Object.values(parsed.stats.models)) { + if (modelStats && typeof modelStats === "object") { + if (typeof modelStats.input_tokens === "number") { + totalInputTokens += modelStats.input_tokens; + } + if (typeof modelStats.output_tokens === "number") { + totalOutputTokens += modelStats.output_tokens; + } + } + } + } + } catch (_e) { + // Not JSON - skip non-JSON lines + } + } + + // Build markdown output + if (lastResponse) { + markdown += "## 🤖 Reasoning\n\n"; + markdown += lastResponse + "\n\n"; + } + + markdown += "## 📊 Information\n\n"; + const totalTokens = totalInputTokens + totalOutputTokens; + if (totalTokens > 0) { + markdown += `**Total Tokens Used:** ${totalTokens.toLocaleString()}\n\n`; + if (totalInputTokens > 0) { + markdown += `**Input Tokens:** ${totalInputTokens.toLocaleString()}\n\n`; + } + if (totalOutputTokens > 0) { + markdown += `**Output Tokens:** ${totalOutputTokens.toLocaleString()}\n\n`; + } + } + + return { + markdown, + logEntries: [], + mcpFailures: [], + maxTurnsHit: false, + }; +} + +// Export for testing +if (typeof module !== "undefined" && module.exports) { + module.exports = { + main, + parseGeminiLog, + }; +} diff --git a/pkg/workflow/enable_api_proxy_test.go b/pkg/workflow/enable_api_proxy_test.go index d49dd5be7b2..6d86cc0a8f5 100644 --- a/pkg/workflow/enable_api_proxy_test.go +++ b/pkg/workflow/enable_api_proxy_test.go @@ -88,4 +88,31 @@ func TestEngineAWFEnableApiProxy(t *testing.T) { t.Error("Expected Codex AWF command to contain '--enable-api-proxy' flag") } }) + + t.Run("Gemini AWF command includes enable-api-proxy flag (supports LLM gateway)", func(t *testing.T) { + workflowData := &WorkflowData{ + Name: "test-workflow", + EngineConfig: &EngineConfig{ + ID: "gemini", + }, + NetworkPermissions: &NetworkPermissions{ + Firewall: &FirewallConfig{ + Enabled: true, + }, + }, + } + + engine := NewGeminiEngine() + steps := engine.GetExecutionSteps(workflowData, "test.log") + + if len(steps) == 0 { + t.Fatal("Expected at least one execution step") + } + + stepContent := strings.Join(steps[0], "\n") + + if !strings.Contains(stepContent, "--enable-api-proxy") { + t.Error("Expected Gemini AWF command to contain '--enable-api-proxy' flag") + } + }) } diff --git a/pkg/workflow/gemini_engine.go b/pkg/workflow/gemini_engine.go index 8f8f7e52c1e..5278d8b87b1 100644 --- a/pkg/workflow/gemini_engine.go +++ b/pkg/workflow/gemini_engine.go @@ -212,7 +212,7 @@ func (e *GeminiEngine) GetExecutionSteps(workflowData *WorkflowData, logFile str LogFile: logFile, WorkflowData: workflowData, UsesTTY: false, - UsesAPIProxy: false, + UsesAPIProxy: e.SupportsLLMGateway() > 0, AllowedDomains: allowedDomains, }) } else { From e0db320a33760284e5a9712c65428e753e90493d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:03:55 +0000 Subject: [PATCH 3/7] refactor: use explicit llmGatewayPort variable in Gemini execution steps and add to TestSupportsLLMGateway Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/gemini_engine.go | 6 +++++- pkg/workflow/strict_mode_llm_gateway_test.go | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/workflow/gemini_engine.go b/pkg/workflow/gemini_engine.go index 5278d8b87b1..7bd9fe171c0 100644 --- a/pkg/workflow/gemini_engine.go +++ b/pkg/workflow/gemini_engine.go @@ -206,13 +206,17 @@ func (e *GeminiEngine) GetExecutionSteps(workflowData *WorkflowData, logFile str npmPathSetup := GetNpmBinPathSetup() geminiCommandWithPath := fmt.Sprintf("%s && %s", npmPathSetup, geminiCommand) + // Enable API proxy sidecar if this engine supports LLM gateway + llmGatewayPort := e.SupportsLLMGateway() + usesAPIProxy := llmGatewayPort > 0 + command = BuildAWFCommand(AWFCommandConfig{ EngineName: "gemini", EngineCommand: geminiCommandWithPath, LogFile: logFile, WorkflowData: workflowData, UsesTTY: false, - UsesAPIProxy: e.SupportsLLMGateway() > 0, + UsesAPIProxy: usesAPIProxy, AllowedDomains: allowedDomains, }) } else { diff --git a/pkg/workflow/strict_mode_llm_gateway_test.go b/pkg/workflow/strict_mode_llm_gateway_test.go index 065ff3a24c4..2cdd042e6df 100644 --- a/pkg/workflow/strict_mode_llm_gateway_test.go +++ b/pkg/workflow/strict_mode_llm_gateway_test.go @@ -287,6 +287,11 @@ func TestSupportsLLMGateway(t *testing.T) { expectedPort: constants.CopilotLLMGatewayPort, description: "Copilot engine uses dedicated port for LLM gateway", }, + { + engineID: "gemini", + expectedPort: constants.GeminiLLMGatewayPort, + description: "Gemini engine uses dedicated port for LLM gateway", + }, } for _, tt := range tests { From 604cb00ae8280fa775251019f406e381a11c3a0b Mon Sep 17 00:00:00 2001 From: Smoke Test Date: Fri, 20 Feb 2026 20:08:52 +0000 Subject: [PATCH 4/7] test: Add smoke test file for run 22239107636 --- smoke_test_push_22239107636.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 smoke_test_push_22239107636.txt diff --git a/smoke_test_push_22239107636.txt b/smoke_test_push_22239107636.txt new file mode 100644 index 00000000000..df22b6a0443 --- /dev/null +++ b/smoke_test_push_22239107636.txt @@ -0,0 +1 @@ +Test file for PR push - smoke test run 22239107636 From b9eeee22652e6f6dd9c70eb1564b21ce772b2285 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:12:51 +0000 Subject: [PATCH 5/7] feat: switch Gemini to --output-format stream-json (streaming JSONL like Claude) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-gemini.lock.yml | 4 ++-- actions/setup/js/parse_gemini_log.cjs | 4 ++-- pkg/workflow/gemini_engine.go | 6 +++--- pkg/workflow/gemini_engine_test.go | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/smoke-gemini.lock.yml b/.github/workflows/smoke-gemini.lock.yml index 9f9b2748568..05b055a9a95 100644 --- a/.github/workflows/smoke-gemini.lock.yml +++ b/.github/workflows/smoke-gemini.lock.yml @@ -901,7 +901,7 @@ jobs: run: | set -o pipefail sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains '*.githubusercontent.com,*.googleapis.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,generativelanguage.googleapis.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.20.2 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && gemini --yolo --output-format json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && gemini --yolo --output-format stream-json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} GH_AW_MCP_CONFIG: ${{ github.workspace }}/.gemini/settings.json @@ -1197,7 +1197,7 @@ jobs: id: agentic_execution run: | set -o pipefail - gemini --yolo --output-format json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" 2>&1 | tee /tmp/gh-aw/threat-detection/detection.log + gemini --yolo --output-format stream-json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} GH_AW_MODEL_DETECTION_GEMINI: ${{ vars.GH_AW_MODEL_DETECTION_GEMINI || '' }} diff --git a/actions/setup/js/parse_gemini_log.cjs b/actions/setup/js/parse_gemini_log.cjs index 683782d89d5..3d2760ce385 100644 --- a/actions/setup/js/parse_gemini_log.cjs +++ b/actions/setup/js/parse_gemini_log.cjs @@ -10,8 +10,8 @@ const main = createEngineLogParser({ }); /** - * Parse Gemini CLI JSON log output and format as markdown. - * Gemini CLI outputs a single JSON object per line when using --output-format json. + * Parse Gemini CLI streaming JSON log output and format as markdown. + * Gemini CLI outputs one JSON object per line when using --output-format stream-json (JSONL). * @param {string} logContent - The raw log content to parse * @returns {{markdown: string, logEntries: Array, mcpFailures: Array, maxTurnsHit: boolean}} Parsed log data */ diff --git a/pkg/workflow/gemini_engine.go b/pkg/workflow/gemini_engine.go index 7bd9fe171c0..a93890e74cf 100644 --- a/pkg/workflow/gemini_engine.go +++ b/pkg/workflow/gemini_engine.go @@ -179,8 +179,8 @@ func (e *GeminiEngine) GetExecutionSteps(workflowData *WorkflowData, logFile str // Without this, Gemini CLI's default approval mode rejects tool calls with "Tool execution denied by policy" geminiArgs = append(geminiArgs, "--yolo") - // Add headless mode with JSON output - geminiArgs = append(geminiArgs, "--output-format", "json") + // Add streaming JSON output (JSONL format, compatible with the log parser) + geminiArgs = append(geminiArgs, "--output-format", "stream-json") // Add prompt argument geminiArgs = append(geminiArgs, "--prompt", "\"$(cat /tmp/gh-aw/aw-prompts/prompt.txt)\"") @@ -221,7 +221,7 @@ func (e *GeminiEngine) GetExecutionSteps(workflowData *WorkflowData, logFile str }) } else { command = fmt.Sprintf(`set -o pipefail -%s 2>&1 | tee %s`, geminiCommand, logFile) +%s 2>&1 | tee -a %s`, geminiCommand, logFile) } // Build environment variables diff --git a/pkg/workflow/gemini_engine_test.go b/pkg/workflow/gemini_engine_test.go index bd85bf7473c..f1ec003d980 100644 --- a/pkg/workflow/gemini_engine_test.go +++ b/pkg/workflow/gemini_engine_test.go @@ -152,7 +152,7 @@ func TestGeminiEngineExecution(t *testing.T) { assert.Contains(t, stepContent, "id: agentic_execution", "Should have agentic_execution ID") assert.Contains(t, stepContent, "gemini", "Should invoke gemini command") assert.Contains(t, stepContent, "--yolo", "Should include --yolo flag for auto-approving tool executions") - assert.Contains(t, stepContent, "--output-format json", "Should use JSON output format") + assert.Contains(t, stepContent, "--output-format stream-json", "Should use streaming JSON output format") assert.Contains(t, stepContent, `--prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"`, "Should include prompt argument with correct shell quoting") assert.Contains(t, stepContent, "/tmp/test.log", "Should include log file") assert.Contains(t, stepContent, "GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}", "Should set GEMINI_API_KEY env var") From 1d1d9f22faf8c596926e8c033ffcb54352578193 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:51:00 +0000 Subject: [PATCH 6/7] feat: use gemini-2.0-flash-lite model for smoke-gemini workflow Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-gemini.lock.yml | 11 +++++------ .github/workflows/smoke-gemini.md | 4 +++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/smoke-gemini.lock.yml b/.github/workflows/smoke-gemini.lock.yml index 05b055a9a95..66b8b08174a 100644 --- a/.github/workflows/smoke-gemini.lock.yml +++ b/.github/workflows/smoke-gemini.lock.yml @@ -28,7 +28,7 @@ # - shared/gh.md # - shared/reporting.md # -# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"1c7e9095a4960a70d4be26e98a3da1fb9a7a3526fc8e60d0614b182f9fbf0904"} +# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"0cbff208fe0695987e6a089673e8346206c6dca1fdb91f20648b52de072e50c6"} name: "Smoke Gemini" "on": @@ -359,7 +359,7 @@ jobs: const awInfo = { engine_id: "gemini", engine_name: "Google Gemini CLI", - model: process.env.GH_AW_MODEL_AGENT_CUSTOM || "", + model: "gemini-2.0-flash-lite", version: "", agent_version: "", workflow_name: "Smoke Gemini", @@ -901,11 +901,10 @@ jobs: run: | set -o pipefail sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains '*.githubusercontent.com,*.googleapis.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,generativelanguage.googleapis.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.20.2 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && gemini --yolo --output-format stream-json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && gemini --model gemini-2.0-flash-lite --yolo --output-format stream-json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} GH_AW_MCP_CONFIG: ${{ github.workspace }}/.gemini/settings.json - GH_AW_MODEL_AGENT_GEMINI: ${{ vars.GH_AW_MODEL_AGENT_GEMINI || '' }} GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} GITHUB_WORKSPACE: ${{ github.workspace }} @@ -1197,10 +1196,9 @@ jobs: id: agentic_execution run: | set -o pipefail - gemini --yolo --output-format stream-json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + gemini --model gemini-2.0-flash-lite --yolo --output-format stream-json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} - GH_AW_MODEL_DETECTION_GEMINI: ${{ vars.GH_AW_MODEL_DETECTION_GEMINI || '' }} GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GITHUB_WORKSPACE: ${{ github.workspace }} - name: Parse threat detection results @@ -1267,6 +1265,7 @@ jobs: timeout-minutes: 15 env: GH_AW_ENGINE_ID: "gemini" + GH_AW_ENGINE_MODEL: "gemini-2.0-flash-lite" GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e ✨ *[{workflow_name}]({run_url}) — Powered by Gemini*\",\"runStarted\":\"✨ Gemini awakens... [{workflow_name}]({run_url}) begins its journey on this {event_type}...\",\"runSuccess\":\"🚀 [{workflow_name}]({run_url}) **MISSION COMPLETE!** Gemini has spoken. ✨\",\"runFailure\":\"⚠️ [{workflow_name}]({run_url}) {status}. Gemini encountered unexpected challenges...\"}" GH_AW_WORKFLOW_ID: "smoke-gemini" GH_AW_WORKFLOW_NAME: "Smoke Gemini" diff --git a/.github/workflows/smoke-gemini.md b/.github/workflows/smoke-gemini.md index 0f03d4dd91d..e5ae75ea644 100644 --- a/.github/workflows/smoke-gemini.md +++ b/.github/workflows/smoke-gemini.md @@ -11,7 +11,9 @@ permissions: issues: read pull-requests: read name: Smoke Gemini -engine: gemini +engine: + id: gemini + model: gemini-2.0-flash-lite strict: true imports: - shared/gh.md From 34fca1866b65cd823e9899f6caa9a3206ec1c1da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 21:26:58 +0000 Subject: [PATCH 7/7] feat: set GEMINI_API_BASE_URL to LLM gateway URL when firewall is enabled Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-gemini.lock.yml | 1 + pkg/workflow/awf_helpers.go | 3 ++- pkg/workflow/gemini_engine.go | 6 ++++++ pkg/workflow/gemini_engine_test.go | 3 +++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/smoke-gemini.lock.yml b/.github/workflows/smoke-gemini.lock.yml index 66b8b08174a..c2fdd1135b0 100644 --- a/.github/workflows/smoke-gemini.lock.yml +++ b/.github/workflows/smoke-gemini.lock.yml @@ -903,6 +903,7 @@ jobs: sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains '*.githubusercontent.com,*.googleapis.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,generativelanguage.googleapis.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.20.2 --skip-pull --enable-api-proxy \ -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && gemini --model gemini-2.0-flash-lite --yolo --output-format stream-json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: + GEMINI_API_BASE_URL: http://host.docker.internal:10003 GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} GH_AW_MCP_CONFIG: ${{ github.workspace }}/.gemini/settings.json GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt diff --git a/pkg/workflow/awf_helpers.go b/pkg/workflow/awf_helpers.go index 8028d8677d6..a4943736826 100644 --- a/pkg/workflow/awf_helpers.go +++ b/pkg/workflow/awf_helpers.go @@ -170,7 +170,8 @@ func BuildAWFArgs(config AWFCommandConfig) []string { awfArgs = append(awfArgs, "--proxy-logs-dir", string(constants.AWFProxyLogsDir)) // Add --enable-host-access when MCP servers are configured (gateway is used) - if HasMCPServers(config.WorkflowData) { + // OR when the API proxy sidecar is enabled (needs to reach host.docker.internal:) + if HasMCPServers(config.WorkflowData) || config.UsesAPIProxy { awfArgs = append(awfArgs, "--enable-host-access") awfHelpersLog.Print("Added --enable-host-access for MCP gateway communication") } diff --git a/pkg/workflow/gemini_engine.go b/pkg/workflow/gemini_engine.go index a93890e74cf..a393bcf216e 100644 --- a/pkg/workflow/gemini_engine.go +++ b/pkg/workflow/gemini_engine.go @@ -236,6 +236,12 @@ func (e *GeminiEngine) GetExecutionSteps(workflowData *WorkflowData, logFile str env["GH_AW_MCP_CONFIG"] = "${{ github.workspace }}/.gemini/settings.json" } + // When the firewall (AWF) is enabled with --enable-api-proxy, point Gemini CLI at the + // LLM gateway sidecar instead of the real googleapis.com endpoint. + if firewallEnabled { + env["GEMINI_API_BASE_URL"] = fmt.Sprintf("http://host.docker.internal:%d", constants.GeminiLLMGatewayPort) + } + // Add safe outputs env applySafeOutputEnvToMap(env, workflowData) diff --git a/pkg/workflow/gemini_engine_test.go b/pkg/workflow/gemini_engine_test.go index f1ec003d980..65ef85b6d7c 100644 --- a/pkg/workflow/gemini_engine_test.go +++ b/pkg/workflow/gemini_engine_test.go @@ -273,6 +273,8 @@ func TestGeminiEngineFirewallIntegration(t *testing.T) { // Should use AWF command assert.Contains(t, stepContent, "awf", "Should use AWF when firewall is enabled") assert.Contains(t, stepContent, "--allow-domains", "Should include allow-domains flag") + assert.Contains(t, stepContent, "--enable-api-proxy", "Should include --enable-api-proxy flag") + assert.Contains(t, stepContent, "GEMINI_API_BASE_URL: http://host.docker.internal:10003", "Should set GEMINI_API_BASE_URL to LLM gateway URL") }) t.Run("firewall disabled", func(t *testing.T) { @@ -293,5 +295,6 @@ func TestGeminiEngineFirewallIntegration(t *testing.T) { // Should use simple command without AWF assert.Contains(t, stepContent, "set -o pipefail", "Should use simple command with pipefail") assert.NotContains(t, stepContent, "awf", "Should not use AWF when firewall is disabled") + assert.NotContains(t, stepContent, "GEMINI_API_BASE_URL", "Should not set GEMINI_API_BASE_URL when firewall is disabled") }) }