@simonyea/holysheep-cli 2.1.30 → 2.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonyea/holysheep-cli",
3
- "version": "2.1.30",
3
+ "version": "2.1.31",
4
4
  "description": "Claude Code/Cursor/Cline API relay for China \u2014 \u00a51=$1, WeChat/Alipay payment, no credit card, no VPN. One command setup for all AI coding tools.",
5
5
  "scripts": {
6
6
  "test": "node tests/droid.test.js && node tests/workspace-store.test.js && node tests/runtime-stale-upgrade.test.js && node tests/hermes.test.js && node tests/preflight.test.js",
@@ -616,6 +616,64 @@ function createProcessProxyServer({ sessionId, configPath = CONFIG_PATH, allowAn
616
616
  )
617
617
  }
618
618
 
619
+ // [HolySheep fork v2.1.30 / hs22b] Buffer the incoming request body ONCE
620
+ // so retries + direct-https fallback can re-send it. Before this, each
621
+ // retry called clientReq.pipe(forwardReq) on a stream that had already
622
+ // been consumed by the previous attempt, making the second/third/...
623
+ // retry (and the fallback) hit CRS with an EMPTY body — upstream
624
+ // responded 400 "model is required" and the user saw no reply. Only
625
+ // the very first (often doomed-to-ECONNREFUSED) attempt had the real
626
+ // body; every retry+fallback arrived empty.
627
+ //
628
+ // Buffer into memory before any forward. Typical claude-agent-acp
629
+ // POST bodies are 5-40 KB so this is fine. Override from wire type
630
+ // to Buffer so downstream functions can pipe(from body stream)
631
+ // multiple times by wrapping a fresh Readable each time.
632
+ let bufferedBody = null
633
+ if (clientReq.method && !['GET', 'HEAD', 'OPTIONS'].includes(clientReq.method.toUpperCase())) {
634
+ try {
635
+ const chunks = []
636
+ for await (const chunk of clientReq) chunks.push(chunk)
637
+ bufferedBody = Buffer.concat(chunks)
638
+ } catch (e) {
639
+ // client disconnected mid-read; bail out early.
640
+ if (!clientRes.headersSent) {
641
+ clientRes.writeHead(400, { 'content-type': 'text/plain' })
642
+ clientRes.end('client disconnected while uploading body: ' + (e && e.message))
643
+ }
644
+ return
645
+ }
646
+ }
647
+ // Shadow `clientReq` with a wrapper that yields a fresh Readable body
648
+ // stream each time `.pipe()` is called. This keeps the rest of the
649
+ // handler unchanged (node-proxy + direct-fallback both still use
650
+ // `clientReq.pipe(upstream)`).
651
+ const { Readable } = require('stream')
652
+ const makeBodyStream = () => {
653
+ if (bufferedBody === null) return null
654
+ const r = new Readable({ read() {} })
655
+ r.push(bufferedBody)
656
+ r.push(null)
657
+ return r
658
+ }
659
+ const originalClientReq = clientReq
660
+ clientReq = new Proxy(originalClientReq, {
661
+ get(target, prop, receiver) {
662
+ if (prop === 'pipe') {
663
+ return (dest, opts) => {
664
+ const s = makeBodyStream()
665
+ if (!s) {
666
+ // GET/HEAD — just end the dest
667
+ if (dest && typeof dest.end === 'function') dest.end()
668
+ return dest
669
+ }
670
+ return s.pipe(dest, opts)
671
+ }
672
+ }
673
+ return Reflect.get(target, prop, receiver)
674
+ },
675
+ })
676
+
619
677
  const doForward = async (lease, attempt) => {
620
678
  const config = readConfig(configPath)
621
679
  const nodeProxyUrl = deriveNodeProxyUrl(lease)