From 44fc7102d62298465c7e8f8f76d97df1ecb22d71 Mon Sep 17 00:00:00 2001 From: Leo Date: Mon, 2 Nov 2026 23:04:54 +0800 Subject: [PATCH] feat(plugin-page): a curated page and a featured image, instead of the whole README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plugin page renders ONE document inline: the Markdown `meta.detail` in `locale/.json` names, or — when nothing declares one — the whole `README.md`. This package declared nothing, so its page rendered 461 lines of engineering notes above the settings card; the report was "why do I have such a big readme here". **`locale/{en,zh}.json` now declare `meta.detail`,** pointing at a 19-line page in each language: the featured image, one paragraph, a four-row table of what the card actually gives you, and three links to where the rest lives. The full README keeps its job — the reference — and stops being the front door. **The featured image is `docs/media/hero.svg`,** drawn in the icon's own language (the pale family frame behind the vibrant OpenCode mark, the DeepSeek-blue to purple gradient) with the meter's ring as the motif, because the meter is the surface this plugin is known by. It opens both the detail page and the README. **Both files are shipped:** `package.json` `files` names the two documents and the image, because the detail-image route serves package-relative paths and a package that ships one but not the other works here and not for a user. **`test/plugin-detail.test.ts` holds the contract**, from the host's own rules: every locale declares a detail, the path is relative and inside the package, the file exists AND npm ships it, the document opens with an image that exists and is shipped, it stays under 40 lines, and it hands off to the reference pages. Verified against the host's own resolver (`@deepseek-ai/dsh-plugin-detail`): `en` and `ja` resolve `docs/detail.md`, `zh-CN` and `zh` resolve `docs/detail.zh-CN.md`, both `origin=locale` rather than the README fallback, and the image resolves and reads in every case. The new document is also added to the language-pair gate, which needed two of its own thresholds relaxed: a page with no commands is legitimate, and a floor tuned to a 461-line README is not a floor a short page should have to meet. --- README.md | 2 + README.zh-CN.md | 2 + docs/detail.md | 18 +++++ docs/detail.zh-CN.md | 18 +++++ docs/media/hero.svg | 55 +++++++++++++++ locale/en.json | 3 +- locale/zh.json | 3 +- package.json | 5 +- test/docs-pairing.test.ts | 10 ++- test/plugin-detail.test.ts | 133 +++++++++++++++++++++++++++++++++++++ 10 files changed, 243 insertions(+), 6 deletions(-) create mode 100644 docs/detail.md create mode 100644 docs/detail.zh-CN.md create mode 100644 docs/media/hero.svg create mode 100644 test/plugin-detail.test.ts diff --git a/README.md b/README.md index 0c015aa8..2e36161d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@

English  ·  简体中文

+

OpenCode Patch — session affinity, gateway headers and live Go quota

+ OpenCode on DeepSeek Harness

dsh-opencode-patch

OpenCode on DeepSeek Harness
Gateway origin headers · Session affinity · Free-tier tool fallback · Live dual-mode quota meter

diff --git a/README.zh-CN.md b/README.zh-CN.md index 8d824557..34224590 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,6 +1,8 @@

English  ·  简体中文

+

OpenCode 补丁——会话保持、网关请求头与实时 Go 额度

+ OpenCode on DeepSeek Harness

dsh-opencode-patch

OpenCode on DeepSeek Harness
网关来源头 · 会话亲和 · 免费层工具回退 · 实时双模式额度计量

diff --git a/docs/detail.md b/docs/detail.md new file mode 100644 index 00000000..7e2866ee --- /dev/null +++ b/docs/detail.md @@ -0,0 +1,18 @@ +![OpenCode Patch — session affinity, gateway headers and live Go quota](./media/hero.svg) + +OpenCode's own CLI and this plugin make the same requests to the same gateway. The difference is what the gateway is told about them, and what the composer shows while they run: this patch restores the request identity the official CLI sends, keeps one session across turns, and puts the live Go quota beside the input box. + +## What you get + +| Surface | What it does | +| :-- | :-- | +| Gateway requests | Sends the official CLI's `User-Agent`, origin headers and workspace project tag, so Cloudflare's WAF checks pass | +| Sessions | One session id per conversation, derived and reused, so Zen and Go stop treating every turn as a stranger | +| Quota | The Go panel — 5-hour, weekly and monthly rings, the monthly allowance per model — and the Zen session spend | +| Routing | Models served on another protocol are routed to the endpoint that speaks it, instead of being refused | + +## Where the rest lives + +- The full reference — configuration, every control, the model tables — is in [README.md](https://github.com/viztor/dsh-opencode-patch#readme). +- The meter's numbers, and what each row means, are in [docs/quota-meter.md](https://github.com/viztor/dsh-opencode-patch/blob/main/docs/quota-meter.md). +- The endpoints, the headers and the measurements behind them are in [docs/redirect-planes.md](https://github.com/viztor/dsh-opencode-patch/blob/main/docs/redirect-planes.md). diff --git a/docs/detail.zh-CN.md b/docs/detail.zh-CN.md new file mode 100644 index 00000000..272ec535 --- /dev/null +++ b/docs/detail.zh-CN.md @@ -0,0 +1,18 @@ +![OpenCode 补丁——会话保持、网关请求头与实时 Go 额度](./media/hero.svg) + +OpenCode 自己的 CLI 和这个插件向同一个网关发的是同一批请求,差别在于网关被告知了什么、以及运行的时候输入框旁边显示了什么:本补丁恢复官方 CLI 的请求身份、让同一段对话共用一个会话、并把实时 Go 额度放在输入框旁。 + +## 你能得到什么 + +| 界面 | 作用 | +| :-- | :-- | +| 网关请求 | 发送官方 CLI 的 `User-Agent`、来源请求头和工作区项目标识,使 Cloudflare WAF 校验通过 | +| 会话 | 每段对话一个会话 id,推导并复用,Zen 与 Go 不再把每一轮都当成陌生人 | +| 额度 | Go 面板——5 小时、每周、每月三个环,以及按模型的月度额度——和 Zen 的会话消耗 | +| 路由 | 走另一种协议的模型会被送到说那种协议的端点,而不是被拒绝 | + +## 其余内容在哪儿 + +- 完整参考——配置、每个控件、模型表——在 [README.zh-CN.md](https://github.com/viztor/dsh-opencode-patch/blob/main/README.zh-CN.md)。 +- 计量表上每个数字、每一行在说什么,在 [docs/quota-meter.zh-CN.md](https://github.com/viztor/dsh-opencode-patch/blob/main/docs/quota-meter.zh-CN.md)。 +- 端点、请求头以及它们背后的实测,在 [docs/redirect-planes.md](https://github.com/viztor/dsh-opencode-patch/blob/main/docs/redirect-planes.md)。 diff --git a/docs/media/hero.svg b/docs/media/hero.svg new file mode 100644 index 00000000..040a6fc5 --- /dev/null +++ b/docs/media/hero.svg @@ -0,0 +1,55 @@ + + OpenCode Patch — session affinity, gateway headers and live Go quota for DeepSeek Harness + + + + + + + + + + + + + + + + + + + + + + + + + + + OpenCode Patch + Zen / Go gateway parity for DeepSeek Harness + + + + + + session affinity + + + + gateway headers + + + + live quota + + + + + + + 73% + + diff --git a/locale/en.json b/locale/en.json index aecfb0b7..f2c186f8 100644 --- a/locale/en.json +++ b/locale/en.json @@ -1,6 +1,7 @@ { "meta": { "title": "OpenCode Patch", - "description": "Restore OpenCode Zen/Go gateway headers and session affinity, and track live Go quota beside the composer." + "description": "Restore OpenCode Zen/Go gateway headers and session affinity, and track live Go quota beside the composer.", + "detail": "docs/detail.md" } } diff --git a/locale/zh.json b/locale/zh.json index ac444997..884bf8df 100644 --- a/locale/zh.json +++ b/locale/zh.json @@ -1,6 +1,7 @@ { "meta": { "title": "OpenCode 补丁", - "description": "恢复 OpenCode Zen/Go 网关请求头与会话保持,并在输入框旁实时显示 Go 额度。" + "description": "恢复 OpenCode Zen/Go 网关请求头与会话保持,并在输入框旁实时显示 Go 额度。", + "detail": "docs/detail.zh-CN.md" } } diff --git a/package.json b/package.json index b90155d3..d2c66e10 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,10 @@ "README.zh-CN.md", "CONTRIBUTING.md", "CHANGELOG.md", - "LICENSE" + "LICENSE", + "docs/detail.md", + "docs/detail.zh-CN.md", + "docs/media/hero.svg" ], "type": "module", "main": "lib/index.mjs", diff --git a/test/docs-pairing.test.ts b/test/docs-pairing.test.ts index a1c9a3f9..5143e15f 100644 --- a/test/docs-pairing.test.ts +++ b/test/docs-pairing.test.ts @@ -26,7 +26,7 @@ import { join } from "node:path"; import { describe, expect, it } from "vitest"; /** Every document published in two languages, by its base name. */ -const PAIRS = ["README", "docs/quota-meter"] as const; +const PAIRS = ["README", "docs/quota-meter", "docs/detail"] as const; /** Read one document of the repository. */ const read = (base: string, suffix: string): string => @@ -96,9 +96,10 @@ describe("the documents published in two languages", () => { it(`${base}: the two languages carry the same code blocks`, () => { // A command that exists in one language only is worse than a typo: the // reader following that language cannot run it. + // No minimum: a page with no commands is a legitimate page, and the + // pairing is what this asserts — not that every document has a fence. const en = fences(read(base, "")); const zh = fences(read(base, ".zh-CN")); - expect(en).toBeGreaterThan(0); expect(zh, `${base}.zh-CN`).toBe(en); }); @@ -126,7 +127,10 @@ describe("the documents published in two languages", () => { for (const base of PAIRS) { for (const suffix of ["", ".zh-CN"]) { const text = read(base, suffix); - expect(text.length, `${base}${suffix}.md`).toBeGreaterThan(1000); + // A floor that proves the file was READ, not a length policy: the + // featured-image page is three short sections and 808 characters, and a + // threshold tuned to it would fail the next short document for no reason. + expect(text.length, `${base}${suffix}.md`).toBeGreaterThan(200); expect(headings(text).length, `${base}${suffix}.md`).toBeGreaterThan(1); expect(tableRows(text).length, `${base}${suffix}.md`).toBeGreaterThan( 0 diff --git a/test/plugin-detail.test.ts b/test/plugin-detail.test.ts new file mode 100644 index 00000000..bab2aa39 --- /dev/null +++ b/test/plugin-detail.test.ts @@ -0,0 +1,133 @@ +/** + * What the plugin page shows about this package. + * + * The page renders ONE document inline: the Markdown that `meta.detail` in + * `locale/.json` names, or — when nothing declares one — the whole + * `README.md`. This package shipped no `meta.detail`, so its page rendered 461 + * lines of engineering notes above the settings card, and the featured image it + * could have had was nothing more than an icon. + * + * The host's rules, from `@deepseek-ai/dsh-plugin-detail`: + * + * - `meta.detail` is a `/`-separated Markdown path relative to the package root, + * and a path that does not exist is an `invalid` failure — a blank page, not a + * fallback; + * - an image the document writes is served from the package, so a path it names + * has to be shipped; + * - the document is rendered INLINE, which is why its length is part of the + * contract rather than a taste question. + * + * @module test/plugin-detail.test + */ + +import { readFileSync, statSync } from "node:fs"; +import { join } from "node:path"; + +import { describe, expect, it } from "vitest"; + +const ROOT = process.cwd(); + +/** Read a text file of the package, failing loudly when it is absent. */ +const read = (relative: string): string => + readFileSync(join(ROOT, relative), "utf8"); + +interface LocaleMeta { + detail?: string; + title?: string; +} + +/** The `meta` block of one locale file. */ +const metaOf = (locale: string): LocaleMeta => { + const parsed = JSON.parse(read(`locale/${locale}.json`)) as { + meta?: LocaleMeta; + }; + return parsed.meta ?? {}; +}; + +/** The paths `package.json` tells npm to ship. */ +const shippedFiles = (): string[] => + (JSON.parse(read("package.json")) as { files?: string[] }).files ?? []; + +/** The locales this package ships, by file name. */ +const LOCALES = ["en", "zh"] as const; + +describe("the document the plugin page renders", () => { + it("is declared by every locale, so no language falls back to the README", () => { + // The fallback is the whole README. A language that declares nothing gets + // that, which is the state this package was in: every page, every language. + for (const locale of LOCALES) { + const { detail } = metaOf(locale); + expect(detail, `locale/${locale}.json meta.detail`).toBeTruthy(); + expect(detail).toMatch(/\.(?:md|markdown)$/u); + // Relative, `/`-separated, and inside the package: the host refuses + // anything else as `invalid`, which renders nothing rather than the README. + expect(detail?.startsWith("/")).toBe(false); + expect(detail).not.toContain("\\"); + expect(detail?.split("/")).not.toContain(".."); + } + }); + + it("names a file that really exists, and that npm really ships", () => { + // Two independent ways to be broken: the file is absent (the host answers + // `invalid`), or the file is present in the repo and missing from the + // tarball (the page works here and not for a user). + const shipped = new Set(shippedFiles()); + for (const locale of LOCALES) { + const detail = metaOf(locale).detail ?? ""; + expect(statSync(join(ROOT, detail)).size, detail).toBeGreaterThan(0); + expect( + shipped.has(detail), + `${detail} must be in package.json files` + ).toBe(true); + } + }); + + it("opens with the featured image, and ships it", () => { + // The page's whole header is this document, so the image has to be the first + // thing in it — and the detail-image route serves package-relative paths, so + // the file has to be there. + const shipped = new Set(shippedFiles()); + for (const locale of LOCALES) { + const detail = metaOf(locale).detail ?? ""; + const [first] = read(detail).split("\n"); + const image = /^!\[[^\]]*\]\(([^)]+)\)$/u.exec(first ?? "")?.[1]; + expect(image, `${detail} must open with an image`).toBeTruthy(); + const relative = image?.replace(/^\.\//u, "") ?? ""; + const resolved = join(detail, "..", relative); + expect( + statSync(resolved).size, + `${resolved} must exist beside the document` + ).toBeGreaterThan(0); + expect( + shipped.has(join(detail, "..", relative)), + `${resolved} must be in package.json files` + ).toBe(true); + } + }); + + it("stays short enough to read above the settings card", () => { + // Not a style rule: the document is rendered inline, and the card is below + // it. The state this replaced was a 461-line README, and the report was + // "why do I have such a big readme here". + for (const locale of LOCALES) { + const detail = metaOf(locale).detail ?? ""; + const lines = read(detail).split("\n").length; + expect(lines, `${detail} is ${lines} lines`).toBeLessThanOrEqual(40); + } + }); + + it("names the pages a reader needs next", () => { + // A short page has to hand off, or it is just a shorter dead end: the full + // reference, the meter's numbers, and the endpoints behind them. + const en = read(metaOf("en").detail ?? ""); + for (const target of [ + "github.com/viztor/dsh-opencode-patch", + "docs/quota-meter.md", + "docs/redirect-planes.md", + ]) { + expect(en, `the English detail document must link ${target}`).toContain( + target + ); + } + }); +});