mirror of
https://github.com/teamchong/pxpipe.git
synced 2026-07-22 02:02:51 +02:00
fix(applicability): match all proxy Anthropic message routes
shouldTransformAnthropicMessages() gated the path with
!path.endsWith('/v1/messages'), but createProxy() routes three exact paths:
/v1/messages, /anthropic/v1/messages, and /anthropic/messages. So an SDK/host
using the public helper to pre-gate got unsupported_path on /anthropic/messages
— a request the proxy actually transforms — while endsWith would also have
wrongly accepted an unrelated /foo/v1/messages.
- lift isAnthropicMessagesPath() into applicability.ts (proxy.ts already imports
from it; no cycle) as the single source of truth
- proxy.ts imports it instead of its own copy
- shouldTransformAnthropicMessages() uses it, so helper and router agree
- public-api tests cover /anthropic/v1/messages and /anthropic/messages
(eligible) plus /v1/messages/count_tokens (still unsupported_path)
This commit is contained in:
@@ -90,13 +90,25 @@ export function isPxpipeSupportedGptModel(model: string | null | undefined): boo
|
||||
return isAllowed(model);
|
||||
}
|
||||
|
||||
/** Canonical set of Anthropic Messages routes pxpipe transforms. Shared with
|
||||
* createProxy (src/core/proxy.ts) so the public applicability helper and the
|
||||
* proxy router can never disagree on which paths are eligible — they did: the
|
||||
* proxy accepts /anthropic/messages, but the helper's old `endsWith` check
|
||||
* rejected it (and would have wrongly accepted /foo/v1/messages). Exact matches
|
||||
* only, so /v1/messages/count_tokens stays unsupported. */
|
||||
export function isAnthropicMessagesPath(pathname: string): boolean {
|
||||
return pathname === '/v1/messages'
|
||||
|| pathname === '/anthropic/v1/messages'
|
||||
|| pathname === '/anthropic/messages';
|
||||
}
|
||||
|
||||
export function shouldTransformAnthropicMessages(
|
||||
input: PxpipeApplicabilityInput,
|
||||
): { eligible: boolean; reason: PxpipeApplicabilityReason } {
|
||||
if (input.method !== undefined && input.method !== null && input.method.toUpperCase() !== 'POST') {
|
||||
return { eligible: false, reason: 'unsupported_method' };
|
||||
}
|
||||
if (input.path !== undefined && input.path !== null && !input.path.endsWith('/v1/messages')) {
|
||||
if (input.path !== undefined && input.path !== null && !isAnthropicMessagesPath(input.path)) {
|
||||
return { eligible: false, reason: 'unsupported_path' };
|
||||
}
|
||||
if (input.bodyBytes !== undefined && input.bodyBytes !== null && input.bodyBytes <= 0) {
|
||||
|
||||
+1
-7
@@ -5,7 +5,7 @@
|
||||
|
||||
import { transformRequest, type TransformOptions, type TransformInfo } from './transform.js';
|
||||
import { transformOpenAIChatCompletions, transformOpenAIResponses } from './openai.js';
|
||||
import { isPxpipeSupportedGptModel, isPxpipeSupportedModel } from './applicability.js';
|
||||
import { isAnthropicMessagesPath, isPxpipeSupportedGptModel, isPxpipeSupportedModel } from './applicability.js';
|
||||
import {
|
||||
buildBaselineCountTokensBody,
|
||||
buildCacheablePrefixCountTokensBody,
|
||||
@@ -508,12 +508,6 @@ function isProviderPrefixedPath(pathname: string): boolean {
|
||||
return PASSTHROUGH_PREFIXES.some((prefix) => pathname.startsWith(prefix));
|
||||
}
|
||||
|
||||
function isAnthropicMessagesPath(pathname: string): boolean {
|
||||
return pathname === '/v1/messages'
|
||||
|| pathname === '/anthropic/v1/messages'
|
||||
|| pathname === '/anthropic/messages';
|
||||
}
|
||||
|
||||
function isOpenAIChatPath(pathname: string): boolean {
|
||||
return pathname === '/v1/chat/completions' || pathname === '/openai/v1/chat/completions';
|
||||
}
|
||||
|
||||
@@ -143,6 +143,16 @@ describe('public library API', () => {
|
||||
path: '/v1/messages',
|
||||
bodyBytes: 10,
|
||||
}).reason).toBe('unsupported_method');
|
||||
// Provider-prefixed routes createProxy() also transforms must be eligible
|
||||
// here too — the old endsWith('/v1/messages') check rejected /anthropic/messages.
|
||||
for (const path of ['/anthropic/v1/messages', '/anthropic/messages']) {
|
||||
expect(shouldTransformAnthropicMessages({
|
||||
model: 'claude-fable-5',
|
||||
method: 'POST',
|
||||
path,
|
||||
bodyBytes: 10,
|
||||
})).toEqual({ eligible: true, reason: 'eligible' });
|
||||
}
|
||||
expect(shouldTransformAnthropicMessages({
|
||||
model: 'claude-fable-5',
|
||||
method: 'POST',
|
||||
|
||||
Reference in New Issue
Block a user