From fb48c080a1cee9717b1779d8618cabc18042667b Mon Sep 17 00:00:00 2001 From: redshift <213178690+1ftredsh@users.noreply.github.com> Date: Fri, 27 Mar 2026 22:48:27 +0000 Subject: [PATCH] fix(daemon): forward original request path and headers to upstream Before this change, routeRequests() was called without the incoming request path, causing the SDK to default to /v1/chat/completions even when Claude Code was calling /v1/messages. This protocol mismatch made Claude Code fail with: Stream completed without receiving message_start event The fix threads the original url.pathname and sanitized request headers (hop-by-hop headers stripped) through resolveRouteRequestContext() and into client.routeRequest(), so: - /v1/messages is forwarded upstream as /v1/messages (not rewritten) - Anthropic-specific headers like anthropic-version are forwarded upstream - Streaming responses use the correct SSE event format (message_start, etc.) This makes the routstr daemon on port 8009 behave identically to a transparent pass-through proxy from Claude Code's perspective. --- scripts/routstr-daemon.ts | 36 ++++++++++++++++++++++++++++++++++++ sdk/routeRequests.ts | 11 +++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/scripts/routstr-daemon.ts b/scripts/routstr-daemon.ts index 45e9ca3..3b8bc11 100644 --- a/scripts/routstr-daemon.ts +++ b/scripts/routstr-daemon.ts @@ -206,6 +206,39 @@ async function saveRequestBody( return filename; } +function toForwardHeaders( + headers: IncomingMessage["headers"] +): Record { + const forwarded: Record = {}; + const hopByHop = new Set([ + "host", + "connection", + "content-length", + "transfer-encoding", + "keep-alive", + "proxy-authenticate", + "proxy-authorization", + "te", + "trailer", + "upgrade", + ]); + + for (const [key, value] of Object.entries(headers)) { + if (!key || hopByHop.has(key.toLowerCase())) { + continue; + } + if (Array.isArray(value)) { + forwarded[key] = value.join(", "); + continue; + } + if (typeof value === "string") { + forwarded[key] = value; + } + } + + return forwarded; +} + async function main(): Promise { const { port, provider, mode } = parseArgs(process.argv); @@ -355,11 +388,14 @@ async function main(): Promise { (req.headers["x-routstr-provider"] as string | undefined) || provider || undefined; + const forwardedHeaders = toForwardHeaders(req.headers); try { const response = await routeRequests({ modelId, requestBody, + path: url.pathname, + headers: forwardedHeaders, forcedProvider, debugLevel: "DEBUG", mode, diff --git a/sdk/routeRequests.ts b/sdk/routeRequests.ts index 3754356..43f9fd6 100644 --- a/sdk/routeRequests.ts +++ b/sdk/routeRequests.ts @@ -30,6 +30,8 @@ export interface RouteRequestOptions { requestBody: unknown; /** Optional: API path (defaults to /v1/chat/completions) */ path?: string; + /** Optional: request headers to forward upstream */ + headers?: Record; /** Optional: force a specific provider base URL */ forcedProvider?: string; /** Wallet adapter for Cashu operations */ @@ -86,6 +88,7 @@ async function resolveRouteRequestContext(options: RouteRequestOptions): Promise baseUrl: string; mintUrl: string; path: string; + headers: Record; modelId: string; proxiedBody: Record; }> { @@ -93,6 +96,7 @@ async function resolveRouteRequestContext(options: RouteRequestOptions): Promise modelId, requestBody, path = "/v1/chat/completions", + headers = {}, forcedProvider, walletAdapter, storageAdapter, @@ -216,6 +220,7 @@ async function resolveRouteRequestContext(options: RouteRequestOptions): Promise baseUrl, mintUrl, path, + headers, modelId, proxiedBody, }; @@ -227,7 +232,7 @@ async function resolveRouteRequestContext(options: RouteRequestOptions): Promise export async function routeRequests( options: RouteRequestOptions ): Promise { - const { client, baseUrl, mintUrl, path, modelId, proxiedBody } = + const { client, baseUrl, mintUrl, path, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options); try { @@ -235,6 +240,7 @@ export async function routeRequests( path, method: "POST", body: proxiedBody, + headers, baseUrl, mintUrl, modelId, @@ -262,7 +268,7 @@ export async function routeRequestsToNodeResponse( options: RouteRequestToNodeResponseOptions ): Promise { const { res } = options; - const { client, baseUrl, mintUrl, path, modelId, proxiedBody } = + const { client, baseUrl, mintUrl, path, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options); try { @@ -270,6 +276,7 @@ export async function routeRequestsToNodeResponse( path, method: "POST", body: proxiedBody, + headers, baseUrl, mintUrl, modelId,