From 04d025a3a7e7b41595fcea5a638f1f8f82bb5eec Mon Sep 17 00:00:00 2001 From: BP602 <3460479+BP602@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:06:54 +0100 Subject: [PATCH] fix: queue concurrent streaming requests to avoid ~60s delay Concurrent streaming requests to Claude Agent SDK would wait ~60s after the first request completes before starting the second. This causes significant delays when OpenCode makes concurrent requests (e.g., one for the actual response and one for title generation). Fix: Add p-queue to serialize requests (concurrency: 1), which eliminates the 60s delay entirely. Requests now execute sequentially. Before: ~67s total for 2 concurrent requests (4s + 60s delay + 3s) After: ~12s total for 2 concurrent requests (4s + 8s sequential execution) --- bun.lock | 7 +++++++ package.json | 3 ++- src/proxy/server.ts | 8 ++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index ec32839..c9f96e6 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,7 @@ "@anthropic-ai/claude-agent-sdk": "^0.2.25", "glob": "^13.0.0", "hono": "^4.11.4", + "p-queue": "^8.0.1", }, "devDependencies": { "@types/bun": "^1.2.21", @@ -59,6 +60,8 @@ "bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="], + "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], + "glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="], "hono": ["hono@4.11.4", "", {}, "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA=="], @@ -69,6 +72,10 @@ "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "p-queue": ["p-queue@8.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ=="], + + "p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="], + "path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], diff --git a/package.json b/package.json index eab0e3a..1e576be 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.25", "glob": "^13.0.0", - "hono": "^4.11.4" + "hono": "^4.11.4", + "p-queue": "^8.0.1" }, "devDependencies": { "@types/bun": "^1.2.21", diff --git a/src/proxy/server.ts b/src/proxy/server.ts index b817a86..a45259b 100644 --- a/src/proxy/server.ts +++ b/src/proxy/server.ts @@ -1,6 +1,7 @@ import { Hono } from "hono" import { cors } from "hono/cors" import { query } from "@anthropic-ai/claude-agent-sdk" +import PQueue from "p-queue" import type { Context } from "hono" import type { ProxyConfig } from "./types" import { DEFAULT_PROXY_CONFIG } from "./types" @@ -28,6 +29,9 @@ const ALLOWED_MCP_TOOLS = [ `mcp__${MCP_SERVER_NAME}__grep` ] +// Queue to serialize Claude Agent SDK queries and avoid ~60s delay on concurrent requests +const requestQueue = new PQueue({ concurrency: 1 }) + function resolveClaudeExecutable(): string { // 1. Try the SDK's bundled cli.js (same dir as this module's SDK) try { @@ -256,8 +260,8 @@ export function createProxyServer(config: Partial = {}) { } } - app.post("/v1/messages", handleMessages) - app.post("/messages", handleMessages) + app.post("/v1/messages", (c) => requestQueue.add(() => handleMessages(c))) + app.post("/messages", (c) => requestQueue.add(() => handleMessages(c))) return { app, config: finalConfig } }