Repository navigation
Conversation
Every reply rendered as the "✅ Done" fallback: assistant messages came back with empty content, zero usage and stopReason "stop". Importing @earendil-works/pi-coding-agent installs an npm-undici global dispatcher as a module side effect. Node 26's bundled fetch honours that dispatcher but does not decompress responses that travelled through it, so a gzipped text/event-stream reaches the provider parser as raw gzip bytes. The stream yields no events and the assistant message ends up empty. Verified against api.anthropic.com: HTTP 200, body magic 1f8b08, and the decompressed SSE contained the expected text_delta events. Pi's own CLI avoids this by calling configureHttpDispatcher() in dist/cli.js, which ends with undici.install(). That helper is not part of the package's public exports, so embedders have to install the undici globals themselves. Add configureHttpRuntime() and call it from both entry points (cli.ts for the binary, startBot() for library use). It is idempotent.
benedict2310
left a comment
There was a problem hiding this comment.
Thanks for the excellent root-cause analysis and focused fix. I reproduced the branch test/build health on both Node 23.11 and Node 26.7 (426 tests pass on each), but found two blockers before merge:
-
configureHttpRuntime()unconditionally callsundici.install(). For library consumers, this overwrites a deliberateglobalThis.fetchoverride. Pi's ownconfigureHttpDispatcher()guards this by recording the original/installed fetch and only reinstalling when fetch has not been replaced by the caller. TelePi should preserve the same behavior, with a regression test for a custom fetch override. -
The new tests verify global identity/idempotence but not the reported gzip failure. Please add an isolated Node 26 regression test (child process + local gzip/SSE server is sufficient) that demonstrates the response is decompressed with the Pi/npm-undici dispatcher interaction. That will also verify the root Undici 8.9 + Pi's nested Undici integration.
Minor cleanup: the static named install import means install?.() cannot actually support an older Undici package lacking that export, and CLI setup/status/help do not need the global mutation because startBot() is the runtime entry point.
|
Thanks again for identifying this Node 26 compatibility issue. I rebuilt the fix on current main as #25 rather than merging this stale branch: it preserves intentional fetch overrides, applies the runtime setup only when the bot starts, and adds a real isolated gzip/SSE regression exercised on Node 26 CI. #25 is now merged, so I’m closing this PR as superseded. |
|
Superseded by merged #25. |
Problem
On Node 26, every TelePi reply renders as the
✅ Donefallback.The assistant messages are genuinely empty — session JSONL shows:
{"role":"assistant","content":[],"usage":{"input":0,"output":0,"totalTokens":0},"stopReason":"stop"}prompt-handler.tsis behaving correctly;buildFinalResponseText()gets anempty string and falls back to
✅ Done.Root cause
Importing
@earendil-works/pi-coding-agentinstalls an npm-undici globaldispatcher as a module side effect (verified:
Symbol.for("undici.globalDispatcher.1")goes from
undefinedto a dispatcher purely on import).Node 26's bundled fetch honours that dispatcher but does not decompress
responses that travelled through it. A gzipped
text/event-streamthereforereaches the provider parser as raw gzip bytes, the stream yields no events, and
the assistant message ends up empty with
stopReason: "stop".Instrumenting
globalThis.fetchin an embedder process confirmed it end to end:Decompressing that body by hand shows the response was fine all along:
Pi's own CLI never hits this because
dist/cli.jscallsconfigureHttpDispatcher()before anything issues a request, and that helperends with
undici.install(). Its source comment names the exact failure mode:configureHttpDispatcheris not part of the package's public exports (only.and
./rpc-entry), so embedders like TelePi have to install the undici globalsthemselves.
Fix
Add
configureHttpRuntime()(src/http-runtime.ts), which callsundici.install()once, and invoke it from both entry points:src/cli.ts— for thetelepibinarystartBot()insrc/index.ts— for library useIt is idempotent, and
install?.()is optional-called so older undici copieswithout
installkeep today's behaviour.Verification
undici.install()fixes it: empty string → the model's actual reply.dist/http-runtime.jsbefore restarting thereal service.
npm test: 426 tests / 32 files pass, including 3 new tests forconfigureHttpRuntime.Notes
Arguably pi should also export
configureHttpDispatcher(or callundici.install()wherever it sets the global dispatcher) so embedders can'tmiss it. Worth raising upstream — but TelePi shouldn't have to wait for that.
Repro environment: Node 26.0.0, macOS 26.3.1 arm64, TelePi 0.4.2,
@earendil-works/pi-coding-agent0.82.1, provideranthropic.