Skip to content

feat(llm): register OrcaRouter as a named OpenAI-compatible provider - #569

Open
XiaoHuo888-hue wants to merge 1 commit into
platonai:mainfrom
XiaoHuo888-hue:feat/orcarouter-provider
Open

feat(llm): register OrcaRouter as a named OpenAI-compatible provider#569
XiaoHuo888-hue wants to merge 1 commit into
platonai:mainfrom
XiaoHuo888-hue:feat/orcarouter-provider

Conversation

@XiaoHuo888-hue

@XiaoHuo888-hue XiaoHuo888-hue commented Aug 10, 2026

Copy link
Copy Markdown

What

Registers OrcaRouter as a named, OpenAI-compatible LLM provider. At startup PulsarContextInitializer (the shared ApplicationContextInitializer used by both the standalone and bundle apps) calls ChatModelFactory.registerProvider(...), so ORCAROUTER_API_KEY is recognized by ChatModelFactory exactly like OPENROUTER_API_KEY and LLM requests route to https://api.orcarouter.ai/v1, with the smart-routing orcarouter/auto default model.

The provider is also surfaced across the config surface: DoctorController.llm-status env-key / property-key lists and key prefixes, the AgentToolExecutor manual key fallback + help text, both app startup help messages, application.properties, the three Dockerfiles, bin/env-manage.sh/ps1, and the provider tables in README.md, README.zh.md, docs/config.md, cli/README.md, skills/browser4-cli/references/agent.md, and AGENTS.md.

Why a named provider

Browser4's LLM SDK resolves providers through a data-driven registry keyed by API-key name. Without this registration an ORCAROUTER_API_KEY would not be recognized as a supported key and requests would fall back to api.openai.com/v1. Registering orcarouter makes it first-class, so no generic passthrough is involved — the same treatment OpenRouter already gets.

OrcaRouter is an OpenAI-compatible model routing gateway that brings 150+ models from OpenAI, Anthropic, Google, DeepSeek, Qwen, MiniMax and xAI behind a single endpoint and API key. Beyond routing, it runs gateway-level, zero-trust security for AI agents on the same endpoint — screening every prompt/response and governing every tool call on a default-deny basis, with no application code changes. This PR registers it as a named provider so users can opt in directly.

Verification

  • mvn install -DskipTests for browser4-boot, browser4-rest, browser4-standalone, browser4-bundle (with -am -P all-main-modules): BUILD SUCCESS across all 15 reactor modules.
  • L3 live against the resolved pulsar-llm SDK with a real key: registerProvider makes SUPPORTED_API_KEY_NAMES include ORCAROUTER_API_KEY; POST /v1/chat/completions with orcarouter/autoHTTP 200 (smart-routed to gemini-3.1-flash-lite-preview, usage returned).

I'm an engineer on the OrcaRouter team.

Summary by CodeRabbit

  • New Features

    • Added OrcaRouter as a supported LLM provider.
    • Supports API key, model, and base URL configuration through environment variables or system properties.
    • Added OrcaRouter to provider detection, status reporting, startup configuration, and CLI tools.
    • Docker deployments now support the OrcaRouter API key.
  • Documentation

    • Updated English and Chinese setup guides, CLI documentation, and configuration references with OrcaRouter details and examples.

Register OrcaRouter via ChatModelFactory.registerProvider in
PulsarContextInitializer so ORCAROUTER_API_KEY is recognized like any
other named LLM provider and routes to https://api.orcarouter.ai/v1
(default model orcarouter/auto).

Also surface the provider across the config surface:
- DoctorController llm-status env keys/property keys/key prefixes
- AgentToolExecutor fallback chain + help message
- both app help messages
- application.properties, Dockerfiles, env-manage scripts
- README / docs / AGENTS / CLI reference tables

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds OrcaRouter as an LLM provider. It registers provider defaults, detects its API key, exposes it in containers and environment tools, updates status messages, and documents configuration options.

Changes

OrcaRouter provider support

Layer / File(s) Summary
Provider registration and configuration
browser4-boot/.../PulsarContextInitializer.kt, application.properties
Application startup registers OrcaRouter with configurable API key, model, and base URL settings. Properties document the provider defaults and API-key priority.
Runtime key detection and status reporting
browser4-agentic/.../AgentToolExecutor.kt, browser4-rest/.../DoctorController.kt, browser4-apps/.../Browser4*Application.kt
Fallback checks, status reporting, and configuration guidance recognize ORCAROUTER_API_KEY.
Deployment and configuration documentation
Dockerfile*, bin/env-manage.*, AGENTS.md, README*, cli/README.md, docs/config.md, skills/browser4-cli/references/agent.md
Container environments, environment registries, and configuration guides include OrcaRouter settings.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Application
  participant PulsarContextInitializer
  participant LLMProviderConfiguration
  Application->>PulsarContextInitializer: initialize application context
  PulsarContextInitializer->>LLMProviderConfiguration: register OrcaRouter provider
  LLMProviderConfiguration-->>PulsarContextInitializer: provider registration result
Loading

Suggested reviewers: insidegalaxyeye

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: registering OrcaRouter as a named OpenAI-compatible LLM provider.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
browser4-boot/src/main/kotlin/ai/platon/pulsar/boot/autoconfigure/PulsarContextInitializer.kt (2)

31-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Declare the public function return type.

Line [31] should declare registerOrcaRouterProvider(): Unit.

As per coding guidelines, Kotlin code should prefer explicit return types.

Suggested change
-fun registerOrcaRouterProvider() {
+fun registerOrcaRouterProvider(): Unit {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@browser4-boot/src/main/kotlin/ai/platon/pulsar/boot/autoconfigure/PulsarContextInitializer.kt`
at line 31, Update the public function registerOrcaRouterProvider in
PulsarContextInitializer to explicitly declare a Unit return type, preserving
its existing implementation.

Source: Coding guidelines


31-45: 🩺 Stability & Availability | 🔵 Trivial

Make configured-provider registration failure observable.

When ChatModelFactory.registerProvider fails, Lines [31]-[45] only emit a warning and continue startup. If ORCAROUTER_API_KEY is configured, later requests can fail because the provider was not registered. Fail startup for an explicitly configured provider, or expose an unavailable-provider state to status and request selection.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@browser4-boot/src/main/kotlin/ai/platon/pulsar/boot/autoconfigure/PulsarContextInitializer.kt`
around lines 31 - 45, Update registerOrcaRouterProvider so a
ChatModelFactory.registerProvider failure is not silently tolerated when
ORCAROUTER_API_KEY is configured: fail startup or mark the provider unavailable
for status and request selection. Preserve non-fatal behavior only when the
provider is not explicitly configured, and ensure the existing warning includes
the failure context.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bin/env-manage.ps1`:
- Line 72: Re-save bin/env-manage.ps1 using UTF-8 encoding with a BOM,
preserving all existing script content and the non-ASCII output strings.

In
`@browser4-agentic/src/main/kotlin/ai/platon/pulsar/agentic/tools/builtin/AgentToolExecutor.kt`:
- Line 58: Add focused tests for AgentToolExecutor.requireLLMConfigured covering
acceptance of a non-blank ORCAROUTER_API_KEY and inclusion of ORCAROUTER_API_KEY
in the missing-key error guidance. Keep the tests minimal and isolated to the
new OrcaRouter fallback behavior, preserving existing configuration cases.

In
`@browser4-boot/src/main/kotlin/ai/platon/pulsar/boot/autoconfigure/PulsarContextInitializer.kt`:
- Around line 31-45: Add focused Unit/Fast tests for
PulsarContextInitializer.registerOrcaRouterProvider() covering the default model
“orcarouter/auto” and base URL, explicit environment or property overrides,
startup when ORCAROUTER_API_KEY is absent, and provider-registration failure.
Verify startup remains non-fatal and logs the expected warning when registration
throws, while preserving existing provider behavior.

In
`@browser4-rest/src/main/kotlin/ai/platon/pulsar/rest/api/controller/DoctorController.kt`:
- Line 127: Update the API-key configuration checks in the DoctorController
logic for ORCAROUTER_API_KEY and orcarouter.api.key to use isNullOrBlank(), so
only non-blank environment and system-property values count as configured. Add
tests covering blank and non-blank OrcaRouter values, matching
AgentToolExecutor.requireLLMConfigured behavior.

In `@Dockerfile`:
- Line 68: Remove the OPENROUTER_API_KEY and ORCAROUTER_API_KEY image-level
environment entries from Dockerfile lines 67-68, Dockerfile.fast lines 35-36,
and Dockerfile.native lines 66-67. Keep both keys supplied only at container
startup rather than through build-time configuration.

---

Nitpick comments:
In
`@browser4-boot/src/main/kotlin/ai/platon/pulsar/boot/autoconfigure/PulsarContextInitializer.kt`:
- Line 31: Update the public function registerOrcaRouterProvider in
PulsarContextInitializer to explicitly declare a Unit return type, preserving
its existing implementation.
- Around line 31-45: Update registerOrcaRouterProvider so a
ChatModelFactory.registerProvider failure is not silently tolerated when
ORCAROUTER_API_KEY is configured: fail startup or mark the provider unavailable
for status and request selection. Preserve non-fatal behavior only when the
provider is not explicitly configured, and ensure the existing warning includes
the failure context.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c9d07693-efe4-4013-b60f-4a68fb510ff0

📥 Commits

Reviewing files that changed from the base of the PR and between f3890ec and 21879b9.

📒 Files selected for processing (17)
  • AGENTS.md
  • Dockerfile
  • Dockerfile.fast
  • Dockerfile.native
  • README.md
  • README.zh.md
  • application.properties
  • bin/env-manage.ps1
  • bin/env-manage.sh
  • browser4-agentic/src/main/kotlin/ai/platon/pulsar/agentic/tools/builtin/AgentToolExecutor.kt
  • browser4-apps/browser4-bundle/src/main/kotlin/ai/platon/pulsar/apps/Browser4BundleApplication.kt
  • browser4-apps/browser4-standalone/src/main/kotlin/ai/platon/pulsar/apps/Browser4StandaloneApplication.kt
  • browser4-boot/src/main/kotlin/ai/platon/pulsar/boot/autoconfigure/PulsarContextInitializer.kt
  • browser4-rest/src/main/kotlin/ai/platon/pulsar/rest/api/controller/DoctorController.kt
  • cli/README.md
  • docs/config.md
  • skills/browser4-cli/references/agent.md

Comment thread bin/env-manage.ps1
@{Name='LLM_API_KEY'; Category='llm'; Default=''; Desc='Generic LLM API key (fallback for all providers)'; Sensitive=$true}
@{Name='OPENAI_API_KEY'; Category='llm'; Default=''; Desc='OpenAI API key'; Sensitive=$true}
@{Name='OPENROUTER_API_KEY'; Category='llm'; Default=''; Desc='OpenRouter API key'; Sensitive=$true}
@{Name='ORCAROUTER_API_KEY'; Category='llm'; Default=''; Desc='OrcaRouter API key (OpenAI-compatible routing gateway)'; Sensitive=$true}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'env-manage\.ps1$' . || true

echo "== file byte-header =="
if [ -f bin/env-manage.ps1 ]; then
  xxd -l 48 bin/env-manage.ps1 || true
  python3 - <<'PY'
from pathlib import Path
p=Path('bin/env-manage.ps1')
b=p.read_bytes()
print("length", len(b))
print("startswith_bom", b.startswith(b'\xef\xbb\xbf'))
print("contains_non_ascii_bytes", any(x > 127 for x in b))
if len(b) > 0: print("first_bytes", b[:48].hex())
# check UTF-8 decodability and non-ascii chars
try:
    t=b.decode('utf-8')
    print("utf8_decode_ok", True)
    chars=[c for i,c in enumerate(t) if ord(c)>127]
    print("non_ascii_char_count", len(chars))
    first_occ={ord(c): (i,c) for i,c in enumerate(t) if ord(c)>127 and ord(c) not in first_occ}
    print("sample_non_ascii_chars", first_occ[:20])
except UnicodeDecodeError as e:
    print("utf8_decode_error", e)
PY
fi

echo "== PSScriptAnalyzer availability/version =="
pwsh -NoProfile -Command 'if (Get-Module -ListAvailable PSStyleModule) { PSStyleModule | Format-Table Name, Path, Version }' || true
pwsh -NoProfile -Command 'try { Show-Command ErrorViewErrorView } catch { Write-Host "Show-Command unavailable" }' || true
pwsh -NoProfile -Command 'Get-Module PSScriptAnalyzer -ListAvailable | Select-Object -ExpandProperty Version | Sort-Object -Descending | Select-Object -First 5' || true

echo "== run PSScriptAnalyzer on target if available =="
pwsh -NoProfile -Command '
if (!(Get-Module PSScriptAnalyzer -ListAvailable)) { exit 0 }
Import-Module PSScriptAnalyzer -Force
$rules=(Get-ScriptAnalyzerRule -Name PSUseBOMForUnicodeEncodedFile)
if (-not $rules) { exit 0 }
$all=Get-ScriptAnalyzerRule
$arr=Invoke-ScriptAnalyzer -Path bin/env-manage.ps1 -Severity Error,Warning,Information,Verbose -Include $rules -Verbose:$false
if (-not $arr) { exit 0 }
$arr | Format-Table Line, Column, RuleName, Severity, Message -AutoSize
' || true

Repository: platonai/Browser4

Length of output: 647


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== environment =="
uname -a || true
pwsh -NoProfile -Command '$PSVersionTable.PSVersion' || true

echo "== PSScriptAnalyzer availability/version =="
pwsh -NoProfile -Command 'Get-Module PSScriptAnalyzer -ListAvailable | Select-Object Name, Version, InstalledLocation | Format-Table -AutoSize;' || true
pwsh -NoProfile -Command 'try { $rules=Get-ScriptAnalyzerRule -Name PSUseBOMForUnicodeEncodedFile -ErrorAction Stop; $rules | Format-List -AutoSize } catch { $_.FullyQualifiedErrorId }; exit $LASTEXITCODE' || true

echo "== run only PSUseBOMForUnicodeEncodedFile if rule is available =="
pwsh -NoProfile -Command '
Import-Module PSScriptAnalyzer -ErrorAction Stop -Force -Verbose:$false
Get-ScriptAnalyzerRule -Name PSUseBOMForUnicodeEncodedFile -ErrorAction Stop | Out-Null
$arr=Invoke-ScriptAnalyzer -Path bin/env-manage.ps1 -IncludeRule PSUseBOMForUnicodeEncodedFile -Verbose:$false
$arr
' || true

echo "== inspect non-ASCII occurrences =="
python3 - <<'PY'
from pathlib import Path
p=Path('bin/env-manage.ps1')
text=p.read_text(encoding='utf-8')
samples=[]
for idx,ch in enumerate(text):
    if ord(ch)>127:
        if (idx, text[max(0,idx-80):idx+80]) not in samples:
            samples.append((idx, text[max(0,idx-80):min(len(text),idx+80)]))
        if len(samples)>=8:
            break
for idx, context in samples:
    print(idx, repr(context).replace('\\n',' '))
PY

Repository: platonai/Browser4

Length of output: 2628


Save bin/env-manage.ps1 with a UTF-8 BOM.

PSUseBOMForUnicodeEncodedFile flags this non-ASCII file as missing BOM encoding. Windows PowerShell can misread the non-ASCII output strings without a BOM. Re-save the file with a UTF-8 BOM.

🧰 Tools
🪛 PSScriptAnalyzer (1.25.0)

[warning] Missing BOM encoding for non-ASCII encoded file 'env-manage.ps1'

(PSUseBOMForUnicodeEncodedFile)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/env-manage.ps1` at line 72, Re-save bin/env-manage.ps1 using UTF-8
encoding with a BOM, preserving all existing script content and the non-ASCII
output strings.

Source: Linters/SAST tools

?: System.getenv("LLM_API_KEY")
?: System.getenv("DEEPSEEK_API_KEY")
?: System.getenv("OPENROUTER_API_KEY")
?: System.getenv("ORCAROUTER_API_KEY")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

fd -t f -i 'test|spec' . | xargs -r rg -n -C 3 \
  'ORCAROUTER_API_KEY|requireLLMConfigured|LLM API key is not configured'

Repository: platonai/Browser4

Length of output: 1670


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== locate AgentToolExecutor.kt =="
fd -t f '^AgentToolExecutor\.kt$' . || true

echo "== inspect target file around lines 40-85 =="
agent_file="$(fd -t f '^AgentToolExecutor\.kt$' . | head -n 1 || true)"
if [ -n "${agent_file:-}" ]; then
  wc -l "$agent_file"
  sed -n '1,120p' "$agent_file" | nl -ba -v1
fi

echo "== candidate tests/modules with relevant identifiers =="
test_files=$(fd -t f . -i 'test|spec' . 2>/dev/null | grep -Ei '(^|/)(browser4-agentic|agentic).*test|/test/|/specs?/' || true)
echo "test file candidates: ${test_files:-none}"

rg -n -C 3 \
  'ORCAROUTER_API_KEY|requireLLMConfigured|LLM API key is not configured|AgentToolExecutor|OrcaRouter|orca.*router|ORCA' \
  --glob '!browser4-tests/**' --glob '!coworker/**' --glob '!build/**' --glob '!target/**' . \
  || true

Repository: platonai/Browser4

Length of output: 469


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

target="./browser4-agentic/src/main/kotlin/ai/platon/pulsar/agentic/tools/builtin/AgentToolExecutor.kt"

echo "== target file lines 1-120 =="
awk '{printf "%4d\t%s\n", NR, $0}' "$target" | sed -n '1,130p'

echo "== focus area lines 45-80 =="
awk '{printf "%4d\t%s\n", NR, $0}' "$target" | sed -n '45,85p'

echo "== repository test files with relevant search terms only =="
rg -n -C 3 \
  'ORCAROUTER_API_KEY|requireLLMConfigured|LLM API key is not configured|AgentToolExecutor|OrcaRouter|orca.*router|ORCA' \
  --glob '!browser4-tests/**' --glob '!coworker/**' --glob '!build/**' --glob '!target/**' \
  --glob '!**/report/**' --glob '!**/report_*/**' . \
  || true

echo "== list tracked test/spec-looking files under browser4-agentic only =="
git ls-files browser4-agentic | awk 'tolower($0) ~ /(test|spec|spec_)|test(s)?`@/` { print }' | head -n 200 || true

Repository: platonai/Browser4

Length of output: 50375


Add focused tests for the new OrcaRouter LLM fallback.

requireLLMConfigured accepts a non-blank ORCAROUTER_API_KEY and lists it in the missing-key error, but there is no targeted AgentToolExecutor test for this case. Add the smallest relevant tests for non-blank env acceptance and the updated error guidance.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@browser4-agentic/src/main/kotlin/ai/platon/pulsar/agentic/tools/builtin/AgentToolExecutor.kt`
at line 58, Add focused tests for AgentToolExecutor.requireLLMConfigured
covering acceptance of a non-blank ORCAROUTER_API_KEY and inclusion of
ORCAROUTER_API_KEY in the missing-key error guidance. Keep the tests minimal and
isolated to the new OrcaRouter fallback behavior, preserving existing
configuration cases.

Source: Coding guidelines

Comment on lines +31 to +45
fun registerOrcaRouterProvider() {
runCatching {
ChatModelFactory.registerProvider(
ProviderConfig(
apiKeyName = "ORCAROUTER_API_KEY",
modelNameKey = "ORCAROUTER_MODEL_NAME",
baseUrlKey = "ORCAROUTER_BASE_URL",
defaultModel = "orcarouter/auto",
defaultBaseUrl = "https://api.orcarouter.ai/v1",
providerName = "orcarouter",
)
)
}.onFailure { e ->
logger.warn("Failed to register OrcaRouter provider (non-fatal): {}", e.message)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 5 --glob '*Test.kt' --glob '*IT.kt' --glob '*Spec.kt' \
  'ORCAROUTER|registerOrcaRouterProvider|ProviderConfig' \
  browser4-boot browser4-agentic

Repository: platonai/Browser4

Length of output: 155


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate PulsarContextInitializer.kt =="
fd -a 'PulsarContextInitializer\.kt$' . || true

echo "== file outline/contents =="
if [ -f browser4-boot/src/main/kotlin/ai/platon/pulsar/boot/autoconfigure/PulsarContextInitializer.kt ]; then
  wc -l browser4-boot/src/main/kotlin/ai/platon/pulsar/boot/autoconfigure/PulsarContextInitializer.kt
  cat -n browser4-boot/src/main/kotlin/ai/platon/pulsar/boot/autoconfigure/PulsarContextInitializer.kt
fi

echo "== related chat model/provider config usages =="
rg -n -C 4 --glob '!**/build/**' --glob '!**/target/**' \
  'ChatModelFactory|registerProvider|ProviderConfig|registerOrcaRouterProvider|ORCAROUTER' \
  . || true

echo "== Kotlin test/config files under relevant modules =="
git ls-files | rg '(^|/)(browser4-boot|browser4-agentic)/.*(Test|IT|Spec)\.kt$|docs/TESTING\.md$|README.*\.md$' || true

echo "== focused search test files only across repo for OrcaRouter terms =="
git ls-files '*Test.kt' '*IT.kt' '*Spec.kt' | xargs rg -n -C 5 'ORCAROUTER|registerOrcaRouterProvider|ProviderConfig' || true

Repository: platonai/Browser4

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euv

echo "== test references for OrcaRouter exact terms =="
git ls-files '*Test.kt' '*IT.kt' '*Spec.kt' | \
  xargs rg -n 'ORCAROUTER|OrcaRouter|registerOrcaRouterProvider|orcarouter/auto' \
  --no-heading \
  --glob '*.kt' | head -n 20 || true

echo "== provider config/registration method definitions =="
fd -a '\.kt$' browser4-core browser4-skeleton browser4-common browser4 | \
  xargs rg -n --no-heading -C 3 'class ProviderConfig|record ProviderConfig|data class ProviderConfig|fun registerProvider|fun isModelConfigured|orcarouter|ORCAROUTER' \
  --glob '*.kt' | head -n 200 || true

echo "== README docs mentioning OrcaRouter property/env defaults =="
rg -n 'ORCAROUTER_MODEL_NAME|ORCAROUTER_BASE_URL|orcarouter/auto|orcarouter.model.name|orcarouter.base.url' README.md README.zh.md cli/README.md docs/config.md application.properties --glob '*.md' --glob '*.properties' || true

echo "== docs/test coverage target excerpts =="
sed -n '1,160p' docs/TESTING.md 2>/dev/null || true

Repository: platonai/Browser4

Length of output: 4569


Add focused tests for OrcaRouter startup coverage.

registerOrcaRouterProvider() now runs during startup and documents orcarouter/auto and https://api.orcarouter.ai/v1, but existing tests do not cover this provider. Add Unit/Fast coverage for default model/base URL, explicit environment or property overrides, startup without an OrcaRouter key, and registration failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@browser4-boot/src/main/kotlin/ai/platon/pulsar/boot/autoconfigure/PulsarContextInitializer.kt`
around lines 31 - 45, Add focused Unit/Fast tests for
PulsarContextInitializer.registerOrcaRouterProvider() covering the default model
“orcarouter/auto” and base URL, explicit environment or property overrides,
startup when ORCAROUTER_API_KEY is absent, and provider-registration failure.
Verify startup remains non-fatal and logs the expected warning when registration
throws, while preserving existing provider behavior.

Source: Coding guidelines

fun llmStatus(): ResponseEntity<Map<String, Any?>> {
val envKeyNames = listOf(
"OPENROUTER_API_KEY",
"ORCAROUTER_API_KEY",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Treat blank API-key values as not configured.

System.getenv(it) != null and System.getProperty(it) != null treat empty values as valid keys. A blank ORCAROUTER_API_KEY or orcarouter.api.key therefore makes /api/doctor/llm-status report configured=true, while AgentToolExecutor.requireLLMConfigured rejects blank values at Line 62.

Use isNullOrBlank() for both checks and add tests for blank and non-blank OrcaRouter values.

Proposed fix
-        val foundEnvVars = envKeyNames.filter { System.getenv(it) != null }
-        val foundProperties = propertyKeyNames.filter { System.getProperty(it) != null }
+        val foundEnvVars = envKeyNames.filter { !System.getenv(it).isNullOrBlank() }
+        val foundProperties = propertyKeyNames.filter { !System.getProperty(it).isNullOrBlank() }

Also applies to: 136-136

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@browser4-rest/src/main/kotlin/ai/platon/pulsar/rest/api/controller/DoctorController.kt`
at line 127, Update the API-key configuration checks in the DoctorController
logic for ORCAROUTER_API_KEY and orcarouter.api.key to use isNullOrBlank(), so
only non-blank environment and system-property values count as configured. Add
tests covering blank and non-blank OrcaRouter values, matching
AgentToolExecutor.requireLLMConfigured behavior.

Source: Coding guidelines

Comment thread Dockerfile
# Ignore BROWSER_CONTEXT_NUMBER, BROWSER_MAX_OPEN_TABS if BROWSER_CONTEXT_MODE is set to DEFAULT
ENV JAVA_OPTS="-Xms2G -Xmx10G -XX:+UseG1GC" \
OPENROUTER_API_KEY=${OPENROUTER_API_KEY} \
ORCAROUTER_API_KEY=${ORCAROUTER_API_KEY} \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files:\n'
git ls-files | rg '(^|/)Dockerfile(\.[^/]+)?$|(^|/)docs/TESTING.md$' || true

printf '\nDockerfile occurrences:\n'
for f in Dockerfile Dockerfile.fast Dockerfile.native; do
  if [ -f "$f" ]; then
    echo "--- $f"
    nl -ba "$f" | sed -n '50,75p' | tee /tmp/dockerfile_section
  fi
done

printf '\nAll ORCAROUTER_API_KEY and OPENROUTER_API_KEY references in Dockerfiles:\n'
rg -n 'ORCAROUTER_API_KEY|OPENROUTER_API_KEY' Dockerfile Dockerfile.fast Dockerfile.native || true

printf '\nExisting .dockerignore / build context hints:\n'
git ls-files | rg '(^|/)\.dockerignore$|(^|/)docker-compose\.ya?ml$|(^|/)compose\.ya?ml$|(^|/)Dockerfile\.fast$|(^|/)Dockerfile\.native$' || true
for f in .dockerignore docker-compose.yml docker-compose.yaml compose.yml compose.yaml; do
  [ -f "$f" ] && { echo "--- $f"; nl -ba "$f" | sed -n '1,120p'; }
done

printf '\nProject docs about secret/env handling:\n'
if [ -f docs/TESTING.md ]; then
  rg -n 'secret|API_KEY|Docker|docker|env|ENV|environment' docs/TESTING.md docs Dockerfile Dockerfile.fast Dockerfile.native 2>/dev/null | head -n 200 || true
else
  echo "docs/TESTING.md missing"
fi

Repository: platonai/Browser4

Length of output: 370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Behavioral/source check: parse Dockerfile ENV/ARG for the referenced keys and show whether runtime CMD/entrypoint propagates them.
python3 - <<'PY'
from pathlib import Path
import re, sys

dfiles = [Path("Dockerfile"), Path("Dockerfile.fast"), Path("Dockerfile.native")]
keys = {"ORCAROUTER_API_KEY", "OPENROUTER_API_KEY"}

for p in dfiles:
    if not p.exists():
        continue
    lines = p.read_text().splitlines()
    envs = []
    args = []
    runtime_vars = []
    for i, line in enumerate(lines, 1):
        s = line.strip()
        if s.upper().startswith("ENV "):
            parts = re.split(r'[;\s]+', s[4:], 1)
            envs.append((i, parts[0]))
        elif s.upper().startswith("ARG "):
            args.append((i, s[5:].split("=",1)[0]))
        elif s.upper().startswith(("CMD ", "ENTRYPOINT ", "ENV ")):
            runtime_vars.append((i, s))
    print(f"--- {p}")
    print("env:", [(i,k) for i,k in envs if k in keys], flush=True)
    print("arg:", [(i,k) for i,k in args if k in keys], flush=True)
    print("runtime_cmd/entrypoint/env:", runtime_vars, flush=True)
    for k in keys:
        print("image_config", k, "=>", any(k in env or (env.upper().startswith(k+"=") if isinstance(env,str) else False) for _,env in envs))
PY

Repository: platonai/Browser4

Length of output: 772


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Environment variable definitions and references:\n'
for f in Dockerfile Dockerfile.fast Dockerfile.native; do
  if [ -f "$f" ]; then
    echo "--- $f"
    grep -n -E 'ENV|ARG|ORCAROUTER_API_KEY|OPENROUTER_API_KEY|JAVA_OPTS|ENTRYPOINT|RUBIKS_ROUTER|ORCA|ROUTER|openrouter|ocarouter' "$f" || true
  fi
done

printf '\nRelevant file slices:\n'
for f in Dockerfile Dockerfile.fast Dockerfile.native; do
  if [ -f "$f" ]; then
    echo "--- $f lines"
    # shellcheck disable=SC2035
    grep -n -E 'ENV|ARG|ORCAROUTER_API_KEY|OPENROUTER_API_KEY|ENTRYPOINT|JAVA_OPTS|CMD' "$f" | head -n 40 | while read -r match; do
      line="${match%%:*}"
      sed -n "$((line>3?line-3:1)),$((line+6))p" "$f"
      echo
    done
  fi
done

printf '\nOther repository references to ORCAROUTER_API_KEY / OPENROUTER_API_KEY:\n'
grep -RIn -E "ORCAROUTER_API_KEY|OPENROUTER_API_KEY" . || true

Repository: platonai/Browser4

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Docker image instructions mentioning secrets-API keys across tracked files:\n'
git ls-files | grep -E '(^|/)(Dockerfile|.*Dockerfile[^/]*)$' | while read -r f; do
  echo "--- $f"
  grep -n -E 'ENV|ARG|ORCAROUTER_API_KEY|OPENROUTER_API_KEY|JAVA_OPTS|ENTRYPOINT|CMD' "$f" || true
done

printf '\nRead-only Dockerfile source scan for EnvInstruction / ArgInstruction around keys:\n'
python3 - "$@" <<'PY'
from pathlib import Path
import re, sys
for path in [Path("Dockerfile"), Path("Dockerfile.fast"), Path("Dockerfile.native")]:
    if not path.exists():
        continue
    print(f"--- {path}")
    for i,line in enumerate(path.read_text().splitlines(),1):
        s=line.strip()
        if not s: continue
        first=s.split(None,1)[0].upper() if s else ""
        if first in ("ENV","ARG") or "ORCAROUTER_API_KEY" in s or "OPENROUTER_API_KEY" in s:
            print("{:d}: {}".format(i,line))
PY

Repository: platonai/Browser4

Length of output: 2023


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Dockerfile image configuration inspection:\n'
for f in Dockerfile Dockerfile.fast Dockerfile.native; do
  if [ -f "$f" ]; then
    echo "--- $f"
    python3 - "$f" <<'PY'
import sys, re, textwrap
from pathlib import Path
keys = {"ORCAROUTER_API_KEY", "OPENROUTER_API_KEY"}
path = Path(sys.argv[1])
lines = path.read_text().splitlines()
for i,l in enumerate(lines,1):
    s = l.strip()
    cmd = s.split(None,1)[0].upper() if s else ""
    instr = s[len(cmd):].strip() if s.startswith(cmd + " ") else (s[len(cmd):].strip() if s.startswith(cmd + "\t") else "")
    if cmd in ("ENV", "ARG") or any(k in s for k in keys):
        print(f"{i:4}: |{instr}|")
    if cmd in ("CMD", "ENTRYPOINT"):
        print(f"{i:4}: {cmd} -> {instr}")
PY
  fi
done

Repository: platonai/Browser4

Length of output: 839


Keep provider API keys out of Docker image configuration.

Dockerfile, Dockerfile.fast, and Dockerfile.native all use OPENROUTER_API_KEY=${OPENROUTER_API_KEY} and ORCAROUTER_API_KEY=${ORCAROUTER_API_KEY}, so a build-time value is baked into image layers and remains exposed unless the key is unset before build. Pass these keys at container startup and remove the image-level secret entries; apply the same treatment to OPENROUTER_API_KEY.

  • Dockerfile#L67-L68: remove OPENROUTER_API_KEY and ORCAROUTER_API_KEY.
  • Dockerfile.fast#L35-L36: remove OPENROUTER_API_KEY and ORCAROUTER_API_KEY.
  • Dockerfile.native#L66-L67: remove OPENROUTER_API_KEY and ORCAROUTER_API_KEY.
📍 Affects 3 files
  • Dockerfile#L68-L68 (this comment)
  • Dockerfile.fast#L36-L36
  • Dockerfile.native#L67-L67
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile` at line 68, Remove the OPENROUTER_API_KEY and ORCAROUTER_API_KEY
image-level environment entries from Dockerfile lines 67-68, Dockerfile.fast
lines 35-36, and Dockerfile.native lines 66-67. Keep both keys supplied only at
container startup rather than through build-time configuration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant