Skip to content

feat: add Claude proxy target APIs with Ollama support#171

Open
awsl233777 wants to merge 16 commits into
mainfrom
feat/claude-chat-completions-proxy
Open

feat: add Claude proxy target APIs with Ollama support#171
awsl233777 wants to merge 16 commits into
mainfrom
feat/claude-chat-completions-proxy

Conversation

@awsl233777
Copy link
Copy Markdown
Collaborator

@awsl233777 awsl233777 commented May 24, 2026

Summary

  • Add Claude proxy target API modes for Responses, Chat Completions, and Ollama.
  • Add config UI controls, badges, persistence, and startup/install wiring for the selected Claude target API.
  • Route Claude-compatible /v1/models and /v1/messages through the selected upstream API, including Anthropic → Ollama /api/chat conversion.
  • Preserve safer UI/runtime behavior around compact layouts, overlays, proxy start/stop, and rollback paths.

Real endpoint validation

Validated the Ollama target mode against a provided real Ollama endpoint without writing to the user's real ~/.codex config:

  • Created a temporary worktree and temporary Claude proxy settings file.
  • Started the built-in Claude proxy with targetApi: 'ollama' and a direct upstream base URL.
  • Verified GET /v1/models routed through the proxy to upstream /api/tags and returned the expected Ollama cloud model list.
  • Verified POST /v1/messages routed through the proxy to upstream /api/chat and converted the response back into an Anthropic-style message:
    • minimax-m3:cloud succeeded with a non-empty response when max_tokens was raised to 128.
    • gemma4:31b-cloud succeeded with a non-empty response.
  • Verified a forbidden upstream model returns a mapped Anthropic error instead of a false success.

Observed edge case: minimax-m3:cloud can return only message.thinking with an empty message.content when max_tokens / Ollama num_predict is too small (for example 32 with a system prompt). This is upstream model behavior; practical configs should avoid very small max token limits for that model.

Validation

  • GitHub checks are green for this PR, including the Node 18/20/22 CI matrix and CodeRabbit review.
  • Local real-endpoint proxy validation passed for the Ollama target mode as described above.
  • Attempted to run the full local unit runner from the temporary worktree, but the local environment was missing zip-lib; that failure was an environment dependency issue, not an Ollama proxy-path failure.
  • Temporary script, worktree, and branch were removed after validation; no local proxy/node process was left running.

Summary by CodeRabbit

Release Notes

  • New Features

    • Support for multiple Claude API targets (Anthropic Responses, OpenAI Chat Completions, Ollama) with configurable selection
    • App update availability notifications displayed in the sidebar
    • Ollama backend integration without requiring API keys
  • Improvements

    • Enhanced Claude configuration UI with target API indicators and selection
    • Extended internationalization support (English, Japanese, Vietnamese, Chinese)
    • Refined tool configuration overlays and form controls

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

Review Change Stack

Warning

Review limit reached

@awsl233777, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 10 minutes and 36 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1b921ecc-29c9-4c66-ac61-2bbd70e8b39d

📥 Commits

Reviewing files that changed from the base of the PR and between 7aa34db and 8d20258.

📒 Files selected for processing (32)
  • cli.js
  • cli/claude-proxy.js
  • cli/update.js
  • tests/e2e/test-claude-proxy.js
  • tests/e2e/test-claude.js
  • tests/unit/claude-proxy-adapter.test.mjs
  • tests/unit/claude-settings-sync.test.mjs
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/install-target-cards.test.mjs
  • tests/unit/provider-share-command.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/app.js
  • web-ui/logic.claude.mjs
  • web-ui/logic.runtime.mjs
  • web-ui/modules/app.methods.claude-config.mjs
  • web-ui/modules/app.methods.index.mjs
  • web-ui/modules/app.methods.install.mjs
  • web-ui/modules/app.methods.session-actions.mjs
  • web-ui/modules/app.methods.session-timeline.mjs
  • web-ui/modules/app.methods.startup-claude.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/modals-basic.html
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/partials/index/panel-config-codex.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/styles/controls-forms.css
  • web-ui/styles/layout-shell.css
📝 Walkthrough

Walkthrough

Adds a canonical Claude config field targetApi and implements mode-aware proxy routing (Anthropic responses, OpenAI chat_completions, Ollama) plus CLI apply lifecycle, web UI state/validation, i18n, update-status flow, and unit/e2e tests.

Changes

Claude Proxy Dual-Mode Routing with targetApi

Layer / File(s) Summary
Proxy adapters and format conversion
cli/claude-proxy.js, tests/unit/claude-proxy-adapter.test.mjs, tests/unit/*
Adds image/data-url helpers, bridge-block filtering, Anthropic→responses updates, OpenAI chat-completions and Ollama request builders, and decoders to translate upstream chat/ollama responses into Anthropic message shape; adds unit/integration tests validating mappings.
Mode-aware proxy runtime & upstream routing
cli/claude-proxy.js, tests/e2e/test-claude-proxy.js
Normalizes targetApi, adds direct-upstream and OpenAI-bridge resolution, chooses upstream path/headers by target, reports mode in health/status, and selects appropriate builder/decoder for /v1/messages; e2e tests cover chat_completions, bridge failure/success, and Ollama routing.
CLI apply/share lifecycle and schema
cli.js, tests/unit/provider-share-command.test.mjs
Persists DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.targetApi, adds normalizeClaudeTargetApi, refactors applyToClaudeSettings to async with proxy start/stop, token/listenUrl writes and rollback on failure; adds --target-api parsing, share payload/command handling allowing empty apiKey for ollama, and MCP schema conditional apiKey requirement.
fetchLatestVersion & update-status flow
cli/update.js, cli.js, web-ui/modules/app.methods.install.mjs
Adds timeout-aware fetchLatestVersion, exposes version-status web action, createInstallMethods adds version comparison, loadAppVersionStatus, and UI helpers to surface update notices.
Web UI: Claude config state, validation, modals, rendering
web-ui/app.js, web-ui/logic.claude.mjs, web-ui/modules/app.methods.claude-config.mjs, web-ui/modules/app.methods.startup-claude.mjs, web-ui/partials/*, web-ui/res/*
Threads targetApi through UI state, normalizes aliases (chat-completions variants → chat_completions, ollama passthrough, default responses), exempts apiKey requirement for ollama, includes targetApi in apply payloads/dedupe, adds modal selects and provider badges, and adds i18n keys.
Unit & E2E tests for modes and settings
tests/e2e/*, tests/unit/*
Adds upstream chat/completions mock and tests, proxy restart/apply/rollback e2e scenarios (chat_completions/ollama), unit tests for request/response conversions, and updates parity/logic tests for new UI keys and layout changes.
UI layout heuristics & overlay tweak
web-ui/logic.runtime.mjs, web-ui/modules/app.methods.session-timeline.mjs, web-ui/styles/controls-forms.css
Removes UA-based compact-mode forcing in layout heuristic, updates call sites, converts .tool-config-write-overlay to fixed full-viewport with safe-area padding and higher z-index; templates/checkbox attributes adjusted.

Estimated code review effort:
🎯 4 (Complex) | ⏱️ ~60 minutes

"I hopped the code and spun a tiny proxy,
Responses, Chat, and Ollama — one rabbit, three toys,
Tokens born locally, rollbacks kept tight,
UI picks the target, badges gleam in the light,
The rabbit cheers — dual-mode proxy runs right!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.02% 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 'feat: add Claude proxy target APIs with Ollama support' clearly and concisely summarizes the main change: adding Claude proxy support for multiple target APIs including Ollama.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/claude-chat-completions-proxy

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
tests/unit/web-ui-logic.test.mjs (1)

84-87: ⚡ Quick win

Cover canonical chat_completions in normalization test.

This test validates two aliases but not the canonical value itself; adding it will better lock the API contract.

Proposed test addition
 test('normalizeClaudeConfig accepts chat completions target api aliases', () => {
+    assert.strictEqual(normalizeClaudeConfig({ targetApi: 'chat_completions' }).targetApi, 'chat_completions');
     assert.strictEqual(normalizeClaudeConfig({ targetApi: 'chat/completions' }).targetApi, 'chat_completions');
     assert.strictEqual(normalizeClaudeConfig({ targetApi: 'chat-completions' }).targetApi, 'chat_completions');
 });
🤖 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 `@tests/unit/web-ui-logic.test.mjs` around lines 84 - 87, Add an assertion to
the existing test that covers the canonical value: call normalizeClaudeConfig
with targetApi: 'chat_completions' and assert its targetApi remains
'chat_completions' so the test verifies aliases plus the canonical form; update
the test block in tests/unit/web-ui-logic.test.mjs around the
normalizeClaudeConfig assertions to include this additional assertion
referencing normalizeClaudeConfig and targetApi.
tests/unit/claude-settings-sync.test.mjs (1)

480-513: ⚡ Quick win

Add a preservation case for non-default targetApi during merge.

Current test only validates default responses; it doesn’t catch regressions where an existing chat_completions value is unintentionally reset on edit.

Proposed additional assertion
 test('mergeClaudeConfig preserves externalCredentialType across edits without api key', () => {
@@
     assert.deepStrictEqual(merged, {
         apiKey: '',
         baseUrl: 'https://api.anthropic.com/',
         model: 'claude-3-7-sonnet',
         hasKey: true,
         externalCredentialType: 'auth-token',
         targetApi: 'responses'
     });
+
+    const mergedChat = mergeClaudeConfig.call(context, {
+        apiKey: '',
+        baseUrl: 'https://api.anthropic.com',
+        model: 'claude-3-7-sonnet',
+        hasKey: true,
+        externalCredentialType: 'auth-token',
+        targetApi: 'chat_completions'
+    }, {
+        model: 'claude-sonnet-4-6'
+    });
+    assert.strictEqual(mergedChat.targetApi, 'chat_completions');
 });
🤖 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 `@tests/unit/claude-settings-sync.test.mjs` around lines 480 - 513, The test
currently only verifies that mergeClaudeConfig preserves defaults
(targetApi='responses') and misses regressions where an existing non-default
targetApi (e.g., 'chat_completions') is lost on merge; update the test for
mergeClaudeConfig in tests/unit/claude-settings-sync.test.mjs to include an
input/initial config with targetApi set to a non-default value (such as
'chat_completions') and assert that the merged result retains that explicit
targetApi rather than being reset to 'responses'; use the same
context.normalizeClaudeConfig and the mergeClaudeConfig function reference to
craft the call and expected assertion so the test will fail if mergeClaudeConfig
incorrectly overwrites non-default targetApi.
tests/e2e/test-claude-proxy.js (1)

335-342: ⚡ Quick win

Use predicate-based request matching instead of positional indexes.

Index-based checks can become flaky if an extra upstream chat call is introduced (retry, preflight, future hook). Matching by payload shape keeps this test stable.

Proposed assertion hardening
 const upstreamChatMessages = upstream.requests.filter((item) => item.path === '/v1/chat/completions');
 assert(upstreamChatMessages.length >= 2, 'claude proxy should hit upstream /v1/chat/completions');
-assert(upstreamChatMessages[0].headers.authorization === 'Bearer sk-claude-upstream', 'claude proxy chat should use provider auth for upstream');
-assert(upstreamChatMessages[0].body.messages[0].role === 'system', 'claude proxy chat should map system prompt to system message');
-assert(upstreamChatMessages[0].body.max_tokens === 128, 'claude proxy chat should map max_tokens to max_tokens');
-assert(upstreamChatMessages[0].body.stream === false, 'claude proxy chat should synthesize Anthropic streaming locally');
-assert(upstreamChatMessages[1].body.tool_choice.function.name === 'lookup', 'claude proxy chat should map tool_choice');
+const plainChatReq = upstreamChatMessages.find((item) =>
+    item.body && item.body.model === 'DeepSeek-V4-pro' && !item.body.tools
+);
+const toolChatReq = upstreamChatMessages.find((item) =>
+    item.body && Array.isArray(item.body.tools) && item.body.tools.length > 0
+);
+assert(plainChatReq && plainChatReq.headers.authorization === 'Bearer sk-claude-upstream', 'claude proxy chat should use provider auth for upstream');
+assert(plainChatReq.body.messages[0].role === 'system', 'claude proxy chat should map system prompt to system message');
+assert(plainChatReq.body.max_tokens === 128, 'claude proxy chat should map max_tokens to max_tokens');
+assert(plainChatReq.body.stream === false, 'claude proxy chat should synthesize Anthropic streaming locally');
+assert(toolChatReq && toolChatReq.body.tool_choice.function.name === 'lookup', 'claude proxy chat should map tool_choice');
🤖 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 `@tests/e2e/test-claude-proxy.js` around lines 335 - 342, The tests are using
positional indexes on upstreamChatMessages which is flaky; update the assertions
to locate requests by predicate on their payload shape instead of array
position: use upstream.requests.filter(...) or find(...) to match by
headers.authorization === 'Bearer sk-claude-upstream' for the provider-auth
check, match a request whose body.messages[0].role === 'system' and
body.max_tokens === 128 and body.stream === false for the
system/max_tokens/stream assertions, and match a request whose
body.tool_choice?.function?.name === 'lookup' for the tool_choice assertion;
replace the index-based assertions that reference upstreamChatMessages[0] and
[1] with these predicate-found entries and assert they exist and have the
expected properties.
🤖 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 `@web-ui/partials/index/modals-basic.html`:
- Around line 136-143: Replace the hardcoded Chinese label, option texts and
hint for the target API selector with i18n keys rendered through t(...);
specifically update the strings inside the select block bound to
newClaudeConfig.targetApi (the label "目标 API", the two option texts "Anthropic /
OpenAI Responses" and "OpenAI Chat Completions (/v1/chat/completions)", and the
form-hint copy) to use translation keys (e.g., t('claude.targetApi.label'),
t('claude.targetApi.option.responses'),
t('claude.targetApi.option.chat_completions'), t('claude.targetApi.hint')) and
ensure the same change is applied to the duplicated block around the other
selector (the block you modified at the later occurrence). Ensure keys exist in
the locale files.

In `@web-ui/partials/index/panel-config-claude.html`:
- Line 132: The subtitle text inside the div with class "card-subtitle" (the
element gated by v-if="config.targetApi === 'chat_completions'") is hardcoded;
replace it with a localized string by calling the translation helper (e.g.,
t('panel.mode.openai_chat_completions')) so it follows the app locale, and add
the corresponding translation key to the i18n resources; ensure the
component/context provides the t(...) function before using it.

In `@web-ui/res/web-ui-render.precompiled.js`:
- Around line 1890-1894: The hardcoded UI strings shown when checking
config.targetApi (e.g., the "OpenAI Chat Completions" subtitle rendered in the
_createElementBlock with class "card-subtitle", plus the field label, option
labels and hint around the same conditional branches at the other locations)
must be replaced with i18n keys and rendered via _ctx.t(...). Update the three
affected render sites to call _ctx.t('key_name') instead of raw text, add
appropriate i18n keys for the card subtitle, field label, option labels and hint
(use descriptive keys like "card.subtitle.openai_chat_completions",
"field.label.target_api", "option.label.x", "field.hint.target_api"), and ensure
the conditional branches still pick the right key based on config.targetApi so
translations are used for language switching.

---

Nitpick comments:
In `@tests/e2e/test-claude-proxy.js`:
- Around line 335-342: The tests are using positional indexes on
upstreamChatMessages which is flaky; update the assertions to locate requests by
predicate on their payload shape instead of array position: use
upstream.requests.filter(...) or find(...) to match by headers.authorization ===
'Bearer sk-claude-upstream' for the provider-auth check, match a request whose
body.messages[0].role === 'system' and body.max_tokens === 128 and body.stream
=== false for the system/max_tokens/stream assertions, and match a request whose
body.tool_choice?.function?.name === 'lookup' for the tool_choice assertion;
replace the index-based assertions that reference upstreamChatMessages[0] and
[1] with these predicate-found entries and assert they exist and have the
expected properties.

In `@tests/unit/claude-settings-sync.test.mjs`:
- Around line 480-513: The test currently only verifies that mergeClaudeConfig
preserves defaults (targetApi='responses') and misses regressions where an
existing non-default targetApi (e.g., 'chat_completions') is lost on merge;
update the test for mergeClaudeConfig in
tests/unit/claude-settings-sync.test.mjs to include an input/initial config with
targetApi set to a non-default value (such as 'chat_completions') and assert
that the merged result retains that explicit targetApi rather than being reset
to 'responses'; use the same context.normalizeClaudeConfig and the
mergeClaudeConfig function reference to craft the call and expected assertion so
the test will fail if mergeClaudeConfig incorrectly overwrites non-default
targetApi.

In `@tests/unit/web-ui-logic.test.mjs`:
- Around line 84-87: Add an assertion to the existing test that covers the
canonical value: call normalizeClaudeConfig with targetApi: 'chat_completions'
and assert its targetApi remains 'chat_completions' so the test verifies aliases
plus the canonical form; update the test block in
tests/unit/web-ui-logic.test.mjs around the normalizeClaudeConfig assertions to
include this additional assertion referencing normalizeClaudeConfig and
targetApi.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6ae0fbd9-703c-4ef4-b64e-cefdd78b6682

📥 Commits

Reviewing files that changed from the base of the PR and between 9ffba03 and 4b24338.

📒 Files selected for processing (13)
  • cli.js
  • cli/claude-proxy.js
  • tests/e2e/test-claude-proxy.js
  • tests/unit/claude-proxy-adapter.test.mjs
  • tests/unit/claude-settings-sync.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/app.js
  • web-ui/logic.claude.mjs
  • web-ui/modules/app.methods.claude-config.mjs
  • web-ui/modules/app.methods.startup-claude.mjs
  • web-ui/partials/index/modals-basic.html
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/res/web-ui-render.precompiled.js
📜 Review details
🔇 Additional comments (10)
cli.js (1)

293-294: LGTM!

Also applies to: 5492-5494

cli/claude-proxy.js (2)

136-151: LGTM!

Also applies to: 238-352, 432-505, 1268-1270


644-647: LGTM!

Also applies to: 676-683, 757-784, 810-813, 1015-1015, 1041-1041, 1074-1077, 1080-1084, 1097-1099, 1198-1199, 1247-1247

web-ui/logic.claude.mjs (1)

72-75: LGTM!

Also applies to: 82-83

web-ui/app.js (1)

278-279: LGTM!

Also applies to: 284-285, 292-294, 539-542

web-ui/modules/app.methods.claude-config.mjs (1)

55-57: LGTM!

Also applies to: 67-69, 88-88, 110-110, 186-186, 208-210

web-ui/modules/app.methods.startup-claude.mjs (1)

250-252: LGTM!

tests/unit/claude-proxy-adapter.test.mjs (1)

7-10: LGTM!

Also applies to: 67-105, 145-172

tests/unit/web-ui-logic.test.mjs (1)

54-55: LGTM!

Also applies to: 66-80

tests/e2e/test-claude-proxy.js (1)

101-142: LGTM!

Also applies to: 260-334, 343-394

Comment thread web-ui/partials/index/modals-basic.html
Comment thread web-ui/partials/index/panel-config-claude.html Outdated
Comment thread web-ui/res/web-ui-render.precompiled.js Outdated
@awsl233777
Copy link
Copy Markdown
Collaborator Author

Updated the PR to close the Claude Chat Completions apply-path gap and the UI i18n review comment.

Changes in this push:

  • Replaced hardcoded Claude target API UI strings with i18n keys and regenerated the precompiled Web UI render.
  • When a Claude config uses targetApi: chat_completions, apply-claude-config now starts the local Claude-compatible proxy and writes ~/.claude/settings.json to that local proxy URL instead of writing the upstream URL directly.
  • The local proxy uses the applied Claude config's upstream baseUrl/key as its direct upstream, so this works even when the Claude config name is not a Codex provider name.
  • Restoring/applying responses mode stops the Claude proxy and resets its saved target API to avoid state leakage into later proxy starts.
  • Added E2E coverage for the direct-Claude-config upstream path and the local-proxy settings URL behavior.

Validation run locally:

  • node -c cli.js
  • node -c cli/claude-proxy.js
  • npm run test:unit (All 533 tests passed)
  • npm run test:e2e
  • npm run lint
  • git diff --check

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

@awsl233777 Thanks for the detailed update! The changes look comprehensive — closing the apply-path gap for chat_completions mode (routing through the local proxy and writing the proxy URL to ~/.claude/settings.json), cleaning up state on responses mode restore, the i18n pass on the UI strings, and the expanded E2E coverage all sound solid. Triggering a full re-review now.

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli.js (1)

9176-9214: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Rollback proxy runtime on post-start failure paths.

If the proxy starts successfully but a later step fails (e.g., reading/writing Claude settings), the function returns an error while leaving the proxy running. That creates inconsistent state (apply failed, runtime still active).

Proposed fix
 async function applyToClaudeSettings(config = {}) {
+    let proxyStarted = false;
     try {
@@
         if (targetApi === 'chat_completions') {
             await stopBuiltinClaudeProxyRuntime();
             proxyResult = await startBuiltinClaudeProxyRuntime({
@@
             if (!proxyResult || proxyResult.error || proxyResult.success === false || !proxyResult.listenUrl) {
+                await stopBuiltinClaudeProxyRuntime();
                 return {
                     success: false,
                     mode: 'claude-proxy',
                     error: (proxyResult && proxyResult.error) || '启动 Claude 兼容代理失败'
                 };
             }
+            proxyStarted = true;
             settingsBaseUrl = proxyResult.listenUrl;
             settingsApiKey = 'codexmate';
         } else {
@@
         const readResult = readJsonObjectFromFile(CLAUDE_SETTINGS_FILE, {});
         if (!readResult.ok) {
+            if (proxyStarted) {
+                await stopBuiltinClaudeProxyRuntime();
+            }
             return { success: false, mode: 'settings-file', error: readResult.error };
         }
@@
     } catch (e) {
+        if (proxyStarted) {
+            try { await stopBuiltinClaudeProxyRuntime(); } catch (_) { }
+        }
         return {
             success: false,
             mode: 'settings-file',
             error: e.message || '应用 Claude 配置失败'
         };
     }
 }

Also applies to: 9262-9267

🤖 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 `@cli.js` around lines 9176 - 9214, The code may leave the built-in Claude
proxy running if later steps fail; ensure we stop the proxy on all post-start
failure paths by calling stopBuiltinClaudeProxyRuntime() before any early return
after a successful start (i.e., when proxyResult indicates success and you later
hit an error while handling CLAUDE_SETTINGS_FILE, readJsonObjectFromFile, or
writeJsonAtomic). Specifically, after using startBuiltinClaudeProxyRuntime() and
assigning settingsBaseUrl/settingsApiKey (and likewise in the branch that writes
BUILTIN_CLAUDE_PROXY_SETTINGS_FILE), add logic to call
stopBuiltinClaudeProxyRuntime() (or otherwise tear down the runtime) whenever
you are about to return an error, so proxyResult/started runtimes are cleaned up
consistently.
🤖 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 `@cli.js`:
- Around line 9178-9197: The code currently sets a predictable proxy credential
(settingsApiKey = 'codexmate') after calling startBuiltinClaudeProxyRuntime,
which makes the proxy guessable if bound to a non-loopback address; replace this
by generating a cryptographically-secure random token for settingsApiKey (e.g.,
using crypto.randomBytes) and assign that token instead of the constant, and
additionally validate proxyResult.listenUrl (from
startBuiltinClaudeProxyRuntime) to ensure it is bound to a loopback address
(127.0.0.1/::1); if the listenUrl is non-loopback either refuse to use a
fixed/predictable key (fail or require explicit config) or log and rotate a
random token, ensuring settingsBaseUrl uses proxyResult.listenUrl and only the
random token is exposed as the API key.

---

Outside diff comments:
In `@cli.js`:
- Around line 9176-9214: The code may leave the built-in Claude proxy running if
later steps fail; ensure we stop the proxy on all post-start failure paths by
calling stopBuiltinClaudeProxyRuntime() before any early return after a
successful start (i.e., when proxyResult indicates success and you later hit an
error while handling CLAUDE_SETTINGS_FILE, readJsonObjectFromFile, or
writeJsonAtomic). Specifically, after using startBuiltinClaudeProxyRuntime() and
assigning settingsBaseUrl/settingsApiKey (and likewise in the branch that writes
BUILTIN_CLAUDE_PROXY_SETTINGS_FILE), add logic to call
stopBuiltinClaudeProxyRuntime() (or otherwise tear down the runtime) whenever
you are about to return an error, so proxyResult/started runtimes are cleaned up
consistently.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f1c4589c-b7ff-4435-a393-681c33a08def

📥 Commits

Reviewing files that changed from the base of the PR and between 4b24338 and 9c40b1a.

📒 Files selected for processing (9)
  • cli.js
  • cli/claude-proxy.js
  • tests/e2e/test-claude.js
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/modules/app.methods.claude-config.mjs
  • web-ui/modules/i18n.dict.mjs
  • web-ui/partials/index/modals-basic.html
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/res/web-ui-render.precompiled.js
✅ Files skipped from review due to trivial changes (1)
  • web-ui/modules/i18n.dict.mjs
📜 Review details
🔇 Additional comments (22)
tests/e2e/test-claude.js (1)

67-91: LGTM!

web-ui/partials/index/panel-config-claude.html (1)

132-132: Ensure claude.targetApi.chatCompletionsBadge is present in i18n resources.

claude.targetApi.chatCompletionsBadge is defined in web-ui/modules/i18n.dict.mjs for all supported locale blocks (zh, ja, en), so the t('claude.targetApi.chatCompletionsBadge') call won’t fail due to a missing key.

web-ui/res/web-ui-render.precompiled.js (1)

1894-1894: LGTM!

Also applies to: 5561-5571, 5675-5685

tests/unit/web-ui-logic.test.mjs (1)

84-88: LGTM!

web-ui/partials/index/modals-basic.html (1)

136-143: LGTM!

Also applies to: 175-182

cli.js (1)

9154-9159: LGTM!

Also applies to: 9399-9403, 11008-11008, 15556-15558

cli/claude-proxy.js (10)

136-151: LGTM!


238-293: LGTM!


295-352: LGTM!


432-505: LGTM!


816-840: LGTM!


1212-1212: LGTM!


1099-1111: LGTM!


526-617: LGTM!


681-684: LGTM!


757-785: LGTM!

web-ui/modules/app.methods.claude-config.mjs (6)

50-59: LGTM!


61-72: LGTM!


85-89: LGTM!


110-127: LGTM!


186-199: LGTM!


202-211: LGTM!

Comment thread cli.js
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli.js (1)

9176-9267: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stop the proxy again if the later settings write path fails.

After Line 9178 starts the runtime, any later failure in readJsonObjectFromFile, backupFileIfNeededOnce, or writeJsonAtomic returns an error but never tears the proxy back down. That leaves a leaked background process and persisted proxy state that no longer matches ~/.claude/settings.json.

🧯 Suggested rollback
 async function applyToClaudeSettings(config = {}) {
     try {
         const apiKey = (config.apiKey || '').trim();
         if (!apiKey) {
             return { success: false, mode: 'settings-file', error: '请先输入 API Key' };
@@
         const targetApi = normalizeClaudeTargetApi(config.targetApi);
         let settingsBaseUrl = baseUrl;
         let settingsApiKey = apiKey;
         let proxyResult = null;
+        let proxyStarted = false;
@@
             proxyResult = await startBuiltinClaudeProxyRuntime({
                 enabled: true,
                 provider: typeof config.name === 'string' ? config.name.trim() : '',
                 authSource: 'provider',
                 targetApi,
@@
             if (!proxyResult || proxyResult.error || proxyResult.success === false || !proxyResult.listenUrl) {
                 return {
                     success: false,
                     mode: 'claude-proxy',
                     error: (proxyResult && proxyResult.error) || '启动 Claude 兼容代理失败'
                 };
             }
+            proxyStarted = true;
             settingsBaseUrl = proxyResult.listenUrl;
             settingsApiKey = 'codexmate';
@@
     } catch (e) {
+        if (proxyStarted) {
+            try {
+                await stopBuiltinClaudeProxyRuntime();
+            } catch (_) { }
+        }
         return {
             success: false,
             mode: 'settings-file',
             error: e.message || '应用 Claude 配置失败'
         };
🤖 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 `@cli.js` around lines 9176 - 9267, The code can leak a running proxy if
startBuiltinClaudeProxyRuntime succeeds but later steps (readJsonObjectFromFile,
backupFileIfNeededOnce, writeJsonAtomic) fail; modify the flow around
startBuiltinClaudeProxyRuntime/stopBuiltinClaudeProxyRuntime so that when
proxyResult indicates a proxy was started you always stop it on any early return
or exception—either wrap the post-proxy logic in a try/finally that calls
stopBuiltinClaudeProxyRuntime() in the finally, or track a boolean like
proxyStarted and call stopBuiltinClaudeProxyRuntime() before each error return
and in the catch block; ensure this covers the branches that return after
readJsonObjectFromFile errors and after writeJsonAtomic/backup failures, and
still preserve returning proxy info only when the operation completes
successfully.
♻️ Duplicate comments (1)
cli.js (1)

9178-9196: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Force loopback before writing the fixed proxy token.

startBuiltinClaudeProxyRuntime(...) is called without a host override, so a previously persisted non-loopback bind can still be reused here. Line 9196 then writes a constant ANTHROPIC_API_KEY, which makes the proxy credential guessable off-box if this runtime comes up anywhere other than loopback.

🔒 Minimal hardening
             proxyResult = await startBuiltinClaudeProxyRuntime({
                 enabled: true,
+                host: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.host,
                 provider: typeof config.name === 'string' ? config.name.trim() : '',
                 authSource: 'provider',
                 targetApi,
                 timeoutMs: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.timeoutMs,
                 upstreamProviderName: typeof config.name === 'string' ? config.name.trim() : '',
                 upstreamBaseUrl: baseUrl,
                 upstreamApiKey: apiKey
             });
             if (!proxyResult || proxyResult.error || proxyResult.success === false || !proxyResult.listenUrl) {
                 return {
                     success: false,
                     mode: 'claude-proxy',
                     error: (proxyResult && proxyResult.error) || '启动 Claude 兼容代理失败'
                 };
             }
+            {
+                const listenHost = new URL(proxyResult.listenUrl).hostname;
+                if (listenHost !== '127.0.0.1' && listenHost !== 'localhost' && listenHost !== '::1') {
+                    await stopBuiltinClaudeProxyRuntime();
+                    return {
+                        success: false,
+                        mode: 'claude-proxy',
+                        error: 'Claude 兼容代理在 chat_completions 模式下必须仅监听 loopback'
+                    };
+                }
+            }
             settingsBaseUrl = proxyResult.listenUrl;
             settingsApiKey = 'codexmate';
🤖 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 `@cli.js` around lines 9178 - 9196, The code currently starts the Claude proxy
without forcing a loopback bind and then writes a fixed proxy API key
('codexmate'/ANTHROPIC_API_KEY), which can leak if the runtime binds
non-loopback; update the call to startBuiltinClaudeProxyRuntime to force a
loopback host (e.g., pass bindHost: '127.0.0.1' or equivalent) and only persist
or expose a generated/ephemeral settingsApiKey when proxyResult.listenUrl is
loopback; replace the constant 'codexmate' assignment to settingsApiKey with a
securely generated random token (or skip persisting the token) when
proxyResult.listenUrl is not loopback so the credential cannot be guessed
off-box (references: startBuiltinClaudeProxyRuntime, settingsApiKey,
ANTHROPIC_API_KEY).
🤖 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 `@cli/claude-proxy.js`:
- Around line 769-776: resolveOpenaiBridgeUpstream may return null/undefined
causing a TypeError when accessing bridgeUpstream.baseUrl; update the logic
around resolveOpenaiBridgeUpstream (the bridgeUpstream variable) to explicitly
check for null/undefined and return an error if so, then only access
bridgeUpstream.baseUrl (used to set bridgeBaseUrl) after confirming
bridgeUpstream is an object and has a baseUrl property; keep existing
isValidHttpUrl(providerName) check and error messages using providerName
unchanged.

---

Outside diff comments:
In `@cli.js`:
- Around line 9176-9267: The code can leak a running proxy if
startBuiltinClaudeProxyRuntime succeeds but later steps (readJsonObjectFromFile,
backupFileIfNeededOnce, writeJsonAtomic) fail; modify the flow around
startBuiltinClaudeProxyRuntime/stopBuiltinClaudeProxyRuntime so that when
proxyResult indicates a proxy was started you always stop it on any early return
or exception—either wrap the post-proxy logic in a try/finally that calls
stopBuiltinClaudeProxyRuntime() in the finally, or track a boolean like
proxyStarted and call stopBuiltinClaudeProxyRuntime() before each error return
and in the catch block; ensure this covers the branches that return after
readJsonObjectFromFile errors and after writeJsonAtomic/backup failures, and
still preserve returning proxy info only when the operation completes
successfully.

---

Duplicate comments:
In `@cli.js`:
- Around line 9178-9196: The code currently starts the Claude proxy without
forcing a loopback bind and then writes a fixed proxy API key
('codexmate'/ANTHROPIC_API_KEY), which can leak if the runtime binds
non-loopback; update the call to startBuiltinClaudeProxyRuntime to force a
loopback host (e.g., pass bindHost: '127.0.0.1' or equivalent) and only persist
or expose a generated/ephemeral settingsApiKey when proxyResult.listenUrl is
loopback; replace the constant 'codexmate' assignment to settingsApiKey with a
securely generated random token (or skip persisting the token) when
proxyResult.listenUrl is not loopback so the credential cannot be guessed
off-box (references: startBuiltinClaudeProxyRuntime, settingsApiKey,
ANTHROPIC_API_KEY).
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7f3075cd-6ca8-427d-85f1-4f2a07df91b4

📥 Commits

Reviewing files that changed from the base of the PR and between 9ffba03 and 9c40b1a.

📒 Files selected for processing (15)
  • cli.js
  • cli/claude-proxy.js
  • tests/e2e/test-claude-proxy.js
  • tests/e2e/test-claude.js
  • tests/unit/claude-proxy-adapter.test.mjs
  • tests/unit/claude-settings-sync.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/app.js
  • web-ui/logic.claude.mjs
  • web-ui/modules/app.methods.claude-config.mjs
  • web-ui/modules/app.methods.startup-claude.mjs
  • web-ui/modules/i18n.dict.mjs
  • web-ui/partials/index/modals-basic.html
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/res/web-ui-render.precompiled.js
📜 Review details
🔇 Additional comments (22)
web-ui/partials/index/panel-config-claude.html (1)

132-132: LGTM!

web-ui/logic.claude.mjs (1)

72-75: LGTM!

Also applies to: 82-83

web-ui/app.js (1)

278-278: LGTM!

Also applies to: 284-284, 292-294, 539-542

web-ui/partials/index/modals-basic.html (1)

136-143: LGTM!

Also applies to: 175-182

web-ui/modules/i18n.dict.mjs (1)

1051-1055: LGTM!

Also applies to: 2112-2116, 3183-3187

tests/unit/claude-settings-sync.test.mjs (1)

490-492: LGTM!

Also applies to: 511-513

tests/unit/claude-proxy-adapter.test.mjs (1)

7-7: LGTM!

Also applies to: 9-9, 67-105, 145-172

tests/unit/web-ui-logic.test.mjs (1)

54-55: LGTM!

Also applies to: 67-68, 79-80, 84-88

tests/e2e/test-claude.js (1)

67-84: LGTM!

Also applies to: 90-92

tests/e2e/test-claude-proxy.js (1)

101-142: LGTM!

Also applies to: 260-384, 392-394

web-ui/modules/app.methods.startup-claude.mjs (1)

250-252: LGTM!

web-ui/res/web-ui-render.precompiled.js (1)

1890-1900: LGTM!

Also applies to: 5560-5572, 5674-5686

web-ui/modules/app.methods.claude-config.mjs (1)

55-56: LGTM!

Also applies to: 67-68, 88-88, 110-110, 112-112, 186-186, 188-188, 208-209

cli.js (1)

293-294: LGTM!

Also applies to: 5492-5494, 9154-9159, 9399-9403, 11007-11008, 15556-15564

cli/claude-proxy.js (8)

136-151: LGTM!


238-293: LGTM!


295-352: LGTM!


432-505: LGTM!


644-695: LGTM!


816-839: LGTM!


1100-1125: LGTM!


1224-1224: LGTM!

Also applies to: 1273-1273, 1294-1296

Comment thread cli/claude-proxy.js
@awsl233777
Copy link
Copy Markdown
Collaborator Author

Pushed a follow-up hardening commit for the latest CodeRabbit actionable comments: 3501aca.

Changes:

  • The Claude Chat Completions apply path now writes a generated random local proxy token into ~/.claude/settings.json instead of a predictable fixed token.
  • The proxy is explicitly bound to the default loopback host for this apply path.
  • If the local proxy starts but the later Claude settings read/write path fails, the function rolls the proxy runtime and saved proxy mode back to the safe responses/off state.
  • OpenAI bridge upstream resolution now handles null/undefined bridge results without throwing.
  • Updated E2E assertions so Chat Completions mode verifies a random local proxy token and confirms the upstream key is not written into Claude Code settings.

Validation run locally after this commit:

  • node -c cli.js
  • node -c cli/claude-proxy.js
  • npm run test:unit (All 533 tests passed)
  • npm run test:e2e
  • npm run lint
  • git diff --check

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

@awsl233777 Thanks for the thorough hardening commit! The changes look well-targeted:

  • Random proxy token — eliminates the predictability concern with the fixed 'codexmate' token.
  • Explicit loopback binding — good defense-in-depth to ensure the proxy isn't inadvertently exposed on a broader interface.
  • Rollback on settings write failure — important atomicity improvement; avoids leaving the proxy running in an inconsistent state.
  • Null/undefined bridge guard — prevents a potential runtime throw in the upstream resolution path.
  • Updated E2E assertions — confirms the random token behavior and that the upstream key is kept out of Claude Code settings.

Triggering a full re-review now.

🐇✨

✅ Actions performed

Full review triggered.

@awsl233777
Copy link
Copy Markdown
Collaborator Author

Systematic test pass added for the Claude chat_completions flow.

What changed in this push:

  • Added e2e coverage that verifies apply-claude-config binds the generated Claude proxy settings to loopback.
  • Added e2e coverage that restoring responses mode stops the local Claude proxy and resets saved proxy targetApi back to responses.
  • Added rollback coverage: if Claude settings cannot be read during chat_completions apply, the API fails cleanly and the proxy runtime/settings are not left half-applied.
  • Added OpenAI bridge edge coverage: if the bridge upstream entry is missing, claude-proxy-start returns an explicit error and does not start the runtime.

Validation run locally:

  • npm run test:e2e
  • npm run test:unit ✅ — 533 tests passed
  • npm run lint ✅ — 200 files passed

Additional real-upstream smoke:

  • Used the locally configured telepub token without printing it.
  • Verified chat_completions apply starts the local Claude proxy and writes Claude settings to a random local proxy token rather than the upstream key.
  • The actual completion request reached the upstream path but returned upstream 429, so I am not marking real completion generation as successful.

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 25, 2026

@awsl233777 Thanks for the systematic test pass! The new coverage looks well-rounded:

  • Loopback binding assertion — confirms the proxy is bound to 127.0.0.1 and not exposed on a broader interface.
  • Responses-mode restore — verifies the proxy is stopped and targetApi is reset, preventing state leakage across proxy starts.
  • Rollback coverage — ensures a clean failure path when Claude settings can't be read mid-apply (no half-applied state left behind).
  • Bridge edge case — explicit error return (rather than a silent throw) when the bridge upstream entry is missing.

Triggering a full re-review now. 🐇✨

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli.js (1)

9216-9251: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Delay proxy shutdown until the settings write succeeds.

In the responses branch, Lines 9217-9218 tear down the existing proxy before CLAUDE_SETTINGS_FILE is even read. If the later read/write fails, apply returns an error but the previously working proxy is already gone.

Suggested fix
         if (targetApi === 'chat_completions') {
             await stopBuiltinClaudeProxyRuntime();
             const proxyToken = crypto.randomBytes(24).toString('hex');
             proxyResult = await startBuiltinClaudeProxyRuntime({
                 enabled: true,
                 host: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.host,
                 provider: typeof config.name === 'string' ? config.name.trim() : '',
                 authSource: 'provider',
                 targetApi,
                 timeoutMs: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.timeoutMs,
                 upstreamProviderName: typeof config.name === 'string' ? config.name.trim() : '',
                 upstreamBaseUrl: baseUrl,
                 upstreamApiKey: apiKey
             });
             if (!proxyResult || proxyResult.error || proxyResult.success === false || !proxyResult.listenUrl) {
                 await stopBuiltinClaudeProxyRuntime();
                 resetBuiltinClaudeProxySavedSettingsToResponses();
                 return {
                     success: false,
                     mode: 'claude-proxy',
                     error: (proxyResult && proxyResult.error) || '启动 Claude 兼容代理失败'
                 };
             }
             proxyStarted = true;
             settingsBaseUrl = proxyResult.listenUrl;
             settingsApiKey = proxyToken;
-        } else {
-            await stopBuiltinClaudeProxyRuntime();
-            resetBuiltinClaudeProxySavedSettingsToResponses();
         }
@@
         ensureDir(CLAUDE_DIR);
         const backupPath = backupFileIfNeededOnce(CLAUDE_SETTINGS_FILE);
         writeJsonAtomic(CLAUDE_SETTINGS_FILE, nextSettings);
+        if (targetApi !== 'chat_completions') {
+            await stopBuiltinClaudeProxyRuntime();
+            resetBuiltinClaudeProxySavedSettingsToResponses();
+        }
🤖 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 `@tests/e2e/test-claude-proxy.js`:
- Around line 356-370: Wrap the block that mutates and tests the bridge settings
in a try...finally so the original file is always restored: move the
fs.writeFileSync(bridgeSettingsPath, JSON.stringify({ providers: {} }, ...)) and
the subsequent api('claude-proxy-start') / asserts into a try block and put
fs.writeFileSync(bridgeSettingsPath, savedBridgeSettings, 'utf-8') inside a
finally block; reference the bridgeSettingsPath and savedBridgeSettings
variables and ensure the api calls missingBridgeStartResult and
missingBridgeStatus remain inside the try so any thrown error still triggers the
finally restoration.

In `@tests/e2e/test-claude.js`:
- Around line 99-109: The test corrupts the Claude settings.json
(claudeSettingsPath) but only restores it at the end, so failures leave the file
corrupted; wrap the mutation and subsequent assertions (the call to
api('apply-claude-config') and the checks against failedChatApply and
claudeProxyStatusAfterFailedApply from api('claude-proxy-status')) in a
try/finally and always write back validClaudeSettings to claudeSettingsPath in
the finally block to guarantee restoration even if an assertion throws.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5ad0bbee-40c7-483e-83ae-36c3bd62ac49

📥 Commits

Reviewing files that changed from the base of the PR and between 9c40b1a and 7b49467.

📒 Files selected for processing (4)
  • cli.js
  • cli/claude-proxy.js
  • tests/e2e/test-claude-proxy.js
  • tests/e2e/test-claude.js
📜 Review details
🔇 Additional comments (3)
cli.js (2)

9161-9172: LGTM!


9192-9215: ⚡ Quick win

Remove the “keep proxyToken and ANTHROPIC_API_KEY in sync” requirement

proxyToken is only generated in cli.js and written to env.ANTHROPIC_API_KEY, but startBuiltinClaudeProxyRuntime(...) is called without it. The proxy’s incoming authentication is driven by process.env.CODEXMATE_HTTP_TOKEN and checks Authorization / x-codexmate-token headers (not x-api-key/ANTHROPIC_API_KEY), so Claude’s ANTHROPIC_API_KEY value doesn’t need to match proxyToken for the proxy to accept requests.

			> Likely an incorrect or invalid review comment.
cli/claude-proxy.js (1)

769-771: LGTM!

Comment thread tests/e2e/test-claude-proxy.js Outdated
Comment thread tests/e2e/test-claude.js Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web-ui/app.js (1)

534-543: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard malformed persisted Claude config entries before assigning targetApi.

On Line 540, config.targetApi = ... assumes config is an object. Corrupted localStorage entries can throw here and break startup normalization.

Suggested fix
 for (const [name, config] of Object.entries(this.claudeConfigs)) {
+    if (!config || typeof config !== 'object' || Array.isArray(config)) {
+        delete this.claudeConfigs[name];
+        continue;
+    }
     if (config.apiKey && config.apiKey.includes('****')) {
         config.apiKey = '';
         config.hasKey = false;
     }
     const targetApiRaw = typeof config.targetApi === 'string' ? config.targetApi.trim().toLowerCase() : '';
     config.targetApi = targetApiRaw === 'chat_completions' || targetApiRaw === 'chat-completions' || targetApiRaw === 'chat/completions'
         ? 'chat_completions'
         : 'responses';
 }
🤖 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 `@web-ui/app.js` around lines 534 - 543, The loop over this.claudeConfigs
assumes each entry is a valid object; guard against malformed persisted entries
by checking that config is a non-null object before reading or writing
properties like config.apiKey, config.hasKey and config.targetApi, and if an
entry is invalid either skip normalization or replace it with a safe default
object (e.g., { apiKey: '', hasKey: false, targetApi: 'responses' }) so the
subsequent assignment to config.targetApi cannot throw.
🤖 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 `@cli.js`:
- Around line 9183-9203: The code allows targetApi === 'chat_completions' to
startBuiltinClaudeProxyRuntime even when config.baseUrl is empty (falling back
to the Anthropic Messages URL), which silently wires the proxy to the wrong
upstream; update the validation around normalizeClaudeTargetApi/targetApi and
baseUrl before calling startBuiltinClaudeProxyRuntime so that if targetApi ===
'chat_completions' and config.baseUrl is missing or equal to the default
Anthropic messages URL you either throw/return a validation error or call
startBuiltinClaudeProxyRuntime without upstreamBaseUrl (omit the upstreamBaseUrl
property) so the runtime resolves the upstream from config.name; adjust the
block that prepares proxyResult (the call to startBuiltinClaudeProxyRuntime and
the upstreamBaseUrl/upstreamProviderName fields) accordingly to enforce this
check.
- Around line 9192-9203: The generated proxyToken from
applyToClaudeSettings(...) is not passed into startBuiltinClaudeProxyRuntime,
causing an auth mismatch; update the call to startBuiltinClaudeProxyRuntime (the
invocation that currently sets host, provider, authSource, targetApi, timeoutMs,
upstreamProviderName, upstreamBaseUrl, upstreamApiKey) to include the generated
proxyToken (e.g., as an authToken or proxyToken parameter) so the runtime
enforces the same token that was written into Claude’s ANTHROPIC_API_KEY, and
also verify cli/claude-proxy.js checks that token (or falls back to
CODEXMATE_HTTP_TOKEN only when appropriate) for non-loopback requests when
DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.host is non-loopback. Ensure parameter
naming matches startBuiltinClaudeProxyRuntime’s signature and update any
doc/comment accordingly.

---

Outside diff comments:
In `@web-ui/app.js`:
- Around line 534-543: The loop over this.claudeConfigs assumes each entry is a
valid object; guard against malformed persisted entries by checking that config
is a non-null object before reading or writing properties like config.apiKey,
config.hasKey and config.targetApi, and if an entry is invalid either skip
normalization or replace it with a safe default object (e.g., { apiKey: '',
hasKey: false, targetApi: 'responses' }) so the subsequent assignment to
config.targetApi cannot throw.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 858ec6da-e7ea-42fc-b114-09db6512dd0d

📥 Commits

Reviewing files that changed from the base of the PR and between 9ffba03 and 7b49467.

📒 Files selected for processing (15)
  • cli.js
  • cli/claude-proxy.js
  • tests/e2e/test-claude-proxy.js
  • tests/e2e/test-claude.js
  • tests/unit/claude-proxy-adapter.test.mjs
  • tests/unit/claude-settings-sync.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/app.js
  • web-ui/logic.claude.mjs
  • web-ui/modules/app.methods.claude-config.mjs
  • web-ui/modules/app.methods.startup-claude.mjs
  • web-ui/modules/i18n.dict.mjs
  • web-ui/partials/index/modals-basic.html
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/res/web-ui-render.precompiled.js
📜 Review details
🔇 Additional comments (9)
cli.js (1)

293-294: LGTM!

Also applies to: 5492-5494, 9154-9172, 9417-9421, 11025-11026

tests/e2e/test-claude-proxy.js (1)

356-370: Always restore bridge settings with try/finally.

Line 358 mutates codexmate-openai-bridge.json, but restore on Line 370 is not guaranteed if an assertion or API call fails first.

tests/e2e/test-claude.js (1)

99-109: Wrap Claude settings corruption/restore in try/finally.

Line 101 corrupts settings.json, but restoration on Line 109 is skipped if intermediate assertions fail.

web-ui/modules/app.methods.claude-config.mjs (1)

55-56: LGTM!

Also applies to: 67-68, 88-88, 110-113, 186-189, 208-209

web-ui/modules/app.methods.startup-claude.mjs (1)

250-251: LGTM!

web-ui/modules/i18n.dict.mjs (1)

1051-1055: LGTM!

Also applies to: 2112-2116, 3183-3187

web-ui/partials/index/modals-basic.html (1)

136-143: LGTM!

Also applies to: 175-182

web-ui/partials/index/panel-config-claude.html (1)

132-132: LGTM!

web-ui/res/web-ui-render.precompiled.js (1)

1890-1900: LGTM!

Also applies to: 5560-5572, 5674-5686

Comment thread cli.js Outdated
Comment thread cli.js
…letions-proxy

# Conflicts:
#	cli.js
#	web-ui/modules/app.methods.claude-config.mjs
#	web-ui/modules/i18n.dict.mjs
#	web-ui/res/web-ui-render.precompiled.js
@awsl233777
Copy link
Copy Markdown
Collaborator Author

Resolved the main merge conflicts and pushed the merge commit 0629d0a.

What changed in this push:

  • Merged origin/main into feat/claude-chat-completions-proxy and resolved the conflicting Claude config apply path so the Chat Completions proxy mode is preserved alongside the new tool-config write guard.
  • Kept the split i18n locale structure from main and added the missing claude.targetApi.* locale keys for zh/en/ja/vi.
  • Regenerated web-ui/res/web-ui-render.precompiled.js from the current Web UI template instead of hand-editing the generated artifact.
  • Updated tests for the merged behavior: edit-modal state now preserves the default targetApi, and the Claude proxy e2e bridge provider includes the now-required provider model.

Local validation run on Node 22.22.1:

  • git diff --check
  • npm run ci:lint → passed
  • npm run ci:test → passed (test:unit + test:e2e)

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 31, 2026

@awsl233777 Thanks for the merge conflict resolution! The changes look well-handled:

  • Conflict resolution — the Chat Completions proxy mode is preserved alongside the tool-config write guard from main.
  • i18n completeness — adding the claude.targetApi.* keys for all four locales (zh/en/ja/vi) ensures no missing translation fallbacks.
  • Precompiled render regeneration — generating from the template rather than hand-editing avoids drift between the source partials and the artifact.
  • Test updates — preserving default targetApi in edit-modal state and adding the required model field to the bridge e2e provider keeps the test suite consistent with the merged behavior.

Triggering a full re-review now. 🐇✨

✅ Actions performed

Full review triggered.

@awsl233777
Copy link
Copy Markdown
Collaborator Author

Minimized the PR diff with 059aad2 by restoring .gitignore to match main; this removes the unrelated trailing-blank-line noise from the PR.

No runtime code changed in this follow-up. The effective PR diff is now 18 files, limited to the Claude Chat Completions proxy path, required Web UI/i18n integration, generated render output, and regression tests.

Previous local validation still applies for the code-bearing merge resolution:

  • git diff --check
  • npm run ci:lint
  • npm run ci:test

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 31, 2026

@awsl233777 Thanks for the cleanup — restoring .gitignore to match main is a good call to keep the diff focused on the actual feature changes. Triggering a full re-review of the now-trimmed 18-file diff. 🐇✨

✅ Actions performed

Full review triggered.

@awsl233777
Copy link
Copy Markdown
Collaborator Author

Updated this PR with the full follow-up pass:

  • Added/finished Claude built-in conversion for OpenAI Responses, OpenAI Chat Completions, and Ollama /api/chat.
  • Preserved supported text/image/tool payloads and stopped forwarding bridge-only incompatible blocks like thinking / document as fake user text.
  • Added the Claude UI target API option for Ollama in add/edit config flows.
  • Fixed compact-layout detection so mobile UA alone does not make desktop-width Web UI diverge.
  • Centered Codex/Claude write-lock overlays against the viewport.

Validation actually run:

  • npm run test:unit — 583 tests passed.
  • npm run lint — passed for 210 files.
  • Real Ollama smoke test against 27.154.56.162:11434: /api/tags reachable; gemma4:31b-cloud /api/chat returned codexmate-ollama-ok through this PR's Claude→Ollama→Anthropic conversion. Other listed cloud models were either subscription-gated (403) or emitted only thinking with empty content, so I did not count those as passing samples.
  • Headless Chrome Web UI checks: desktop UA/viewport, iPhone UA with desktop viewport, narrow touch compact mode, Codex/Claude overlay centering, and Ollama option presence.

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

@awsl233777 Thanks for the substantial follow-up pass! The additions look well-scoped:

  • Ollama /api/chat conversion — extending the Claude proxy to support a third wire format alongside Responses and Chat Completions is a meaningful capability addition.
  • Thinking/document block filtering — stopping the forwarding of bridge-incompatible blocks as fake user text is an important correctness fix; those blocks would likely cause unexpected behavior or errors at the upstream.
  • Ollama UI option — surfacing targetApi for Ollama in the add/edit config flows keeps the UI consistent with the new backend path.
  • Compact-layout detection fix — preventing mobile UA alone from triggering compact mode on a desktop-width viewport is a good UX fix.
  • Overlay centering — viewport-relative centering for the Codex/Claude write-lock overlays is a visual polish improvement.

The test count growth (533 → 583) and the real Ollama smoke test (including the codexmate-ollama-ok roundtrip) provide good confidence. Triggering a full review now. 🐇

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli.js (1)

15816-15829: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the MCP schema match the runtime's ollama rules.

applyToClaudeSettings() allows targetApi: 'ollama' without an API key, but this MCP tool still requires apiKey unconditionally. That blocks the new Ollama mode for MCP clients even though the backend supports it.

Suggested fix
         inputSchema: {
-            type: 'object',
-            properties: {
-                apiKey: { type: 'string' },
-                baseUrl: { type: 'string' },
-                model: { type: 'string' },
-                name: { type: 'string' },
-                targetApi: { type: 'string' }
-            },
-            required: ['apiKey'],
-            additionalProperties: false
+            oneOf: [
+                {
+                    type: 'object',
+                    properties: {
+                        baseUrl: { type: 'string' },
+                        model: { type: 'string' },
+                        name: { type: 'string' },
+                        targetApi: { enum: ['ollama'] }
+                    },
+                    additionalProperties: false
+                },
+                {
+                    type: 'object',
+                    properties: {
+                        apiKey: { type: 'string' },
+                        baseUrl: { type: 'string' },
+                        model: { type: 'string' },
+                        name: { type: 'string' },
+                        targetApi: { type: 'string' }
+                    },
+                    required: ['apiKey'],
+                    additionalProperties: false
+                }
+            ]
         },
🤖 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 `@cli.js` around lines 15816 - 15829, The MCP tool's inputSchema for action
'codexmate.claude.config.apply' currently requires apiKey unconditionally, which
conflicts with applyToClaudeSettings() that allows targetApi: 'ollama' without
an API key; update the schema so apiKey is optional by removing it from the
top-level required array and add a JSON Schema conditional (if/then) that only
requires apiKey when targetApi is not 'ollama' (or equivalently, require apiKey
when targetApi equals the non-ollama value(s)); reference the inputSchema object
for 'codexmate.claude.config.apply' and the applyToClaudeSettings() behavior
when making this change.
♻️ Duplicate comments (1)
cli.js (1)

9360-9378: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast when chat_completions has no OpenAI-compatible upstream.

targetApi === 'chat_completions' still falls back to the Anthropic Messages base URL and passes it as upstreamBaseUrl. That silently starts the proxy against the wrong wire API instead of rejecting the config or letting runtime-side provider resolution take over.

Suggested fix
-        const baseUrl = (config.baseUrl || (targetApi === 'ollama' ? 'http://127.0.0.1:11434' : 'https://open.bigmodel.cn/api/anthropic')).trim();
+        const configuredBaseUrl = typeof config.baseUrl === 'string' ? config.baseUrl.trim() : '';
+        const baseUrl = (configuredBaseUrl || (targetApi === 'ollama' ? 'http://127.0.0.1:11434' : 'https://open.bigmodel.cn/api/anthropic')).trim();
         const model = (config.model || DEFAULT_CLAUDE_MODEL).trim();
         let settingsBaseUrl = baseUrl;
         let settingsApiKey = apiKey;
         let proxyResult = null;

         if (targetApi === 'chat_completions' || targetApi === 'ollama') {
+            if (targetApi === 'chat_completions' && !configuredBaseUrl && !(typeof config.name === 'string' && config.name.trim())) {
+                return { success: false, mode: 'claude-proxy', error: 'chat_completions 模式需要显式的上游 Base URL 或可解析的 provider 名称' };
+            }
             await stopBuiltinClaudeProxyRuntime();
             const proxyToken = crypto.randomBytes(24).toString('hex');
             proxyResult = await startBuiltinClaudeProxyRuntime({
                 enabled: true,
                 host: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.host,
@@
-                upstreamBaseUrl: baseUrl,
+                ...(configuredBaseUrl ? { upstreamBaseUrl: configuredBaseUrl } : {}),
                 upstreamApiKey: apiKey
             });
🤖 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 `@cli.js` around lines 9360 - 9378, When targetApi === 'chat_completions' the
code currently uses the Anthropic default baseUrl and blindly starts the
built-in proxy with that upstreamBaseUrl; instead, in the block that calls
stopBuiltinClaudeProxyRuntime() and startBuiltinClaudeProxyRuntime() validate
that baseUrl (or config.baseUrl) is OpenAI-compatible before starting the proxy.
If the upstream is not OpenAI-compatible, reject/fail fast (throw or return an
error) or skip starting the proxy and allow runtime-side provider resolution to
proceed; update the logic around targetApi, upstreamBaseUrl, and
startBuiltinClaudeProxyRuntime to either set upstreamBaseUrl only when it's
OpenAI-compatible or to error out when targetApi === 'chat_completions' and no
OpenAI-compatible upstream is provided.
🤖 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.

Outside diff comments:
In `@cli.js`:
- Around line 15816-15829: The MCP tool's inputSchema for action
'codexmate.claude.config.apply' currently requires apiKey unconditionally, which
conflicts with applyToClaudeSettings() that allows targetApi: 'ollama' without
an API key; update the schema so apiKey is optional by removing it from the
top-level required array and add a JSON Schema conditional (if/then) that only
requires apiKey when targetApi is not 'ollama' (or equivalently, require apiKey
when targetApi equals the non-ollama value(s)); reference the inputSchema object
for 'codexmate.claude.config.apply' and the applyToClaudeSettings() behavior
when making this change.

---

Duplicate comments:
In `@cli.js`:
- Around line 9360-9378: When targetApi === 'chat_completions' the code
currently uses the Anthropic default baseUrl and blindly starts the built-in
proxy with that upstreamBaseUrl; instead, in the block that calls
stopBuiltinClaudeProxyRuntime() and startBuiltinClaudeProxyRuntime() validate
that baseUrl (or config.baseUrl) is OpenAI-compatible before starting the proxy.
If the upstream is not OpenAI-compatible, reject/fail fast (throw or return an
error) or skip starting the proxy and allow runtime-side provider resolution to
proceed; update the logic around targetApi, upstreamBaseUrl, and
startBuiltinClaudeProxyRuntime to either set upstreamBaseUrl only when it's
OpenAI-compatible or to error out when targetApi === 'chat_completions' and no
OpenAI-compatible upstream is provided.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8710ccd6-8260-45da-8a0b-5de00b139384

📥 Commits

Reviewing files that changed from the base of the PR and between 7b49467 and 5a5af30.

📒 Files selected for processing (21)
  • cli.js
  • cli/claude-proxy.js
  • tests/e2e/test-claude-proxy.js
  • tests/e2e/test-claude.js
  • tests/unit/claude-proxy-adapter.test.mjs
  • tests/unit/claude-settings-sync.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/app.js
  • web-ui/logic.claude.mjs
  • web-ui/logic.runtime.mjs
  • web-ui/modules/app.methods.claude-config.mjs
  • web-ui/modules/app.methods.session-timeline.mjs
  • web-ui/modules/app.methods.startup-claude.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/modals-basic.html
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/styles/controls-forms.css
💤 Files with no reviewable changes (3)
  • web-ui/modules/app.methods.session-timeline.mjs
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/logic.runtime.mjs
✅ Files skipped from review due to trivial changes (3)
  • web-ui/modules/i18n/locales/zh.mjs
  • tests/unit/claude-settings-sync.test.mjs
  • web-ui/modules/i18n/locales/ja.mjs
🚧 Files skipped from review as they are similar to previous changes (9)
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/app.js
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/app.methods.startup-claude.mjs
  • web-ui/logic.claude.mjs
  • web-ui/partials/index/modals-basic.html
  • web-ui/modules/app.methods.claude-config.mjs
  • tests/e2e/test-claude.js
  • tests/e2e/test-claude-proxy.js
📜 Review details
🔇 Additional comments (20)
web-ui/modules/i18n/locales/en.mjs (1)

1094-1100: LGTM!

tests/unit/web-ui-logic.test.mjs (1)

514-525: LGTM!

Also applies to: 982-983, 1003-1004

web-ui/styles/controls-forms.css (1)

409-413: LGTM!

Also applies to: 441-532

cli/claude-proxy.js (12)

87-114: LGTM!


135-141: LGTM!

Also applies to: 163-165


277-364: LGTM!


366-423: LGTM!


427-538: LGTM!


693-740: LGTM!


916-921: LGTM!


995-1024: LGTM!


1056-1082: LGTM!


1283-1284: LGTM!

Also applies to: 1307-1307


1342-1373: LGTM!


1472-1472: LGTM!

Also applies to: 1521-1521, 1543-1546

tests/unit/claude-proxy-adapter.test.mjs (5)

1-14: LGTM!


69-90: LGTM!


132-169: LGTM!


171-227: LGTM!


296-317: LGTM!

@awsl233777 awsl233777 changed the title feat: add Claude chat completions proxy mode feat: add Claude proxy target modes and config UI Jun 1, 2026
@awsl233777
Copy link
Copy Markdown
Collaborator Author

Follow-up E2E pass completed after the previous update.

What the end-to-end run covered:

  • Started the built-in Claude-compatible proxy as a real local HTTP server.
  • Exercised /health, /v1/models, /v1/messages, and stream SSE.
  • Used capture upstreams for Responses and Chat Completions to verify the actual HTTP bodies crossing the service boundary: image payloads and tool schemas are preserved, while thinking / document blocks are not leaked upstream as fake user text.
  • Used the real Ollama endpoint 27.154.56.162:11434: proxy /v1/models reached upstream /api/tags; proxy /v1/messages reached upstream /api/chat; gemma4:31b-cloud returned codexmate-ollama-ok through the full Claude→Ollama→Anthropic roundtrip.

The E2E run caught a real bug: Ollama upstream paths were being built as /v1/api/tags and /v1/api/chat, which returns 404 on Ollama. Fixed in 881a179 by routing Ollama target paths to native /api/* without injecting /v1, and added a regression test that asserts the proxy calls exactly GET /api/tags and POST /api/chat.

Validation after the fix:

  • npm run test:unit — 584 tests passed.
  • npm run lint — passed for 211 files.
  • E2E proxy script — Responses, Chat Completions, and real Ollama target all passed.

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

@awsl233777 Great catch on the Ollama path bug — /v1/api/tags silently routing to 404 is exactly the kind of issue that only surfaces against a real upstream. The fix in 881a179 (stripping the /v1 prefix injection for Ollama so paths resolve to native /api/tags and /api/chat) is the right approach, and adding the regression test that asserts the exact upstream paths (GET /api/tags, POST /api/chat) will prevent regressions from future refactors. Triggering a full review now. 🐇

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@awsl233777
Copy link
Copy Markdown
Collaborator Author

Follow-up for the latest CodeRabbit review is pushed in 93b2dfd.

Changes:

  • Updated the MCP schema for codexmate.claude.config.apply so targetApi: 'ollama' is allowed without apiKey, matching applyToClaudeSettings() runtime behavior.
  • Kept apiKey required for non-Ollama/default Claude config apply requests.
  • Added a fail-fast guard for chat_completions proxy setup when neither an explicit upstream Base URL nor a resolvable provider name is provided, so it no longer silently falls back to the Anthropic default URL.
  • Added unit coverage for both schema/guard contracts.

Validation:

  • npm run test:unit — 586 tests passed.
  • npm run lint — passed for 210 files.
  • git diff --check — clean.

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

@awsl233777 Thanks for the follow-up! The changes in 93b2dfd look well-targeted:

  • MCP schema alignment — allowing apiKey to be omitted when targetApi: 'ollama' matches the runtime behavior in applyToClaudeSettings() and prevents the MCP caller from being blocked by a now-incorrect schema constraint.
  • Fail-fast guard for chat_completions — requiring either an explicit upstream Base URL or a resolvable provider name before starting the proxy is a solid improvement; silent fallback to the Anthropic default URL would have been a confusing failure mode for users targeting a self-hosted endpoint.
  • Unit coverage — adding tests for both the schema contract and the guard ensures regressions are caught without needing a full e2e run.

Triggering a full review now. 🐇

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@awsl233777
Copy link
Copy Markdown
Collaborator Author

Fixed the Ollama apply path that could leave Claude Code on a stale remote ANTHROPIC_BASE_URL.

Changes in 89f84bc:

  • Allow targetApi: 'ollama' configs to run saveAndApplyConfig without an API key instead of returning early as “saved only”.
  • Allow clicking an Ollama config card to call apply-claude-config without an API key.
  • Persist normalized externalCredentialType / targetApi when editing/saving Claude configs so stale responses state does not leak into the apply path.
  • Added regression coverage for both “save and apply Ollama without API key” and “apply Ollama card without API key”.

Validation run locally:

  • npm run test:unit — 588 tests passed
  • npm run lint — passed for 210 files
  • git diff --check — passed

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

@awsl233777 Thanks for the Ollama apply-path fix! The changes in 89f84bc address a real correctness gap — without it, clicking an Ollama config card or saving one without an API key would silently skip the apply step, leaving Claude Code pointed at a stale remote ANTHROPIC_BASE_URL. The targetApi/externalCredentialType normalization persistence on edit/save is also a solid fix for the state-leakage scenario. Triggering a full review now. 🐇

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/unit/claude-settings-sync.test.mjs (1)

1239-1261: ⚡ Quick win

Consider behavioral tests for CLI validation.

The static analysis tests (regex matching on source code) verify that important guards and schema constraints exist, but they're brittle—they'll break if the implementation is refactored even when behavior stays correct.

For better maintainability, consider testing the actual runtime behavior instead. For example:

  • For the chat_completions guard (lines 1239-1249): call applyToClaudeSettings with targetApi: 'chat_completions' and no baseUrl/provider, then verify it rejects with the expected error.
  • For the schema validation (lines 1251-1261): test that the schema correctly validates/rejects configs at runtime rather than checking its structure.

That said, the current approach does provide value by ensuring critical code paths exist.

🤖 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 `@tests/unit/claude-settings-sync.test.mjs` around lines 1239 - 1261, The tests
are fragile because they assert source text instead of behavior; replace or
augment the regex-based checks with behavioral tests: call the async function
applyToClaudeSettings with targetApi: 'chat_completions' and no
baseUrl/upstreamProvider and assert it rejects/throws with the expected error
message, and instead of matching the CLI schema text for the tool name
'codexmate.claude.config.apply' extract or import the schema used by that tool
and run it against example payloads (one with targetApi:'ollama' and no apiKey
should pass, one with other targets should require apiKey) to assert runtime
validation succeeds/fails as intended.
🤖 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.

Nitpick comments:
In `@tests/unit/claude-settings-sync.test.mjs`:
- Around line 1239-1261: The tests are fragile because they assert source text
instead of behavior; replace or augment the regex-based checks with behavioral
tests: call the async function applyToClaudeSettings with targetApi:
'chat_completions' and no baseUrl/upstreamProvider and assert it rejects/throws
with the expected error message, and instead of matching the CLI schema text for
the tool name 'codexmate.claude.config.apply' extract or import the schema used
by that tool and run it against example payloads (one with targetApi:'ollama'
and no apiKey should pass, one with other targets should require apiKey) to
assert runtime validation succeeds/fails as intended.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2d01381f-e5f1-4549-83d8-93915635f291

📥 Commits

Reviewing files that changed from the base of the PR and between 881a179 and 89f84bc.

📒 Files selected for processing (3)
  • cli.js
  • tests/unit/claude-settings-sync.test.mjs
  • web-ui/modules/app.methods.claude-config.mjs
🚧 Files skipped from review as they are similar to previous changes (2)
  • web-ui/modules/app.methods.claude-config.mjs
  • cli.js
📜 Review details
🔇 Additional comments (1)
tests/unit/claude-settings-sync.test.mjs (1)

6-6: LGTM!

Also applies to: 589-592, 651-714, 753-802

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web-ui/modules/app.methods.claude-config.mjs (1)

206-213: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing externalCredentialType check causes inconsistent behavior.

Line 206 checks !config.apiKey && config.targetApi !== 'ollama' but does not account for externalCredentialType. This is inconsistent with:

  • Line 43 (validation): !apiKey && !externalCredentialType && targetApi !== 'ollama'
  • Line 96 (onClaudeModelChange): !this.claudeConfigs[name].apiKey && !this.claudeConfigs[name].externalCredentialType && this.claudeConfigs[name].targetApi !== 'ollama'

A user with external credentials configured but no API key will see "已保存(未填写 API Key)" and the config will not be applied, even though external auth should be sufficient.

Proposed fix
-if (!config.apiKey && config.targetApi !== 'ollama') {
+if (!config.apiKey && !config.externalCredentialType && config.targetApi !== 'ollama') {
     this.showMessage('已保存(未填写 API Key)', 'info');
     this.closeEditConfigModal();
🤖 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 `@web-ui/modules/app.methods.claude-config.mjs` around lines 206 - 213, The
early-exit condition in the save flow checks only config.apiKey and
config.targetApi, causing configs that rely on externalCredentialType to be
treated as "missing API Key"; update the if condition in the block that calls
this.showMessage('已保存(未填写 API Key)', ...), this.closeEditConfigModal(), and
this.refreshClaudeModelContext() to also check config.externalCredentialType
(i.e., require both apiKey and externalCredentialType be missing before showing
the "未填写 API Key" message), mirroring the validation logic used at line 43 and
the onClaudeModelChange logic, so external credential-based configs are accepted
and applied when saved.
♻️ Duplicate comments (1)
tests/e2e/test-claude-proxy.js (1)

358-372: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Bridge settings restoration still needs try/finally wrapper.

The previous review comment flagged this issue and it was marked as addressed, but the fix doesn't appear to be present. If assertions at lines 369-371 fail, line 372 (fs.writeFileSync(bridgeSettingsPath, savedBridgeSettings, 'utf-8')) is skipped, leaving the bridge settings file in a broken state. The outer finally block (lines 405-416) does not restore this file.

Suggested fix
 const bridgeSettingsPath = path.join(tmpHome, '.codex', 'codexmate-openai-bridge.json');
 const savedBridgeSettings = fs.readFileSync(bridgeSettingsPath, 'utf-8');
-fs.writeFileSync(bridgeSettingsPath, JSON.stringify({ providers: {} }, null, 2), 'utf-8');
-const missingBridgeStartResult = await api('claude-proxy-start', {
-    host: '127.0.0.1',
-    port: proxyPort,
-    provider: 'claude-proxy-openai-bridge-e2e',
-    authSource: 'provider',
-    targetApi: 'chat_completions',
-    timeoutMs: 5000
-});
-assert(missingBridgeStartResult.error && missingBridgeStartResult.error.includes('OpenAI 转换未配置'), 'claude proxy should return an explicit error when OpenAI bridge upstream is missing');
-const missingBridgeStatus = await api('claude-proxy-status');
-assert(missingBridgeStatus.running === false, 'failed OpenAI bridge resolution must not start Claude proxy runtime');
-fs.writeFileSync(bridgeSettingsPath, savedBridgeSettings, 'utf-8');
+try {
+    fs.writeFileSync(bridgeSettingsPath, JSON.stringify({ providers: {} }, null, 2), 'utf-8');
+    const missingBridgeStartResult = await api('claude-proxy-start', {
+        host: '127.0.0.1',
+        port: proxyPort,
+        provider: 'claude-proxy-openai-bridge-e2e',
+        authSource: 'provider',
+        targetApi: 'chat_completions',
+        timeoutMs: 5000
+    });
+    assert(missingBridgeStartResult.error && missingBridgeStartResult.error.includes('OpenAI 转换未配置'), 'claude proxy should return an explicit error when OpenAI bridge upstream is missing');
+    const missingBridgeStatus = await api('claude-proxy-status');
+    assert(missingBridgeStatus.running === false, 'failed OpenAI bridge resolution must not start Claude proxy runtime');
+} finally {
+    fs.writeFileSync(bridgeSettingsPath, savedBridgeSettings, 'utf-8');
+}
🤖 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 `@tests/e2e/test-claude-proxy.js` around lines 358 - 372, The test modifies the
bridge settings file but doesn’t guarantee restoration if assertions fail; wrap
the block that writes to bridgeSettingsPath, calls api('claude-proxy-start') and
api('claude-proxy-status'), and runs the assertions in a try/finally so
savedBridgeSettings is always written back in the finally; specifically, move
the fs.writeFileSync(bridgeSettingsPath, savedBridgeSettings, 'utf-8') into a
finally that pairs with the try starting before the write of JSON.stringify({
providers: {} }, ...) to ensure restoration even on test assertion failures.
🤖 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 `@cli.js`:
- Around line 9367-9412: You tear down the existing builtin Claude proxy
(stopBuiltinClaudeProxyRuntime /
resetBuiltinClaudeProxySavedSettingsToResponses) before performing settings I/O
(readJsonObjectFromFile, backupFileIfNeededOnce, writeJsonAtomic), so failures
leave the old runtime un-restored; instead capture the current runtime/settings
state before touching anything (e.g. save whether a runtime was running and its
baseUrl/apiKey), delay calling stopBuiltinClaudeProxyRuntime until after
settings I/O succeeds, or if you must stop it first then on any subsequent
failure restart the previous runtime by calling startBuiltinClaudeProxyRuntime
with the saved parameters and restore saved settings via
resetBuiltinClaudeProxySavedSettingsToResponses; update the branches around
startBuiltinClaudeProxyRuntime, proxyStarted, settingsBaseUrl and settingsApiKey
and the error-return paths (including the later block noted at 9462-9465) so
failures perform restoration and do not leave Claude pointing at a stopped
proxy.
- Around line 15831-15846: The schema exemption currently only matches exact raw
targetApi 'ollama' so inputs like "Ollama" or "  ollama  " are rejected; update
the allOf/if clause in codexmate.claude.config.apply inputSchema to match
normalized values by replacing the const check for targetApi with a regex-based
pattern that allows optional surrounding whitespace and is case-insensitive
(e.g. use a pattern like '^[\\s]*[oO][lL][lL][aA][mM][aA][\\s]*$' for broad JSON
Schema compatibility), so the schema exemption mirrors normalizeClaudeTargetApi
and the behavior in applyToClaudeSettings.

In `@tests/unit/claude-proxy-adapter.test.mjs`:
- Around line 399-408: The test's findFreePortForTest() creates a TOCTOU by
probing a free port then closing it before
controller.startBuiltinClaudeProxyRuntime() binds, causing flaky EADDRINUSE; fix
by either (preferred) adding support for port: 0 in the proxy runtime so
startBuiltinClaudeProxyRuntime(controller/startBuiltinClaudeProxyRuntime or the
runtime that validates settings.port) accepts 0, performs server.listen(0,
host), and returns the actual bound port in its listenUrl/settings report, or
(alternatively) implement a small retry loop around
controller.startBuiltinClaudeProxyRuntime() in the test to catch EADDRINUSE and
retry a few times with backoff; update the runtime validation that currently
rejects port <= 0 to allow 0 and ensure the bound port is surfaced back to the
caller.

In `@tests/unit/claude-settings-sync.test.mjs`:
- Around line 1239-1261: The current tests only regex-match fragments and can be
fooled by unrelated strings; update them to assert the actual code
structure/behavior: for applyToClaudeSettings/cmdClaude, slice the source
between the identified indices (startIndex/endIndex), parse that slice into an
AST or evaluate the exported function to ensure the branch condition (targetApi
=== 'chat_completions' && !configuredBaseUrl && !upstreamProviderName) is
present as a guard (not just as text) and that the spread expression uses
configuredBaseUrl in the resulting returned object; for the MCP Claude schema
(name: 'codexmate.claude.config.apply' / handler -> applyToClaudeSettings),
extract the schema object (parse the JS AST or require the module) and assert
that the conditional schema is structured so that the property targetApi
const:'ollama' is paired with a then:{ required:['apiKey'] } (i.e., the then is
nested under the if/allOf clause), rather than just matching two separate
regexes.

In `@web-ui/logic.claude.mjs`:
- Around line 72-86: The duplicate/match helpers that compare Claude configs
(used by normalizeClaudeConfig and any config deduping/matching logic) currently
only compare baseUrl, model, and credential fields, so different targetApi
values (responses, chat_completions, ollama) collapse into one config; update
those helper functions to include the normalized targetApi value (the targetApi
produced in normalizeClaudeConfig) in their equality/hash checks and any
canonical key generation so configs with different targetApi are treated as
distinct. Ensure you reference the targetApi returned by normalizeClaudeConfig
and update all callers that build or compare identity keys so they incorporate
targetApi consistently.

---

Outside diff comments:
In `@web-ui/modules/app.methods.claude-config.mjs`:
- Around line 206-213: The early-exit condition in the save flow checks only
config.apiKey and config.targetApi, causing configs that rely on
externalCredentialType to be treated as "missing API Key"; update the if
condition in the block that calls this.showMessage('已保存(未填写 API Key)', ...),
this.closeEditConfigModal(), and this.refreshClaudeModelContext() to also check
config.externalCredentialType (i.e., require both apiKey and
externalCredentialType be missing before showing the "未填写 API Key" message),
mirroring the validation logic used at line 43 and the onClaudeModelChange
logic, so external credential-based configs are accepted and applied when saved.

---

Duplicate comments:
In `@tests/e2e/test-claude-proxy.js`:
- Around line 358-372: The test modifies the bridge settings file but doesn’t
guarantee restoration if assertions fail; wrap the block that writes to
bridgeSettingsPath, calls api('claude-proxy-start') and
api('claude-proxy-status'), and runs the assertions in a try/finally so
savedBridgeSettings is always written back in the finally; specifically, move
the fs.writeFileSync(bridgeSettingsPath, savedBridgeSettings, 'utf-8') into a
finally that pairs with the try starting before the write of JSON.stringify({
providers: {} }, ...) to ensure restoration even on test assertion failures.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6f4a6567-0a47-4768-a4fc-a6bf2d22f2fc

📥 Commits

Reviewing files that changed from the base of the PR and between 7aa34db and 89f84bc.

📒 Files selected for processing (21)
  • cli.js
  • cli/claude-proxy.js
  • tests/e2e/test-claude-proxy.js
  • tests/e2e/test-claude.js
  • tests/unit/claude-proxy-adapter.test.mjs
  • tests/unit/claude-settings-sync.test.mjs
  • tests/unit/web-ui-logic.test.mjs
  • web-ui/app.js
  • web-ui/logic.claude.mjs
  • web-ui/logic.runtime.mjs
  • web-ui/modules/app.methods.claude-config.mjs
  • web-ui/modules/app.methods.session-timeline.mjs
  • web-ui/modules/app.methods.startup-claude.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/modals-basic.html
  • web-ui/partials/index/panel-config-claude.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/styles/controls-forms.css
💤 Files with no reviewable changes (2)
  • web-ui/modules/app.methods.session-timeline.mjs
  • web-ui/logic.runtime.mjs
📜 Review details
🔇 Additional comments (37)
web-ui/modules/i18n/locales/en.mjs (1)

1094-1100: LGTM!

web-ui/partials/index/panel-config-claude.html (1)

152-153: LGTM!

web-ui/modules/i18n/locales/vi.mjs (1)

239-245: LGTM!

web-ui/app.js (4)

280-280: LGTM!


286-286: LGTM!


294-295: LGTM!


549-556: LGTM!

web-ui/modules/i18n/locales/zh.mjs (1)

1097-1103: LGTM!

cli.js (1)

9603-9607: LGTM!

Also applies to: 11267-11268

cli/claude-proxy.js (13)

87-114: LGTM!


135-165: LGTM!


175-190: LGTM!


277-423: LGTM!


427-538: LGTM!


618-740: LGTM!


869-876: LGTM!


920-942: LGTM!


1004-1063: LGTM!


1065-1091: LGTM!


1351-1382: LGTM!


1469-1469: LGTM!


1548-1558: LGTM!

tests/e2e/test-claude.js (2)

107-118: Always restore settings.json in a finally block.

If any assertion from Line 110 through Line 117 throws, the temp Claude settings stay corrupted and can poison later E2E cases.


1-2: LGTM!

Also applies to: 6-6, 78-105

web-ui/modules/app.methods.startup-claude.mjs (1)

245-258: LGTM!

web-ui/res/web-ui-render.precompiled.js (1)

1912-1925: LGTM!

Also applies to: 5800-5813, 5953-5966

tests/e2e/test-claude-proxy.js (4)

1-2: LGTM!


103-144: LGTM!


263-347: LGTM!


412-414: LGTM!

web-ui/modules/app.methods.claude-config.mjs (7)

26-29: LGTM!


43-45: LGTM!


96-100: LGTM!


119-127: LGTM!

Also applies to: 147-159, 181-189


161-179: LGTM!


215-217: LGTM!


234-258: LGTM!

Also applies to: 282-321

Comment thread cli.js
Comment on lines +9367 to 9412
if (targetApi === 'chat_completions' || targetApi === 'ollama') {
const upstreamProviderName = typeof config.name === 'string' ? config.name.trim() : '';
if (targetApi === 'chat_completions' && !configuredBaseUrl && !upstreamProviderName) {
return {
success: false,
mode: 'claude-proxy',
error: 'chat_completions 模式需要显式的上游 Base URL 或可解析的 provider 名称'
};
}
await stopBuiltinClaudeProxyRuntime();
const proxyToken = crypto.randomBytes(24).toString('hex');
proxyResult = await startBuiltinClaudeProxyRuntime({
enabled: true,
host: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.host,
provider: upstreamProviderName,
authSource: 'provider',
targetApi,
timeoutMs: DEFAULT_BUILTIN_CLAUDE_PROXY_SETTINGS.timeoutMs,
upstreamProviderName,
...(configuredBaseUrl ? { upstreamBaseUrl: configuredBaseUrl } : {}),
upstreamApiKey: apiKey
});
if (!proxyResult || proxyResult.error || proxyResult.success === false || !proxyResult.listenUrl) {
await stopBuiltinClaudeProxyRuntime();
resetBuiltinClaudeProxySavedSettingsToResponses();
return {
success: false,
mode: 'claude-proxy',
error: (proxyResult && proxyResult.error) || '启动 Claude 兼容代理失败'
};
}
proxyStarted = true;
settingsBaseUrl = proxyResult.listenUrl;
settingsApiKey = proxyToken;
} else {
await stopBuiltinClaudeProxyRuntime();
resetBuiltinClaudeProxySavedSettingsToResponses();
}

const readResult = readJsonObjectFromFile(CLAUDE_SETTINGS_FILE, {});
if (!readResult.ok) {
if (proxyStarted) {
await stopBuiltinClaudeProxyRuntime();
resetBuiltinClaudeProxySavedSettingsToResponses();
}
return { success: false, mode: 'settings-file', error: readResult.error };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Preserve the current Claude runtime until settings I/O succeeds.

This path tears down the active builtin Claude proxy before settings.json is even read. If readJsonObjectFromFile(...), backupFileIfNeededOnce(...), or writeJsonAtomic(...) fails, the rollback only stops the new proxy and resets saved mode; it never restores the previously running proxy or the old Claude settings. That can leave Claude still pointing at a proxy URL that has already been stopped, so a failed apply breaks an otherwise working setup.

Also applies to: 9462-9465

🤖 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 `@cli.js` around lines 9367 - 9412, You tear down the existing builtin Claude
proxy (stopBuiltinClaudeProxyRuntime /
resetBuiltinClaudeProxySavedSettingsToResponses) before performing settings I/O
(readJsonObjectFromFile, backupFileIfNeededOnce, writeJsonAtomic), so failures
leave the old runtime un-restored; instead capture the current runtime/settings
state before touching anything (e.g. save whether a runtime was running and its
baseUrl/apiKey), delay calling stopBuiltinClaudeProxyRuntime until after
settings I/O succeeds, or if you must stop it first then on any subsequent
failure restart the previous runtime by calling startBuiltinClaudeProxyRuntime
with the saved parameters and restore saved settings via
resetBuiltinClaudeProxySavedSettingsToResponses; update the branches around
startBuiltinClaudeProxyRuntime, proxyStarted, settingsBaseUrl and settingsApiKey
and the error-return paths (including the later block noted at 9462-9465) so
failures perform restoration and do not leave Claude pointing at a stopped
proxy.

Comment thread cli.js
Comment on lines +399 to +408
function findFreePortForTest() {
return new Promise((resolve, reject) => {
const server = net.createServer();
server.once('error', reject);
server.listen(0, '127.0.0.1', () => {
const port = server.address().port;
server.close(() => resolve(port));
});
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Show the relevant portion of the test file around the reported lines
sed -n '340,520p' tests/unit/claude-proxy-adapter.test.mjs | nl -ba | sed -n '1,220p'

# 2) Locate where the helper is called and how the port is passed into the runtime/controller
rg -n "findFreePortForTest|createBuiltinClaudeProxyRuntimeController|startBuiltinClaudeProxyRuntime" tests/unit/claude-proxy-adapter.test.mjs

# 3) Find the implementation that actually binds/listens on the chosen port
#    (search in repo for the controller/runtime creation function name)
rg -n "createBuiltinClaudeProxyRuntimeController" -S .
rg -n "startBuiltinClaudeProxyRuntime" -S .

# 4) Inspect the implementation(s) that accept a port argument and listen/bind
#    (grab a limited window around likely definitions)
rg -n "listen\\(|createServer\\(|net\\.createServer\\(" -S . | head -n 50

Repository: SakuraByteCore/codexmate

Length of output: 111


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Show relevant portion with line numbers
sed -n '340,520p' tests/unit/claude-proxy-adapter.test.mjs | cat -n

# 2) Find where helper/controller functions are called in the test file
rg -n "findFreePortForTest|createBuiltinClaudeProxyRuntimeController|startBuiltinClaudeProxyRuntime" tests/unit/claude-proxy-adapter.test.mjs

# 3) Locate implementations in repo
rg -n "createBuiltinClaudeProxyRuntimeController" -S .
rg -n "startBuiltinClaudeProxyRuntime" -S .

# 4) Look for port-binding/listen logic (limited)
rg -n "listen\\(|net\\.createServer\\(" -S tests/unit/claude-proxy-adapter.test.mjs

Repository: SakuraByteCore/codexmate

Length of output: 9470


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Show relevant portion with line numbers
sed -n '340,520p' tests/unit/claude-proxy-adapter.test.mjs | cat -n

# 2) Find where helper/controller functions are called in the test file
rg -n "findFreePortForTest|createBuiltinClaudeProxyRuntimeController|startBuiltinClaudeProxyRuntime" tests/unit/claude-proxy-adapter.test.mjs

# 3) Locate implementations in repo
rg -n "createBuiltinClaudeProxyRuntimeController" -S .
rg -n "startBuiltinClaudeProxyRuntime" -S .

# 4) Look for port-binding/listen logic (limited)
rg -n "listen\\(|net\\.createServer\\(" -S tests/unit/claude-proxy-adapter.test.mjs

Repository: SakuraByteCore/codexmate

Length of output: 9470


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the implementation details where the runtime binds the provided port
# (startBuiltinClaudeProxyRuntime + any listen/createServer calls in its body)
sed -n '1420,1565p' cli/claude-proxy.js | cat -n

# Also capture the body more narrowly around server.listen / http(s).listen usage
rg -n "startBuiltinClaudeProxyRuntime\\(|listen\\(|createServer\\(|http\\.createServer\\(|https\\.createServer\\(" cli/claude-proxy.js

# Show a tighter window around the most relevant listen call(s) if found
# (we'll use rg output line numbers later, but for now print a broader window around 1453)
sed -n '1440,1515p' cli/claude-proxy.js | cat -n
sed -n '1515,1605p' cli/claude-proxy.js | cat -n

Repository: SakuraByteCore/codexmate

Length of output: 10884


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and view buildBuiltinClaudeProxyListenUrl
rg -n "function buildBuiltinClaudeProxyListenUrl|buildBuiltinClaudeProxyListenUrl\\(" cli/claude-proxy.js
# Print around its definition (bounded window)
sed -n '1300,1425p' cli/claude-proxy.js | cat -n

# Inspect createBuiltinClaudeProxyServer around where it listens and sets listenUrl
sed -n '1360,1465p' cli/claude-proxy.js | cat -n

# Confirm second usage range in the test file
sed -n '420,490p' tests/unit/claude-proxy-adapter.test.mjs | cat -n

Repository: SakuraByteCore/codexmate

Length of output: 15956


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Print buildBuiltinClaudeProxyListenUrl implementation
sed -n '960,1025p' cli/claude-proxy.js | cat -n

# Print any callers if needed (bounded)
rg -n "buildBuiltinClaudeProxyListenUrl\\(" cli/claude-proxy.js

Repository: SakuraByteCore/codexmate

Length of output: 3884


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '380,490p' tests/unit/claude-proxy-adapter.test.mjs | cat -n

Repository: SakuraByteCore/codexmate

Length of output: 5323


Fix TOCTOU risk from findFreePortForTest() by retrying or supporting port: 0.

In tests/unit/claude-proxy-adapter.test.mjs (399-408), findFreePortForTest() probes a port with server.listen(0, ...) and then immediately server.close()s it; later (439-470) the test passes that proxyPort into controller.startBuiltinClaudeProxyRuntime(), which binds with server.listen(settings.port, settings.host, ...). This creates a short TOCTOU window where another process can grab the port and make the test flaky (EADDRINUSE).

Either add port: 0 support in the proxy runtime (it currently rejects port <= 0) so the controller reports the actual bound port for listenUrl, or wrap startBuiltinClaudeProxyRuntime() in a small retry when it fails due to EADDRINUSE.

🤖 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 `@tests/unit/claude-proxy-adapter.test.mjs` around lines 399 - 408, The test's
findFreePortForTest() creates a TOCTOU by probing a free port then closing it
before controller.startBuiltinClaudeProxyRuntime() binds, causing flaky
EADDRINUSE; fix by either (preferred) adding support for port: 0 in the proxy
runtime so
startBuiltinClaudeProxyRuntime(controller/startBuiltinClaudeProxyRuntime or the
runtime that validates settings.port) accepts 0, performs server.listen(0,
host), and returns the actual bound port in its listenUrl/settings report, or
(alternatively) implement a small retry loop around
controller.startBuiltinClaudeProxyRuntime() in the test to catch EADDRINUSE and
retry a few times with backoff; update the runtime validation that currently
rejects port <= 0 to allow 0 and ensure the bound port is surfaced back to the
caller.

Comment thread tests/unit/claude-settings-sync.test.mjs
Comment thread web-ui/logic.claude.mjs
@awsl233777
Copy link
Copy Markdown
Collaborator Author

Update pushed in 9c7e5cc:

  • Temporarily disabled the Claude Ollama Chat (/api/chat) target option in add/edit config modals, while keeping the existing backend path untouched.
  • Added the same green transform-provider breathing indicator used by Codex cards to Claude configs that use internal conversion targets (chat_completions / existing ollama).
  • Added locale text for the disabled marker and regenerated the checked-in precompiled Web UI render bundle.
  • Added UI template assertions covering the disabled Ollama option and Claude transform indicator.

Validation run:

  • npm run test:unit -- tests/unit/config-tabs-ui.test.mjs tests/unit/web-ui-source-bundle.test.mjs tests/unit/claude-settings-sync.test.mjs → All 588 tests passed.
  • npm run lint → passed for 210 files.

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

@awsl233777 Thanks for the polish pass! The changes in 9c7e5cc look well-considered:

  • Disabled Ollama UI option — phasing the UI surface separately from the backend path is a prudent approach; the backend conversion is validated but the UX can be enabled once the flow is fully polished.
  • Transform-provider breathing indicator — reusing the same green indicator from Codex cards for chat_completions/ollama Claude configs gives users a clear, consistent visual signal that internal conversion is active.
  • Locale + precompiled bundle — adding the disabled marker text to all locales and regenerating the bundle keeps everything in sync.
  • UI template assertions — covering the disabled Ollama option and the transform indicator in tests is the right way to guard against accidental regressions.

Triggering a full re-review now. 🐇

✅ Actions performed

Full review triggered.

@awsl233777
Copy link
Copy Markdown
Collaborator Author

Follow-up pushed in 2f252ef:

  • Re-enabled the Claude Ollama Chat (/api/chat) target option in add/edit modals.
  • Kept and directly tested the intended validation behavior: Claude Ollama target configs may use an empty API key (apiKey: '') for both add and edit flows.
  • Added autocomplete="off" to the Codex and Claude provider-write permission checkboxes so browser form-state restoration does not briefly paint a stale write-enabled toggle before Vue rebinds the actual read-only permission state after refresh.
  • Regenerated the checked-in precompiled Web UI render bundle.

Validation run:

  • npm run test:unit -- tests/unit/config-tabs-ui.test.mjs tests/unit/web-ui-source-bundle.test.mjs tests/unit/claude-settings-sync.test.mjs → All 589 tests passed.
  • npm run lint → passed for 210 files.

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

@awsl233777 Thanks for the follow-up! The changes in 2f252ef look well-rounded:

  • Re-enabled Ollama option — surfacing the Ollama target in the add/edit modals now that the backend path and validation are solid.
  • Empty apiKey validation — explicitly testing that Ollama configs pass add/edit flows without an API key ensures the UX matches the backend contract.
  • autocomplete="off" on write-permission checkboxes — a subtle but important fix; browser form-state restoration could briefly show a stale write-enabled state before Vue reconciles, which would be a confusing UX signal for a security-relevant toggle.
  • Bundle regeneration — keeping the precompiled render in sync with the template changes.

Triggering a full re-review now. 🐇

✅ Actions performed

Full review triggered.

@awsl233777
Copy link
Copy Markdown
Collaborator Author

Follow-up pushed in 7ec2afa:

  • Added an async version-status API that checks the npm latest package version with a short timeout and returns the current package version.
  • Added a subtle left-sidebar update notice under the brand area. It only appears when the current app version is lower than the latest npm version, and clicking it opens the Docs/CLI install page with the update command selected.
  • Kept the notice intentionally low-emphasis: small inline card, muted color, no modal/toast, no topbar interruption.
  • Added version comparison/update notice tests and regenerated the checked-in precompiled Web UI render bundle.

Validation run:

  • npm run test:unit -- tests/unit/config-tabs-ui.test.mjs tests/unit/web-ui-source-bundle.test.mjs tests/unit/install-target-cards.test.mjs tests/unit/web-ui-behavior-parity.test.mjs → All 589 tests passed.
  • npm run lint → passed for 210 files.
  • npm run test:e2e → passed.
  • Manual npm latest probe via fetchLatestVersion({ timeoutMs: 2500 }) returned 0.0.41.

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

@awsl233777 Thanks for the follow-up! The additions in 7ec2afa look well-scoped:

  • version-status API — fetching the npm latest version with a short timeout and a clean timeout fallback is a good approach; avoids blocking the UI when the registry is slow or unreachable.
  • Low-emphasis update notice — keeping it as a small muted inline card in the left sidebar (rather than a modal or topbar banner) is the right UX call for an update nudge; it informs without interrupting.
  • Version comparison tests + bundle regeneration — covering the comparison logic and keeping the precompiled bundle in sync with the template ensures no silent regressions.

Triggering a full re-review now. 🐇

✅ Actions performed

Full review triggered.

@awsl233777 awsl233777 changed the title feat: add Claude proxy target modes and config UI feat: add Claude proxy target APIs with Ollama support Jun 2, 2026
@awsl233777
Copy link
Copy Markdown
Collaborator Author

Updated the Ollama Claude share/import path.

Changes:

  • export-claude-share now allows targetApi: ollama configs without an API key and includes targetApi in the exported payload.
  • Claude share commands now preserve Ollama imports with an empty API-key placeholder and --target-api ollama, e.g. codexmate claude http://127.0.0.1:11434 '' llama3.1:8b --target-api ollama.
  • codexmate claude now parses --target-api responses|chat_completions|ollama; Ollama no longer requires an API key.
  • The Claude target API label now shows Anthropic instead of Anthropic / OpenAI Responses.
  • Added regression coverage for Ollama no-key share payloads, generated import commands, CLI parsing, and E2E apply behavior.
  • Addressed the previous CodeRabbit E2E robustness note by restoring the temporary Claude settings.json in a finally block.

Validation actually run:

  • npm test → All 593 tests passed.
  • npm run lint → Lint passed for 210 file(s).

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

@awsl233777 Thanks for the Ollama share/import path update! The changes look well-rounded:

  • export-claude-share Ollama support — allowing no-key exports and including targetApi in the payload keeps share/import parity with the rest of the Ollama flow.
  • Import command format — the empty API-key placeholder ('') in the generated codexmate claude command is a clean way to satisfy the positional argument while making it obvious no key is needed for Ollama targets.
  • CLI --target-api parsing — surfacing responses|chat_completions|ollama at the CLI level with the Ollama no-key exemption completes the end-to-end apply path.
  • Label simplificationAnthropic is cleaner than Anthropic / OpenAI Responses; the former was always slightly redundant.
  • finally block for settings restore — addresses the E2E robustness gap; ensures cleanup happens even on assertion failures.

Triggering a full re-review now. 🐇

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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 `@cli.js`:
- Around line 9585-9591: The CLI currently accepts --target-api/--targetApi and
uses normalizeClaudeTargetApi(nextValue) but silently maps unknown values to
"responses"; change this to validate the normalized value and throw an
informative Error when normalizeClaudeTargetApi returns an unrecognized value so
typos don't fall back to responses and bypass proxy/token flow; update the same
validation where targetApi is later handled (the branch that applies direct
Claude settings) to reject unknown targetApi values as well, referencing the
normalizeClaudeTargetApi function and the targetApi variable to locate the
changes.
- Around line 8000-8012: The current validation checks raw config strings
(apiKey, baseUrl, model) before trimming, so whitespace-only values pass and
produce empty payload fields; fix by trimming each value up-front (e.g., assign
trimmedBaseUrl = (config.baseUrl || '').trim(), trimmedApiKey = (config.apiKey
|| '').trim(), trimmedModel = (config.model || '').trim()), then use those
trimmed variables for the existence checks (and when calling
normalizeClaudeTargetApi if needed) and finally serialize
trimmedBaseUrl/trimmedApiKey/(trimmedModel || DEFAULT_CLAUDE_MODEL) into the
payload so whitespace-only inputs are rejected correctly; update references to
apiKey/baseUrl/model in this block accordingly.
- Line 16361: Update the help text for the "codexmate claude" usage string so it
shows the Ollama no-key shorthand rather than implying an API key is required:
modify the console.log that currently prints "codexmate claude <BaseURL> <API密钥>
[模型] [--target-api responses|chat_completions|ollama]  写入 Claude Code 配置" to
document the optional/alternate form (e.g., "codexmate claude <BaseURL>
[<API密钥>] <模型> --target-api ollama" or explicitly note that the API密钥 is
optional for --target-api ollama); locate and update the string used by the
help/output generator where "codexmate claude" is emitted so both the standard
and Ollama no-key forms are shown or the API key is marked optional.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: aa9bbb3e-ebe6-4472-91f0-9d9bad9c5d08

📥 Commits

Reviewing files that changed from the base of the PR and between 2f252ef and f1dce82.

📒 Files selected for processing (18)
  • cli.js
  • cli/update.js
  • tests/e2e/test-claude.js
  • tests/unit/config-tabs-ui.test.mjs
  • tests/unit/install-target-cards.test.mjs
  • tests/unit/provider-share-command.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • web-ui/app.js
  • web-ui/modules/app.methods.index.mjs
  • web-ui/modules/app.methods.install.mjs
  • web-ui/modules/app.methods.session-actions.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/modules/i18n/locales/zh.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/res/web-ui-render.precompiled.js
  • web-ui/styles/layout-shell.css
✅ Files skipped from review due to trivial changes (4)
  • tests/unit/web-ui-behavior-parity.test.mjs
  • web-ui/modules/i18n/locales/en.mjs
  • web-ui/modules/i18n/locales/ja.mjs
  • web-ui/modules/i18n/locales/zh.mjs
🚧 Files skipped from review as they are similar to previous changes (4)
  • web-ui/modules/i18n/locales/vi.mjs
  • web-ui/app.js
  • web-ui/res/web-ui-render.precompiled.js
  • tests/e2e/test-claude.js
📜 Review details
🔇 Additional comments (17)
cli.js (1)

151-151: LGTM!

Also applies to: 11114-11134

tests/unit/provider-share-command.test.mjs (4)

377-398: LGTM!


400-413: LGTM!


429-442: LGTM!


444-461: LGTM!

web-ui/modules/app.methods.session-actions.mjs (2)

389-393: LGTM!


394-398: LGTM!

tests/unit/config-tabs-ui.test.mjs (3)

138-140: LGTM!

Also applies to: 148-149


292-297: LGTM!


298-305: LGTM!

cli/update.js (1)

67-104: LGTM!

web-ui/modules/app.methods.install.mjs (2)

20-63: LGTM!

Also applies to: 94-99


65-92: Align version-status response fields with loadAppVersionStatus

cli.js’s case 'version-status' returns { currentVersion, latestVersion } on success and { currentVersion, latestVersion: '', error } on failure, matching loadAppVersionStatus’s usage of res.currentVersion, res.latestVersion, and res.error.

web-ui/modules/app.methods.index.mjs (1)

92-92: LGTM!

web-ui/partials/index/layout-header.html (1)

113-124: LGTM!

web-ui/styles/layout-shell.css (1)

510-563: LGTM!

tests/unit/install-target-cards.test.mjs (1)

45-78: LGTM!

Comment thread cli.js
Comment thread cli.js
Comment on lines +9585 to +9591
if (token === '--target-api' || token === '--targetApi') {
const nextValue = String(argv[i + 1] ?? '');
if (!nextValue || nextValue.startsWith('--')) {
throw new Error('错误: --target-api 需要一个值(responses、chat_completions 或 ollama)');
}
targetApi = normalizeClaudeTargetApi(nextValue);
i += 1;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject unknown --target-api values instead of silently falling back to responses.

Line 9590 maps any unrecognized value to responses. Here that is not a safe default: a typo skips the proxy-backed path and Line 9640-Line 9645 will apply direct Claude settings instead of the local proxy/token flow.

Proposed fix
         if (token === '--target-api' || token === '--targetApi') {
             const nextValue = String(argv[i + 1] ?? '');
             if (!nextValue || nextValue.startsWith('--')) {
                 throw new Error('错误: --target-api 需要一个值(responses、chat_completions 或 ollama)');
             }
-            targetApi = normalizeClaudeTargetApi(nextValue);
+            const rawTargetApi = nextValue.trim().toLowerCase();
+            const allowedTargetApis = new Set([
+                'responses',
+                'chat_completions',
+                'chat-completions',
+                'chat/completions',
+                'ollama'
+            ]);
+            if (!allowedTargetApis.has(rawTargetApi)) {
+                throw new Error(`错误: 不支持的 --target-api 值: ${nextValue}`);
+            }
+            targetApi = normalizeClaudeTargetApi(rawTargetApi);
             i += 1;
             continue;
         }

Also applies to: 9640-9645

🤖 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 `@cli.js` around lines 9585 - 9591, The CLI currently accepts
--target-api/--targetApi and uses normalizeClaudeTargetApi(nextValue) but
silently maps unknown values to "responses"; change this to validate the
normalized value and throw an informative Error when normalizeClaudeTargetApi
returns an unrecognized value so typos don't fall back to responses and bypass
proxy/token flow; update the same validation where targetApi is later handled
(the branch that applies direct Claude settings) to reject unknown targetApi
values as well, referencing the normalizeClaudeTargetApi function and the
targetApi variable to locate the changes.

Comment thread cli.js
@awsl233777
Copy link
Copy Markdown
Collaborator Author

Updated the Ollama Claude share/import path and added a real end-to-end acceptance check.

Changes in this round:

  • Persist direct Claude proxy upstream settings for shared Ollama imports (upstreamBaseUrl, optional upstreamApiKey, upstreamProviderName) so the imported config still works after restarting codexmate.
  • Keep provider/responses selection from being polluted by a previously persisted direct upstream.
  • Added regression coverage for restarting the Claude proxy from saved Ollama share-import settings.
  • Kept the OpenAI bridge rollback E2E guarded with try/finally.
  • Kept Claude config duplicate detection distinct by targetApi.
  • Made the MCP Claude config schema tolerate whitespace/case around ollama while still requiring API keys for non-Ollama targets.

Validation actually run:

  • npm test
    • unit tests: All 595 tests passed
    • e2e: node tests/e2e/run.js exited 0
  • npm run lint
    • Lint passed for 210 file(s).

Real project-demand acceptance run against the provided Ollama server:

  • Upstream: http://27.154.56.162:11434
  • Working model used: gemma4:31b-cloud
  • Exported share payload preserved:
    • baseUrl: http://27.154.56.162:11434
    • apiKey: ""
    • model: gemma4:31b-cloud
    • targetApi: ollama
  • Imported in a fresh isolated HOME via the share command.
  • Restarted codexmate in that fresh HOME and started the Claude proxy from saved settings.
  • Verified runtime mode: anthropic-to-ollama with upstream http://27.154.56.162:11434.
  • Ran Claude Code against that imported config with a real small project request: create index.html with a todo input, Add/Clear buttons, list area, and inline JS.
  • Acceptance checked the generated index.html for:
    • HTML document present
    • 1 input
    • at least 2 buttons
    • script present
    • todo/list semantics present
    • event binding present

Note: subscription-gated models on that Ollama server (minimax-m2.7:cloud, deepseek-v4-pro:cloud) returned 403, so the successful project acceptance used gemma4:31b-cloud.

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

@awsl233777 Thanks for the comprehensive round! The changes close the remaining lifecycle gap in the Ollama share/import path — persisting upstreamBaseUrl/upstreamApiKey/upstreamProviderName across restarts is the key piece that makes the share/import flow durable. A few highlights worth calling out:

  • Upstream persistence for Ollama imports — without this, the imported config would work once but lose its upstream on restart; now the proxy can resume from saved settings.
  • Provider/responses pollution guard — preventing a previously persisted direct upstream from bleeding into the responses/provider selection is an important state isolation fix.
  • Duplicate detection by targetApi — useful for users who might have the same endpoint configured for different wire formats (e.g., a local server reachable via both Ollama and OpenAI-compatible paths).
  • MCP schema whitespace/case tolerance — a small but user-friendly UX improvement; allows Ollama, ollama, etc. without a validation rejection.
  • Real project acceptance test — end-to-end generation of a working todo HTML page through the full Claude Code → codexmate proxy → Ollama → gemma4:31b-cloud chain is strong evidence the conversion path is correct.

Triggering a full re-review now. 🐇

✅ Actions performed

Full review triggered.

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