When `"npmCommand": ["bun"]` is configured in settings.json, pi fails to
start because it invokes `bun root -g`, which doesn't exist:
error: Failed to run bun root -g: error: Script not found "root"
Add a check for the Bun runtime using the logic already used elsewhere,
and build the relative path starting from Bun's bin directory.
* feat(ai): add Cloudflare Workers AI as a provider
Cloudflare Workers AI hosts open-weight LLMs (Kimi K2.6, GPT-OSS,
GLM-4.7, Llama 4, Gemma 4, Nemotron 3) on Cloudflare's GPU network with
an OpenAI-compatible endpoint. Reuses the openai-completions API
protocol; the per-account URL contains a {CLOUDFLARE_ACCOUNT_ID}
placeholder resolved at request time by a small helper.
Pi automatically sets x-session-affinity for prefix caching:
https://developers.cloudflare.com/workers-ai/features/prompt-caching/
Auth: CLOUDFLARE_API_KEY (matches pi's *_API_KEY convention) +
CLOUDFLARE_ACCOUNT_ID. The User-Agent identifies traffic as
'pi-coding-agent' in Cloudflare analytics.
Verified end-to-end against a real Cloudflare account: 17 e2e tests
pass across stream/empty/tokens/unicode/tool-call-without-result/
total-tokens against @cf/moonshotai/kimi-k2.6.
Cloudflare AI Gateway is a separate, larger change (it requires routing
through provider-specific subpaths with the matching API protocol per
upstream) and will land in a follow-up PR.
* refactor(ai): move Cloudflare User-Agent and session-affinity flag to per-model metadata
Instead of conditionally setting them in openai-completions.ts based on
provider detection, declare them as model-level fields in the catalog
(headers + compat). This is consistent with how the github-copilot and
kimi-coding entries already declare their static headers.
packages/ai/scripts/generate-models.ts: emit headers and compat fields
on each cloudflare-workers-ai entry (CLOUDFLARE_STATIC_HEADERS).
packages/ai/src/providers/openai-completions.ts: drop the
isCloudflareProvider conditional that injected User-Agent and the
isCloudflareWorkersAI override of sendSessionAffinityHeaders.
packages/ai/src/models.generated.ts: re-spliced 8 cloudflare-workers-ai
entries with headers + compat.
Behavior is unchanged - verified via fetch interceptor that User-Agent
and x-session-affinity / session_id / x-client-request-id are still sent
on outbound requests. 5/5 e2e tests pass.
Long local-LLM SSE streams (e.g. vLLM buffering large tool calls) exceeded
undici's default 300s bodyTimeout and aborted with UND_ERR_BODY_TIMEOUT.
retry.provider.timeoutMs cannot lift this cap because it controls the
provider SDK's AbortController deadline, not undici's per-socket idle timer.
Pass { bodyTimeout: 0, headersTimeout: 0 } to EnvHttpProxyAgent. Provider
SDKs still enforce their own timeouts via retry.provider.timeoutMs.
closes#3715
Bun compiled binaries have an empty process.env when running inside
sandbox environments (e.g. nono on Linux/macOS). This broke API key
detection and model discovery because all process.env.* lookups returned
undefined.
- Add restoreSandboxEnv() helper that reads /proc/self/environ when Bun
is detected and process.env is empty, populating process.env before
any other code runs (coding-agent/src/bun/cli.ts entry point)
- Add getProcEnv() fallback in env-api-keys.ts for direct @mariozechner/pi-ai
consumers that may not go through the coding-agent entry point
- Add unit tests for restoreSandboxEnv
Bun bug oven-sh/bun#27802 causes process.env to be empty when running inside a Linux sandbox (Landlock/seccomp).
This breaks npm resolution because PATH is missing, crashing pi on startup with:
Failed to run npm root -g: Executable not found in $PATH: "npm"
Add getSandboxEnv() to read /proc/self/environ as a fallback on Linux when process.env has zero keys.
Pass this env to all spawn calls in package-manager.ts.
Also fix runCommandSync error handling: check result.error in addition to result.status, and include result.error?.message in the error text so spawn failures (e.g. ENOENT) produce useful diagnostics instead of "null".
Fixes#3806