Skip to content

fix CC compatible v1 route handling - #904

Closed
rdself wants to merge 2 commits into
cheaper-inference:mainfrom
rdself:coder/cc-compatible-path-fix
Closed

rdself wants to merge 2 commits into
cheaper-inference:mainfrom
rdself:coder/cc-compatible-path-fix

Conversation

@rdself

@rdself rdself commented Apr 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • restore the CC Compatible default endpoint to /v1/messages?beta=true
  • split generic Anthropic-compatible URL normalization from CC-specific endpoint normalization
  • normalize create, update, validate, and runtime URL building so pasted CC endpoints still resolve /models and /v1/messages?beta=true correctly

Root Cause

The CC Compatible flow on the latest main was normalizing provider URLs like a generic Anthropic-compatible endpoint.

That broke the hidden CC bridge in two ways:

  • the default CC request path effectively resolved to /messages?beta=true instead of /v1/messages?beta=true
  • pasted CC endpoints such as /v1/messages?beta=true were not normalized consistently across create, update, validation, and runtime execution

The failing log that triggered this fix showed OmniRoute calling https://www.xxx.com/messages?beta=true, getting the site's HTML homepage back, and then surfacing that as Invalid JSON response from provider, which bubbled up as a 502.

Impact

  • CC Compatible providers now keep the intended Claude Code request shape and hit the expected /v1/messages?beta=true endpoint by default
  • generic Anthropic-compatible providers keep their previous /v1/models and /v1/messages probing behavior
  • users can paste a CC provider root URL, /v1, or a full /v1/messages?beta=true URL and still get correct runtime and validation behavior

Validation

  • node --import tsx/esm --test tests/unit/cc-compatible-provider.test.mjs tests/unit/provider-validation-branches.test.mjs
  • npx eslint 'open-sse/services/claudeCodeCompatible.ts' 'open-sse/executors/default.ts' 'open-sse/services/provider.ts' 'src/lib/providers/validation.ts' 'src/app/api/provider-nodes/route.ts' 'src/app/api/provider-nodes/[id]/route.ts' 'src/app/api/provider-nodes/validate/route.ts' 'src/app/(dashboard)/dashboard/providers/page.tsx' 'src/app/(dashboard)/dashboard/providers/[id]/page.tsx' 'tests/unit/cc-compatible-provider.test.mjs'
  • pre-commit hook npm run test:unit passed locally during git commit (1233 tests)

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request refactors URL construction and sanitization for Claude Code (CC) compatible providers. It updates default chat paths to include the /v1 prefix, adjusts default base URLs, and introduces specialized joining and stripping functions to handle versioned endpoints more robustly. Review feedback suggests updating the default models path for consistency, expanding URL sanitization logic to include the /models endpoint, and reducing code duplication by centralizing sanitization functions in a shared service.

export const CLAUDE_CODE_COMPATIBLE_PREFIX = "anthropic-compatible-cc-";
export const CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH = "/messages?beta=true";
export const CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH = "/v1/messages?beta=true";
export const CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH = "/models";

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.

high

The default models path should be updated to /v1/models to be consistent with the updated chat path and the new URL normalization logic. Since stripClaudeCodeCompatibleEndpointSuffix strips the /v1 segment when a full endpoint is provided, using /models as the default will result in an incorrect URL (e.g., .../models instead of .../v1/models) if a user pastes a full messages URL as the base URL.

Suggested change
export const CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH = "/models";
export const CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH = "/v1/models";

const CC_COMPATIBLE_DETAILS_TITLE = "CC Compatible Details";
const CC_COMPATIBLE_DEFAULT_CHAT_PATH = "/messages?beta=true";
const CC_COMPATIBLE_DEFAULT_CHAT_PATH = "/v1/messages?beta=true";
const CC_COMPATIBLE_DEFAULT_MODELS_PATH = "/models";

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.

high

The default models path should be updated to /v1/models for consistency with the service and to ensure correct URL construction for CC compatible providers.

Suggested change
const CC_COMPATIBLE_DEFAULT_MODELS_PATH = "/models";
const CC_COMPATIBLE_DEFAULT_MODELS_PATH = "/v1/models";

const ADD_CC_COMPATIBLE_LABEL = "Add CC Compatible";
const CC_COMPATIBLE_DEFAULT_CHAT_PATH = "/messages?beta=true";
const CC_COMPATIBLE_DEFAULT_CHAT_PATH = "/v1/messages?beta=true";
const CC_COMPATIBLE_DEFAULT_MODELS_PATH = "/models";

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.

high

The default models path should be updated to /v1/models for consistency with the service and other UI components.

Suggested change
const CC_COMPATIBLE_DEFAULT_MODELS_PATH = "/models";
const CC_COMPATIBLE_DEFAULT_MODELS_PATH = "/v1/models";

@@ -45,14 +45,39 @@ export function stripAnthropicMessagesSuffix(baseUrl: string | null | undefined)
return normalized.replace(/\/messages(?:\?[^#]*)?$/i, "");

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.

medium

The generic Anthropic endpoint stripping logic should also handle the /models suffix. This ensures that if a user pastes a models endpoint as the base URL, it is correctly sanitized before being used to construct other endpoints.

Suggested change
return normalized.replace(/\/messages(?:\?[^#]*)?$/i, "");
return normalized.replace(/\/(?:messages|models)(?:\?[^#]*)?$/i, "");

.trim()
.replace(/\/$/, "");
if (!normalized) return "";
return normalized.replace(/\/(?:v\d+\/)?messages(?:\?[^#]*)?$/i, "");

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.

medium

The CC-specific endpoint stripping logic should also handle the /models suffix (optionally prefixed with a version segment). This is important for robust URL normalization when users paste various endpoint URLs into the base URL field.

Suggested change
return normalized.replace(/\/(?:v\d+\/)?messages(?:\?[^#]*)?$/i, "");
return normalized.replace(/\/(?:v\d+\/)?(?:messages|models)(?:\?[^#]*)?$/i, "");

Comment on lines +20 to +32
function sanitizeAnthropicBaseUrl(baseUrl: string) {
return (baseUrl || "")
.trim()
.replace(/\/$/, "")
.replace(/\/messages(?:\?[^#]*)?$/i, "");
}

function sanitizeClaudeCodeCompatibleBaseUrl(baseUrl: string) {
return (baseUrl || "")
.trim()
.replace(/\/$/, "")
.replace(/\/(?:v\d+\/)?messages(?:\?[^#]*)?$/i, "");
}

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.

medium

These sanitization functions are duplicated from open-sse/services/claudeCodeCompatible.ts. To ensure consistency and simplify maintenance, they should be imported from the shared service instead of being redefined locally in multiple API route files.

Comment on lines +28 to +33
function sanitizeClaudeCodeCompatibleBaseUrl(baseUrl: string) {
return (baseUrl || "")
.trim()
.replace(/\/$/, "")
.replace(/\/(?:v\d+\/)?messages(?:\?[^#]*)?$/i, "");
}

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.

medium

This function is duplicated from open-sse/services/claudeCodeCompatible.ts. It should be imported from the shared service to maintain consistency across the codebase.

Comment on lines +14 to +19
function sanitizeClaudeCodeCompatibleBaseUrl(baseUrl: string) {
return (baseUrl || "")
.trim()
.replace(/\/$/, "")
.replace(/\/(?:v\d+\/)?messages(?:\?[^#]*)?$/i, "");
}

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.

medium

This function is duplicated from open-sse/services/claudeCodeCompatible.ts. It should be imported from the shared service.

@rdself
rdself marked this pull request as ready for review April 2, 2026 00:05
Copilot AI review requested due to automatic review settings April 2, 2026 00:05
@rdself rdself changed the title [codex] fix CC compatible v1 route handling fix CC compatible v1 route handling Apr 2, 2026
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Obrigado pela contribuição! 🎉 A funcionalidade/correção foi integrada localmente na branch release/v3.4.4 e fará parte do próximo release.

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Integrated into release/v3.4.4.

"GET https://proxy.example.com/v1/models",
"POST https://proxy.example.com/v1/messages?beta=true",
]
["GET https://proxy.example.com/models", "POST https://proxy.example.com/v1/messages?beta=true"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: Inconsistent URL construction - the test expects /models without the /v1 prefix while the chat path uses /v1/messages?beta=true. This is the buggy behavior that the existing HIGH priority comments correctly flag. When CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH is updated to /v1/models, this test expectation should change to include the /v1 prefix for consistency.

const data = await response.json();
assert.match(data.node.id, /^anthropic-compatible-cc-/);
assert.equal(data.node.baseUrl, "https://proxy.example.com/v1");
assert.equal(data.node.baseUrl, "https://proxy.example.com");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: This test expects baseUrl without the /v1 segment (https://proxy.example.com instead of https://proxy.example.com/v1). This is correct behavior when the full endpoint URL is pasted and sanitized. However, verify this aligns with the new URL normalization logic - the test passes currently but may need adjustment if models path is updated to include /v1.

@kilo-code-bot

kilo-code-bot Bot commented Apr 2, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 10 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 10
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
open-sse/services/claudeCodeCompatible.ts 5 Existing: The default models path should be updated to /v1/models to be consistent with the updated chat path and the new URL normalization logic
src/app/(dashboard)/dashboard/providers/[id]/page.tsx 474 Existing: The default models path should be updated to /v1/models for consistency with the service and to ensure correct URL construction
src/app/(dashboard)/dashboard/providers/page.tsx 33 Existing: The default models path should be updated to /v1/models for consistency with the service and other UI components
open-sse/services/claudeCodeCompatible.ts 45 Existing: The generic Anthropic endpoint stripping logic should also handle the /models suffix
open-sse/services/claudeCodeCompatible.ts 55 Existing: The CC-specific endpoint stripping logic should also handle the /models suffix
src/app/api/provider-nodes/[id]/route.ts 32 Existing: These sanitization functions are duplicated from open-sse/services/claudeCodeCompatible.ts. They should be imported from the shared service
src/app/api/provider-nodes/route.ts 33 Existing: This function is duplicated from open-sse/services/claudeCodeCompatible.ts. It should be imported from the shared service
src/app/api/provider-nodes/validate/route.ts 19 Existing: This function is duplicated from open-sse/services/claudeCodeCompatible.ts. It should be imported from the shared service
tests/unit/cc-compatible-provider.test.mjs 262 NEW: Inconsistent URL construction - the test expects /models without the /v1 prefix while the chat path uses /v1/messages?beta=true. When CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH is updated to /v1/models, this test expectation should also change
tests/unit/cc-compatible-provider.test.mjs 311 NEW: This test expects baseUrl without the /v1 segment. Verify this aligns with the new URL normalization logic when models path is updated to include /v1
Other Observations (not in diff)

No issues found in unchanged code.

Files Reviewed (10 files)
  • open-sse/executors/default.ts - No new issues
  • open-sse/services/claudeCodeCompatible.ts - 5 existing issues
  • open-sse/services/provider.ts - No new issues
  • src/app/(dashboard)/dashboard/providers/[id]/page.tsx - 1 existing issue
  • src/app/(dashboard)/dashboard/providers/page.tsx - 1 existing issue
  • src/app/api/provider-nodes/[id]/route.ts - 1 existing issue
  • src/app/api/provider-nodes/route.ts - 1 existing issue
  • src/app/api/provider-nodes/validate/route.ts - 1 existing issue
  • src/lib/providers/validation.ts - No new issues
  • tests/unit/cc-compatible-provider.test.mjs - 2 new issues

Analysis Summary:

This PR addresses CC Compatible URL normalization issues by splitting generic Anthropic-compatible URL handling from CC-specific handling. The changes are well-intentioned and target the correct root cause.

Existing issues are valid:

  • The HIGH priority comments correctly identify that CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH should be updated to /v1/models for consistency with the chat path
  • The MEDIUM priority comments about /models suffix handling and code duplication are also valid concerns

New issues identified:

  • Test file expectations at lines 262 and 311 will need adjustment when the models path fix is applied

Recommendation: The existing comments provide clear suggestions for fixes. Once those are addressed, this PR should be mergeable.

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Obrigado pela contribuição! 🎉 A funcionalidade/correção foi integrada localmente na branch release/v3.4.4 e fará parte do próximo release.

Copilot AI left a comment

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.

Pull request overview

Fixes Claude Code (CC) compatible provider URL handling so CC endpoints reliably resolve to /v1/messages?beta=true while keeping generic Anthropic-compatible normalization unchanged.

Changes:

  • Restore CC default chat path to /v1/messages?beta=true and introduce CC-specific URL joining/stripping helpers.
  • Update validation + runtime URL building to use CC-specific normalization (joinClaudeCodeCompatibleUrl, stripClaudeCodeCompatibleEndpointSuffix).
  • Align API/UI create/update/validate flows and add unit tests covering CC URL joins and message-shape edge cases.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/unit/cc-compatible-provider.test.mjs Adds/updates tests for CC URL joining, executor URL building, validation call ordering, and message fallback behavior.
src/lib/providers/validation.ts Splits Anthropic vs CC base URL normalization; uses CC join helper for /models + /v1/messages?beta=true validation probes.
src/app/api/provider-nodes/validate/route.ts Uses CC-specific base URL sanitization when validating CC nodes.
src/app/api/provider-nodes/route.ts Sanitizes CC base URLs differently during create; routes CC vs generic Anthropic normalization.
src/app/api/provider-nodes/[id]/route.ts Applies CC vs generic Anthropic base URL sanitization during updates.
src/app/(dashboard)/dashboard/providers/page.tsx Updates CC defaults in “Add CC Compatible” flow (base URL + chat path).
src/app/(dashboard)/dashboard/providers/[id]/page.tsx Updates CC defaults in edit flow (base URL + chat path).
open-sse/services/provider.ts Uses CC-specific URL joiner for CC provider runtime URL construction.
open-sse/services/claudeCodeCompatible.ts Restores CC default chat path, adds CC strip/join helpers, and adjusts CC message building behavior.
open-sse/executors/default.ts Uses CC-specific URL joiner when building CC runtime URLs.

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

const rawBaseUrl = baseUrl || ANTHROPIC_COMPATIBLE_DEFAULTS.baseUrl;
const sanitizedBaseUrl =
compatMode === "cc"
? sanitizeClaudeCodeCompatibleBaseUrl(rawBaseUrl)

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

sanitizeClaudeCodeCompatibleBaseUrl() only strips a trailing /messages (and optional /vN/messages), but leaves a bare /v1 intact. That means creating a CC node with no baseUrl (or with baseUrl: https://api.anthropic.com/v1) will persist .../v1, and later /models validation will be built as .../v1/models rather than the intended root .../models. Consider using a CC-specific default base URL (e.g., https://api.anthropic.com) and/or stripping a trailing /v\d+ segment for CC mode so pasted /v1 inputs normalize to the same stored base URL as root inputs.

Suggested change
? sanitizeClaudeCodeCompatibleBaseUrl(rawBaseUrl)
? sanitizeClaudeCodeCompatibleBaseUrl(rawBaseUrl.replace(/\/v\d+\/?$/i, ""))

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +79
export function stripClaudeCodeCompatibleEndpointSuffix(
baseUrl: string | null | undefined
): string {
const normalized = String(baseUrl || "")
.trim()
.replace(/\/$/, "");
if (!normalized) return "";
return normalized.replace(/\/(?:v\d+\/)?messages(?:\?[^#]*)?$/i, "");
}

function joinNormalizedBaseUrlAndPath(baseUrl: string, path: string): string {
const normalizedBase = String(baseUrl || "").replace(/\/$/, "");
const normalizedPath = String(path || "").startsWith("/")
? String(path)
: `/${String(path || "")}`;
const versionMatch = normalizedBase.match(/(\/v\d+)$/i);
if (
versionMatch &&
normalizedPath.toLowerCase().startsWith(`${versionMatch[1].toLowerCase()}/`)
) {
return `${normalizedBase}${normalizedPath.slice(versionMatch[1].length)}`;
}
return `${normalizedBase}${normalizedPath}`;
}

export function joinBaseUrlAndPath(baseUrl: string, path: string): string {
return joinNormalizedBaseUrlAndPath(stripAnthropicMessagesSuffix(baseUrl), path);
}

export function joinClaudeCodeCompatibleUrl(baseUrl: string, path: string): string {
return joinNormalizedBaseUrlAndPath(stripClaudeCodeCompatibleEndpointSuffix(baseUrl), path);
}

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

joinClaudeCodeCompatibleUrl() preserves a trailing /vN on the base URL, which can change CC "root" endpoints. For example, joinClaudeCodeCompatibleUrl('https://proxy.example.com/v1', '/models') currently yields https://proxy.example.com/v1/models, but the CC flow (and tests in this PR) expects models probing at the root /models. To match the stated normalization goals (accept base URLs pasted as root, /v1, or full /v1/messages?...), consider updating CC base URL normalization (e.g., stripClaudeCodeCompatibleEndpointSuffix) to also strip a trailing /v\d+, or otherwise ensure /models is always joined against the versionless root.

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +33
function sanitizeClaudeCodeCompatibleBaseUrl(baseUrl: string) {
return (baseUrl || "")
.trim()
.replace(/\/$/, "")
.replace(/\/(?:v\d+\/)?messages(?:\?[^#]*)?$/i, "");
}

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

The CC base URL sanitization logic is now duplicated in multiple API routes (this file, src/app/api/provider-nodes/validate/route.ts, and src/app/api/provider-nodes/[id]/route.ts) while similar normalization also exists in open-sse/services/claudeCodeCompatible.ts (stripClaudeCodeCompatibleEndpointSuffix). This duplication increases the risk that create/update/validate/runtime will drift again. Consider extracting a single shared sanitizer (e.g., in a shared URL util) and reusing it across the routes and validation helpers.

Copilot uses AI. Check for mistakes.
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.

3 participants