{"_id":"@dianshuv/copilot-api-router","_rev":"13-2af84c4af3f8a9162b17c94f59a96d61","name":"@dianshuv/copilot-api-router","dist-tags":{"latest":"0.7.0"},"versions":{"0.1.0":{"name":"@dianshuv/copilot-api-router","version":"0.1.0","_id":"@dianshuv/copilot-api-router@0.1.0","maintainers":[{"name":"dianshuv","email":"feiyuegeng@gmail.com"}],"bin":{"copilot-api-router":"dist/main.mjs"},"dist":{"shasum":"3c59e50f1e5fae251b52f193f56207683141d527","tarball":"https://registry.npmjs.org/@dianshuv/copilot-api-router/-/copilot-api-router-0.1.0.tgz","fileCount":3,"integrity":"sha512-JqKN9BhcPT04nOqO8NVjESeIBjWpfVUu2v5ke/CB6bXDNE0SZz/ch/dBGGBJsLJJUV5PRwRmCn0fyLU+0xk7MA==","signatures":[{"sig":"MEQCIDGuEkdrCFsTr7V52+HeV70cW40tKgSMi8XbVO6CTG0DAiB9QnZN3uJB9LVgklqwVVyUS6e3yhff5I5iw6uRlRrr0A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":89295},"type":"module","gitHead":"33f79975f8d8d31cef24d103abe7783394c829bb","scripts":{"dev":"bun run --watch ./src/main.ts","test":"bun test tests/unit/","build":"npx tsdown","start":"bun run ./src/main.ts","release":"npm publish --access public --//registry.npmjs.org/:_authToken=$NPM_TOKEN","test:all":"bun test tests/unit/ && bun test tests/integration/ --max-concurrency=1","typecheck":"tsc","test:integration":"bun test tests/integration/ --max-concurrency=1"},"_npmUser":{"name":"dianshuv","email":"feiyuegeng@gmail.com"},"_npmVersion":"11.17.0","description":"Local aggregating router fronting N independent copilot-api backends (one GitHub account each): least-outstanding load spread + prefix affinity, single Anthropic/OpenAI-compatible interface bound to loopback.","directories":{},"_nodeVersion":"26.4.0","_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.1","@types/bun":"^1.3.7","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/copilot-api-router_0.1.0_1782819186649_0.917881990922581","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@dianshuv/copilot-api-router","version":"0.1.1","_id":"@dianshuv/copilot-api-router@0.1.1","maintainers":[{"name":"dianshuv","email":"feiyuegeng@gmail.com"}],"bin":{"copilot-api-router":"dist/main.mjs"},"dist":{"shasum":"7859880f37cb9332c7e29eefe1c945e28c92cd56","tarball":"https://registry.npmjs.org/@dianshuv/copilot-api-router/-/copilot-api-router-0.1.1.tgz","fileCount":3,"integrity":"sha512-agtAxh1KVCGTLEzBd5oNkwlMydjF5BomT8NqwT1dBWi9VVOoL8zS4tHG3RHJkZ2mGaBMo19x0dsQm6fr8plZew==","signatures":[{"sig":"MEYCIQCEgq9gD/2t5utdNA1jp+EI0KzUC0vgp9CxB1K/DUJW9AIhAMSuljKqwKBGV1ysU6SD5negThJ4JTJRUd6lngGVGGwb","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":93402},"type":"module","gitHead":"b1309f9d91acce10f6908b0b9098829fb2ada7b2","scripts":{"dev":"bun run --watch ./src/main.ts","test":"bun test tests/unit/","build":"npx tsdown","start":"bun run ./src/main.ts","release":"npm publish --access public --//registry.npmjs.org/:_authToken=$NPM_TOKEN","test:all":"bun test tests/unit/ && bun test tests/integration/ --max-concurrency=1","typecheck":"tsc","test:integration":"bun test tests/integration/ --max-concurrency=1"},"_npmUser":{"name":"dianshuv","email":"feiyuegeng@gmail.com"},"_npmVersion":"11.17.0","description":"Local aggregating router fronting N independent copilot-api backends (one GitHub account each): least-outstanding load spread + prefix affinity, single Anthropic/OpenAI-compatible interface bound to loopback.","directories":{},"_nodeVersion":"26.4.0","_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.1","@types/bun":"^1.3.7","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/copilot-api-router_0.1.1_1782942943135_0.09732247988286935","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@dianshuv/copilot-api-router","version":"0.1.2","_id":"@dianshuv/copilot-api-router@0.1.2","maintainers":[{"name":"dianshuv","email":"feiyuegeng@gmail.com"}],"bin":{"copilot-api-router":"dist/main.mjs"},"dist":{"shasum":"736adcd4fa4a8ad7fd1c112fff65b25a80e8343d","tarball":"https://registry.npmjs.org/@dianshuv/copilot-api-router/-/copilot-api-router-0.1.2.tgz","fileCount":3,"integrity":"sha512-O6wodpmL12IBxygBP4LXsoO5BV6PDf1VDIx9G103N3RL9jj6aaRPA7GySHE3N+Zp+lHW0pTO2ABxYpUpewLung==","signatures":[{"sig":"MEUCIQCR5IpArb3/9QFhNFIkEXZPA14J4HsMIbuEeFJVU+gSYwIgBDu+YHTQhOqsn0EX2Auhn9ThWH7QDuee3RnWufAq0cY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":88298},"type":"module","gitHead":"ec782caa415b9ebd07b14ea55d70b0109faf0059","scripts":{"dev":"bun run --watch ./src/main.ts","test":"bun test tests/unit/","build":"npx tsdown","start":"bun run ./src/main.ts","release":"npm publish --access public --//registry.npmjs.org/:_authToken=$NPM_TOKEN","test:all":"bun test tests/unit/ && bun test tests/integration/ --max-concurrency=1","typecheck":"tsc","test:integration":"bun test tests/integration/ --max-concurrency=1"},"_npmUser":{"name":"dianshuv","email":"feiyuegeng@gmail.com"},"_npmVersion":"11.17.0","description":"Local aggregating router fronting N independent copilot-api backends (one GitHub account each): least-outstanding load spread + prefix affinity, single Anthropic/OpenAI-compatible interface bound to loopback.","directories":{},"_nodeVersion":"26.4.0","_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.1","@types/bun":"^1.3.7","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/copilot-api-router_0.1.2_1782966636785_0.4424309749768345","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@dianshuv/copilot-api-router","version":"0.1.3","_id":"@dianshuv/copilot-api-router@0.1.3","maintainers":[{"name":"dianshuv","email":"feiyuegeng@gmail.com"}],"bin":{"copilot-api-router":"dist/main.mjs"},"dist":{"shasum":"bb7104e50554c5c114100cdffffe2b368ea1fcf3","tarball":"https://registry.npmjs.org/@dianshuv/copilot-api-router/-/copilot-api-router-0.1.3.tgz","fileCount":3,"integrity":"sha512-o349oZfYUucNmwvvO1rY7V0Ypiqhn7Tk+SZlhfpik406u5nDPdwJGQlagrU/wH3Yjy44G/xW/DoUT99u2KKjsA==","signatures":[{"sig":"MEUCIQDsd5nU3IGWU0uEEWNguphqoRTRO7AcgwoRNQXTS1l7JwIgMuD7iwisI3bVhuwLrbwooiFxO2EaSxFeUx8IgUzE2ew=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":99489},"type":"module","gitHead":"29e61d6109275ebb28dc384c7bb5a96e625baa57","scripts":{"dev":"bun run --watch ./src/main.ts","test":"bun test tests/unit/","build":"npx tsdown","start":"bun run ./src/main.ts","release":"npm publish --access public --//registry.npmjs.org/:_authToken=$NPM_TOKEN","test:all":"bun test tests/unit/ && bun test tests/integration/ --max-concurrency=1","typecheck":"tsc","test:integration":"bun test tests/integration/ --max-concurrency=1"},"_npmUser":{"name":"dianshuv","email":"feiyuegeng@gmail.com"},"_npmVersion":"11.17.0","description":"Local aggregating router fronting N independent copilot-api backends (one GitHub account each): least-outstanding load spread + prefix affinity, single Anthropic/OpenAI-compatible interface bound to loopback.","directories":{},"_nodeVersion":"26.4.0","_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.1","@types/bun":"^1.3.7","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/copilot-api-router_0.1.3_1783066359721_0.946764174828638","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@dianshuv/copilot-api-router","version":"0.1.4","_id":"@dianshuv/copilot-api-router@0.1.4","maintainers":[{"name":"dianshuv","email":"feiyuegeng@gmail.com"}],"bin":{"copilot-api-router":"dist/main.mjs"},"dist":{"shasum":"f514a45c4858b3f3295b0bec3ea66447c61f9ab5","tarball":"https://registry.npmjs.org/@dianshuv/copilot-api-router/-/copilot-api-router-0.1.4.tgz","fileCount":3,"integrity":"sha512-psTrTZPNCaBNDTK7PPqCuozVPYTQejdwwGS401hovxu69mj8LquqdgQAuEOqUg0ws92cm0l0jdlWXuXBvv+L4A==","signatures":[{"sig":"MEUCIGnyguWWqfuaYuLsKRausfZjCTzOkj1661KIRsplY5LBAiEAk8NwYVeN/0NJjHVADedZOpTcgABURnzDSZXzhmawOpY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":107250},"type":"module","gitHead":"f75d780cee0c8707d35fa1bc4762ab8453dee691","scripts":{"dev":"bun run --watch ./src/main.ts","test":"bun test tests/unit/","build":"npx tsdown","start":"bun run ./src/main.ts","release":"npm publish --access public --//registry.npmjs.org/:_authToken=$NPM_TOKEN","test:all":"bun test tests/unit/ && bun test tests/integration/ --max-concurrency=1","typecheck":"tsc","test:integration":"bun test tests/integration/ --max-concurrency=1"},"_npmUser":{"name":"dianshuv","email":"feiyuegeng@gmail.com"},"_npmVersion":"11.17.0","description":"Local aggregating router fronting N independent copilot-api backends (one GitHub account each): least-outstanding load spread + prefix affinity, single Anthropic/OpenAI-compatible interface bound to loopback.","directories":{},"_nodeVersion":"26.4.0","_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.1","@types/bun":"^1.3.7","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/copilot-api-router_0.1.4_1783083125246_0.7542537373692602","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@dianshuv/copilot-api-router","version":"0.2.0","_id":"@dianshuv/copilot-api-router@0.2.0","maintainers":[{"name":"dianshuv","email":"feiyuegeng@gmail.com"}],"bin":{"copilot-api-router":"dist/main.mjs"},"dist":{"shasum":"76c09a545c390301bf38cf5a23a75a5e1691cad2","tarball":"https://registry.npmjs.org/@dianshuv/copilot-api-router/-/copilot-api-router-0.2.0.tgz","fileCount":3,"integrity":"sha512-SE6oapFV46+jSWSKEQTOTqsUBT9ovUQNwpYiuTCeL5DYAOKO0yMcpeDI94aCqy28dEI9JtqbdcbjIa2CLlCFOw==","signatures":[{"sig":"MEYCIQCq+QJcLcIiPLZL8FnPTjtqGqSiSsF7IXF7D987TbWe/wIhAI/PyQHzqtRyedhgbGJTXmWMDHjrnimHaHIbaKeHPnmI","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":73804},"type":"module","gitHead":"fff25cdf7d3a75e4cf5bd91fd108313d5a57e11d","scripts":{"dev":"bun run --watch ./src/main.ts","test":"bun test tests/unit/","build":"npx tsdown","start":"bun run ./src/main.ts","release":"npm publish --access public --//registry.npmjs.org/:_authToken=$NPM_TOKEN","test:all":"bun test tests/unit/ && bun test tests/integration/ --max-concurrency=1","typecheck":"tsc","test:integration":"bun test tests/integration/ --max-concurrency=1"},"_npmUser":{"name":"dianshuv","email":"feiyuegeng@gmail.com"},"_npmVersion":"11.17.0","description":"Local aggregating router fronting N independent copilot-api backends (one GitHub account each): least-outstanding load spread + prefix affinity, single Anthropic/OpenAI-compatible interface bound to loopback.","directories":{},"_nodeVersion":"26.4.0","_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.1","@types/bun":"^1.3.7","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/copilot-api-router_0.2.0_1783245457836_0.14668090942338896","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@dianshuv/copilot-api-router","version":"0.3.0","_id":"@dianshuv/copilot-api-router@0.3.0","maintainers":[{"name":"dianshuv","email":"feiyuegeng@gmail.com"}],"bin":{"copilot-api-router":"dist/main.mjs"},"dist":{"shasum":"de62aa84142254aa6acdc43bda8510f21654f431","tarball":"https://registry.npmjs.org/@dianshuv/copilot-api-router/-/copilot-api-router-0.3.0.tgz","fileCount":3,"integrity":"sha512-vC9PSuydfiFs4wd11S/SLo4iHnN6spN7TkSZOr0Rz+acw0X2P5EvSqKEZrhoLw0IkS1V+QWP3Kj7V1MnWVCoVQ==","signatures":[{"sig":"MEYCIQDqgR5dOIJbc618nTPkJHDU3un/eCX2TtUlIqgVV/fCuAIhANwFtC3tfkC2gTshPefP0FQSuJYaWMWupruast2D/fBp","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":79436},"type":"module","gitHead":"4855a9097612c5bd1fa218a61bd09a5220899285","scripts":{"dev":"bun run --watch ./src/main.ts","test":"bun test tests/unit/","build":"npx tsdown","start":"bun run ./src/main.ts","release":"npm publish --access public --//registry.npmjs.org/:_authToken=$NPM_TOKEN","test:all":"bun test tests/unit/ && bun test tests/integration/ --max-concurrency=1","typecheck":"tsc","test:integration":"bun test tests/integration/ --max-concurrency=1"},"_npmUser":{"name":"dianshuv","email":"feiyuegeng@gmail.com"},"_npmVersion":"11.17.0","description":"Local aggregating router fronting N independent copilot-api backends (one GitHub account each): least-outstanding load spread + prefix affinity, single Anthropic/OpenAI-compatible interface bound to loopback.","directories":{},"_nodeVersion":"26.4.0","_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.1","@types/bun":"^1.3.7","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/copilot-api-router_0.3.0_1783315081759_0.4551281012257653","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@dianshuv/copilot-api-router","version":"0.4.0","_id":"@dianshuv/copilot-api-router@0.4.0","maintainers":[{"name":"dianshuv","email":"feiyuegeng@gmail.com"}],"bin":{"copilot-api-router":"dist/main.mjs"},"dist":{"shasum":"a8b89b8b3025b35758ec1804f05373f0b42e837d","tarball":"https://registry.npmjs.org/@dianshuv/copilot-api-router/-/copilot-api-router-0.4.0.tgz","fileCount":3,"integrity":"sha512-bU6oYjJsbJlyVRro3wa9TlcU5upii6vSwqVzoGS/4SXyp/PuZnzRV99WOqwZqr8aeDq5wutNke3GEe3KD9yYRQ==","signatures":[{"sig":"MEUCIQC5cOXpX3BRmzklKbCnt40FiUahfhPv3c35w66ocEPY5AIgFhD3he3Spv5306Z6IksFnzAAsai37vdjpe9QhXcG+44=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":84009},"type":"module","gitHead":"68878e2602965dbb9cafc4494a06b82272a5b124","scripts":{"dev":"bun run --watch ./src/main.ts","test":"bun test tests/unit/","build":"npx tsdown","start":"bun run ./src/main.ts","release":"npm publish --access public --//registry.npmjs.org/:_authToken=$NPM_TOKEN","test:all":"bun test tests/unit/ && bun test tests/integration/ --max-concurrency=1","typecheck":"tsc","test:integration":"bun test tests/integration/ --max-concurrency=1"},"_npmUser":{"name":"dianshuv","email":"feiyuegeng@gmail.com"},"_npmVersion":"11.17.0","description":"Local aggregating router fronting N independent copilot-api backends (one GitHub account each): least-outstanding load spread + prefix affinity, single Anthropic/OpenAI-compatible interface bound to loopback.","directories":{},"_nodeVersion":"26.4.0","_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.1","@types/bun":"^1.3.7","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/copilot-api-router_0.4.0_1783422320853_0.08109982362431523","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@dianshuv/copilot-api-router","version":"0.5.0","_id":"@dianshuv/copilot-api-router@0.5.0","maintainers":[{"name":"dianshuv","email":"feiyuegeng@gmail.com"}],"bin":{"copilot-api-router":"dist/main.mjs"},"dist":{"shasum":"ae85869a389fdbfccbf7f6e819750efaf02e7405","tarball":"https://registry.npmjs.org/@dianshuv/copilot-api-router/-/copilot-api-router-0.5.0.tgz","fileCount":3,"integrity":"sha512-21Yb731XAbad/pPUONt92ejJDSWkOyXVueRJ1e1nZbsZY5SpjuS+SM3B/ocAZiSC7HRCKyh471iGyV7ijo4IKw==","signatures":[{"sig":"MEQCIF/jg2HG5O9UFOIhA0IoEYDaab4nUiG+afzt4Y9qiDvrAiBhxN58xepfFGK/phlv9aMkIPeziqNrDCGLA6sY06f0ZA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":152747},"type":"module","gitHead":"9ba4e1123c5a1af70176c7de3ccd8c13f56035ae","scripts":{"dev":"bun run --watch ./src/main.ts","test":"bun test tests/unit/","build":"npx tsdown","start":"bun run ./src/main.ts","release":"npm publish --access public --//registry.npmjs.org/:_authToken=$NPM_TOKEN","test:all":"bun test tests/unit/ && bun test tests/integration/ --max-concurrency=1","typecheck":"tsc","test:integration":"bun test tests/integration/ --max-concurrency=1"},"_npmUser":{"name":"dianshuv","email":"feiyuegeng@gmail.com"},"_npmVersion":"11.17.0","description":"Local aggregating router fronting N independent copilot-api backends (one GitHub account each): least-outstanding load spread + prefix affinity, single Anthropic/OpenAI-compatible interface bound to loopback.","directories":{},"_nodeVersion":"26.5.0","_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.1","@types/bun":"^1.3.7","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/copilot-api-router_0.5.0_1783657093200_0.890612824622889","host":"s3://npm-registry-packages-npm-production"}},"0.5.1":{"name":"@dianshuv/copilot-api-router","version":"0.5.1","_id":"@dianshuv/copilot-api-router@0.5.1","maintainers":[{"name":"dianshuv","email":"feiyuegeng@gmail.com"}],"bin":{"copilot-api-router":"dist/main.mjs"},"dist":{"shasum":"47b251d5beb5796fc4c152d58aec891af47efef3","tarball":"https://registry.npmjs.org/@dianshuv/copilot-api-router/-/copilot-api-router-0.5.1.tgz","fileCount":3,"integrity":"sha512-kWbvQDCxI7D43ymRG8e9nmJPXF4jHMREWtaRp0w4XWYAml+ylZXgw79KZBROaqlPGyOO95Y0k2ui0RtCQxKtsw==","signatures":[{"sig":"MEQCIDpUdUhd7oSJBlWpuTmrQkWchMMNX1ysHcgcq+tlWvV/AiBCRDJuADTOWqNnG3qhxvcYuMtuoO6NU6zyDxLmWR2Jow==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":84531},"type":"module","gitHead":"9422b2101d62b980c657610b6416ee0367fe3a91","scripts":{"dev":"bun run --watch ./src/main.ts","test":"bun test tests/unit/","build":"npx tsdown","start":"bun run ./src/main.ts","release":"npm publish --access public --//registry.npmjs.org/:_authToken=$NPM_TOKEN","test:all":"bun test tests/unit/ && bun test tests/integration/ --max-concurrency=1","typecheck":"tsc","test:integration":"bun test tests/integration/ --max-concurrency=1"},"_npmUser":{"name":"dianshuv","email":"feiyuegeng@gmail.com"},"_npmVersion":"11.17.0","description":"Local aggregating router fronting N independent copilot-api backends (one GitHub account each): least-outstanding load spread + prefix affinity, single Anthropic/OpenAI-compatible interface bound to loopback.","directories":{},"_nodeVersion":"26.5.0","_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.1","@types/bun":"^1.3.7","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/copilot-api-router_0.5.1_1783669388513_0.7306576206866162","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@dianshuv/copilot-api-router","version":"0.6.0","_id":"@dianshuv/copilot-api-router@0.6.0","maintainers":[{"name":"dianshuv","email":"feiyuegeng@gmail.com"}],"bin":{"copilot-api-router":"dist/main.mjs"},"dist":{"shasum":"b2da9fd7109c0efcaf8fcdb83c5449ff1adafe02","tarball":"https://registry.npmjs.org/@dianshuv/copilot-api-router/-/copilot-api-router-0.6.0.tgz","fileCount":3,"integrity":"sha512-kmxXMTe08JrpTTfI0yHK0GZsSLz+GGpBqYpct6sAnOCDbY1qFSgLkPWFVCBHsuK9Qv7ntaW0YA4UjE2mM3xk2A==","signatures":[{"sig":"MEYCIQDRT4zg6bWB+2sr2JeLqTJuYq72Eq7JkuZzXCXhDE+WHgIhAPKfLMT0t31vA3lfMPkAVgKeZIDk7FQA0y0E5QKbO+EG","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":154693},"type":"module","gitHead":"6ff58efeb7b3acb36bc8a02ebbf10d7396ca7ee1","scripts":{"dev":"bun run --watch ./src/main.ts","test":"bun test tests/unit/","build":"npx tsdown","start":"bun run ./src/main.ts","release":"npm publish --access public --//registry.npmjs.org/:_authToken=$NPM_TOKEN","test:all":"bun test tests/unit/ && bun test tests/integration/ --max-concurrency=1","typecheck":"tsc","test:integration":"bun test tests/integration/ --max-concurrency=1"},"_npmUser":{"name":"dianshuv","email":"feiyuegeng@gmail.com"},"_npmVersion":"11.17.0","description":"Local aggregating router fronting N independent copilot-api backends (one GitHub account each): least-outstanding load spread + prefix affinity, single Anthropic/OpenAI-compatible interface bound to loopback.","directories":{},"_nodeVersion":"26.5.0","_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.1","@types/bun":"^1.3.7","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/copilot-api-router_0.6.0_1784174967845_0.41509016324609216","host":"s3://npm-registry-packages-npm-production"}},"0.6.1":{"name":"@dianshuv/copilot-api-router","version":"0.6.1","_id":"@dianshuv/copilot-api-router@0.6.1","maintainers":[{"name":"dianshuv","email":"feiyuegeng@gmail.com"}],"bin":{"copilot-api-router":"dist/main.mjs"},"dist":{"shasum":"0add600b1e24979d6534033948b4686e47b95e15","tarball":"https://registry.npmjs.org/@dianshuv/copilot-api-router/-/copilot-api-router-0.6.1.tgz","fileCount":3,"integrity":"sha512-XwCiRUoev4sx+TZDMPPh97nX+Xx4KmwZvXKplA3lmi8vf9tiBcfFHFHix1XXjgfESeHk/HxlPOYBI0ToF/h0fw==","signatures":[{"sig":"MEUCIFgw/Dog4OYX5yPXVJUkdacYporaXgvch7qZ2pkjoJghAiEA9vQGA+i9RrhmJUge9wHobhXtrkemSwOZJHOV1E0Rn0c=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":154693},"type":"module","gitHead":"d95a15084964afd70451a98f8b88c2b4fc4bfc0e","scripts":{"dev":"bun run --watch ./src/main.ts","test":"bun test tests/unit/","build":"npx tsdown","start":"bun run ./src/main.ts","release":"npm publish --access public --//registry.npmjs.org/:_authToken=$NPM_TOKEN","test:all":"bun test tests/unit/ && bun test tests/integration/ --max-concurrency=1","typecheck":"tsc","test:integration":"bun test tests/integration/ --max-concurrency=1"},"_npmUser":{"name":"dianshuv","email":"feiyuegeng@gmail.com"},"_npmVersion":"11.17.0","description":"Local aggregating router fronting N independent copilot-api backends (one GitHub account each): least-outstanding load spread + prefix affinity, single Anthropic/OpenAI-compatible interface bound to loopback.","directories":{},"_nodeVersion":"26.5.0","_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.20.1","@types/bun":"^1.3.7","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/copilot-api-router_0.6.1_1784251804180_0.4802930571527366","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"@dianshuv/copilot-api-router","version":"0.7.0","description":"Local aggregating router fronting N independent copilot-api backends: deterministic session/subagent affinity with least-outstanding fallback spread.","type":"module","bin":{"copilot-api-router":"dist/main.mjs"},"scripts":{"build":"npx tsdown","start":"bun run ./src/main.ts","dev":"bun run --watch ./src/main.ts","test":"bun test tests/unit/","test:integration":"bun test tests/integration/ --max-concurrency=1","test:all":"bun test tests/unit/ && bun test tests/integration/ --max-concurrency=1","typecheck":"tsc","release":"npm publish --access public --//registry.npmjs.org/:_authToken=$NPM_TOKEN"},"devDependencies":{"@types/bun":"^1.3.7","tsdown":"^0.20.1","typescript":"^5.9.3"},"gitHead":"92d8918920c4bb68ce4bc22f28c91f94cc48830b","_id":"@dianshuv/copilot-api-router@0.7.0","_nodeVersion":"26.5.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-TNrZwJ0ijsHTDOTG7fCs+jzKOCED7K4oEPhhrnlNNBh3qdbDflxWa/CyDVa+pg9AGF4ePtU8lhTvg/YSB3Dxdg==","shasum":"7e3e59b4e1a3fa1134f340b6d3be6e01d214e161","tarball":"https://registry.npmjs.org/@dianshuv/copilot-api-router/-/copilot-api-router-0.7.0.tgz","fileCount":3,"unpackedSize":159679,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIE1oqiu2kvmg0Iv9Gl1f+WceyZLRKfG0exGTL7LwgR7nAiEA7k4a7UK635lXe11jTjS5oOmofvn+E0QjFUU1WZyr+qM="}]},"_npmUser":{"name":"dianshuv","email":"feiyuegeng@gmail.com"},"directories":{},"maintainers":[{"name":"dianshuv","email":"feiyuegeng@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/copilot-api-router_0.7.0_1785394998933_0.2987688140361642"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-30T11:33:06.488Z","modified":"2026-07-30T07:03:19.248Z","0.1.0":"2026-06-30T11:33:07.077Z","0.1.1":"2026-07-01T21:55:43.293Z","0.1.2":"2026-07-02T04:30:36.923Z","0.1.3":"2026-07-03T08:12:39.864Z","0.1.4":"2026-07-03T12:52:05.369Z","0.2.0":"2026-07-05T09:57:37.959Z","0.3.0":"2026-07-06T05:18:01.891Z","0.4.0":"2026-07-07T11:05:21.004Z","0.5.0":"2026-07-10T04:18:13.332Z","0.5.1":"2026-07-10T07:43:08.638Z","0.6.0":"2026-07-16T04:09:27.979Z","0.6.1":"2026-07-17T01:30:04.311Z","0.7.0":"2026-07-30T07:03:19.075Z"},"description":"Local aggregating router fronting N independent copilot-api backends: deterministic session/subagent affinity with least-outstanding fallback spread.","maintainers":[{"name":"dianshuv","email":"feiyuegeng@gmail.com"}],"readme":"# copilot-api-router\n\nA local aggregating **router** that fronts N independent\n[`copilot-api`](https://github.com/) backends — each bound to its own GitHub\naccount (concurrency quota + prompt cache) on its own VM — behind a single\nAnthropic/OpenAI-compatible interface.\n\nThe router keeps each logical parent/subagent session on one backend with\ndeterministic Rendezvous affinity, while completion requests without a session\nsignal still spread by least-outstanding in-flight count. It binds **loopback\nonly** and has **no inbound auth** (single-user host assumption); it presents\neach backend's Proxy API key outbound. Backends are expected to be reachable on the router's own private\nnetwork (same-subnet LAN, VPN, etc.) — deployment topology is the operator's\nresponsibility.\n\n> Status: in progress. Implemented so far: config-driven backend list, loopback\n> bind, session/subagent Rendezvous affinity, least-outstanding fallback spread,\n> raw-byte request forwarding with byte-for-byte SSE streaming, non-completion\n> passthrough, a minimal\n> `GET /_router/status` plane, periodic **deep** health checks (authenticated\n> `GET /v1/models`) with ejection/recovery, a passive connection-level circuit\n> breaker that cools a backend after repeated hard failures, request-path\n> resource limits (max body size + bounded-concurrency\n> backpressure), and\n> [client-specified backend pinning](#client-specified-backend-pinning) via a\n> `/backends/<alias>/…` URL prefix.\n>\n> Stateful Responses items and encrypted reasoning are scoped to the backend that\n> created them. Parent sessions and independent subagents therefore receive\n> separate stable affinity keys: parallel agents distribute statistically, while\n> every agent's own continuation stays on its original backend. Explicit URL\n> pins still override affinity.\n\n## Install\n\nRequires [Bun](https://bun.sh) installed locally (the bin's shebang is\n`#!/usr/bin/env bun`; the bundled binary calls Bun-specific APIs at runtime).\n\nInstall once and run:\n\n```bash\nnpm install -g @dianshuv/copilot-api-router\ncopilot-api-router --config ./router.config.json --port 4141\n```\n\nOr run without installing:\n\n```bash\nbunx @dianshuv/copilot-api-router --config ./router.config.json --port 4141\n```\n\n## Run from source\n\n```bash\nbun install\nbun run src/main.ts --config ./router.config.json --port 4141\n```\n\n`--host` / `ROUTER_HOST` override the bind address; the default is `127.0.0.1`\n(an unconfigured instance is never exposed on the network).\n\n### Config\n\nThe backend list is config-driven — the count N is never hardcoded. Adding or\nremoving an account is a config-only change:\n\n```json\n{\n  \"backends\": [\n    { \"url\": \"http://10.0.0.1:4141\", \"apiKey\": \"proxy-key-1\", \"name\": \"acct-a\" },\n    { \"url\": \"http://10.0.0.2:4141\", \"apiKey\": \"proxy-key-2\", \"name\": \"acct-b\" }\n  ]\n}\n```\n\nEach backend carries its own Proxy API key, which the router attaches outbound\n(`Authorization: Bearer …` / `x-api-key`).\n\nEach backend may also carry an optional `name` — a stable alias used by\n[client-specified backend pinning](#client-specified-backend-pinning) so a\nclient's URL survives reordering `backends[]`. `name` must be a non-empty string,\nunique across `backends[]`, and NOT all decimal digits (an all-digits name would\ncollide with the numeric-index alias form).\n\n#### Optional knobs\n\nAll optional; an unset knob falls back to the documented default below. Health\ncadence and the request hard-ceiling come from earlier slices; the resource\nlimits (max body size + backpressure) are Issue 07; the passive connection-level\ncircuit breaker (`breaker*`) is ADR 0002. v0.2.0 removed the\n`endpoints`, `prefixTtlMs`, and `prefixMaxEntries` knobs; a config file that\nstill carries any of these will fail to load with a migration hint.\n\n| Key | Default | Meaning |\n|---|---|---|\n| `healthCheckIntervalMs` | `20000` | Per-backend deep-probe cadence (authenticated `GET /v1/models`). |\n| `healthCheckFailureThreshold` | `3` | Consecutive failed probes that eject a backend. |\n| `breakerFailureThreshold` | `3` | Consecutive connection-level failures (refused/reset before first byte) on real traffic that trip a backend's cooldown. |\n| `breakerCooldownMs` | `30000` | How long (ms) a breaker-tripped backend is skipped by selection before auto-re-admission. |\n| `requestHardTimeoutMs` | `900000` | Hard ceiling (15 min) for one completion; never failed over. |\n| `maxBodyBytes` | `8388608` | Max inbound request body (8 MiB). A larger body is rejected with `413` and never buffered/forwarded. |\n| `maxConcurrentRequests` | `64` | Max completions streaming through the router at once (backpressure ceiling). |\n| `maxQueueDepth` | `256` | Max requests allowed to wait once the ceiling is reached; past this the router sheds load with `503` (set `0` to reject immediately, never queue). |\n| `recentLogsLimit` | `500` | Retained-history capacity for `/_router/logs/recent` (the remote log tail snapshot). Integer in `[0, 10000]`; `0` disables retention (snapshot stays empty but live SSE still fires and any `Last-Event-ID` reconnect gets `event: gap`). |\n\n## Logs\n\nThe router prints **two lines per dispatched request** — a dim `[....]` start\nline the moment the router commits to a backend for this request, then a\n`[ OK ]`/`[FAIL]` completion line when the request settles — so long-running\nstreams are visible in flight instead of silent until they finish. Requests\nthat fail *before* the router commits (413 oversize body, 503 no-healthy-backend,\npin 404/503, queue-overflow) emit only the completion line — nothing was in\nflight, so there is no start line to pair.\n\n```\n14:23:00  [....]  /v1/messages          spread    #2/acct-a                   claude-opus-4\n14:23:01  [ OK ]  /v1/messages          spread    #2/acct-a     200   1.243s  claude-opus-4\n14:23:01  [....]  /v1/messages          spread    #0/acct-b                   claude-opus-4\n14:23:02  [ OK ]  /v1/messages          spread    #0/acct-b     200   0.932s  claude-opus-4\n14:23:05  [FAIL]  /v1/messages          none      --            503   0.004s\n14:23:06  [....]  /v1/models            passthru  #1/acct-b\n14:23:06  [ OK ]  /v1/models            passthru  #1/acct-b     200   0.088s\n```\n\nColumns are locked to the same fixed widths for both line variants so start and\nend lines align cell-for-cell: local time · `[....]`/`[ OK ]`/`[FAIL]` prefix ·\npath (min-padded, never truncated) · routing outcome (`spread` / `passthru` /\n`pinned` / `none`) · backend (`#index/alias` if the backend was named in\nconfig, `#index` otherwise, or `--` when no backend served) · HTTP status ·\ntotal duration (seconds, 3 decimals) · client-requested model (from the\nrequest body, when a completion carried one). The start line leaves the\nstatus and duration columns blank — a start has neither yet. For a streamed\ncompletion the duration runs to the **last byte** (the whole request); for a\npassthrough or an error response it runs to the response headers. Map a\nbackend index to its URL via `GET /_router/status`, which also reports\nper-backend inflight/health and the most recent routing decisions.\n\n**Failover** (e.g. `[....] spread #0` followed by `[ OK ] spread #1`) is\ndiagnostic gold, not a bug: it means the first backend refused the connection\nand the router transparently retried on another. See `## Client-specified\nbackend pinning` below for the case where failover is intentionally suppressed.\n\nWhen stdout is a TTY the router additionally keeps a **sticky footer** on the\nlast line showing the live backpressure state — e.g. `[....] 3 in flight, 12\nqueued requests` — refreshed on every request completion. Piping the router's\noutput (e.g. into a file or `less`) suppresses the footer so captured logs stay\none pair per request; `NO_COLOR=1` disables ANSI colouring, and `FORCE_COLOR=1`\nenables it even when stdout is not a TTY (e.g. under `systemd` with\n`StandardOutput=append:...`, so `tail -F` piped to a terminal still renders\ncolour).\n\n### Remote tail\n\n`copilot-api-router --tail <URL>` opens the router's observation plane and\nstreams the SAME per-request lines the server writes to its stdout — useful\nwhen the router runs headless (systemd, container, another host on the LAN).\nTwo forms of credential are accepted, matching the reverse-proxy contract:\n\n```bash\ncopilot-api-router --tail http://router.lan:4141 --token $ROUTER_KEY\nROUTER_API_KEY=$ROUTER_KEY copilot-api-router --tail http://router.lan:4141\n```\n\nThe observation plane is two HTTP endpoints on the router:\n\n- `GET /_router/logs/recent` — the newest N completed requests as JSON, so a\n  freshly-opened tail immediately shows history (capacity governed by\n  `recentLogsLimit`).\n- `GET /_router/logs/stream` — a live `text/event-stream` of every dispatch\n  START and completion END frame. Honours `Last-Event-ID` for resume-across-\n  reconnect, and issues a visible `event: gap` when a reconnect crossed a\n  server restart or fell behind retention.\n\n`--max-reconnects <n|infinity>` / `ROUTER_TAIL_MAX_RECONNECTS` sets the\nbetween-session reconnect budget; the default is `infinity` so the tail\nsurvives a router restart. `NO_COLOR` / `FORCE_COLOR` are honoured on the\nsame rules as the server's own stdout renderer.\n\n## Session and subagent affinity\n\nUnpinned completion requests use a stable key when the client supplies one:\n\n| Client | Parent key | Subagent key |\n|---|---|---|\n| Pi | Responses `prompt_cache_key` | child session `prompt_cache_key` |\n| OpenCode | `prompt_cache_key` or `x-session-affinity` | child session key |\n| Codex | Responses `prompt_cache_key` | `x-codex-turn-metadata.thread_id` |\n| Claude Code | `x-claude-code-session-id` | `__SUBAGENT_MARKER__` session + agent IDs |\n\nThe router applies Rendezvous hashing over the key and each backend's stable\n`name` (falling back to URL). Independent parent/child keys distribute\nstatistically; the same key ignores inflight fluctuations and remains on one\nbackend. Codex child requests also have their forwarded `prompt_cache_key`\nrewritten to the child thread key, while encrypted reasoning is preserved.\n\nAffinity is strict: if the owning backend is unhealthy, cooling, or refuses the\nconnection, the router does not move that continuation to another backend.\nIt returns 503/502 so connection-bound state is never silently replayed on the\nwrong account. Requests with no usable affinity signal retain the original\nleast-outstanding spread and connection-level failover behavior.\n\n## Client-specified backend pinning\n\nSome callers want to bypass affinity/spread entirely — \"for THIS request, use\nbackend X\". The router exposes a URL-prefix channel for that:\n\n```\nPOST /backends/<alias>/v1/messages\nPOST /backends/<alias>/v1/chat/completions\nPOST /backends/<alias>/v1/responses\nGET  /backends/<alias>/v1/models       # passthrough paths are pinned too\n```\n\n`<alias>` is either a `backends[].name` from config or the numeric index\n(`/backends/0/…`). The prefix is a router-side selector: it is stripped before\nthe request is forwarded, so the upstream backend receives the original path\n(`/v1/messages`, not `/backends/0/v1/messages`). Everything else — request\nbody, headers (minus router-rewritten auth), streaming — is unchanged.\n\n**Contract**\n\n| Situation | Response |\n|---|---|\n| Alias resolves to a healthy backend | Request forwarded to that backend; response streamed back |\n| Unknown alias (misspelt / deleted / OOB index) | `404` `{\"error\":{\"message\":\"unknown backend \\\"…\\\"\"}}` |\n| Alias resolves but backend is unhealthy | `503` `{\"error\":{\"message\":\"pinned backend \\\"…\\\" is unhealthy\"}}` — **never** silently rerouted |\n| Pinned backend refuses connection at request time | `502` (upstream error) — **no failover** to any other backend |\n| `/backends/<alias>/_router/status` | `404` (router-owned paths cannot be pinned) |\n\n**Semantics**\n\n- Pin bypasses session affinity and fallback spread — the addressed backend is used unconditionally.\n- Pinned requests are **NOT** failed over. The plain (unpinned) path retries a connection-level failure on another backend (per US-14); a pinned request surfaces the upstream error instead, because silently delivering the request to a different backend violates the pin's whole point.\n- Pinned rounds show up on `GET /_router/status` as `kind: \"pinned\"` and in the CLI access log as the `pinned` column value, so an unbalanced-looking spread is diagnosable as \"the client pinned it, not a router bug\".\n- Pin is **session/process level**, not per-request-within-a-CLI. The three supported CLIs (Claude Code / Codex / opencode) all bake `base_url` at startup and do not expose per-request overrides. Switch backend by starting a fresh CLI process (or, in Codex, by re-invoking `codex exec` with a different `-c` override).\n\n**Client configuration** — point the CLI's base URL at the router's pin prefix:\n\n```bash\n# Claude Code — env or settings.json\nexport ANTHROPIC_BASE_URL=http://127.0.0.1:4141/backends/acct-a\nclaude\n\n# Codex — ~/.codex/config.toml\n# [model_providers.copilot-router]\n# base_url = \"http://127.0.0.1:4141/backends/acct-a/v1\"\n\n# opencode — opencode.json\n# \"provider\": {\n#   \"gh-router\": {\n#     \"npm\": \"@ai-sdk/openai-compatible\",\n#     \"options\": { \"baseURL\": \"http://127.0.0.1:4141/backends/acct-a/v1\" }\n#   }\n# }\n```\n\nOmit the prefix entirely (point at `http://127.0.0.1:4141`) to use automatic\nsession affinity (or least-outstanding spread when no key exists) — pin is\nstrictly opt-in.\n\n## Test\n\n```bash\nbun run test               # unit (pure logic: selection, config, bind-host)\nbun run test:integration   # integration (spawn router + controllable fake backends)\nbun run test:all           # both\nbun run typecheck\n```\n\nIntegration tests follow the `copilot-api` prior art: spawn the router as a\nchild process with a readiness probe and ambient-env stripping, in front of\ncontrollable fake backends, and assert end-to-end behaviour (which backend was\nchosen, byte-for-byte stream fidelity, in-flight spread, credentials forwarded).\n\n## End-to-end (Makefile)\n\nA `Makefile` wraps the router for end-to-end checks (it's what the `e2e-verify`\nworkflow drives). The router is a loopback-only API service with **no frontend**,\nso verification is backend-only — curl the status plane.\n\n```bash\nmake up          # start the router in the foreground (`make down` to stop)\nmake smoke       # probe a running router: GET /_router/status\nmake e2e         # one-shot: fake-backend stack + a real completion, then tear down\nmake down        # stop the router (or e2e-stack) started above\n```\n\n`make up` needs a config with at least one backend. If none exists it writes a\nplaceholder to `.tmp/router.config.json` pointing at an unreachable backend: the\nrouter still binds and `GET /_router/status` confirms it is live, but completions\ncannot be served (no reachable backend). Point it at a reachable backend to\nexercise real forwarding (all knobs overridable):\n\n```bash\nmake up CONFIG=./router.config.json   # also: PORT=4242 HOST=127.0.0.1\n```\n\nFor **real forwarding** without a live backend, `make e2e` stands up N\ncontrollable fake backends (the integration-test fakes), points the router at\nthem, fires one `/v1/messages`, and asserts the SSE `hello from <backend>` came\nback — then tears the whole stack down. `make e2e-stack` leaves that stack\nrunning in the foreground so you can poke it by hand (`make down` stops it):\n\n```bash\nmake e2e BACKENDS=3      # assert real forwarding across 3 fake backends\nmake e2e-stack           # leave the stack running; curl it, then `make down`\n```\n\n## Release\n\nBundled via `tsdown` to a single `dist/main.mjs` and published to npm. The\npackage ships only the built artifact (`dist/*.mjs`) plus `README.md` /\n`package.json` — no source, tests, or sourcemaps.\n\n```bash\nNPM_TOKEN=<your-npm-token> bun run release\n```\n\n`prepublishOnly` gates the publish on `typecheck` + the full test suite\n(`test:all`); `prepack` (re)builds `dist/`. `.npmrc` reads `NPM_TOKEN` from the\nenvironment, so no token lives on disk.\n","readmeFilename":"README.md"}