{"_id":"@amartinr/pi-bifrost-reasoning-fix","_rev":"3-63027461237e5a0de10bbf5d5472ab71","name":"@amartinr/pi-bifrost-reasoning-fix","dist-tags":{"latest":"0.2.2"},"versions":{"0.2.0":{"name":"@amartinr/pi-bifrost-reasoning-fix","version":"0.2.0","keywords":["pi-extension","pi-package","pi","bifrost","deepseek","reasoning","tool-calls","sse"],"author":{"name":"A. Martin","email":"abel.martin.ruiz@gmail.com"},"license":"MIT","_id":"@amartinr/pi-bifrost-reasoning-fix@0.2.0","maintainers":[{"name":"amartinr","email":"abel.martin.ruiz@gmail.com"}],"homepage":"https://github.com/amartinr/pi-bifrost-reasoning-fix#readme","bugs":{"url":"https://github.com/amartinr/pi-bifrost-reasoning-fix/issues"},"pi":{"extensions":["./dist/index.js"]},"dist":{"shasum":"c7677388c658024310652f28be975f801392bd99","tarball":"https://registry.npmjs.org/@amartinr/pi-bifrost-reasoning-fix/-/pi-bifrost-reasoning-fix-0.2.0.tgz","fileCount":12,"integrity":"sha512-phDMw5G9u4N64RhyGctG6xpymyGzXJdFIc5brZXL5nH6sjwfCDL7i1cZMNXAUEIrRwXRsUKqSnw3akePUgOQ9g==","signatures":[{"sig":"MEYCIQDr3itakSt2TlKjxHhYkNyA8xB4G0tNe79Gb6LTIfbhwgIhAMu7MizdQWNPSNxyWXX4BUp2q0SNhbcUB9mFn4v/YGzU","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":29355},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18"},"gitHead":"fe2ba1e03b1fce06bff8e0edc663a87a3d856cb6","scripts":{"test":"node --test test/normalize.test.mjs","build":"tsc","clean":"rm -rf dist","verify":"node scripts/verify-fix.mjs","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"amartinr","email":"abel.martin.ruiz@gmail.com"},"repository":{"url":"git+https://github.com/amartinr/pi-bifrost-reasoning-fix.git","type":"git"},"_npmVersion":"11.13.0","description":"Pi extension that fixes the DeepSeek-behind-Bifrost reasoning drop on tool-calling turns. Bifrost relays DeepSeek reasoning in non-standard fields (reasoning / reasoning_details instead of reasoning_content); in tool-calling contexts DeepSeek requires rea","directories":{},"_nodeVersion":"24.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.3","@types/node":"^20.19.43","@earendil-works/pi-coding-agent":"^0.84.3"},"peerDependencies":{"@earendil-works/pi-coding-agent":"*"},"_npmOperationalInternal":{"tmp":"tmp/pi-bifrost-reasoning-fix_0.2.0_1787823937510_0.9722864983786554","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@amartinr/pi-bifrost-reasoning-fix","version":"0.2.1","keywords":["pi-extension","pi-package","pi","bifrost","deepseek","reasoning","tool-calls","sse"],"author":{"name":"A. Martin","email":"abel.martin.ruiz@gmail.com"},"license":"MIT","_id":"@amartinr/pi-bifrost-reasoning-fix@0.2.1","maintainers":[{"name":"amartinr","email":"abel.martin.ruiz@gmail.com"}],"homepage":"https://github.com/amartinr/pi-bifrost-reasoning-fix#readme","bugs":{"url":"https://github.com/amartinr/pi-bifrost-reasoning-fix/issues"},"pi":{"extensions":["./dist/index.js"]},"dist":{"shasum":"33d4903a17f39559bcd471361267c17b22cd60d2","tarball":"https://registry.npmjs.org/@amartinr/pi-bifrost-reasoning-fix/-/pi-bifrost-reasoning-fix-0.2.1.tgz","fileCount":12,"integrity":"sha512-XOu/FL/Aq1KJhNhaMkL9BEzfVtOHb11pGOolOrgP534WmDI2UXCKkbUwlSxlQunX/FhdrR9gK6RXXj5ATyYmXg==","signatures":[{"sig":"MEQCIALuGobctHpspW7jB0f6k8rVYau6srwKNtlRvbgZMKnyAiA5xcssn0SkEBR2JIeTzyWIyhmPhrNtZxySSCaAVN/dEg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":29359},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18"},"gitHead":"fe2ba1e03b1fce06bff8e0edc663a87a3d856cb6","scripts":{"test":"node --test test/normalize.test.mjs","build":"tsc","clean":"rm -rf dist","verify":"node scripts/verify-fix.mjs","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"amartinr","email":"abel.martin.ruiz@gmail.com"},"repository":{"url":"git+https://github.com/amartinr/pi-bifrost-reasoning-fix.git","type":"git"},"_npmVersion":"11.19.0","description":"Pi extension that fixes the DeepSeek-behind-Bifrost reasoning drop on tool-calling turns. Bifrost relays DeepSeek reasoning in non-standard fields (reasoning / reasoning_details instead of reasoning_content); in tool-calling contexts DeepSeek requires rea","directories":{},"_nodeVersion":"24.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.3","@types/node":"^20.19.43","@earendil-works/pi-coding-agent":"^0.84.3"},"peerDependencies":{"@earendil-works/pi-coding-agent":"*"},"_npmOperationalInternal":{"tmp":"tmp/pi-bifrost-reasoning-fix_0.2.1_1787827537617_0.7925031163196716","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@amartinr/pi-bifrost-reasoning-fix","version":"0.2.2","description":"Pi extension that fixes the DeepSeek-behind-Bifrost reasoning drop on tool-calling turns. Bifrost relays DeepSeek reasoning in non-standard fields (reasoning / reasoning_details instead of reasoning_content); in tool-calling contexts DeepSeek requires rea","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","scripts":{"build":"tsc","prepublishOnly":"npm run build","clean":"rm -rf dist","typecheck":"tsc --noEmit","verify":"node scripts/verify-fix.mjs","test":"node --test test/normalize.test.mjs"},"peerDependencies":{"@earendil-works/pi-coding-agent":"*"},"devDependencies":{"@earendil-works/pi-coding-agent":"^0.84.3","@types/node":"^20.19.43","typescript":"^5.9.3"},"pi":{"extensions":["./dist/index.js"]},"repository":{"type":"git","url":"git+https://github.com/amartinr/pi-bifrost-reasoning-fix.git"},"keywords":["pi-extension","pi-package","pi","bifrost","deepseek","reasoning","tool-calls","sse"],"engines":{"node":">=18"},"license":"MIT","author":{"name":"A. Martin","email":"abel.martin.ruiz@gmail.com"},"publishConfig":{"access":"public"},"gitHead":"ab852381fcec4503945775ee68a0edd302656127","_id":"@amartinr/pi-bifrost-reasoning-fix@0.2.2","bugs":{"url":"https://github.com/amartinr/pi-bifrost-reasoning-fix/issues"},"homepage":"https://github.com/amartinr/pi-bifrost-reasoning-fix#readme","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-UmyslKen5AqymaRqiAJXHOn83N08IOWNZcK7B7zg1zsH1JCER3DIr9YEn1gFrx+091LI32n8D6hTy3UY4ZdhAw==","shasum":"f8c893ae885407db9284e7e48235d15707685ca2","tarball":"https://registry.npmjs.org/@amartinr/pi-bifrost-reasoning-fix/-/pi-bifrost-reasoning-fix-0.2.2.tgz","fileCount":12,"unpackedSize":29741,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCAtjrhUtUxwP8skwqL82Z9e1WgBUfxXnkc9KowzZnxbQIgHeWvUAFebc9SFgjrvmeyDN9YTUH8g3c4Oy3ZIFXP/qY="}]},"_npmUser":{"name":"amartinr","email":"abel.martin.ruiz@gmail.com"},"directories":{},"maintainers":[{"name":"amartinr","email":"abel.martin.ruiz@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/pi-bifrost-reasoning-fix_0.2.2_1787844151337_0.7497197405756069"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-27T09:45:37.335Z","modified":"2026-08-27T15:22:31.649Z","0.2.0":"2026-08-27T09:45:37.640Z","0.2.1":"2026-08-27T10:45:37.753Z","0.2.2":"2026-08-27T15:22:31.490Z"},"bugs":{"url":"https://github.com/amartinr/pi-bifrost-reasoning-fix/issues"},"author":{"name":"A. Martin","email":"abel.martin.ruiz@gmail.com"},"license":"MIT","homepage":"https://github.com/amartinr/pi-bifrost-reasoning-fix#readme","keywords":["pi-extension","pi-package","pi","bifrost","deepseek","reasoning","tool-calls","sse"],"repository":{"type":"git","url":"git+https://github.com/amartinr/pi-bifrost-reasoning-fix.git"},"description":"Pi extension that fixes the DeepSeek-behind-Bifrost reasoning drop on tool-calling turns. Bifrost relays DeepSeek reasoning in non-standard fields (reasoning / reasoning_details instead of reasoning_content); in tool-calling contexts DeepSeek requires rea","maintainers":[{"name":"amartinr","email":"abel.martin.ruiz@gmail.com"}],"readme":"# @amartinr/pi-bifrost-reasoning-fix\n\nPi extension that restores DeepSeek reasoning on tool-calling turns when the provider routes through Bifrost.\n\n## Problem\n\nBifrost relays DeepSeek reasoning in non-standard fields instead of the OpenAI `reasoning_content`:\n\n- streaming: `delta.reasoning` / `delta.reasoning_details`\n- messages: `reasoning` / `reasoning_details`\n\nDeepSeek requires every assistant message in a tool-calling context to carry `reasoning_content`. The requirement derives from history content — once an assistant has produced `tool_calls` — not from whether the current request still ships `tools`. pi often re-sends history after a tool call without `tools` in the payload; DeepSeek still drops reasoning and may return an HTTP 400. Bifrost does not re-expose `reasoning_content`, so the replayed history reaches Bifrost with `reasoning` / `reasoning_details` and DeepSeek stops reasoning on the next tool-calling turn.\n\n## Mechanism\n\nThe extension registers `before_provider_request` and rewrites the provider payload before it is sent:\n\n1. Moves assistant `reasoning` / `reasoning_details` text into `reasoning_content`.\n2. When the history contains a tool call (or the request carries `tools`), guarantees every assistant message carries a `reasoning_content` field (empty if none yet), so DeepSeek keeps reasoning.\n\nThe rewrite is deterministic and stable across requests: a given assistant message always serializes identically once tool-calling is in scope, and user/system/tool messages are never touched. A stable prefix is therefore preserved for the provider's prefix cache.\n\nThe rename step is content-driven — it fires only when an assistant message carries `reasoning` / `reasoning_details`. The forcing step is not content-driven: it fires for any tool-calling payload (request `tools` or tool-call history). Model gating is defined by the extension configuration (see [Extension configuration](#extension-configuration-configjson)).\n\n## Scope of application\n\nThe extension applies to the model IDs listed under `models` in the extension configuration, matched by exact ID or prefix.\n\n- **Missing config file or empty `models` list** — the extension applies to every request. In this mode the forcing step also touches tool-calling payloads from other providers, which may reject unknown `reasoning_content` fields.\n- **Listed models** — the extension applies only to those models. Listing the DeepSeek models routed through Bifrost is the recommended setup.\n\n## Streaming (outlet)\n\n`after_provider_response` is a no-op by design. pi reads streaming reasoning via the OpenAI SDK, which parses `delta.reasoning` / `delta.reasoning_details` and tolerates SSE comments (`: heartbeat`) and `data: [DONE]`. The drop occurs only when history is replayed on the inbound (request) side, which this extension fixes. No stream rewriting is required on the pi path.\n\n## Provider configuration (models.json)\n\nThe extension performs the inbound rewrite, so `models.json` does not need workarounds for the `reasoning` / `reasoning_details` dialect. It requires a Bifrost provider whose models declare `reasoning: true` and a `thinkingLevelMap` aligned with DeepSeek's official effort levels.\n\n```json\n{\n  \"providers\": {\n    \"bifrost\": {\n      \"baseUrl\": \"http://<bifrost-host>/v1\",\n      \"api\": \"openai-completions\",\n      \"apiKey\": \"<your-api-key>\",\n      \"compat\": {\n        \"supportsDeveloperRole\": false,\n        \"supportsReasoningEffort\": true\n      },\n      \"models\": [\n        {\n          \"id\": \"deepseek/deepseek-v4-flash\",\n          \"name\": \"DeepSeek V4 Flash (Bifrost)\",\n          \"reasoning\": true,\n          \"thinkingLevelMap\": {\n            \"minimal\": null,\n            \"low\": \"low\",\n            \"medium\": null,\n            \"high\": \"high\",\n            \"xhigh\": null,\n            \"max\": \"max\"\n          },\n          \"input\": [\"text\"],\n          \"contextWindow\": 1000000,\n          \"maxTokens\": 384000\n        }\n      ]\n    }\n  }\n}\n```\n\nNotes:\n\n- `apiKey` is your Bifrost credential. Keep it in the file as usual for pi; never commit it. The key shown here is a placeholder.\n- `supportsReasoningEffort: true` lets pi forward the reasoning level from the toggle.\n- `thinkingLevelMap` exposes DeepSeek's three official effort levels (`low`, `high`, `max`) using exactly those values, so only `reasoning_effort` values DeepSeek documents are ever sent. The intermediate pi levels (`minimal`, `medium`, `xhigh`) map to `null` (hidden). This avoids sending `medium`/`xhigh` — which DeepSeek silently re-maps to `high` and a strict gateway like Bifrost may reject.\n- Gateway-specific headers (`bifrost.headers`) are deployment-specific; keep them out of public documentation.\n\n## Extension configuration (config.json)\n\nConfiguration is read from a JSON file at the extension's global config path:\n\n```\n~/.pi/agent/extensions/pi-bifrost-reasoning-fix/config.json\n```\n\nA missing file applies defaults. An invalid file falls back to defaults with a warning.\n\nThe `models` list determines which requests the fix rewrites. In the example below, only the two DeepSeek models are affected.\n\n```json\n{\n  \"models\": [\"deepseek/deepseek-v4-flash\", \"deepseek/deepseek-v4-pro\"],\n  \"forceReasoningContentOnTools\": true,\n  \"logLevel\": \"off\"\n}\n```\n\n| Key | Type | Default | Description |\n|-----|------|---------|-------------|\n| `models` | string[] | `[]` | Model IDs (or prefixes) to which the fix applies. An empty list applies the fix to every request. |\n| `forceReasoningContentOnTools` | boolean | `true` | Ensures every assistant message carries `reasoning_content` when the history has a tool call (applies even without `tools`). |\n| `logLevel` | string | `\"off\"` | `off` / `error` / `info` diagnostics to stderr. |\n\n## Credentials\n\nThe extension does not send requests to Bifrost. It rewrites the provider payload in memory; pi performs the authenticated HTTP call using the `apiKey` and `headers` declared in `models.json`. The extension never sees or forwards credentials, and no secret belongs in its config file.\n\n- Never store an API key in the extension config file; keep it in `models.json` (or the environment) as pi expects.\n- If the extension ever calls Bifrost directly, use a configurable auth header (like `GATEWAY_AUTH_HEADER`/`GATEWAY_AUTH_VALUE`) rather than a hardcoded secret.\n\n## Installation\n\nInstall the package as a pi package:\n\n```bash\npi install npm:@amartinr/pi-bifrost-reasoning-fix\n```\n\nAlternatively, add it to `packages` in `~/.pi/agent/settings.json` and reload pi (`/reload`) so the extension is auto-discovered.\n\nAfter installation, create the extension configuration and list the target models (see [Extension configuration](#extension-configuration-configjson)). Without a config file, the extension applies to every request.\n\n## Development\n\n```bash\nnpm install\nnpm run build\nnpm run typecheck\n```\n\n## Publishing\n\nThe package is scoped to `@amartinr` and published public. `prepublishOnly` runs the build so `dist/` is always current.\n\n## References\n\n- [`filters/bifrost_reasoning_content_fix`](https://github.com/amartinr/open-webui-extensions/tree/master/filters/bifrost_reasoning_content_fix) — same normalization for the Open WebUI → Bifrost path.\n- [`pipes/agent_loop_guard`](https://github.com/amartinr/open-webui-extensions/tree/master/pipes/agent_loop_guard) — robust SSE forwarding for Bifrost's non-standard stream.\n- Upstream Bifrost issues:\n  - [maximhq/bifrost#5325](https://github.com/maximhq/bifrost/issues/5325) — reasoning emitted in non-standard fields.\n  - [maximhq/bifrost#974](https://github.com/maximhq/bifrost/issues/974) — streaming `delta.reasoning` dropped for some providers.\n  - [maximhq/bifrost#6523](https://github.com/maximhq/bifrost/issues/6523) — opening role-only SSE chunks dropped.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}