fix(claude-cli): keep upstream path as /v1/messages for ClaudeCli

target_endpoint_for_provider was rewriting the upstream path to
/v1/chat/completions for any provider that wasn't Anthropic/Vercel,
which made Plano POST /v1/chat/completions to the brightstaff bridge.
The bridge only accepts POST /v1/messages, so it returned a plain
"not found" 404 to the client.

Treat ClaudeCli the same as Anthropic for path selection (and force
/v1/messages even when the client framed the request as OpenAI Chat
Completions or Responses, since the bridge always speaks Anthropic
Messages on the wire).
This commit is contained in:
Spherrrical 2026-05-04 13:09:17 -07:00
parent 9fdfeb7cbf
commit fc0ccfb416

View file

@ -175,7 +175,10 @@ impl SupportedAPIsFromClient {
match self {
SupportedAPIsFromClient::AnthropicMessagesAPI(AnthropicApi::Messages) => {
match provider_id {
ProviderId::Anthropic | ProviderId::Vercel => {
// ClaudeCli speaks Anthropic Messages on the wire (the
// brightstaff bridge only accepts `POST /v1/messages`),
// so keep the path as-is just like the real Anthropic.
ProviderId::Anthropic | ProviderId::Vercel | ProviderId::ClaudeCli => {
build_endpoint("/v1", "/messages")
}
ProviderId::AmazonBedrock => {
@ -198,11 +201,18 @@ impl SupportedAPIsFromClient {
| ProviderId::XAI
| ProviderId::ChatGPT
| ProviderId::Vercel => route_by_provider("/responses"),
// ClaudeCli: bridge only accepts Anthropic Messages.
ProviderId::ClaudeCli => build_endpoint("/v1", "/messages"),
// All other providers: translate to /chat/completions
_ => route_by_provider("/chat/completions"),
}
}
SupportedAPIsFromClient::OpenAIChatCompletions(_) => {
// ClaudeCli: bridge only accepts Anthropic Messages, regardless
// of how the client framed the request.
if matches!(provider_id, ProviderId::ClaudeCli) {
return build_endpoint("/v1", "/messages");
}
// For Chat Completions API, use the standard chat/completions path
route_by_provider("/chat/completions")
}
@ -633,6 +643,35 @@ mod tests {
);
}
/// The brightstaff `claude-cli` bridge only accepts `POST /v1/messages`.
/// Make sure that no matter how a client framed the request, the upstream
/// path stays `/v1/messages`.
#[test]
fn test_claude_cli_endpoint_always_v1_messages() {
for client_api in [
SupportedAPIsFromClient::AnthropicMessagesAPI(AnthropicApi::Messages),
SupportedAPIsFromClient::OpenAIChatCompletions(OpenAIApi::ChatCompletions),
SupportedAPIsFromClient::OpenAIResponsesAPI(OpenAIApi::Responses),
] {
for request_path in ["/v1/messages", "/v1/chat/completions", "/v1/responses"] {
assert_eq!(
client_api.target_endpoint_for_provider(
&ProviderId::ClaudeCli,
request_path,
"claude-cli/sonnet",
false,
None,
false
),
"/v1/messages",
"client_api={:?} request_path={} should map to /v1/messages",
client_api,
request_path,
);
}
}
}
#[test]
fn test_non_v1_request_paths() {
let api = SupportedAPIsFromClient::OpenAIChatCompletions(OpenAIApi::ChatCompletions);