Skip to content

Add configurable path_suffix for OpenAI-compat endpoints#2558

Open
RefuseOdd wants to merge 2 commits into
Hmbown:mainfrom
RefuseOdd:feat/provider-path-suffix
Open

Add configurable path_suffix for OpenAI-compat endpoints#2558
RefuseOdd wants to merge 2 commits into
Hmbown:mainfrom
RefuseOdd:feat/provider-path-suffix

Conversation

@RefuseOdd
Copy link
Copy Markdown

@RefuseOdd RefuseOdd commented Jun 2, 2026

Summary

Adds an optional path_suffix field to provider config that lets users override the API path for OpenAI-compatible endpoints. Some third-party endpoints reject /v1/chat/completions and only accept /chat/completions. This change allows users to set path_suffix = "/chat/completions" in their provider config to bypass the default /v1 versioning.

Changes

  • ProviderConfigToml (config crate): added path_suffix: Option<String>
  • ProviderConfig (tui crate): added path_suffix: Option<String>
  • merge_provider_config: propagates the new field
  • merge_project_provider_config: propagates the new field
  • api_url: delegates to new api_url_with_suffix
  • api_url_with_suffix: when suffix is set, uses base URL directly with the suffix path (skips /v1)
  • DeepSeekClient::new: reads path_suffix from config
  • DeepSeekClient methods: all URL-building calls use api_url_with_suffix
  • config.example.toml: documents the new option
  • Tests for the new URL building behavior

Closes #2089

Greptile Summary

Adds an optional path_suffix field to provider config so users can override the API path for OpenAI-compatible endpoints that reject the default /v1/chat/completions route (e.g. providers that only accept /chat/completions).

  • api_url_with_suffix correctly uses unversioned_base_url to strip the version segment before appending the suffix, and the suffix guard is scoped to path == \"chat/completions\" only — models, health probes, and beta/ FIM paths are all unaffected.
  • path_suffix is wired through DeepSeekClient, both chat call sites in chat.rs, and all three config merge paths; tests cover the main URL-building cases including the no-suffix default, version stripping, and path isolation.

Confidence Score: 4/5

Safe to merge with one design decision worth revisiting before shipping.

The core URL-building logic is correct and well-tested. The one open question is whether path_suffix should be settable from untrusted project-level config: the merge_project_provider_config helper is explicitly documented to exclude endpoint fields, yet path_suffix — which redirects chat API calls to an arbitrary path on the provider — was added to it. This doesn't cross a host boundary, so exploitation is limited, but it contradicts the function's stated security policy and may deserve a second look before the feature is released.

crates/config/src/lib.rs — specifically whether path_suffix belongs in merge_project_provider_config given that function's documented exclusion of endpoint overrides.

Important Files Changed

Filename Overview
crates/tui/src/client.rs Adds api_url_with_suffix that correctly strips the version segment via unversioned_base_url and only applies the suffix for the chat/completions path; path_suffix field added to DeepSeekClient and read from provider config on construction.
crates/tui/src/client/chat.rs Both streaming and non-streaming chat call sites updated from api_url to api_url_with_suffix with self.path_suffix.as_deref(); straightforward mechanical change.
crates/config/src/lib.rs path_suffix added to ProviderConfigToml and propagated in merge_project_provider_config, but that helper is explicitly documented to exclude endpoint-related fields from untrusted project config.
crates/tui/src/config.rs path_suffix field added to ProviderConfig and merge_provider_config; all changes are consistent with the existing field pattern.
config.example.toml Adds a commented-out path_suffix example under the custom provider block with a clear description.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["api_url_with_suffix(base_url, path, suffix)"] --> B{path starts with 'beta/'?}
    B -- yes --> C["format: unversioned_base_url(base_url) / path"]
    B -- no --> D{path == 'chat/completions' AND suffix set?}
    D -- yes --> E["format: unversioned_base_url(base_url) / suffix.trim_start_matches('/')"]
    D -- no --> F["versioned = versioned_base_url(base_url)"]
    F --> G{versioned ends with 'beta'?}
    G -- yes --> H["versioned = unversioned_base_url / v1"]
    G -- no --> I["use versioned as-is"]
    H --> J["format: versioned / path"]
    I --> J

    subgraph Call Sites
        K["chat.rs streaming"] -->|path_suffix| A
        L["chat.rs non-streaming"] -->|path_suffix| A
        M["client.rs translate"] -->|path_suffix| A
        N["client.rs FIM beta/completions"] -->|None| A
        O["client.rs list_models"] -->|api_url wraps with None| A
        P["client.rs health probe"] -->|api_url wraps with None| A
    end
Loading

Fix All in Codex Fix All in Claude Code Fix All in Cursor

Reviews (2): Last reviewed commit: "Limit path suffix to chat completions" | Re-trigger Greptile

Adds an optional path_suffix field that lets users override the API path
for OpenAI-compatible endpoints. When set, the suffix replaces the default
/v1/<path> pattern, enabling use with endpoints that don't accept /v1/
prefixes (e.g. /chat/completions instead of /v1/chat/completions).

Changes:
- ProviderConfigToml (config crate): path_suffix field
- ProviderConfig (tui crate): path_suffix field
- merge_provider_config: propagates path_suffix
- merge_project_provider_config: propagates path_suffix
- api_url: delegates to new api_url_with_suffix function
- api_url_with_suffix: uses suffix when present, skips /v1 versioning
- DeepSeekClient: reads path_suffix from config, passes to URL builder
- config.example.toml: documents the new option
- Tests for the new URL building behavior

Closes Hmbown#2089
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

Comment thread crates/tui/src/client.rs Outdated
Comment thread crates/tui/src/client.rs
Comment thread crates/tui/src/client.rs Outdated
@Hmbown
Copy link
Copy Markdown
Owner

Hmbown commented Jun 2, 2026

Thanks for tackling path_suffix; the user need is real, especially for OpenAI-compatible endpoints that accept /chat/completions but not /v1/chat/completions.

I am not harvesting this revision into v0.8.50 yet because the current plumbing applies the suffix too broadly. The fix should be narrow:

  1. In the suffix branch, build from unversioned_base_url(base_url) so base_url = "https://api.example.com/v1" plus path_suffix = "/chat/completions" yields https://api.example.com/chat/completions.
  2. Apply path_suffix only for chat-completions request construction. Keep model listing, health checks, and probes on the normal models path.
  3. Pass None for beta/FIM paths so it is explicit that suffix overrides do not affect /beta/completions.
  4. Add tests for all three paths: chat suffix stripping /v1, model listing ignoring suffix, and beta path ignoring suffix.

Once those pass, this is the kind of small provider-compatibility feature that should be harvestable.

@Hmbown
Copy link
Copy Markdown
Owner

Hmbown commented Jun 2, 2026

Hey @RefuseOdd — this is a solid feature! Several third-party OpenAI-compatible endpoints do need a custom path, and a config field is the right place for it.

Two small things before this is harvest-ready:

  1. `api_url_with_suffix` should strip the existing version segment from `base_url` before appending the suffix — right now a config with `base_url = ".../v1"` and `path_suffix = "/chat/completions"` still produces `.../v1/chat/completions`.
  2. `path_suffix` should only apply to chat-completions endpoints, not model-listing (health checks and `/models` should keep using the default path).

Fix those two and this is good to go! Happy to review the update. 🐋

@RefuseOdd
Copy link
Copy Markdown
Author

@Hmbown pushed the requested fixes in 1c5c1212:

  • api_url_with_suffix now appends path_suffix from unversioned_base_url(base_url), so base_url = ".../v1" with path_suffix = "/chat/completions" resolves to .../chat/completions.
  • path_suffix is now only applied to chat-completions URL construction. /models, health/recovery probes, and /beta/completions continue using the normal URL paths.

I also added focused tests for chat suffix stripping, model listing ignoring suffix, and beta paths ignoring suffix. cargo fmt --check and cargo test -p codewhale-tui api_url_with_suffix pass.

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.

Configurable path suffix for OpenAI-compat endpoints (closes #1874)

2 participants