Skip to content

feat: add OpenClaw/Hermes/OpenCode proxy_config support and UI integration#2505

Open
dashitongzhi wants to merge 9 commits intofarion1231:mainfrom
dashitongzhi:feat/openclaw-auto-rotation
Open

feat: add OpenClaw/Hermes/OpenCode proxy_config support and UI integration#2505
dashitongzhi wants to merge 9 commits intofarion1231:mainfrom
dashitongzhi:feat/openclaw-auto-rotation

Conversation

@dashitongzhi
Copy link
Copy Markdown

Summary

Extends proxy_config table support to all 6 app types (Claude, Codex, Gemini, OpenCode, OpenClaw, Hermes), enabling health monitoring, circuit breaker config, and failover settings for additive-mode apps.

Changes

Database (schema.rs)

  • Expand proxy_config CHECK constraint to include opencode/openclaw/hermes
  • Add seed data for new app types with appropriate defaults
  • Add v10->v11 migration for existing databases
  • Bump SCHEMA_VERSION to 11

Backend (proxy.rs, types.rs)

  • Add hermes field to ProxyTakeoverStatus struct
  • Read OpenClaw/OpenCode/Hermes takeover status from DB instead of hardcoding false
  • HTTP proxy interception remains disabled for additive-mode apps (by design)

Frontend

  • ProxyPanel: Add OpenClaw and Hermes to app takeover grid
  • PricingConfigPanel: Add OpenClaw and Hermes to pricing apps list
  • usage.ts: Add opencode/openclaw to AppTypeFilter type
  • AboutSection: Add openclaw/hermes to tool names

Notes

  • OpenClaw/Hermes/OpenCode use additive mode (all providers coexist in config), not switch mode (HTTP proxy interception). The proxy_config settings (circuit breaker, retries, timeouts) are now available for health monitoring and future config-based failover.
  • HTTP proxy takeover still only applies to Claude/Codex/Gemini. Additive-mode apps manage providers in their own config files.

Future Work

  • Implement config-based failover for additive-mode apps (reorder providers based on health)
  • This will be a separate PR as it requires changes to stream_check and provider sync logic

Kral added 4 commits May 1, 2026 15:41
…r support

- Expand proxy_config CHECK constraint to include opencode/openclaw/hermes
- Add seed data for new app types with appropriate defaults
- Add v10->v11 migration for existing databases
- Update proxy.rs to read OpenClaw/OpenCode takeover status from DB
- Bump SCHEMA_VERSION to 11
…out section

- Add OpenClaw to ProxyPanel app takeover grid
- Add OpenClaw to PricingConfigPanel apps list
- Add opencode/openclaw to AppTypeFilter type
- Add openclaw/hermes to AboutSection tool names
- Add hermes field to ProxyTakeoverStatus struct
- Read hermes takeover status from DB
- Add hermes to ProxyPanel and PricingConfigPanel
Copilot AI review requested due to automatic review settings May 1, 2026 08:04
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 098d8ebc07

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/proxy/ProxyPanel.tsx Outdated
</p>
<div className="grid gap-2 sm:grid-cols-3">
{(["claude", "codex", "gemini"] as const).map((appType) => {
{(["claude", "codex", "gemini", "openclaw", "hermes"] as const).map((appType) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Exclude unsupported apps from takeover switches

Adding openclaw and hermes to the app takeover grid makes the UI expose toggles that cannot succeed: when users enable either one, the request goes through set_takeover_for_app, which still routes additive-mode apps to the unsupported path (sync_live_to_provider/backup_live_config_strict/takeover_live_config_strict return "该应用不支持代理功能"). In practice this creates a permanently failing control for running proxy sessions, so these apps should not be listed in the takeover switch UI until backend takeover logic supports them.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends proxy_config support and related UI wiring to additional app types (OpenCode, OpenClaw, Hermes), with the goal of enabling consistent health monitoring/circuit-breaker configuration across all supported tools.

Changes:

  • Expanded DB proxy_config schema constraints, added seed rows for opencode/openclaw/hermes, and introduced a v10→v11 migration (plus schema version bump).
  • Extended backend takeover status struct to include hermes and started reading opencode/openclaw/hermes enabled state from DB.
  • Updated frontend filters/panels to include the new app/tool names in multiple places.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/types/usage.ts Extends AppTypeFilter union to include opencode/openclaw/hermes.
src/components/usage/PricingConfigPanel.tsx Adds openclaw/hermes to pricing configuration apps list.
src/components/settings/AboutSection.tsx Adds openclaw/hermes to tool environment/version grid list.
src/components/proxy/ProxyPanel.tsx Adds openclaw/hermes to the “app takeover” switch grid.
src-tauri/src/services/proxy.rs Reads opencode/openclaw/hermes enabled state from DB when building takeover status.
src-tauri/src/proxy/types.rs Adds hermes field to ProxyTakeoverStatus.
src-tauri/src/database/schema.rs Expands proxy_config CHECK constraint, adds seeds, and adds v10→v11 migration.
src-tauri/src/database/mod.rs Bumps SCHEMA_VERSION to 11.
.gitignore Ignores .codex-plans/.
Comments suppressed due to low confidence (2)

src/components/proxy/ProxyPanel.tsx:283

  • This UI now offers “应用接管” switches for openclaw/hermes, but the backend takeover flow rejects these app types (e.g. returns “该应用不支持代理功能”), and the hint text claims requests will be forwarded through the local proxy. Either remove these app types from the takeover grid, or render them disabled with an explicit explanation and route their settings to the non-takeover/health-monitoring features instead.
                  {(["claude", "codex", "gemini", "openclaw", "hermes"] as const).map((appType) => {
                    const isEnabled =
                      takeoverStatus?.[
                        appType as keyof typeof takeoverStatus
                      ] ?? false;
                    return (
                      <div
                        key={appType}
                        className="flex items-center justify-between rounded-md border border-primary/20 bg-background/60 px-3 py-2"
                      >
                        <span className="text-sm font-medium capitalize">
                          {appType}
                        </span>
                        <Switch
                          checked={isEnabled}
                          onCheckedChange={(checked) =>
                            handleTakeoverChange(appType, checked)
                          }
                          disabled={setTakeoverForApp.isPending}
                        />
                      </div>
                    );
                  })}
                </div>
                <p className="text-xs text-muted-foreground">
                  {t("proxy.takeover.hint", {
                    defaultValue:
                      "选择要接管的应用,启用后该应用的请求将通过本地代理转发",
                  })}

src-tauri/src/database/schema.rs:146

  • Comments around proxy_config still describe it as a “三行结构” (three-row structure) and “初始化三行数据”, but the table/seed now covers 6 app types. Updating these comments to reflect the current per-app row model will prevent confusion during future schema/migration work.
        // 8. Proxy Config 表(三行结构,app_type 主键)
        conn.execute("CREATE TABLE IF NOT EXISTS proxy_config (
            app_type TEXT PRIMARY KEY CHECK (app_type IN ('claude','codex','gemini','opencode','openclaw','hermes')),
            proxy_enabled INTEGER NOT NULL DEFAULT 0, listen_address TEXT NOT NULL DEFAULT '127.0.0.1',
            listen_port INTEGER NOT NULL DEFAULT 15721, enable_logging INTEGER NOT NULL DEFAULT 1,
            enabled INTEGER NOT NULL DEFAULT 0, auto_failover_enabled INTEGER NOT NULL DEFAULT 0,
            max_retries INTEGER NOT NULL DEFAULT 3, streaming_first_byte_timeout INTEGER NOT NULL DEFAULT 60,
            streaming_idle_timeout INTEGER NOT NULL DEFAULT 120, non_streaming_timeout INTEGER NOT NULL DEFAULT 600,
            circuit_failure_threshold INTEGER NOT NULL DEFAULT 4, circuit_success_threshold INTEGER NOT NULL DEFAULT 2,
            circuit_timeout_seconds INTEGER NOT NULL DEFAULT 60, circuit_error_rate_threshold REAL NOT NULL DEFAULT 0.6,
            circuit_min_requests INTEGER NOT NULL DEFAULT 10,
            default_cost_multiplier TEXT NOT NULL DEFAULT '1',
            pricing_model_source TEXT NOT NULL DEFAULT 'response',
            created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now'))
        )", []).map_err(|e| AppError::Database(e.to_string()))?;

        // 初始化三行数据(每应用不同默认值)
        //
        // 兼容旧数据库:
        // - 老版本 proxy_config 是单例表(没有 app_type 列),此时不能执行三行 seed insert;
        // - 旧表会在 apply_schema_migrations() 中迁移为三行结构后再插入。
        if Self::has_column(conn, "proxy_config", "app_type")? {
            conn.execute(
                "INSERT OR IGNORE INTO proxy_config (app_type, max_retries,

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 36 to 37
const PRICING_APPS = ["claude", "codex", "gemini", "openclaw", "hermes"] as const;
type PricingApp = (typeof PRICING_APPS)[number];
Comment thread src-tauri/src/services/proxy.rs Outdated
Comment on lines 286 to 313
let opencode_enabled = self
.db
.get_proxy_config_for_app("opencode")
.await
.map(|c| c.enabled)
.unwrap_or(false);
let openclaw_enabled = self
.db
.get_proxy_config_for_app("openclaw")
.await
.map(|c| c.enabled)
.unwrap_or(false);

let hermes_enabled = self
.db
.get_proxy_config_for_app("hermes")
.await
.map(|c| c.enabled)
.unwrap_or(false);

Ok(ProxyTakeoverStatus {
claude: claude_enabled,
codex: codex_enabled,
gemini: gemini_enabled,
opencode: opencode_enabled,
openclaw: openclaw_enabled,
hermes: hermes_enabled,
})
Comment on lines +1257 to +1259
// 复制已有数据
conn.execute(
"INSERT INTO proxy_config_new SELECT * FROM proxy_config",
}

const TOOL_NAMES = ["claude", "codex", "gemini", "opencode"] as const;
const TOOL_NAMES = ["claude", "codex", "gemini", "opencode", "openclaw", "hermes"] as const;
jimmyzhuu and others added 5 commits May 1, 2026 23:18
…n1231#2322)

* feat(providers): add baidu qianfan coding plan presets

* refactor(providers): align qianfan presets with existing format

* chore(providers): narrow qianfan coding plan scope
Add the window-state plugin and explicitly save size and position across app exit, restart, and lightweight-mode transitions.
- PricingConfigPanel: add openclaw/hermes/opencode to appConfigs init (#1)
- proxy/types: add hermes field to ProxyTakeoverStatus
- proxy.rs: return consistent false for unsupported app takeover (farion1231#2)
- schema.rs: use explicit column list + preserve live_takeover_active in migration (farion1231#3)
- AboutSection + misc.rs: add openclaw/hermes to VALID_TOOLS (farion1231#4)
- ProxyPanel: filter unsupported apps from takeover switches (farion1231#5)
@farion1231
Copy link
Copy Markdown
Owner

@claude review

@claude
Copy link
Copy Markdown

claude Bot commented May 1, 2026

Claude finished @farion1231's task in 5m 6s —— View job


Claude's Review of PR #2505

  • Gather context and read changed files
  • Analyze schema changes (schema.rs, mod.rs)
  • Analyze backend changes (proxy.rs, types.rs, stream_check.rs)
  • Analyze frontend changes (ProxyPanel.tsx, PricingConfigPanel.tsx, AboutSection.tsx, usage.ts)
  • Check migration logic
  • Post comprehensive review

This PR extends proxy_config support to OpenClaw/Hermes/OpenCode and wires up UI accordingly. The concept is sound, but there is one critical migration bug that will break all existing users on upgrade, plus several smaller issues worth addressing.


🔴 Critical: v10→v11 migration will fail for all existing users

File: src-tauri/src/database/schema.rs:1273

The v10→v11 migration uses:

conn.execute("INSERT INTO proxy_config_new SELECT * FROM proxy_config", [])?;

The problem: every v10 database already has a live_takeover_active column in proxy_config (added in the v9→v10 migration at line 804, and also attempted via ALTER TABLE at line 319). The new proxy_config_new table created in migrate_v10_to_v11 (lines 1254–1268) does not include live_takeover_active. SQLite will reject the SELECT * insert because it produces 21 values for a 20-column table.

Fix: Either include live_takeover_active in the proxy_config_new CREATE TABLE inside migrate_v10_to_v11, or use an explicit column list: Fix this →


🟠 High: SQL interpolation via format! in migration

File: src-tauri/src/database/schema.rs:1279–1285

conn.execute(
    &format!("INSERT OR IGNORE INTO proxy_config_new ... VALUES ('{app}', 3, ...)"),
    [],
)?;

The app values come from a hardcoded slice so there's no actual injection risk, but mixing format! into SQL strings is bad practice and inconsistent with the parameterized pattern used everywhere else. Use ? placeholders and rusqlite::params![]: Fix this →


🟠 High: stream_check.rs auth change breaks Anthropic-strategy health checks

File: src-tauri/src/services/stream_check.rs:437–440

The PR replaces ClaudeAdapter::get_auth_headers(auth) with:

request_builder = request_builder.header("authorization", format!("Bearer {}", auth.api_key));
if auth.strategy == AuthStrategy::Anthropic {
    request_builder = request_builder.header("x-api-key", &auth.api_key);
}

For AuthStrategy::Anthropic, this now sends both Authorization: Bearer and x-api-key. The comment that was removed explicitly warned this was a problem: "避免之前'无条件 Bearer + 条件 x-api-key 双发'导致的假阴性 / auth conflict" (avoid auth conflict from unconditional Bearer + conditional x-api-key double-send). Some proxies that expect only x-api-key will reject the spurious Bearer header.

The removed code deliberately used ClaudeAdapter::get_auth_headers as the single source of truth to avoid exactly this. The new code reintroduces the pre-fix double-send pattern.


🟡 Medium: ProxyPanel switch order is nondeterministic

File: src/components/proxy/ProxyPanel.tsx:254–277

The old code used a hardcoded ["claude", "codex", "gemini"] array — explicit and deterministic. The new code uses:

Object.entries(takeoverStatus ?? {})
  .filter(([appType]) => !(["opencode", "openclaw", "hermes"] as string[]).includes(appType))
  .map(...)

The display order now depends on the serialization order of the ProxyTakeoverStatus struct fields. If the struct field order changes in types.rs, the UI order silently changes too. A simple fix is to keep a hardcoded ["claude", "codex", "gemini"] as const array and look up values from takeoverStatus: Fix this →


🟡 Medium: Stale comments describe "三行结构" (3-row structure)

File: src-tauri/src/database/schema.rs:122, 143

// 8. Proxy Config 表(三行结构,app_type 主键)
// ...
// 初始化三行数据(每应用不同默认值)

These comments now misrepresent the table, which holds 6 rows. Minor but causes confusion during future schema/migration work.


🟢 Confirmed OK (after closer inspection)

  • AboutSection.tsx / VALID_TOOLS: Copilot flagged that openclaw/hermes in TOOL_NAMES wouldn't return version info. This is fixed — VALID_TOOLS in misc.rs:110 now includes both, and get_single_tool_version_impl has matching match arms (fetch_github_latest_version with the respective repos). Verify the repo slugs (openclaw/openclaw, NousResearch/hermes-agent) are correct.

  • PricingConfigPanel.tsx: Copilot raised a concern about appConfigs[app] being undefined for new apps. The PR correctly initializes all 6 keys in both useState (line 54–61) and the newState object in loadAllConfigs (lines 102–108). The isDirty check and handleSaveAll iterate PRICING_APPS, all of which are now initialized. This concern is resolved.

  • ProxyPanel.tsx takeover switches: Previous reviews flagged that openclaw/hermes switches would expose non-functional controls. The filter in this PR correctly hides them — only Claude/Codex/Gemini appear in the grid now.

  • ProxyTakeoverStatus / get_takeover_status: openclaw_enabled and hermes_enabled remain hardcoded false despite having proxy_config.enabled rows. This is intentional (HTTP takeover is not supported for additive-mode apps) and correctly documented in the PR notes.


Summary

The PR's intent is solid and the frontend/backend wiring is mostly correct. The v10→v11 migration column mismatch is a release-blocker that will crash the database upgrade for every existing user. The auth regression in stream_check.rs is also worth investigating before merge, as it reintroduces a previously-fixed double-header issue.

@farion1231
Copy link
Copy Markdown
Owner

@claude review

(Re-running with the updated review prompt for comparison.)

@claude
Copy link
Copy Markdown

claude Bot commented May 2, 2026

Claude finished @farion1231's task in 5m 13s —— View job


Review (re-run with high-signal-only criteria)

One Important issue. The author's "address all PR review feedback" commit (92b1619) fixed migrate_proxy_config_to_per_app but missed migrate_v10_to_v11 — the column-count bug originally flagged is still live.


🔴 Important — migrate_v10_to_v11 will fail with column count mismatch on every existing v10 install (confidence: 95)

src-tauri/src/database/schema.rs:1253-1275

The new proxy_config_new defined in v10→v11 has 20 columns (no live_takeover_active):

// lines 1254-1267 — no live_takeover_active
CREATE TABLE proxy_config_new (
    app_type ..., proxy_enabled, ..., circuit_min_requests,
    default_cost_multiplier, pricing_model_source,
    created_at, updated_at
)

But every existing v10 database has proxy_config with 21 columns including live_takeover_active. It gets there via two converging paths, both still present:

  • create_tables_on_conn runs before migrations and unconditionally ALTER TABLE proxy_config ADD COLUMN live_takeover_active … at schema.rs:318-321.
  • The (now-fixed) migrate_proxy_config_to_per_app rebuilds the table with live_takeover_active (schema.rs:804).

So INSERT INTO proxy_config_new SELECT * FROM proxy_config at schema.rs:1273 produces 21 values for a 20-column target → SQLite errors out with table proxy_config_new has 20 columns but 21 values were supplied. The savepoint rolls back, and the user is wedged on v10 unable to run v11.

This is the same finding from the prior review; the fix in commit 92b1619 only patched the older migration helper. Even an explicit column list isn't enough on its own — proxy_config_new also needs live_takeover_active (or the column needs to be deliberately dropped, in which case the explicit list should omit it from the source side too).

Fix this →


Notes on prior findings (verified resolved or out of scope)

  • Earlier review's stream_check.rs auth-header concern is not introduced by this PR — that file's recent changes come from PRs fix(proxy): preserve Vertex AI full URLs #2415 / feat(providers): add Baidu Qianfan Coding Plan for Claude Code #2322 visible in unrelated history; the apparent diff is a no-merge-base artifact. Skipping.
  • PricingConfigPanel (appConfigs init), AboutSection / VALID_TOOLS, and ProxyPanel filtering of additive-mode apps are all addressed in commit 92b1619.
  • The format!()-built migration insert at schema.rs:1280-1285 only interpolates a hardcoded &["opencode","openclaw","hermes"] slice — no injection surface, style-only, not posting.

Tasks

  • Verify v10→v11 migration column claim
  • Confirm stream_check.rs is out of PR scope
  • Re-check ProxyPanel / PricingConfigPanel / AboutSection post-feedback
  • Post review
    • Branch: feat/openclaw-auto-rotation

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.

7 participants