@dan-ai-studio/dshopencodego 0.1.83 → 0.1.85
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.en.md +8 -5
- package/README.md +8 -5
- package/lib/client.js +38 -0
- package/lib/index.js +1313 -34
- package/lib/types/catalog/constants.d.ts +13 -0
- package/lib/types/catalog/index.d.ts +34 -2
- package/lib/types/catalog/snapshot-cache.d.ts +38 -6
- package/package.json +3 -1
package/README.en.md
CHANGED
|
@@ -95,7 +95,7 @@ Configuration lives in the profile's `cordis.patch.yml`:
|
|
|
95
95
|
baseURL: https://opencode.ai/inference/go/v1 # default (OpenCode Go endpoint)
|
|
96
96
|
# http(s) only, and no query or fragment; writing ?x=1 is refused when the profile is saved
|
|
97
97
|
# or https://opencode.ai/inference/v1 (Console main endpoint, pay-as-you-go/free models)
|
|
98
|
-
refreshMinutes: 60 # live catalog TTL, 1 ~ 10080 (7 days)
|
|
98
|
+
refreshMinutes: 60 # live catalog TTL, 1 ~ 10080 (7 days); stale past 24 hours it refreshes in the background anyway
|
|
99
99
|
streamIdleTimeoutMs: 300000 # stream idle timeout: longest silence between events (ms)
|
|
100
100
|
keepAliveTimeoutMs: 600000 # connection kept idle between requests (ms, 1 ~ 1800000)
|
|
101
101
|
# the timer restarts on every answer, so a connection in
|
|
@@ -199,8 +199,9 @@ The gateway's `/v1/models` answers only *which* models exist, never *how* to cal
|
|
|
199
199
|
- a model the provider **adds** appears on the next refresh; if no metadata source describes it, it is labelled as inferred and can be pinned with `modelProtocols`;
|
|
200
200
|
- a model the provider **retires** disappears on the next refresh;
|
|
201
201
|
- the bundled catalog is **frozen at the plugin's version** and affects only the quality of the "how to call it" decision, never *which* models exist.
|
|
202
|
+
- **fresh installs do not wait**: the bundle carries a directory snapshot from publish time (`src/catalog/seed.snapshot.json`), which answers at once when the disk cache is empty and refreshes behind it; past 24 hours without a refresh counts as stale and refreshes in the background too, whatever the TTL says. A timer re-checks on a live host, so long-running processes stay current without a request to trigger it.
|
|
202
203
|
|
|
203
|
-
When the gateway is **temporarily unavailable**, the plugin keeps the last listing that succeeded, marks it stale on the page, and retries in the background. The retry rate is governed by a **five-minute back-off** rather than by the TTL, so a gateway that comes back is picked up within five minutes; while it stays down it is asked once, not once per model request. The settings page's Recheck button revalidates at any time and is not bound by that back-off.
|
|
204
|
+
When the gateway is **temporarily unavailable**, the plugin keeps the last listing that succeeded, marks it stale on the page, and retries in the background. The retry rate is governed by a **five-minute back-off** rather than by the TTL, so a gateway that comes back is picked up within five minutes; while it stays down it is asked once, not once per model request. When the settings page opens on a stale reading, it follows up on its own (about every 5 seconds) until the reading turns current — the follow-ups are cheap local reads, and the host's back-off still decides when the gateway is really asked — so there is no need to press Recheck by hand. The settings page's Recheck button revalidates at any time and is not bound by that back-off.
|
|
204
205
|
|
|
205
206
|
The only thing that waits on a plugin release is a missing wire quirk in the pi-ai catalog (DeepSeek's `thinkingFormat`, say) — the model itself is already usable.
|
|
206
207
|
|
|
@@ -285,17 +286,19 @@ npm run lint # oxlint: static checks (the release workflow runs this too,
|
|
|
285
286
|
npm run typecheck # both halves: host and client
|
|
286
287
|
npm run build # build first: emits lib/index.js and lib/client.js
|
|
287
288
|
npm test # contract tests against a real local HTTP mock gateway
|
|
289
|
+
npm run seed # refresh the bundled catalog seed before a release (see "Releasing")
|
|
288
290
|
```
|
|
289
291
|
|
|
290
292
|
**Order matters**: `npm run build` must come before `npm test` — `tests/pack-contents.spec.ts` walks every `exports` target, and whether `lib/` exists decides whether that check runs or is silently skipped (it does not fail, it just stops protecting you). `package.json` no longer carries `prepare`, so `npm ci` will not build for you; all three CI workflows run `npm run build` explicitly.
|
|
291
293
|
|
|
292
|
-
The suite covers configuration validation, the protocol ladder, gateway and online metadata, the catalog projection, the adapter on the wire (all three protocols), Remote contracts and services, session-header invariants, usage windows and the meter, the client controller and usage pill, both halves' mounting and registration, the error mapping for stream idle timeouts and aborts, the exit-code contract of the verification scripts, and several **structural invariants** (inject declarations match the source's reads, peer coverage holds both ways, the READMEs match the manifest, and the READMEs' claims about the upstream match the upstream actually installed). That is **
|
|
294
|
+
The suite covers configuration validation, the protocol ladder, gateway and online metadata, the catalog projection, the adapter on the wire (all three protocols), Remote contracts and services, session-header invariants, usage windows and the meter, the client controller and usage pill, both halves' mounting and registration, the error mapping for stream idle timeouts and aborts, the exit-code contract of the verification scripts, and several **structural invariants** (inject declarations match the source's reads, peer coverage holds both ways, the READMEs match the manifest, and the READMEs' claims about the upstream match the upstream actually installed). That is **60 test files** — a number `tests/readme-consistency.spec.ts` checks against the `tests/` directory, so it cannot drift.
|
|
293
295
|
|
|
294
296
|
## Releasing (maintainers)
|
|
295
297
|
|
|
296
298
|
1. **Version**: align `package.json`'s `version` with the tag you are about to push (`v<version>`); CI checks the asset name against the tag.
|
|
297
|
-
2. **
|
|
298
|
-
3. **
|
|
299
|
+
2. **Seed**: run `npm run seed` to refresh `src/catalog/seed.snapshot.json` (public endpoints, no key) and commit it, so fresh installs start with a usable catalog.
|
|
300
|
+
3. **GitHub Release**: commit and push `main`, then push the tag. `release.yml` runs the tests, `npm pack`s, and uploads the tarball as the release asset.
|
|
301
|
+
4. **npm (trusted publishing / OIDC, no token)**: `.github/workflows/publish-npm.yml` is manual-only and authenticates through GitHub Actions' OIDC — this repository **has no `NPM_TOKEN` and needs none**. Run it with:
|
|
299
302
|
|
|
300
303
|
```sh
|
|
301
304
|
gh workflow run publish-npm --repo dan-ai-studio/dshopencodego -f tag=v<version>
|
package/README.md
CHANGED
|
@@ -95,7 +95,7 @@ DSH 桌面端是 Electron 壳 + 完整的 dsh Web 应用,本插件对它同样
|
|
|
95
95
|
baseURL: https://opencode.ai/inference/go/v1 # 默认值(OpenCode Go 端点)
|
|
96
96
|
# 只能是不带 query / fragment 的 http(s) 地址;写 ?x=1 会在保存配置时被拒
|
|
97
97
|
# 也可指定为 https://opencode.ai/inference/v1(Console 主端点,含按量/免费模型)
|
|
98
|
-
refreshMinutes: 60 # 目录缓存时长,1 ~ 10080(7
|
|
98
|
+
refreshMinutes: 60 # 目录缓存时长,1 ~ 10080(7 天);超过 24 小时未刷新会被视为过期并在后台更新
|
|
99
99
|
streamIdleTimeoutMs: 300000 # 流空闲超时:两次流事件之间的最长静默(毫秒)
|
|
100
100
|
keepAliveTimeoutMs: 600000 # 连接空闲保留:两次请求之间连接留多久(毫秒,1 ~ 1800000)
|
|
101
101
|
# 每次收到回答后重新计时,所以常用时连接不会被断开;
|
|
@@ -198,8 +198,9 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
|
|
|
198
198
|
- 官方**新增**模型 → 下次刷新即出现,协议/容量若不在内置目录中则标为"推断"并可被 `modelProtocols` 覆盖;
|
|
199
199
|
- 官方**下线**模型 → 下次刷新即消失;
|
|
200
200
|
- 内置目录**冻结**在插件版本里,仅影响"怎么调"的推断质量,不影响"有哪些"。
|
|
201
|
+
- **首次安装不等待**:包里带了一份发布时生成的目录快照(`src/catalog/seed.snapshot.json`,28 个模型上下),磁盘缓存为空时直接用它回答并在后台刷新;过期(超 24 小时未刷新)同样后台刷新,绝不为等网络拦住请求。插件挂载后还会定时触发一次,保证长期运行的进程目录常新。
|
|
201
202
|
|
|
202
|
-
网关**临时不可用**时,插件保留上次成功获取的列表并在页面上标注陈旧,同时在后台重试:重试由**5 分钟的退避**控制,而不是等满一个 TTL,所以网关恢复后 5
|
|
203
|
+
网关**临时不可用**时,插件保留上次成功获取的列表并在页面上标注陈旧,同时在后台重试:重试由**5 分钟的退避**控制,而不是等满一个 TTL,所以网关恢复后 5 分钟内列表就会更新;网关持续不可用时,它只被问一次,不会每次模型请求都去问一遍。设置页打开时若读到的是旧目录,会自动跟进重读直到拿到最新的(约每 5 秒一次读本地快照;何时真问网关由 Host 的 5 分钟退避决定,不会因此多发请求),不用手动点「重新检查」;「重新检查」随时可以强制重验,不受这个退避限制。
|
|
203
204
|
|
|
204
205
|
只有当 pi-ai 内置目录**缺了某个模型的线怪癖**(如 DeepSeek 的 `thinkingFormat`)时,才需要等插件同步 pi-ai 的新版本——而模型本身早已可用。
|
|
205
206
|
|
|
@@ -284,17 +285,19 @@ npm run lint # oxlint:静态检查(发布流程也会跑,失败即
|
|
|
284
285
|
npm run typecheck # host 与 client 两半
|
|
285
286
|
npm run build # 先构建:产出 lib/index.js 与 lib/client.js
|
|
286
287
|
npm test # 契约测试:真实 HTTP mock 网关,断言请求头/体、目录阶梯、用量
|
|
288
|
+
npm run seed # 发版前刷新捆绑的目录种子(见「发布」)
|
|
287
289
|
```
|
|
288
290
|
|
|
289
291
|
**顺序有讲究**:`npm run build` 要排在 `npm test` 之前——`tests/pack-contents.spec.ts` 会遍历 `exports` 的每个目标,`lib/` 存在与否决定这条检查是执行还是被跳过(不报错,但会少一层保护)。`package.json` 已移除 `prepare`,因此 `npm ci` 不会再替你隐式构建;三个 CI workflow 都显式跑了 `npm run build`。
|
|
290
292
|
|
|
291
|
-
测试覆盖配置校验、协议阶梯、网关与在线元数据、目录投影、适配器(三种协议的线上请求)、Remote 契约与服务、会话头不变量、用量窗口与计量、客户端控制器与用量组件、两半的挂载与登记、流空闲超时与中止的错误映射、验证脚本的退出码契约、以及几条**结构性不变量**(inject 声明与源码读取一致、peer 覆盖双向、README 与 manifest 一致、README 对上游的断言与实装的上游一致)。共 **
|
|
293
|
+
测试覆盖配置校验、协议阶梯、网关与在线元数据、目录投影、适配器(三种协议的线上请求)、Remote 契约与服务、会话头不变量、用量窗口与计量、客户端控制器与用量组件、两半的挂载与登记、流空闲超时与中止的错误映射、验证脚本的退出码契约、以及几条**结构性不变量**(inject 声明与源码读取一致、peer 覆盖双向、README 与 manifest 一致、README 对上游的断言与实装的上游一致)。共 **60 个测试文件**——这个数字由 `tests/readme-consistency.spec.ts` 对着 `tests/` 目录校验,不会漂移。
|
|
292
294
|
|
|
293
295
|
## 发布(维护者)
|
|
294
296
|
|
|
295
297
|
1. **版本**:把 `package.json` 的 `version` 与即将打出的 tag 对齐(`v<版本>`)。CI 会校验资产名与 tag。
|
|
296
|
-
2.
|
|
297
|
-
3. **
|
|
298
|
+
2. **种子**:运行 `npm run seed` 刷新 `src/catalog/seed.snapshot.json`(公开接口,无需 key)并一并提交,让新装用户开箱即有目录可用。
|
|
299
|
+
3. **GitHub Release**:提交并推送 `main`,打 tag 推送。`release.yml` 会跑测试、`npm pack` 并把 tarball 作为 Release 资产上传。
|
|
300
|
+
4. **npm(Trusted Publishing / OIDC,无需令牌)**:`.github/workflows/publish-npm.yml` 仅手动触发,认证走 GitHub Actions 的 OIDC——本仓库**没有也不需要** `NPM_TOKEN`。运行方式:
|
|
298
301
|
|
|
299
302
|
```sh
|
|
300
303
|
gh workflow run publish-npm --repo dan-ai-studio/dshopencodego -f tag=v<版本>
|
package/lib/client.js
CHANGED
|
@@ -31,6 +31,7 @@ var BACKGROUND_RETRY_BACKOFF_MS = 5 * 6e4;
|
|
|
31
31
|
var MODEL_LISTING_MAX_BYTES = 1024 * 1024;
|
|
32
32
|
var MODEL_METADATA_MAX_BYTES = 16 * 1024 * 1024;
|
|
33
33
|
var UNKNOWN_MODEL_PROBE_COOLDOWN_MS = 5 * 6e4;
|
|
34
|
+
var MAX_CATALOG_STALENESS_MS = 24 * 60 * 6e4;
|
|
34
35
|
|
|
35
36
|
// src/catalog/metadata.ts
|
|
36
37
|
var INPUT_MODALITIES = ["text", "image", "audio", "video", "pdf"];
|
|
@@ -1048,6 +1049,7 @@ var section_default = { "badge": "d7lz0W_badge", "block": "d7lz0W_block", "butto
|
|
|
1048
1049
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1049
1050
|
var API_KEY_REF = "OPENCODE_GO_API_KEY";
|
|
1050
1051
|
var VISIBILITY_WRITE_DELAY_MS = 250;
|
|
1052
|
+
var FOLLOW_UP_MS = 5e3;
|
|
1051
1053
|
var INITIAL = {
|
|
1052
1054
|
loading: true,
|
|
1053
1055
|
reading: void 0,
|
|
@@ -1098,6 +1100,8 @@ var SectionController = class _SectionController {
|
|
|
1098
1100
|
pendingVisibility;
|
|
1099
1101
|
/** Timer of the coalescing window, when one is armed. */
|
|
1100
1102
|
visibilityTimer;
|
|
1103
|
+
/** The armed stale-reading follow-up, if one is waiting. */
|
|
1104
|
+
followTimer;
|
|
1101
1105
|
constructor(services) {
|
|
1102
1106
|
this.services = services;
|
|
1103
1107
|
const scope = services.scope;
|
|
@@ -1125,6 +1129,10 @@ var SectionController = class _SectionController {
|
|
|
1125
1129
|
dispose() {
|
|
1126
1130
|
this.listeners.clear();
|
|
1127
1131
|
for (const dispose of this.disposers.splice(0)) dispose();
|
|
1132
|
+
if (this.followTimer !== void 0) {
|
|
1133
|
+
clearTimeout(this.followTimer);
|
|
1134
|
+
this.followTimer = void 0;
|
|
1135
|
+
}
|
|
1128
1136
|
const scope = this.services.scope;
|
|
1129
1137
|
if (this.visibilityTimer !== void 0 && scope !== void 0) void this.flushVisibility(scope);
|
|
1130
1138
|
}
|
|
@@ -1139,6 +1147,7 @@ var SectionController = class _SectionController {
|
|
|
1139
1147
|
const result = refresh ? await this.services.remote.opencodeGoCatalog.refresh() : await this.services.remote.opencodeGoCatalog.read();
|
|
1140
1148
|
if (!result.ok) throw result.error;
|
|
1141
1149
|
this.set({ loading: false, reading: result.value, failure: void 0 });
|
|
1150
|
+
if (result.value.cached === true) this.followReading();
|
|
1142
1151
|
} catch (error) {
|
|
1143
1152
|
this.set({
|
|
1144
1153
|
loading: false,
|
|
@@ -1146,6 +1155,35 @@ var SectionController = class _SectionController {
|
|
|
1146
1155
|
});
|
|
1147
1156
|
}
|
|
1148
1157
|
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Re-read a stale reading until it turns live, then stop.
|
|
1160
|
+
*
|
|
1161
|
+
* Only one follow-up is ever armed. The Host decides the real refresh
|
|
1162
|
+
* rhythm, so this never hammers the gateway — a follow-up is a plain
|
|
1163
|
+
* `read`, and the Host's own back-off decides how often the gateway is
|
|
1164
|
+
* really asked. A failed or still-stale read re-arms; only a live reading
|
|
1165
|
+
* or a disposal lets it lapse.
|
|
1166
|
+
*/
|
|
1167
|
+
followReading() {
|
|
1168
|
+
if (this.followTimer !== void 0) return;
|
|
1169
|
+
const timer = setTimeout(() => {
|
|
1170
|
+
this.followTimer = void 0;
|
|
1171
|
+
void (async () => {
|
|
1172
|
+
try {
|
|
1173
|
+
const result = await this.services.remote.opencodeGoCatalog.read();
|
|
1174
|
+
if (result.ok) {
|
|
1175
|
+
this.set({ reading: result.value, failure: void 0 });
|
|
1176
|
+
if (result.value.cached !== true) return;
|
|
1177
|
+
}
|
|
1178
|
+
} catch {
|
|
1179
|
+
}
|
|
1180
|
+
this.followReading();
|
|
1181
|
+
})();
|
|
1182
|
+
}, FOLLOW_UP_MS);
|
|
1183
|
+
const releasable = timer;
|
|
1184
|
+
if (typeof releasable.unref === "function") releasable.unref();
|
|
1185
|
+
this.followTimer = timer;
|
|
1186
|
+
}
|
|
1149
1187
|
async loadKey() {
|
|
1150
1188
|
try {
|
|
1151
1189
|
const described = await this.services.remote.credentials.describe([API_KEY_REF]);
|