mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-10-05 12:28:22 +00:00
fix: allow /v1/messages/count_tokens through the proxy allowlist
The exact-match endpoint allowlist in the proxy omitted `messages/count_tokens`, so every Claude Code / Anthropic SDK request was 404'd with `Path '/v1/messages/count_tokens' not found` before it ever reached the (fully supported) forwarding path. Add the endpoint to `_ALLOWED_ENDPOINTS` and a regression test that pins it as always reachable on POST. Regression history: -933ba105"add missing messages endpoint" (2026-04-01): added messages/count_tokens handling to the forwarding layer. -164ed775"support /message/count_tokens endpoint" (2026-05-09): added the local count_tokens handler. -96661384"update not found proxy" (2026-05-14): only GET was gated, so POST count_tokens passed implicitly. -0217002e"Gate proxy forwarding behind a segment-anchored API path allowlist" (2026-08-23): POST gated, but the "v1/" prefix still carried count_tokens. -5af04364"Restrict proxy forwarding to an exact method/path allowlist" (2026-08-24): switched to an exact table, added "messages" but omitted "messages/count_tokens". This is where the endpoint got locked out.
This commit is contained in:
@@ -277,6 +277,10 @@ _ALLOWED_ENDPOINTS: dict[str, frozenset[str]] = {
|
||||
"completions": frozenset({"POST"}),
|
||||
"responses": frozenset({"POST"}),
|
||||
"messages": frozenset({"POST"}),
|
||||
# Anthropic token-counting subroute; the proxy's allowlist is exact, so the
|
||||
# "messages" entry above does not carry it. Clients (Claude Code, the
|
||||
# Anthropic SDKs) call it before every request.
|
||||
"messages/count_tokens": frozenset({"POST"}),
|
||||
"embeddings": frozenset({"POST"}),
|
||||
# TypeSafe System One decision endpoint: POST {state, model, questions}
|
||||
# -> {answers, usage}. Non-streaming, JSON in/out; billed from the
|
||||
|
||||
@@ -51,6 +51,8 @@ def test_ambiguous_paths_are_rejected(path: str) -> None:
|
||||
"v1/chat/completions",
|
||||
"chat/completions",
|
||||
"v1/responses",
|
||||
"v1/messages",
|
||||
"v1/messages/count_tokens",
|
||||
"v1/embeddings",
|
||||
"models",
|
||||
"v1/models/gpt-4",
|
||||
@@ -138,6 +140,7 @@ def test_known_prefix_does_not_carry_an_unknown_endpoint(path: str) -> None:
|
||||
("completions", "POST"),
|
||||
("v1/responses", "POST"),
|
||||
("v1/messages", "POST"),
|
||||
("v1/messages/count_tokens", "POST"),
|
||||
("v1/embeddings", "POST"),
|
||||
("models", "GET"),
|
||||
("attestation", "GET"),
|
||||
@@ -163,6 +166,24 @@ def test_method_must_match_the_endpoint(path: str, method: str) -> None:
|
||||
assert _forwarding_allowed(path, method) is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path",
|
||||
[
|
||||
"messages/count_tokens",
|
||||
"v1/messages/count_tokens",
|
||||
"v1/messages/count_tokens/",
|
||||
],
|
||||
)
|
||||
def test_count_tokens_endpoint_stays_allowed(path: str) -> None:
|
||||
# Regression guard: /v1/messages/count_tokens is supported end-to-end
|
||||
# (local handler when the upstream lacks native Anthropic support, plain
|
||||
# forward otherwise), but the exact-match allowlist once omitted it, so
|
||||
# Claude Code and the Anthropic SDKs were 404'd on every request. It must
|
||||
# always be reachable, on POST only.
|
||||
assert _forwarding_allowed(path, "POST") is True
|
||||
assert _forwarding_allowed(path, "GET") is False
|
||||
|
||||
|
||||
def test_operator_additions_are_parsed_per_endpoint() -> None:
|
||||
parsed = _parse_extra_allowed_endpoints("POST:v1/rerank, GET:batches ,post:audio/x")
|
||||
assert parsed == {
|
||||
|
||||
Reference in New Issue
Block a user