Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions docker/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Environment variables for Browser Operator DevTools
# Copy this file to .env and set your API keys

# OpenAI API Key (for GPT models)
OPENAI_API_KEY=sk-your-openai-api-key-here

# OpenRouter API Key (for multiple LLM providers)
OPENROUTER_API_KEY=sk-or-your-openrouter-api-key-here

# Groq API Key (for fast inference)
GROQ_API_KEY=gsk_your-groq-api-key-here

# LiteLLM API Key (for self-hosted LLM proxy)
LITELLM_API_KEY=your-litellm-api-key-here

# Usage:
# 1. Copy this file: cp .env.example .env
# 2. Set your API keys in the .env file
# 3. Build and run: docker-compose up --build
#
# The API keys will be embedded into the Docker image during build time
# and used as fallbacks when localStorage is empty.
#
# Priority order:
# 1. localStorage (user settings in DevTools)
# 2. Environment variables (this .env file)
# 3. Empty (no API key configured)
20 changes: 18 additions & 2 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Multi-stage build for Chrome DevTools Frontend
FROM --platform=linux/amd64 ubuntu:22.04 AS builder

# BuildKit is required for secret mounting
# Secrets are mounted at build time but not stored in layers
# Usage: DOCKER_BUILDKIT=1 docker-compose build

# Install required packages
RUN apt-get update && apt-get install -y \
curl \
Expand Down Expand Up @@ -47,7 +51,12 @@ RUN git remote add upstream https://github.com/BrowserOperator/browser-operator-
RUN git fetch upstream
RUN git checkout upstream/main

# Build Browser Operator version
# Copy our local modifications into the container
COPY front_end/panels/ai_chat/core/EnvironmentConfig.ts /workspace/devtools/devtools-frontend/front_end/panels/ai_chat/core/EnvironmentConfig.ts
COPY front_end/panels/ai_chat/ui/SettingsDialog.ts /workspace/devtools/devtools-frontend/front_end/panels/ai_chat/ui/SettingsDialog.ts
COPY front_end/entrypoints/devtools_app/devtools_app.ts /workspace/devtools/devtools-frontend/front_end/entrypoints/devtools_app/devtools_app.ts

# Build Browser Operator version with our modifications
RUN npm run build

# Production stage
Expand All @@ -59,4 +68,11 @@ COPY --from=builder /workspace/devtools/devtools-frontend/out/Default/gen/front_
# Copy nginx config
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 8000
# Copy and setup entrypoint script for runtime configuration
COPY docker/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh

EXPOSE 8000

# Use entrypoint to generate runtime config and start nginx
ENTRYPOINT ["/docker-entrypoint.sh"]
13 changes: 10 additions & 3 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,20 @@ services:
ports:
- "8000:8000"
restart: unless-stopped
volumes:
# volumes:
# For development: mount the built files directly (optional)
# Uncomment the line below to use local files instead of container files
# - ../out/Default/gen/front_end:/usr/share/nginx/html:ro
# Uncomment the lines below to use local files instead of container files
# volumes:
# - ../out/Default/gen/front_end:/usr/share/nginx/html:ro
environment:
- NGINX_HOST=localhost
- NGINX_PORT=8000
# Runtime API key injection - keys are passed to container at runtime
# These are injected into the DevTools frontend via runtime-config.js
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-}
- GROQ_API_KEY=${GROQ_API_KEY:-}
- LITELLM_API_KEY=${LITELLM_API_KEY:-}
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8000/"]
interval: 30s
Expand Down
65 changes: 65 additions & 0 deletions docker/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/bin/sh
# Docker entrypoint script for Browser Operator DevTools
# This script generates runtime configuration from environment variables
# and starts nginx to serve the DevTools frontend

set -e

# Log configuration status to container logs
echo "DevTools runtime configuration generated:"
[ -n "${OPENAI_API_KEY}" ] && echo " ✓ OPENAI_API_KEY configured" || echo " ✗ OPENAI_API_KEY not set"
[ -n "${OPENROUTER_API_KEY}" ] && echo " ✓ OPENROUTER_API_KEY configured" || echo " ✗ OPENROUTER_API_KEY not set"
[ -n "${GROQ_API_KEY}" ] && echo " ✓ GROQ_API_KEY configured" || echo " ✗ GROQ_API_KEY not set"
[ -n "${LITELLM_API_KEY}" ] && echo " ✓ LITELLM_API_KEY configured" || echo " ✗ LITELLM_API_KEY not set"

# Inject API keys directly into DevTools JavaScript files
echo "🔧 Injecting API keys directly into JavaScript files..."

# Find and modify the main DevTools entry point files
for js_file in /usr/share/nginx/html/entrypoints/*/devtools_app.js /usr/share/nginx/html/entrypoints/*/inspector_main.js; do
if [ -f "$js_file" ]; then
echo " ✓ Injecting into $(basename "$js_file")"
# Inject a global configuration object at the beginning of the file
sed -i '1i\
// Runtime API key injection\
window.__RUNTIME_CONFIG__ = {\
OPENAI_API_KEY: "'"${OPENAI_API_KEY:-}"'",\
OPENROUTER_API_KEY: "'"${OPENROUTER_API_KEY:-}"'",\
GROQ_API_KEY: "'"${GROQ_API_KEY:-}"'",\
LITELLM_API_KEY: "'"${LITELLM_API_KEY:-}"'",\
timestamp: "'"$(date -Iseconds)"'",\
source: "docker-runtime"\
};\
// Auto-save to localStorage\
if (window.__RUNTIME_CONFIG__.OPENAI_API_KEY) localStorage.setItem("ai_chat_api_key", window.__RUNTIME_CONFIG__.OPENAI_API_KEY);\
if (window.__RUNTIME_CONFIG__.OPENROUTER_API_KEY) localStorage.setItem("ai_chat_openrouter_api_key", window.__RUNTIME_CONFIG__.OPENROUTER_API_KEY);\
if (window.__RUNTIME_CONFIG__.GROQ_API_KEY) localStorage.setItem("ai_chat_groq_api_key", window.__RUNTIME_CONFIG__.GROQ_API_KEY);\
if (window.__RUNTIME_CONFIG__.LITELLM_API_KEY) localStorage.setItem("ai_chat_litellm_api_key", window.__RUNTIME_CONFIG__.LITELLM_API_KEY);\
console.log("[RUNTIME-CONFIG] API keys injected directly into JavaScript:", {hasOpenAI: !!window.__RUNTIME_CONFIG__.OPENAI_API_KEY, hasOpenRouter: !!window.__RUNTIME_CONFIG__.OPENROUTER_API_KEY, hasGroq: !!window.__RUNTIME_CONFIG__.GROQ_API_KEY, hasLiteLLM: !!window.__RUNTIME_CONFIG__.LITELLM_API_KEY});\
' "$js_file"
fi
done
Comment on lines +18 to +41
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Make injection idempotent and safely escape API key values.

Current sed prepends on every start and doesn’t escape quotes/backslashes in env values. Add a marker/guard and escape values before injecting.

 set -e

 # Log configuration status to container logs
@@
-# Find and modify the main DevTools entry point files
-for js_file in /usr/share/nginx/html/entrypoints/*/devtools_app.js /usr/share/nginx/html/entrypoints/*/inspector_main.js; do
+# Escape for JSON-in-JS string literals
+escape_for_js() {
+  # escapes \ and " characters
+  # shellcheck disable=SC2001
+  printf '%s' "${1-}" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
+}
+OPENAI_API_KEY_JS="$(escape_for_js "${OPENAI_API_KEY-}")"
+OPENROUTER_API_KEY_JS="$(escape_for_js "${OPENROUTER_API_KEY-}")"
+GROQ_API_KEY_JS="$(escape_for_js "${GROQ_API_KEY-}")"
+LITELLM_API_KEY_JS="$(escape_for_js "${LITELLM_API_KEY-}")"
+INJECT_MARKER='/* RUNTIME_CONFIG: injected */'
+
+# Find and modify the main DevTools entry point files (idempotent)
+for js_file in /usr/share/nginx/html/entrypoints/*/devtools_app.js /usr/share/nginx/html/entrypoints/*/inspector_main.js; do
   if [ -f "$js_file" ]; then
     echo "  ✓ Injecting into $(basename "$js_file")"
-    # Inject a global configuration object at the beginning of the file
-    sed -i '1i\
+    if grep -q "__RUNTIME_CONFIG__" "$js_file"; then
+      echo "    ↷ Already injected, skipping"
+      continue
+    fi
+    # Inject a global configuration object at the beginning of the file
+    sed -i '1i\
+/* RUNTIME_CONFIG: injected */\
 // Runtime API key injection\
 window.__RUNTIME_CONFIG__ = {\
-  OPENAI_API_KEY: "'"${OPENAI_API_KEY:-}"'",\
-  OPENROUTER_API_KEY: "'"${OPENROUTER_API_KEY:-}"'",\
-  GROQ_API_KEY: "'"${GROQ_API_KEY:-}"'",\
-  LITELLM_API_KEY: "'"${LITELLM_API_KEY:-}"'",\
+  OPENAI_API_KEY: "'"${OPENAI_API_KEY_JS}"'",\
+  OPENROUTER_API_KEY: "'"${OPENROUTER_API_KEY_JS}"'",\
+  GROQ_API_KEY: "'"${GROQ_API_KEY_JS}"'",\
+  LITELLM_API_KEY: "'"${LITELLM_API_KEY_JS}"'",\
   timestamp: "'"$(date -Iseconds)"'",\
   source: "docker-runtime"\
 };\
 // Auto-save to localStorage\
 if (window.__RUNTIME_CONFIG__.OPENAI_API_KEY) localStorage.setItem("ai_chat_api_key", window.__RUNTIME_CONFIG__.OPENAI_API_KEY);\
 if (window.__RUNTIME_CONFIG__.OPENROUTER_API_KEY) localStorage.setItem("ai_chat_openrouter_api_key", window.__RUNTIME_CONFIG__.OPENROUTER_API_KEY);\
 if (window.__RUNTIME_CONFIG__.GROQ_API_KEY) localStorage.setItem("ai_chat_groq_api_key", window.__RUNTIME_CONFIG__.GROQ_API_KEY);\
 if (window.__RUNTIME_CONFIG__.LITELLM_API_KEY) localStorage.setItem("ai_chat_litellm_api_key", window.__RUNTIME_CONFIG__.LITELLM_API_KEY);\
 console.log("[RUNTIME-CONFIG] API keys injected directly into JavaScript:", {hasOpenAI: !!window.__RUNTIME_CONFIG__.OPENAI_API_KEY, hasOpenRouter: !!window.__RUNTIME_CONFIG__.OPENROUTER_API_KEY, hasGroq: !!window.__RUNTIME_CONFIG__.GROQ_API_KEY, hasLiteLLM: !!window.__RUNTIME_CONFIG__.LITELLM_API_KEY});\
 ' "$js_file"
   fi
 done
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Find and modify the main DevTools entry point files
for js_file in /usr/share/nginx/html/entrypoints/*/devtools_app.js /usr/share/nginx/html/entrypoints/*/inspector_main.js; do
if [ -f "$js_file" ]; then
echo " ✓ Injecting into $(basename "$js_file")"
# Inject a global configuration object at the beginning of the file
sed -i '1i\
// Runtime API key injection\
window.__RUNTIME_CONFIG__ = {\
OPENAI_API_KEY: "'"${OPENAI_API_KEY:-}"'",\
OPENROUTER_API_KEY: "'"${OPENROUTER_API_KEY:-}"'",\
GROQ_API_KEY: "'"${GROQ_API_KEY:-}"'",\
LITELLM_API_KEY: "'"${LITELLM_API_KEY:-}"'",\
timestamp: "'"$(date -Iseconds)"'",\
source: "docker-runtime"\
};\
// Auto-save to localStorage\
if (window.__RUNTIME_CONFIG__.OPENAI_API_KEY) localStorage.setItem("ai_chat_api_key", window.__RUNTIME_CONFIG__.OPENAI_API_KEY);\
if (window.__RUNTIME_CONFIG__.OPENROUTER_API_KEY) localStorage.setItem("ai_chat_openrouter_api_key", window.__RUNTIME_CONFIG__.OPENROUTER_API_KEY);\
if (window.__RUNTIME_CONFIG__.GROQ_API_KEY) localStorage.setItem("ai_chat_groq_api_key", window.__RUNTIME_CONFIG__.GROQ_API_KEY);\
if (window.__RUNTIME_CONFIG__.LITELLM_API_KEY) localStorage.setItem("ai_chat_litellm_api_key", window.__RUNTIME_CONFIG__.LITELLM_API_KEY);\
console.log("[RUNTIME-CONFIG] API keys injected directly into JavaScript:", {hasOpenAI: !!window.__RUNTIME_CONFIG__.OPENAI_API_KEY, hasOpenRouter: !!window.__RUNTIME_CONFIG__.OPENROUTER_API_KEY, hasGroq: !!window.__RUNTIME_CONFIG__.GROQ_API_KEY, hasLiteLLM: !!window.__RUNTIME_CONFIG__.LITELLM_API_KEY});\
' "$js_file"
fi
done
set -e
# Log configuration status to container logs
# Escape for JSON-in-JS string literals
escape_for_js() {
# escapes \ and " characters
# shellcheck disable=SC2001
printf '%s' "${1-}" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
}
OPENAI_API_KEY_JS="$(escape_for_js "${OPENAI_API_KEY-}")"
OPENROUTER_API_KEY_JS="$(escape_for_js "${OPENROUTER_API_KEY-}")"
GROQ_API_KEY_JS="$(escape_for_js "${GROQ_API_KEY-}")"
LITELLM_API_KEY_JS="$(escape_for_js "${LITELLM_API_KEY-}")"
INJECT_MARKER='/* RUNTIME_CONFIG: injected */'
# Find and modify the main DevTools entry point files (idempotent)
for js_file in /usr/share/nginx/html/entrypoints/*/devtools_app.js /usr/share/nginx/html/entrypoints/*/inspector_main.js; do
if [ -f "$js_file" ]; then
echo " ✓ Injecting into $(basename "$js_file")"
if grep -q "__RUNTIME_CONFIG__" "$js_file"; then
echo " ↷ Already injected, skipping"
continue
fi
# Inject a global configuration object at the beginning of the file
sed -i '1i\
/* RUNTIME_CONFIG: injected */\
// Runtime API key injection\
window.__RUNTIME_CONFIG__ = {\
OPENAI_API_KEY: "'"${OPENAI_API_KEY_JS}"'",\
OPENROUTER_API_KEY: "'"${OPENROUTER_API_KEY_JS}"'",\
GROQ_API_KEY: "'"${GROQ_API_KEY_JS}"'",\
LITELLM_API_KEY: "'"${LITELLM_API_KEY_JS}"'",\
timestamp: "'"$(date -Iseconds)"'",\
source: "docker-runtime"\
};\
// Auto-save to localStorage\
if (window.__RUNTIME_CONFIG__.OPENAI_API_KEY) localStorage.setItem("ai_chat_api_key", window.__RUNTIME_CONFIG__.OPENAI_API_KEY);\
if (window.__RUNTIME_CONFIG__.OPENROUTER_API_KEY) localStorage.setItem("ai_chat_openrouter_api_key", window.__RUNTIME_CONFIG__.OPENROUTER_API_KEY);\
if (window.__RUNTIME_CONFIG__.GROQ_API_KEY) localStorage.setItem("ai_chat_groq_api_key", window.__RUNTIME_CONFIG__.GROQ_API_KEY);\
if (window.__RUNTIME_CONFIG__.LITELLM_API_KEY) localStorage.setItem("ai_chat_litellm_api_key", window.__RUNTIME_CONFIG__.LITELLM_API_KEY);\
console.log("[RUNTIME-CONFIG] API keys injected directly into JavaScript:", {hasOpenAI: !!window.__RUNTIME_CONFIG__.OPENAI_API_KEY, hasOpenRouter: !!window.__RUNTIME_CONFIG__.OPENROUTER_API_KEY, hasGroq: !!window.__RUNTIME_CONFIG__.GROQ_API_KEY, hasLiteLLM: !!window.__RUNTIME_CONFIG__.LITELLM_API_KEY});\
' "$js_file"
fi
done
🤖 Prompt for AI Agents
In docker/docker-entrypoint.sh around lines 18-41, the sed-based prepend is not
idempotent and will inject every start and also fails to escape
quotes/backslashes in env values; update the script to first check for a unique
marker (e.g. /* RUNTIME-CONFIG-INJECTED */) in each target file and skip
injection if present, and build the injection payload from properly escaped
environment variables (escape backslashes and quotes/newlines or serialize as
JSON) into a temporary string/file before inserting so values are safe for
inclusion in JS; perform the insertion atomically (e.g. write payload to a temp
file and use awk/sed/printf to insert only when marker missing) to ensure
idempotence and correct escaping.


# Also inject into AI Chat panel files specifically
for js_file in /usr/share/nginx/html/panels/ai_chat/*.js; do
if [ -f "$js_file" ]; then
echo " ✓ Injecting into AI Chat panel $(basename "$js_file")"
sed -i '1i\
// Runtime API key injection for AI Chat\
if (typeof window !== "undefined" && !window.__RUNTIME_CONFIG__) {\
window.__RUNTIME_CONFIG__ = {\
OPENAI_API_KEY: "'"${OPENAI_API_KEY:-}"'",\
OPENROUTER_API_KEY: "'"${OPENROUTER_API_KEY:-}"'",\
GROQ_API_KEY: "'"${GROQ_API_KEY:-}"'",\
LITELLM_API_KEY: "'"${LITELLM_API_KEY:-}"'",\
timestamp: "'"$(date -Iseconds)"'",\
source: "docker-runtime"\
};\
}\
' "$js_file"
fi
done
Comment on lines +43 to +61
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Apply the same idempotency/escaping to AI Chat panel files.

Mirror the guard and escaped values so we don't prepend twice.

-# Also inject into AI Chat panel files specifically
+# Also inject into AI Chat panel files specifically (idempotent)
 for js_file in /usr/share/nginx/html/panels/ai_chat/*.js; do
   if [ -f "$js_file" ]; then
     echo "  ✓ Injecting into AI Chat panel $(basename "$js_file")"
-    sed -i '1i\
+    if grep -q "__RUNTIME_CONFIG__" "$js_file"; then
+      echo "    ↷ Already injected, skipping"
+      continue
+    fi
+    sed -i '1i\
+/* RUNTIME_CONFIG: injected */\
 // Runtime API key injection for AI Chat\
 if (typeof window !== "undefined" && !window.__RUNTIME_CONFIG__) {\
   window.__RUNTIME_CONFIG__ = {\
-    OPENAI_API_KEY: "'"${OPENAI_API_KEY:-}"'",\
-    OPENROUTER_API_KEY: "'"${OPENROUTER_API_KEY:-}"'",\
-    GROQ_API_KEY: "'"${GROQ_API_KEY:-}"'",\
-    LITELLM_API_KEY: "'"${LITELLM_API_KEY:-}"'",\
+    OPENAI_API_KEY: "'"${OPENAI_API_KEY_JS}"'",\
+    OPENROUTER_API_KEY: "'"${OPENROUTER_API_KEY_JS}"'",\
+    GROQ_API_KEY: "'"${GROQ_API_KEY_JS}"'",\
+    LITELLM_API_KEY: "'"${LITELLM_API_KEY_JS}"'",\
     timestamp: "'"$(date -Iseconds)"'",\
     source: "docker-runtime"\
   };\
 }\
 ' "$js_file"
   fi
 done
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Also inject into AI Chat panel files specifically
for js_file in /usr/share/nginx/html/panels/ai_chat/*.js; do
if [ -f "$js_file" ]; then
echo " ✓ Injecting into AI Chat panel $(basename "$js_file")"
sed -i '1i\
// Runtime API key injection for AI Chat\
if (typeof window !== "undefined" && !window.__RUNTIME_CONFIG__) {\
window.__RUNTIME_CONFIG__ = {\
OPENAI_API_KEY: "'"${OPENAI_API_KEY:-}"'",\
OPENROUTER_API_KEY: "'"${OPENROUTER_API_KEY:-}"'",\
GROQ_API_KEY: "'"${GROQ_API_KEY:-}"'",\
LITELLM_API_KEY: "'"${LITELLM_API_KEY:-}"'",\
timestamp: "'"$(date -Iseconds)"'",\
source: "docker-runtime"\
};\
}\
' "$js_file"
fi
done
# Also inject into AI Chat panel files specifically (idempotent)
for js_file in /usr/share/nginx/html/panels/ai_chat/*.js; do
if [ -f "$js_file" ]; then
echo " ✓ Injecting into AI Chat panel $(basename "$js_file")"
if grep -q "__RUNTIME_CONFIG__" "$js_file"; then
echo " ↷ Already injected, skipping"
continue
fi
sed -i '1i\
/* RUNTIME_CONFIG: injected */\
// Runtime API key injection for AI Chat\
if (typeof window !== "undefined" && !window.__RUNTIME_CONFIG__) {\
window.__RUNTIME_CONFIG__ = {\
OPENAI_API_KEY: "'"${OPENAI_API_KEY_JS}"'",\
OPENROUTER_API_KEY: "'"${OPENROUTER_API_KEY_JS}"'",\
GROQ_API_KEY: "'"${GROQ_API_KEY_JS}"'",\
LITELLM_API_KEY: "'"${LITELLM_API_KEY_JS}"'",\
timestamp: "'"$(date -Iseconds)"'",\
source: "docker-runtime"\
};\
}\
' "$js_file"
fi
done
🤖 Prompt for AI Agents
In docker/docker-entrypoint.sh around lines 43 to 61, the AI Chat panel
injection lacks the idempotent guard and proper escaping used elsewhere so the
snippet can be prepended multiple times and variables may not be safely escaped;
update the sed insert to mirror the same guard (check typeof window !==
"undefined" && !window.__RUNTIME_CONFIG__) and inject the same escaped values
format for each API key and timestamp so the snippet is only added once and
variables are safely expanded; ensure the insertion matches the existing
injection logic (same guard, same escaped quoting) used for other panels to
prevent duplicate prepends and injection bugs.


# Start nginx in foreground
echo "Starting nginx..."
exec nginx -g 'daemon off;'
3 changes: 3 additions & 0 deletions front_end/entrypoints/devtools_app/devtools_app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ import '../../panels/ai_chat/ai_chat-meta.js';
import * as Root from '../../core/root/root.js';
import * as Main from '../main/main.js';

// Runtime config is loaded via HTML script injection (docker-entrypoint.sh)
// No need for dynamic loading since it's already in the HTML head

// @ts-expect-error Exposed for legacy layout tests
self.runtime = Root.Runtime.Runtime.instance({forceNew: true});
new Main.MainImpl.MainImpl();
2 changes: 2 additions & 0 deletions front_end/panels/ai_chat/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ devtools_module("ai_chat") {
"core/Types.ts",
"core/AgentService.ts",
"core/Constants.ts",
"core/EnvironmentConfig.ts",
"core/GraphConfigs.ts",
"core/ConfigurableGraph.ts",
"core/BaseOrchestratorAgent.ts",
Expand Down Expand Up @@ -133,6 +134,7 @@ _ai_chat_sources = [
"core/Types.ts",
"core/AgentService.ts",
"core/Constants.ts",
"core/EnvironmentConfig.ts",
"core/GraphConfigs.ts",
"core/ConfigurableGraph.ts",
"core/BaseOrchestratorAgent.ts",
Expand Down
42 changes: 24 additions & 18 deletions front_end/panels/ai_chat/LLM/GroqProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { LLMBaseProvider } from './LLMProvider.js';
import { LLMRetryManager } from './LLMErrorHandler.js';
import { LLMResponseParser } from './LLMResponseParser.js';
import { createLogger } from '../core/Logger.js';
import { getEnvironmentConfig } from '../core/EnvironmentConfig.js';

const logger = createLogger('GroqProvider');

Expand Down Expand Up @@ -38,10 +39,29 @@ export class GroqProvider extends LLMBaseProvider {

readonly name: LLMProvider = 'groq';

private readonly envConfig = getEnvironmentConfig();

constructor(private readonly apiKey: string) {
super();
}

/**
* Get the API key with fallback hierarchy:
* 1. Constructor parameter (for backward compatibility)
* 2. localStorage (user-configured)
* 3. Build-time environment config
* 4. Empty string
*/
private getApiKey(): string {
// Constructor parameter (highest priority for backward compatibility)
if (this.getApiKey() && this.getApiKey().trim() !== '') {
return this.getApiKey().trim();
}

// Use environment config which handles localStorage -> build-time -> empty fallback
return this.envConfig.getApiKey('groq');
}
Comment on lines +55 to +63
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix infinite recursion in getApiKey().

this.getApiKey() calls itself repeatedly. Use this.apiKey.

   private getApiKey(): string {
     // Constructor parameter (highest priority for backward compatibility)
-    if (this.getApiKey() && this.getApiKey().trim() !== '') {
-      return this.getApiKey().trim();
+    if (this.apiKey && this.apiKey.trim() !== '') {
+      return this.apiKey.trim();
     }
     
     // Use environment config which handles localStorage -> build-time -> empty fallback
-    return this.envConfig.getApiKey('groq');
+    return this.envConfig.getApiKey('groq')?.trim() ?? '';
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private getApiKey(): string {
// Constructor parameter (highest priority for backward compatibility)
if (this.getApiKey() && this.getApiKey().trim() !== '') {
return this.getApiKey().trim();
}
// Use environment config which handles localStorage -> build-time -> empty fallback
return this.envConfig.getApiKey('groq');
}
private getApiKey(): string {
// Constructor parameter (highest priority for backward compatibility)
if (this.apiKey && this.apiKey.trim() !== '') {
return this.apiKey.trim();
}
// Use environment config which handles localStorage -> build-time -> empty fallback
return this.envConfig.getApiKey('groq')?.trim() ?? '';
}
🧰 Tools
🪛 ESLint

[error] 60-60: Trailing spaces not allowed.

(@stylistic/no-trailing-spaces)

🤖 Prompt for AI Agents
In front_end/panels/ai_chat/LLM/GroqProvider.ts around lines 55 to 63,
getApiKey() currently calls this.getApiKey() causing infinite recursion; replace
those self-calls with the instance field this.apiKey (guarding for
null/undefined), e.g. check if this.apiKey is a non-empty string, return
this.apiKey.trim(), otherwise return this.envConfig.getApiKey('groq'); keep the
same priority order and trimming behavior.


/**
* Get the chat completions endpoint URL
*/
Expand Down Expand Up @@ -92,7 +112,7 @@ export class GroqProvider extends LLMBaseProvider {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`,
'Authorization': `Bearer ${this.getApiKey()}`,
},
Comment on lines 114 to 116
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Unquote Authorization header and guard against empty key.

  • Style: no quotes needed around Authorization.
  • Behavior: avoid sending an empty Bearer header.
-          'Authorization': `Bearer ${this.getApiKey()}`,
+          Authorization: (() => {
+            const k = this.getApiKey();
+            if (!k) throw new Error('Groq API key is missing. Configure it in Settings.');
+            return `Bearer ${k}`;
+          })(),
-          'Authorization': `Bearer ${this.getApiKey()}`,
+          Authorization: (() => {
+            const k = this.getApiKey();
+            if (!k) throw new Error('Groq API key is missing. Configure it in Settings.');
+            return `Bearer ${k}`;
+          })(),

Also applies to: 281-283

🧰 Tools
🪛 ESLint

[error] 115-115: Unnecessarily quoted property 'Authorization' found.

(@stylistic/quote-props)

🤖 Prompt for AI Agents
In front_end/panels/ai_chat/LLM/GroqProvider.ts around lines 114-116 (and
similarly at 281-283), the Authorization header is written with unnecessary
quotes around the header name and may send an empty Bearer token; change it to
use an unquoted header name and build the header only after retrieving and
trimming the API key into a local variable (e.g., const key =
this.getApiKey()?.trim()), then if key is falsy either throw a clear error or
omit the Authorization header instead of sending "Bearer " with an empty token.
Ensure the header includes `Bearer ${key}` only when key is non-empty.

body: JSON.stringify(payloadBody),
});
Expand Down Expand Up @@ -259,7 +279,7 @@ export class GroqProvider extends LLMBaseProvider {
const response = await fetch(this.getModelsEndpoint(), {
method: 'GET',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Authorization': `Bearer ${this.getApiKey()}`,
},
});

Expand Down Expand Up @@ -432,29 +452,15 @@ export class GroqProvider extends LLMBaseProvider {
* Validate that required credentials are available for Groq
*/
validateCredentials(): {isValid: boolean, message: string, missingItems?: string[]} {
const storageKeys = this.getCredentialStorageKeys();
const apiKey = localStorage.getItem(storageKeys.apiKey!);

if (!apiKey) {
return {
isValid: false,
message: 'Groq API key is required. Please add your API key in Settings.',
missingItems: ['API Key']
};
}

return {
isValid: true,
message: 'Groq credentials are configured correctly.'
};
return this.envConfig.validateCredentials('groq');
}

/**
* Get the storage keys this provider uses for credentials
*/
getCredentialStorageKeys(): {apiKey: string} {
return {
apiKey: 'ai_chat_groq_api_key'
apiKey: this.envConfig.getStorageKey('groq')
};
}
}
24 changes: 22 additions & 2 deletions front_end/panels/ai_chat/LLM/OpenAIProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { LLMBaseProvider } from './LLMProvider.js';
import { LLMRetryManager } from './LLMErrorHandler.js';
import { LLMResponseParser } from './LLMResponseParser.js';
import { createLogger } from '../core/Logger.js';
import { getEnvironmentConfig } from '../core/EnvironmentConfig.js';

const logger = createLogger('OpenAIProvider');

Expand Down Expand Up @@ -42,10 +43,29 @@ export class OpenAIProvider extends LLMBaseProvider {

readonly name: LLMProvider = 'openai';

private readonly envConfig = getEnvironmentConfig();

constructor(private readonly apiKey: string) {
super();
}

/**
* Get the API key with fallback hierarchy:
* 1. Constructor parameter (for backward compatibility)
* 2. localStorage (user-configured)
* 3. Build-time environment config
* 4. Empty string
*/
private getApiKey(): string {
// Constructor parameter (highest priority for backward compatibility)
if (this.apiKey && this.apiKey.trim() !== '') {
return this.apiKey.trim();
}

// Use environment config which handles localStorage -> build-time -> empty fallback
return this.envConfig.getApiKey('openai');
}

/**
* Determines the model family based on the model name
*/
Expand Down Expand Up @@ -280,7 +300,7 @@ export class OpenAIProvider extends LLMBaseProvider {
metadata: {
provider: 'openai',
errorType: 'api_error',
hasApiKey: !!this.apiKey
hasApiKey: !!this.getApiKey()
}
}, context.traceId);
}
Expand All @@ -299,7 +319,7 @@ export class OpenAIProvider extends LLMBaseProvider {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.apiKey}`,
Authorization: `Bearer ${this.getApiKey()}`,
},
Comment on lines 321 to 323
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid sending empty Authorization header.

If no key is present, fail fast with a clear error instead of Authorization: Bearer .

-          Authorization: `Bearer ${this.getApiKey()}`,
+          ...(this.getApiKey()
+            ? { Authorization: `Bearer ${this.getApiKey()}` }
+            : (() => { throw new Error('OpenAI API key is missing. Configure it in Settings.'); })()),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'Content-Type': 'application/json',
Authorization: `Bearer ${this.apiKey}`,
Authorization: `Bearer ${this.getApiKey()}`,
},
'Content-Type': 'application/json',
...(this.getApiKey()
? { Authorization: `Bearer ${this.getApiKey()}` }
: (() => { throw new Error('OpenAI API key is missing. Configure it in Settings.'); })()),
},
🤖 Prompt for AI Agents
In front_end/panels/ai_chat/LLM/OpenAIProvider.ts around lines 321 to 323, the
code unconditionally sets Authorization: `Bearer ${this.getApiKey()}` which can
create an empty Authorization header; change this to fail fast when no API key
is present by checking this.getApiKey() before building headers and throw a
clear error (or return early) if it's missing, otherwise include the
Authorization header with the key; ensure headers only include Authorization
when a non-empty key exists.

body: JSON.stringify(payloadBody),
});
Expand Down
Loading