opencode-thinking-fix 3.2.0 → 3.3.0
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/README.md +89 -15
- package/package.json +1 -1
- package/proxy/core.js +44 -0
- package/proxy/proxy.js +4 -6
package/README.md
CHANGED
|
@@ -10,23 +10,65 @@
|
|
|
10
10
|
npm install opencode-thinking-fix
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
> Restores reasoning content that clients and SDKs drop from multi-turn conversations — DeepSeek, Kimi, GLM, MiMo, MiniMax,
|
|
13
|
+
> Restores reasoning content that clients and SDKs drop from multi-turn conversations — DeepSeek, Kimi, GLM, MiMo, MiniMax, OpenCode Go, and OpenRouter.
|
|
14
14
|
>
|
|
15
|
-
> **Zero config.** Install via `Ctrl+P`, restart OpenCode, done. The plugin
|
|
15
|
+
> **Zero config.** Install via `Ctrl+P`, restart OpenCode, done. The plugin detects reasoning models and only patches when needed.
|
|
16
16
|
>
|
|
17
17
|
> Docs: [OpenCode Plugins](https://opencode.ai/docs/plugins)
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
## The problem
|
|
20
20
|
|
|
21
|
-
When DeepSeek or Kimi answers you, it scribbles notes first. "Let me think... the user wants a login page... I should use React Hook Form... check the API docs..." These notes are `reasoning_content`. You never see them. But the AI needs them.
|
|
21
|
+
When DeepSeek or Kimi answers you, it scribbles notes first. "Let me think... the user wants a login page... I should use React Hook Form... check the API docs..." These notes are `reasoning_content`. You never see them. But the AI needs them — the provider docs require the notes to be sent back on every later turn.
|
|
22
22
|
|
|
23
|
-
The client and its SDK throw the notebook away. Next turn, the AI reaches for it
|
|
23
|
+
The client and its SDK throw the notebook away. Next turn, the AI reaches for it, but the request already went out without it. The AI doesn't crash. It just forgot everything it was thinking. That is why your AI seems dumber on turn 2. It's not dumber. It lost its notes.
|
|
24
24
|
|
|
25
|
-
The drop happens in SDK serializers and client transforms (truthy checks, missing type members, stripped signatures), not in the providers
|
|
25
|
+
The drop happens in SDK serializers and client transforms (truthy checks, missing type members, stripped signatures), not in the providers. The providers do exactly what their docs say.
|
|
26
26
|
|
|
27
|
-
**Without the
|
|
27
|
+
**Without the fix:** "Build me a login page." → AI builds it. "Now add password reset." → notes gone, the answer comes back generic.
|
|
28
28
|
|
|
29
|
-
**With the
|
|
29
|
+
**With the fix:** "Now add password reset." → AI reads its notes: "I used React Hook Form for login, I'll extend that." → works.
|
|
30
|
+
|
|
31
|
+
## See it happen in 5 minutes
|
|
32
|
+
|
|
33
|
+
You need OpenCode and any reasoning model. No proxy, no plugin — this reproduces the drop on a stock install.
|
|
34
|
+
|
|
35
|
+
1. Start a session with a reasoning model (free tier works, e.g. `opencode/hy3-free`) and give it a real task. It thinks, answers, and emits `reasoning_content`.
|
|
36
|
+
2. Ask a follow-up that depends on the first answer, then capture the outgoing request. Every `reasoning_content` field from turn 1 arrives empty or missing.
|
|
37
|
+
3. The same request through the SDK alone keeps the field. The client is the difference.
|
|
38
|
+
|
|
39
|
+
Captured evidence for exactly this sequence: [`proof/free-models/`](proof/free-models/). The A/B controls that isolate the field as the only variable: [`proof/curl-control-results.txt`](proof/curl-control-results.txt).
|
|
40
|
+
|
|
41
|
+
## Why you've probably never seen it
|
|
42
|
+
|
|
43
|
+
This bug is conditional, and the conditions are "you work the tool hard":
|
|
44
|
+
|
|
45
|
+
- **Short sessions never hit it.** The drop happens on turn 2+, worst after tool calls. A two-message chat looks fine.
|
|
46
|
+
- **Most gateways don't enforce the contract.** On the free `opencode/zen` tier and OpenRouter, a missing field is accepted, so nothing errors. The model just quietly loses context. No 400, no warning, only worse answers.
|
|
47
|
+
- **Non-reasoning models are unaffected.** GPT, Claude, Qwen, Gemini never emit the field.
|
|
48
|
+
|
|
49
|
+
If you run long sessions, heavy tool calls, model switching, or subagents, you have probably been hit and blamed the model. That is who this fix is for. If you send three messages a day, you may genuinely never notice — and the plugin detects that and does nothing.
|
|
50
|
+
|
|
51
|
+
## The evidence
|
|
52
|
+
|
|
53
|
+
We ran four passive taps (DeepSeek, OpenCode Go, OpenCode Zen, OpenRouter) that forwarded traffic byte-for-byte untouched and logged only metadata. They ran for a week of real work, then were retired. Methodology and raw data: [`proof/` directory](proof/).
|
|
54
|
+
|
|
55
|
+
- **1,968 drops captured** — a turn that carried reasoning earlier in the same session arrives at the API without it.
|
|
56
|
+
- **2.29 million characters** of reasoning erased over a week of real traffic.
|
|
57
|
+
- **68% of drops happened with zero model change** — same model, same session. Not a model-switch artifact.
|
|
58
|
+
- **OpenRouter drops it too** — 22 drops with confirmed session identity on models served through openrouter.ai, plus the same pattern on DeepSeek and OpenCode Go lanes.
|
|
59
|
+
- **The SDK is innocent.** `@ai-sdk/openai-compatible` round-trips the field correctly; the drop happens in the client's own transform. Captured side by side in [`proof/free-models/`](proof/free-models/).
|
|
60
|
+
|
|
61
|
+
Every number is recomputable from [`proof/reasoning-drop/drops.csv`](proof/reasoning-drop/drops.csv) and [`proof/reasoning-drop/switches.csv`](proof/reasoning-drop/switches.csv).
|
|
62
|
+
|
|
63
|
+
## For the skeptics
|
|
64
|
+
|
|
65
|
+
Three objections come up every time this is posted. Fair questions, with the evidence:
|
|
66
|
+
|
|
67
|
+
**"Works on my side."** Probably true. The drop is silent on most gateways — no 400, just worse answers, so nothing looks broken unless you compare turn-3 quality with and without. The A/B controls hold everything constant except the field. And the 68% stat rules out model switching as the explanation.
|
|
68
|
+
|
|
69
|
+
**"Your config is broken."** The configs this was captured on are published, redacted, in [`proof/` directory](proof/). The root cause is in the client's message transform, not config — the SDK control shows the field surviving when only the client is removed from the path.
|
|
70
|
+
|
|
71
|
+
**"The model cards already say to pass reasoning back."** They do. That is the point. The provider requires the field; the client drops it. The model card is the spec this repo implements.
|
|
30
72
|
|
|
31
73
|
---
|
|
32
74
|
|
|
@@ -125,7 +167,7 @@ opencode plugin opencode-thinking-fix
|
|
|
125
167
|
For a specific version:
|
|
126
168
|
|
|
127
169
|
```bash
|
|
128
|
-
opencode plugin opencode-thinking-fix@3.
|
|
170
|
+
opencode plugin opencode-thinking-fix@3.3.0
|
|
129
171
|
```
|
|
130
172
|
|
|
131
173
|
Restart OpenCode after installing.
|
|
@@ -150,13 +192,17 @@ See also: [OpenCode plugin docs](https://opencode.ai/docs/plugins)
|
|
|
150
192
|
|
|
151
193
|
---
|
|
152
194
|
|
|
153
|
-
- [
|
|
195
|
+
- [The problem](#the-problem)
|
|
196
|
+
- [See it happen in 5 minutes](#see-it-happen-in-5-minutes)
|
|
197
|
+
- [Why you've probably never seen it](#why-youve-probably-never-seen-it)
|
|
198
|
+
- [The evidence](#the-evidence)
|
|
199
|
+
- [For the skeptics](#for-the-skeptics)
|
|
154
200
|
- [Option 1: Plugin (safety net)](#option-1-plugin-safety-net)
|
|
155
201
|
- [Option 2: Proxy (replays real reasoning)](#option-2-proxy-replays-real-reasoning)
|
|
156
202
|
- [Option 3: Watchdog (auto-recovery)](#option-3-watchdog-auto-recovery)
|
|
157
203
|
- [How they work together](#how-they-work-together)
|
|
158
204
|
- [Affected models](#affected-models)
|
|
159
|
-
- [Model routing](#model-routing)
|
|
205
|
+
- [Model routing](#model-routing-proxy-port-3457)
|
|
160
206
|
- [Is it working?](#is-it-working)
|
|
161
207
|
- [This bug is everywhere](#this-bug-is-everywhere)
|
|
162
208
|
- [Files in this repo](#files-in-this-repo)
|
|
@@ -171,7 +217,7 @@ The client/SDK dropped the field before it reached the API — the providers are
|
|
|
171
217
|
|
|
172
218
|
> **Deprecated:** the historical symptom of this drop — `HTTP 400: The reasoning_content in the thinking mode must be passed back to the API` — is **resolved** by this fix. This repo is about the drop itself, not the error it used to cause.
|
|
173
219
|
|
|
174
|
-
DeepSeek V4 (and Kimi K2.7, GLM 5.x, MiMo V2.5) require that `reasoning_content` from every prior assistant turn gets included in subsequent API requests. The [docs](https://api-docs.deepseek.com/guides/thinking_mode)
|
|
220
|
+
DeepSeek V4 (and Kimi K2.7, GLM 5.x, MiMo V2.5) require that `reasoning_content` from every prior assistant turn gets included in subsequent API requests. The [docs](https://api-docs.deepseek.com/guides/thinking_mode) describe the contract, and the strictness varies by provider and endpoint: some reject the request, some ignore the field, and the conversation still loses its context either way. All five providers confirm the passback requirement in their official documentation:
|
|
175
221
|
|
|
176
222
|
- **DeepSeek**: "[The reasoning_content will be ignored by the API](https://api-docs.deepseek.com/guides/thinking_mode)", but the conversation history must contain the field.
|
|
177
223
|
- **Z.AI / GLM**: "[Key: return reasoning_content to keep the reasoning coherent](https://docs.z.ai/en/api/thinking)."
|
|
@@ -349,6 +395,7 @@ The plugin is the safety net. If the proxy goes down, the plugin still injects e
|
|
|
349
395
|
| MiMo V2.5 | Yes | Nice to have | `reasoning_content` on `api.xiaomimimo.com/v1` |
|
|
350
396
|
| **MiniMax-M3** | Yes | **Recommended** | `reasoning_details[]` array; ~40% quality loss if stripped. Proxy injects `reasoning_split:true` to keep thinking separate from content. |
|
|
351
397
|
| OpenCode Go | Yes | Recommended for chat/message routes | Chat uses `reasoning`; Anthropic messages are handled; unsupported response formats pass through |
|
|
398
|
+
| OpenRouter (reasoning models via openrouter.ai) | Yes | Recommended | Field depends on the upstream model; the fixed-upstream proxy replays `reasoning_content` and passes everything else through |
|
|
352
399
|
| Qwen, GPT, Claude, Gemini, Llama, Mistral | No | No | No reasoning_content |
|
|
353
400
|
|
|
354
401
|
---
|
|
@@ -374,15 +421,37 @@ The proxy auto-routes by model name prefix. All routes:
|
|
|
374
421
|
|
|
375
422
|
Unknown models fall back to `https://api.deepseek.com` with reasoning disabled.
|
|
376
423
|
|
|
424
|
+
### OpenRouter (port 3462)
|
|
425
|
+
|
|
426
|
+
OpenRouter wraps many upstream models, and reasoning models served through it lose their thinking the same way. Run a third fixed-upstream proxy and point your OpenRouter provider at it:
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
PORT=3462 UPSTREAM_URL=https://openrouter.ai/api/v1 REASONING_KEY=reasoning_content node proxy/proxy.js
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
```json
|
|
433
|
+
{
|
|
434
|
+
"provider": {
|
|
435
|
+
"openrouter": {
|
|
436
|
+
"options": {
|
|
437
|
+
"baseURL": "http://127.0.0.1:3462/v1"
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
The passive-tap corpus includes an OpenRouter lane: 22 session-confirmed drops, concentrated on `minimax/minimax-m3:free` within one long session — enough to show the pattern exists on OpenRouter, with the full model-by-model matrix left to a follow-up corpus. See [The evidence](#the-evidence).
|
|
445
|
+
|
|
377
446
|
---
|
|
378
447
|
|
|
379
448
|
## Is it working?
|
|
380
449
|
|
|
381
450
|
The plugin writes a structured JSON log: `~/.local/share/opencode/thinking-fix.log`.
|
|
382
451
|
|
|
383
|
-
|
|
452
|
+
Every request logs an `inspect` event (field coverage, whether the model is a reasoning model, how many turns were patched), so the plugin is auditable even when it patches nothing. Non-reasoning models are skipped by design — zero false patches is a property of the self-detection, not a promise bolted on.
|
|
384
453
|
|
|
385
|
-
|
|
454
|
+
From a real 104-message session:
|
|
386
455
|
|
|
387
456
|
```
|
|
388
457
|
Before: reasoning dropped on every turn → AI forgets its thinking
|
|
@@ -454,11 +523,16 @@ systemd/
|
|
|
454
523
|
reasoning-cache.service # proxy systemd unit (port 3457)
|
|
455
524
|
reasoning-cache-go.service # OpenCode Go proxy unit (port 3458)
|
|
456
525
|
reasoning-proxy-watchdog.service # watchdog systemd unit
|
|
526
|
+
tests/
|
|
527
|
+
test-proxy.js # 127 proxy assertions
|
|
528
|
+
test-plugin.js # 12 plugin assertions
|
|
529
|
+
live-minimax-replay.mjs # self-contained replay gate (mock upstream)
|
|
530
|
+
proof/ # evidence: captures, controls, CSVs, repro scripts
|
|
457
531
|
```
|
|
458
532
|
|
|
459
533
|
## Release highlights
|
|
460
534
|
|
|
461
|
-
- **v3.2.0:** R1 `strip` sentinel (actively remove reasoning for `deepseek-r1`/`deepseek-reasoner`), multibyte-safe SSE decoding (StringDecoder), response hop-by-hop header stripping, oversized-reasoning skip-not-truncate, MiniMax replay shape behind `MINIMAX_REASONING_DETAILS_SHAPE` (default `minimal`), a real-core test suite (95 assertions), glm-5.2 go-mode `reasoning_content` key (
|
|
535
|
+
- **v3.2.0:** R1 `strip` sentinel (actively remove reasoning for `deepseek-r1`/`deepseek-reasoner`), multibyte-safe SSE decoding (StringDecoder), response hop-by-hop header stripping, oversized-reasoning skip-not-truncate, MiniMax replay shape behind `MINIMAX_REASONING_DETAILS_SHAPE` (default `minimal`), a real-core test suite (95 assertions), glm-5.2 go-mode `reasoning_content` key (live-verified across two turns), derived session keys, and `x-session-id` no longer forwarded upstream. See the changelog.
|
|
462
536
|
- **v3.1.4:** proxy-owned structured JSONL logging with session identifiers truncated in logs.
|
|
463
537
|
- **v3.1.3:** Anthropic-wire dialect detection for OpenCode Go routes.
|
|
464
538
|
- **v3.1.2:** bounded cache/session memory, raw-stream preservation, hop-by-hop header removal, and safer parser failure handling.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-thinking-fix",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Restores reasoning content dropped from multi-turn OpenCode conversations (DeepSeek, Kimi, GLM, MiMo, MiniMax, OpenCode Go). Plugin + proxy + watchdog.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/proxy/core.js
CHANGED
|
@@ -39,6 +39,21 @@ export function writeLog(entry) {
|
|
|
39
39
|
const DEBUG = process.env.DEBUG === '1'
|
|
40
40
|
|
|
41
41
|
export const ROUTES = {
|
|
42
|
+
// Zen free-tier lane — BARE model ids as sent by the `opencode` provider
|
|
43
|
+
// section (port 3459, universal mode). MUST stay at the top: generic
|
|
44
|
+
// prefixes below ('deepseek-v4-flash', 'mimo', ...) would otherwise swallow
|
|
45
|
+
// these names and route free traffic to provider-native endpoints.
|
|
46
|
+
'x-preview-f-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
47
|
+
'hy3-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
48
|
+
'mimo-v2.5-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
49
|
+
'muse-spark-1.2-contributor-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
50
|
+
'deepseek-v4-flash-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
51
|
+
'ling-3.0-flash-fin-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
52
|
+
'laguna-s-2.1-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
53
|
+
'big-pickle': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
54
|
+
'ox-alpha-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
55
|
+
'nemotron-3-ultra-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
56
|
+
'nemotron-3.5-lightning-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
42
57
|
// R1 aliases: reasoning must not be echoed back — the 'strip' sentinel
|
|
43
58
|
// admits the request to patchRequestBody (to REMOVE any reasoning fields a
|
|
44
59
|
// client/plugin attached) while excluding it from response-side caching.
|
|
@@ -62,10 +77,30 @@ export const ROUTES = {
|
|
|
62
77
|
gemini: { base: 'https://generativelanguage.googleapis.com/v1beta/openai', reasoningKey: null },
|
|
63
78
|
llama: { base: 'https://api.together.xyz', reasoningKey: null },
|
|
64
79
|
mistral: { base: 'https://api.mistral.ai', reasoningKey: null },
|
|
80
|
+
// opencode/ zen free-tier models — served by the OpenCode zen gateway at
|
|
81
|
+
// zen/v1 (chat/completions wire, reasoning_content carrier). Distinct from
|
|
82
|
+
// the opencode-go namespace (port 3458) handled by fixedUpstreamRoute.
|
|
83
|
+
// Explicit per-model prefixes only: no catch-all `opencode/` prefix, so paid
|
|
84
|
+
// opencode/* models keep their existing DEFAULT_ROUTE behavior.
|
|
85
|
+
'opencode/hy3-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
86
|
+
'opencode/mimo-v2.5-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
87
|
+
'opencode/muse-spark-1.2-contributor-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
88
|
+
'opencode/deepseek-v4-flash-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
89
|
+
'opencode/nemotron-3-ultra-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
90
|
+
'opencode/nemotron-3.5-lightning-free': { base: 'https://opencode.ai/zen/v1', reasoningKey: 'reasoning_content' },
|
|
65
91
|
}
|
|
66
92
|
|
|
67
93
|
export const DEFAULT_ROUTE = { base: 'https://api.deepseek.com', reasoningKey: null }
|
|
68
94
|
|
|
95
|
+
// F13 (2026-08-27): clients may speak either OpenAI-style relative paths
|
|
96
|
+
// (/v1/...) or the upstream's full official prefix (/api/v1/... — OpenRouter's
|
|
97
|
+
// documented base is https://openrouter.ai/api/v1). Never double the prefix.
|
|
98
|
+
export function upstreamPathFor(reqUrl, basePath) {
|
|
99
|
+
if (!basePath || basePath === '/' || basePath === '/v1') return reqUrl
|
|
100
|
+
if (reqUrl === basePath || reqUrl.startsWith(basePath + '/')) return reqUrl
|
|
101
|
+
return basePath + reqUrl.replace(/^\/v1/, '')
|
|
102
|
+
}
|
|
103
|
+
|
|
69
104
|
export function route(modelName) {
|
|
70
105
|
if (!modelName) return DEFAULT_ROUTE
|
|
71
106
|
const lower = modelName.toLowerCase()
|
|
@@ -91,6 +126,15 @@ export function fixedUpstreamRoute(modelName, requestUrl = '') {
|
|
|
91
126
|
if (stripped === 'glm-5.2' && String(requestUrl).includes('/chat/completions')) {
|
|
92
127
|
return { base: '', reasoningKey: 'reasoning_content' }
|
|
93
128
|
}
|
|
129
|
+
// REASONING_KEY env override (F12, 2026-08-27): lanes fixed to upstreams whose
|
|
130
|
+
// wire is reasoning_content-based (zen/v1 free models, OpenRouter normalized
|
|
131
|
+
// per-message passback) set REASONING_KEY=reasoning_content in their unit.
|
|
132
|
+
// OpenRouter docs define top-level `reasoning` as a config OBJECT on requests,
|
|
133
|
+
// so a string injection there must use reasoning_content instead. Units without
|
|
134
|
+
// the env (validated go lane, 3458) keep the unconditional 'reasoning' behavior.
|
|
135
|
+
if (process.env.REASONING_KEY) {
|
|
136
|
+
return { base: '', reasoningKey: process.env.REASONING_KEY }
|
|
137
|
+
}
|
|
94
138
|
// Unconditional 'reasoning' for go mode: matches current working behavior
|
|
95
139
|
// for chat/completions models; /responses bodies have no `messages` array
|
|
96
140
|
// and pass through untouched.
|
package/proxy/proxy.js
CHANGED
|
@@ -58,7 +58,7 @@ import https from 'node:https'
|
|
|
58
58
|
import url from 'node:url'
|
|
59
59
|
import {
|
|
60
60
|
ROUTES, cache, stats, route, fixedUpstreamRoute, getSessionCache, patchRequestBody,
|
|
61
|
-
extractReasoningFromJson, createStreamParser, writeLog, deriveSessionId,
|
|
61
|
+
extractReasoningFromJson, createStreamParser, writeLog, deriveSessionId, upstreamPathFor,
|
|
62
62
|
} from './core.js'
|
|
63
63
|
|
|
64
64
|
const PORT = parseInt(process.env.PORT || '3457', 10)
|
|
@@ -170,11 +170,9 @@ const server = http.createServer((req, res) => {
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
// ── Upstream request options ──
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
upstreamPath = basePath + req.url.replace(/^\/v1/, '')
|
|
177
|
-
}
|
|
173
|
+
// F13: path normalization lives in core.js (upstreamPathFor) — never double
|
|
174
|
+
// the upstream's official prefix.
|
|
175
|
+
const upstreamPath = upstreamPathFor(req.url, upstream.path || '/')
|
|
178
176
|
const transport = upstream.protocol === 'https:' ? https : http
|
|
179
177
|
const HOP_BY_HOP = new Set(['connection', 'keep-alive', 'te', 'trailer', 'transfer-encoding', 'upgrade', 'proxy-', 'x-session-id'])
|
|
180
178
|
const safeHeaders = {}
|