dsh-opencode-free-tier 0.1.1 → 0.1.2
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 +13 -8
- package/lib/index.js +37 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,14 +29,19 @@ A zero-dependency Cordis plugin that fixes all three at the fetch transport laye
|
|
|
29
29
|
`AsyncLocalStorage` across each adapter stream, so the fetch layer knows which DSH
|
|
30
30
|
conversation a request belongs to (stable session → optimal upstream prompt-cache routing).
|
|
31
31
|
- **Fetch middleware, scoped strictly to `opencode.ai`** (+ subdomains) — every other
|
|
32
|
-
host passes through byte-for-byte untouched:
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
`x-opencode-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
host passes through byte-for-byte untouched. It distinguishes the two lanes:
|
|
33
|
+
- **Anonymous free lane** (`/zen/...`) — the full CLI disguise: missing/non-CLI
|
|
34
|
+
`User-Agent` → `opencode/<cli-version> (platform arch; node...)`;
|
|
35
|
+
missing/malformed `x-opencode-session` → canonicalized (`ses_` + 26) from the DSH
|
|
36
|
+
conversation id (an already-canonical id passes through, preserving cache affinity);
|
|
37
|
+
`x-opencode-client: cli`, `x-session-affinity`, `X-Session-Id`,
|
|
38
|
+
`x-opencode-request`, `x-opencode-project` filled when absent; chat-completions
|
|
39
|
+
bodies missing `bash`/`read` tools get the stubs appended (`tool_choice: "none"`
|
|
40
|
+
when the caller had no tools, so the model never calls them).
|
|
41
|
+
- **Paid OpenCode Go lane** (`/zen/go/...`) — only the stable canonical
|
|
42
|
+
`x-opencode-session` the Go docs require; the harness's real `User-Agent` and the
|
|
43
|
+
request body stay untouched. This also fixes DSH's missing session header on the
|
|
44
|
+
Go lane ([discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495)).
|
|
40
45
|
|
|
41
46
|
Already-correct requests pass through untouched (idempotent) — e.g. it coexists with
|
|
42
47
|
`opencode2dsh` instead of breaking it.
|
package/lib/index.js
CHANGED
|
@@ -257,6 +257,13 @@ async function readBodyText(input, init) {
|
|
|
257
257
|
* Options:
|
|
258
258
|
* - hosts: allowlist (default opencode.ai)
|
|
259
259
|
* - isEnabled: () => boolean, consulted per matching request
|
|
260
|
+
*
|
|
261
|
+
* Two lanes share the host:
|
|
262
|
+
* - the anonymous free lane (`/zen/...`) needs the full CLI disguise (UA,
|
|
263
|
+
* canonical session, `bash`/`read` tools) or it answers 403 FreeTierError;
|
|
264
|
+
* - the paid OpenCode Go lane (`/zen/go/...`) only needs the stable
|
|
265
|
+
* `x-opencode-session` header the Go docs ask for, and should keep the
|
|
266
|
+
* harness's real User-Agent, so it is never disguised and gets no tools.
|
|
260
267
|
*/
|
|
261
268
|
export function createFreeTierMiddleware(options) {
|
|
262
269
|
const hosts = options?.hosts?.length ? options.hosts : DEFAULT_HOSTS
|
|
@@ -265,6 +272,7 @@ export function createFreeTierMiddleware(options) {
|
|
|
265
272
|
const url = requestUrlOf(input)
|
|
266
273
|
if (!url || !hostMatches(url.hostname, hosts)) return next(input, init)
|
|
267
274
|
if (!isEnabled()) return next(input, init)
|
|
275
|
+
const freeLane = !url.pathname.startsWith('/zen/go/')
|
|
268
276
|
|
|
269
277
|
// Merge whichever header source would actually reach the wire (fetch spec:
|
|
270
278
|
// init.headers replaces Request headers when present).
|
|
@@ -276,10 +284,13 @@ export function createFreeTierMiddleware(options) {
|
|
|
276
284
|
: undefined
|
|
277
285
|
const headers = new Headers(source ?? undefined)
|
|
278
286
|
|
|
279
|
-
// 1. User-Agent must start with opencode/
|
|
280
|
-
|
|
281
|
-
if (
|
|
282
|
-
headers.
|
|
287
|
+
// 1. User-Agent must start with opencode/ (free lane only; Go keeps the
|
|
288
|
+
// harness identity the Go docs ask clients to send).
|
|
289
|
+
if (freeLane) {
|
|
290
|
+
const ua = headers.get('user-agent')
|
|
291
|
+
if (!ua || !ua.toLowerCase().startsWith('opencode/')) {
|
|
292
|
+
headers.set('user-agent', opencodeUserAgent())
|
|
293
|
+
}
|
|
283
294
|
}
|
|
284
295
|
|
|
285
296
|
// 2. x-opencode-session must be canonical ses_+26; preserve a correct one
|
|
@@ -295,27 +306,31 @@ export function createFreeTierMiddleware(options) {
|
|
|
295
306
|
session = canonicalSessionID(signal)
|
|
296
307
|
headers.set(HEADER_SESSION, session)
|
|
297
308
|
}
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
309
|
+
if (freeLane) {
|
|
310
|
+
if (!headers.get('x-opencode-client')) headers.set('x-opencode-client', 'cli')
|
|
311
|
+
if (!headers.get('x-session-affinity')) headers.set('x-session-affinity', session)
|
|
312
|
+
if (!headers.get('x-session-id') && !headers.get('X-Session-Id')) headers.set('X-Session-Id', session)
|
|
313
|
+
if (!headers.get('x-opencode-request')) headers.set('x-opencode-request', randomID('req', 16))
|
|
314
|
+
if (!headers.get('x-opencode-project')) headers.set('x-opencode-project', defaultProjectID())
|
|
315
|
+
}
|
|
303
316
|
|
|
304
|
-
// 3.
|
|
317
|
+
// 3. Free lane only: body must carry bash+read tools (chat-completions shape).
|
|
305
318
|
let newBodyText
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
319
|
+
if (freeLane) {
|
|
320
|
+
try {
|
|
321
|
+
const bodyText = await readBodyText(input, init)
|
|
322
|
+
if (typeof bodyText === 'string' && bodyText.length > 0) {
|
|
323
|
+
let parsed
|
|
324
|
+
try {
|
|
325
|
+
parsed = JSON.parse(bodyText)
|
|
326
|
+
} catch {
|
|
327
|
+
parsed = undefined
|
|
328
|
+
}
|
|
329
|
+
const fixed = parsed === undefined ? undefined : ensureFreeLaneShape(parsed)
|
|
330
|
+
if (fixed !== undefined) newBodyText = JSON.stringify(fixed)
|
|
314
331
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
} catch {}
|
|
332
|
+
} catch {}
|
|
333
|
+
}
|
|
319
334
|
|
|
320
335
|
if (newBodyText === undefined) {
|
|
321
336
|
return next(input, { ...init, headers })
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-opencode-free-tier",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Make DSH llm-pi-ai opencode routes pass Zen free-tier gate: opencode User-Agent + canonical ses_ session + bash/read tools. Scoped to opencode.ai only.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|