@dan-ai-studio/dshopencodego 0.1.51 → 0.1.53
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 +1 -1
- package/README.md +1 -1
- package/lib/client.js +4 -0
- package/lib/index.js +245 -28
- package/lib/types/catalog/go-doc-cache.d.ts +43 -0
- package/lib/types/catalog/index.d.ts +51 -1
- package/lib/types/catalog/reading.d.ts +10 -0
- package/lib/types/catalog/snapshot-cache.d.ts +50 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -247,7 +247,7 @@ npm test # contract tests against a real local HTTP mock gateway
|
|
|
247
247
|
|
|
248
248
|
**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.
|
|
249
249
|
|
|
250
|
-
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 **
|
|
250
|
+
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 **52 test files** — a number `tests/readme-consistency.spec.ts` checks against the `tests/` directory, so it cannot drift.
|
|
251
251
|
|
|
252
252
|
## Releasing (maintainers)
|
|
253
253
|
|
package/README.md
CHANGED
|
@@ -247,7 +247,7 @@ npm test # 契约测试:真实 HTTP mock 网关,断言请求头/
|
|
|
247
247
|
|
|
248
248
|
**顺序有讲究**:`npm run build` 要排在 `npm test` 之前——`tests/pack-contents.spec.ts` 会遍历 `exports` 的每个目标,`lib/` 存在与否决定这条检查是执行还是被跳过(不报错,但会少一层保护)。`package.json` 已移除 `prepare`,因此 `npm ci` 不会再替你隐式构建;三个 CI workflow 都显式跑了 `npm run build`。
|
|
249
249
|
|
|
250
|
-
测试覆盖配置校验、协议阶梯、网关与在线元数据、目录投影、适配器(三种协议的线上请求)、Remote 契约与服务、会话头不变量、用量窗口与计量、客户端控制器与用量组件、两半的挂载与登记、流空闲超时与中止的错误映射、验证脚本的退出码契约、以及几条**结构性不变量**(inject 声明与源码读取一致、peer 覆盖双向、README 与 manifest 一致、README 对上游的断言与实装的上游一致)。共 **
|
|
250
|
+
测试覆盖配置校验、协议阶梯、网关与在线元数据、目录投影、适配器(三种协议的线上请求)、Remote 契约与服务、会话头不变量、用量窗口与计量、客户端控制器与用量组件、两半的挂载与登记、流空闲超时与中止的错误映射、验证脚本的退出码契约、以及几条**结构性不变量**(inject 声明与源码读取一致、peer 覆盖双向、README 与 manifest 一致、README 对上游的断言与实装的上游一致)。共 **52 个测试文件**——这个数字由 `tests/readme-consistency.spec.ts` 对着 `tests/` 目录校验,不会漂移。
|
|
251
251
|
|
|
252
252
|
## 发布(维护者)
|
|
253
253
|
|
package/lib/client.js
CHANGED
|
@@ -129,6 +129,7 @@ function parseCatalogReading(value) {
|
|
|
129
129
|
...typeof row["error"] === "string" ? { error: row["error"] } : {},
|
|
130
130
|
degradations: parseDegradations(row["degradations"]),
|
|
131
131
|
...typeof row["factsCachedAt"] === "number" ? { factsCachedAt: row["factsCachedAt"] } : {},
|
|
132
|
+
...row["cached"] === true ? { cached: true } : {},
|
|
132
133
|
fetchedAtMs: row["fetchedAtMs"],
|
|
133
134
|
quotaSource: quotaSource ?? "seed",
|
|
134
135
|
counts: {
|
|
@@ -337,6 +338,7 @@ var en = {
|
|
|
337
338
|
catalogUnconfigured: "not configurable",
|
|
338
339
|
quotaSeed: "Allowances are the built-in snapshot, transcribed",
|
|
339
340
|
factsCached: "Model metadata is from the last successful reading,",
|
|
341
|
+
catalogRefreshing: "Showing the last known catalog; a refresh is running in the background.",
|
|
340
342
|
refreshLabel: "Catalog refresh (minutes)",
|
|
341
343
|
refreshHint: "How long requests and the model picker reuse one listing.",
|
|
342
344
|
writeRejected: "The settings write was rejected (the namespace was reloaded) \u2014 try again",
|
|
@@ -437,6 +439,7 @@ var zh = {
|
|
|
437
439
|
catalogUnconfigured: "\u65E0\u6CD5\u914D\u7F6E",
|
|
438
440
|
quotaSeed: "\u989D\u5EA6\u6765\u81EA\u5185\u7F6E\u5FEB\u7167\uFF0C\u8F6C\u5199\u4E8E",
|
|
439
441
|
factsCached: "\u6A21\u578B\u5143\u6570\u636E\u6765\u81EA\u4E0A\u6B21\u6210\u529F\u83B7\u53D6\uFF0C",
|
|
442
|
+
catalogRefreshing: "\u663E\u793A\u4E0A\u6B21\u5DF2\u77E5\u7684\u76EE\u5F55\uFF0C\u540E\u53F0\u6B63\u5728\u5237\u65B0\u3002",
|
|
440
443
|
refreshLabel: "\u76EE\u5F55\u5237\u65B0\uFF08\u5206\u949F\uFF09",
|
|
441
444
|
refreshHint: "\u8BF7\u6C42\u4E0E\u6A21\u578B\u9009\u62E9\u5668\u590D\u7528\u540C\u4E00\u4EFD\u5217\u8868\u7684\u65F6\u957F\u3002",
|
|
442
445
|
writeRejected: "\u8BBE\u7F6E\u5199\u5165\u88AB\u62D2\u7EDD\uFF08\u5DF2\u91CD\u65B0\u8F7D\u5165\uFF09\uFF0C\u8BF7\u91CD\u8BD5",
|
|
@@ -1252,6 +1255,7 @@ function Section({ controller, t, getLocale }) {
|
|
|
1252
1255
|
" ",
|
|
1253
1256
|
new Date(reading.factsCachedAt).toISOString().slice(0, 10)
|
|
1254
1257
|
] }),
|
|
1258
|
+
reading !== void 0 && reading.cached === true && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: section_default.hint, children: t("catalogRefreshing") }),
|
|
1255
1259
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: section_default.row, children: [
|
|
1256
1260
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: section_default.hint, htmlFor: "dshopencodego-refresh", children: t("refreshLabel") }),
|
|
1257
1261
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
package/lib/index.js
CHANGED
|
@@ -17788,8 +17788,8 @@ function getBunSandboxEnvValue(name2) {
|
|
|
17788
17788
|
if (procEnvCache === null) {
|
|
17789
17789
|
procEnvCache = /* @__PURE__ */ new Map();
|
|
17790
17790
|
try {
|
|
17791
|
-
const { readFileSync:
|
|
17792
|
-
const data =
|
|
17791
|
+
const { readFileSync: readFileSync5 } = __require("node:fs");
|
|
17792
|
+
const data = readFileSync5("/proc/self/environ", "utf-8");
|
|
17793
17793
|
for (const entry of data.split("\0")) {
|
|
17794
17794
|
const idx = entry.indexOf("=");
|
|
17795
17795
|
if (idx > 0) {
|
|
@@ -42700,6 +42700,45 @@ function rememberPaths(cacheDir, discovered) {
|
|
|
42700
42700
|
return merged;
|
|
42701
42701
|
}
|
|
42702
42702
|
|
|
42703
|
+
// src/catalog/go-doc-cache.ts
|
|
42704
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
42705
|
+
import { join as join3 } from "node:path";
|
|
42706
|
+
var CACHE_FILE2 = "go-doc-cache.json";
|
|
42707
|
+
var CACHE_VERSION2 = 1;
|
|
42708
|
+
function readDocCache(cacheDir) {
|
|
42709
|
+
if (cacheDir === void 0) return void 0;
|
|
42710
|
+
try {
|
|
42711
|
+
const parsed = JSON.parse(readFileSync2(join3(cacheDir, CACHE_FILE2), "utf8"));
|
|
42712
|
+
if (parsed.version !== CACHE_VERSION2) return void 0;
|
|
42713
|
+
if (typeof parsed.url !== "string" || typeof parsed.body !== "string") return void 0;
|
|
42714
|
+
if (parsed.body.length === 0) return void 0;
|
|
42715
|
+
return {
|
|
42716
|
+
url: parsed.url,
|
|
42717
|
+
etag: typeof parsed.etag === "string" ? parsed.etag : void 0,
|
|
42718
|
+
body: parsed.body,
|
|
42719
|
+
savedAt: typeof parsed.savedAt === "number" ? parsed.savedAt : 0
|
|
42720
|
+
};
|
|
42721
|
+
} catch {
|
|
42722
|
+
return void 0;
|
|
42723
|
+
}
|
|
42724
|
+
}
|
|
42725
|
+
function writeDocCache(cacheDir, entry) {
|
|
42726
|
+
if (cacheDir === void 0 || entry.body.length === 0) return;
|
|
42727
|
+
try {
|
|
42728
|
+
mkdirSync2(cacheDir, { recursive: true });
|
|
42729
|
+
const file = {
|
|
42730
|
+
version: CACHE_VERSION2,
|
|
42731
|
+
url: entry.url,
|
|
42732
|
+
...entry.etag === void 0 ? {} : { etag: entry.etag },
|
|
42733
|
+
body: entry.body,
|
|
42734
|
+
savedAt: Date.now()
|
|
42735
|
+
};
|
|
42736
|
+
writeFileSync2(join3(cacheDir, CACHE_FILE2), `${JSON.stringify(file)}
|
|
42737
|
+
`, "utf8");
|
|
42738
|
+
} catch {
|
|
42739
|
+
}
|
|
42740
|
+
}
|
|
42741
|
+
|
|
42703
42742
|
// src/catalog/go-doc.ts
|
|
42704
42743
|
var GO_DOC_URLS = [
|
|
42705
42744
|
"https://raw.githubusercontent.com/anomalyco/opencode/dev/services/www/src/docs/content/console/go.mdx",
|
|
@@ -42778,36 +42817,47 @@ function parseGoDocument(markdown) {
|
|
|
42778
42817
|
}
|
|
42779
42818
|
return { quotas, protocols };
|
|
42780
42819
|
}
|
|
42781
|
-
async function readSource(url, signal) {
|
|
42820
|
+
async function readSource(url, signal, etag) {
|
|
42782
42821
|
const timeout = AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS);
|
|
42783
42822
|
try {
|
|
42784
42823
|
const response = await fetch(url, {
|
|
42785
42824
|
redirect: "error",
|
|
42786
|
-
headers: {
|
|
42825
|
+
headers: {
|
|
42826
|
+
...attributionHeaders2(),
|
|
42827
|
+
accept: "text/plain",
|
|
42828
|
+
"cache-control": "no-cache",
|
|
42829
|
+
...etag === void 0 ? {} : { "if-none-match": etag }
|
|
42830
|
+
},
|
|
42787
42831
|
signal: signal === void 0 ? timeout : AbortSignal.any([signal, timeout])
|
|
42788
42832
|
});
|
|
42833
|
+
if (response.status === 304) {
|
|
42834
|
+
await response.body?.cancel().catch(() => {
|
|
42835
|
+
});
|
|
42836
|
+
return { url, notModified: true };
|
|
42837
|
+
}
|
|
42789
42838
|
if (!response.ok) {
|
|
42790
42839
|
await response.body?.cancel().catch(() => {
|
|
42791
42840
|
});
|
|
42792
|
-
return { failure: `${url} answered HTTP ${response.status}` };
|
|
42841
|
+
return { url, failure: `${url} answered HTTP ${response.status}` };
|
|
42793
42842
|
}
|
|
42794
|
-
const
|
|
42843
|
+
const body = await readBoundedText(response, GO_DOC_MAX_BYTES);
|
|
42844
|
+
const document = parseGoDocument(body);
|
|
42795
42845
|
if (document.quotas.size === 0 && document.protocols.size === 0) {
|
|
42796
|
-
return { failure: `${url} carried neither an allowance table nor an endpoint table` };
|
|
42846
|
+
return { url, failure: `${url} carried neither an allowance table nor an endpoint table` };
|
|
42797
42847
|
}
|
|
42798
|
-
return { document };
|
|
42848
|
+
return { url, document, body, etag: response.headers.get("etag") ?? void 0 };
|
|
42799
42849
|
} catch (error) {
|
|
42800
|
-
return { failure: `${url}: ${error instanceof Error ? error.message : String(error)}` };
|
|
42850
|
+
return { url, failure: `${url}: ${error instanceof Error ? error.message : String(error)}` };
|
|
42801
42851
|
}
|
|
42802
42852
|
}
|
|
42803
42853
|
async function fetchGoDocument(signal, options = {}) {
|
|
42804
42854
|
const failures = [];
|
|
42805
|
-
const known = await readSources(GO_DOC_URLS, signal);
|
|
42855
|
+
const known = await readSources(GO_DOC_URLS, signal, options.cacheDir);
|
|
42806
42856
|
if (known.document !== void 0) return known.document;
|
|
42807
42857
|
failures.push(...known.failures);
|
|
42808
42858
|
const remembered = readCachedPaths(options.cacheDir).filter((path5) => !GO_DOC_URLS.some((url) => url.endsWith(path5)));
|
|
42809
42859
|
if (remembered.length > 0) {
|
|
42810
|
-
const cached = await readSources(remembered.flatMap(mirrorsOf), signal);
|
|
42860
|
+
const cached = await readSources(remembered.flatMap(mirrorsOf), signal, options.cacheDir);
|
|
42811
42861
|
if (cached.document !== void 0) return cached.document;
|
|
42812
42862
|
failures.push(...cached.failures);
|
|
42813
42863
|
}
|
|
@@ -42821,7 +42871,7 @@ async function fetchGoDocument(signal, options = {}) {
|
|
|
42821
42871
|
if (fresh.length === 0) {
|
|
42822
42872
|
failures.push("the provider repository named the path that was already tried");
|
|
42823
42873
|
} else {
|
|
42824
|
-
const result = await readSources(fresh, signal);
|
|
42874
|
+
const result = await readSources(fresh, signal, options.cacheDir);
|
|
42825
42875
|
if (result.document !== void 0) return result.document;
|
|
42826
42876
|
failures.push(...result.failures);
|
|
42827
42877
|
}
|
|
@@ -42831,12 +42881,32 @@ async function fetchGoDocument(signal, options = {}) {
|
|
|
42831
42881
|
"DOCUMENT_UNAVAILABLE"
|
|
42832
42882
|
);
|
|
42833
42883
|
}
|
|
42834
|
-
async function readSources(urls, signal) {
|
|
42835
|
-
const attempts = await Promise.all(urls.map((url) => readSource(url, signal)));
|
|
42884
|
+
async function readSources(urls, signal, cacheDir) {
|
|
42836
42885
|
const failures = [];
|
|
42886
|
+
const cached = readDocCache(cacheDir);
|
|
42887
|
+
if (cached !== void 0 && urls.includes(cached.url)) {
|
|
42888
|
+
const attempt = await readSource(cached.url, signal, cached.etag);
|
|
42889
|
+
if ("notModified" in attempt) {
|
|
42890
|
+
const document = parseGoDocument(cached.body);
|
|
42891
|
+
if (document.quotas.size > 0 || document.protocols.size > 0) {
|
|
42892
|
+
return { document, failures };
|
|
42893
|
+
}
|
|
42894
|
+
failures.push(`${attempt.url} answered 304, and the remembered copy carries no table this build can read`);
|
|
42895
|
+
} else if ("document" in attempt) {
|
|
42896
|
+
writeDocCache(cacheDir, { url: attempt.url, etag: attempt.etag, body: attempt.body });
|
|
42897
|
+
return { document: attempt.document, failures };
|
|
42898
|
+
} else {
|
|
42899
|
+
failures.push(attempt.failure);
|
|
42900
|
+
}
|
|
42901
|
+
}
|
|
42902
|
+
const rest = cached === void 0 ? urls : urls.filter((url) => url !== cached.url);
|
|
42903
|
+
const attempts = await Promise.all(rest.map((url) => readSource(url, signal)));
|
|
42837
42904
|
for (const attempt of attempts) {
|
|
42838
|
-
if ("document" in attempt)
|
|
42839
|
-
|
|
42905
|
+
if ("document" in attempt) {
|
|
42906
|
+
writeDocCache(cacheDir, { url: attempt.url, etag: attempt.etag, body: attempt.body });
|
|
42907
|
+
return { document: attempt.document, failures };
|
|
42908
|
+
}
|
|
42909
|
+
if ("failure" in attempt) failures.push(attempt.failure);
|
|
42840
42910
|
}
|
|
42841
42911
|
return { failures };
|
|
42842
42912
|
}
|
|
@@ -43055,15 +43125,15 @@ function toPiModel(facts, baseURL) {
|
|
|
43055
43125
|
}
|
|
43056
43126
|
|
|
43057
43127
|
// src/catalog/metadata-cache.ts
|
|
43058
|
-
import { mkdirSync as
|
|
43059
|
-
import { join as
|
|
43060
|
-
var
|
|
43061
|
-
var
|
|
43128
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
43129
|
+
import { join as join4 } from "node:path";
|
|
43130
|
+
var CACHE_FILE3 = "models-dev-facts.json";
|
|
43131
|
+
var CACHE_VERSION3 = 1;
|
|
43062
43132
|
function readFactsCache(cacheDir) {
|
|
43063
43133
|
if (cacheDir === void 0) return void 0;
|
|
43064
43134
|
try {
|
|
43065
|
-
const parsed = JSON.parse(
|
|
43066
|
-
if (parsed.version !==
|
|
43135
|
+
const parsed = JSON.parse(readFileSync3(join4(cacheDir, CACHE_FILE3), "utf8"));
|
|
43136
|
+
if (parsed.version !== CACHE_VERSION3 || parsed.facts === null || typeof parsed.facts !== "object") return void 0;
|
|
43067
43137
|
const facts = /* @__PURE__ */ new Map();
|
|
43068
43138
|
for (const [id, fact] of Object.entries(parsed.facts)) {
|
|
43069
43139
|
if (fact === null || typeof fact !== "object") continue;
|
|
@@ -43083,14 +43153,69 @@ function readFactsCache(cacheDir) {
|
|
|
43083
43153
|
function writeFactsCache(cacheDir, facts, etag) {
|
|
43084
43154
|
if (cacheDir === void 0 || facts.size === 0) return;
|
|
43085
43155
|
try {
|
|
43086
|
-
|
|
43156
|
+
mkdirSync3(cacheDir, { recursive: true });
|
|
43087
43157
|
const file = {
|
|
43088
|
-
version:
|
|
43158
|
+
version: CACHE_VERSION3,
|
|
43089
43159
|
facts: Object.fromEntries(facts),
|
|
43090
43160
|
updatedAt: Date.now(),
|
|
43091
43161
|
...etag === void 0 ? {} : { etag }
|
|
43092
43162
|
};
|
|
43093
|
-
|
|
43163
|
+
writeFileSync3(join4(cacheDir, CACHE_FILE3), `${JSON.stringify(file)}
|
|
43164
|
+
`, "utf8");
|
|
43165
|
+
} catch {
|
|
43166
|
+
}
|
|
43167
|
+
}
|
|
43168
|
+
|
|
43169
|
+
// src/catalog/snapshot-cache.ts
|
|
43170
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
43171
|
+
import { join as join5 } from "node:path";
|
|
43172
|
+
var CACHE_FILE4 = "catalog-snapshot.json";
|
|
43173
|
+
var CACHE_VERSION4 = 1;
|
|
43174
|
+
function readSnapshotCache(cacheDir) {
|
|
43175
|
+
if (cacheDir === void 0) return void 0;
|
|
43176
|
+
try {
|
|
43177
|
+
const parsed = JSON.parse(readFileSync4(join5(cacheDir, CACHE_FILE4), "utf8"));
|
|
43178
|
+
if (parsed.version !== CACHE_VERSION4) return void 0;
|
|
43179
|
+
if (parsed.facts === null || typeof parsed.facts !== "object") return void 0;
|
|
43180
|
+
const facts = /* @__PURE__ */ new Map();
|
|
43181
|
+
for (const [id, fact] of Object.entries(parsed.facts)) {
|
|
43182
|
+
if (fact === null || typeof fact !== "object") continue;
|
|
43183
|
+
if (typeof fact.id !== "string" || typeof fact.api !== "string") continue;
|
|
43184
|
+
facts.set(id, fact);
|
|
43185
|
+
}
|
|
43186
|
+
if (facts.size === 0) return void 0;
|
|
43187
|
+
const quotas = /* @__PURE__ */ new Map();
|
|
43188
|
+
for (const [id, quota] of Object.entries(parsed.quotas ?? {})) {
|
|
43189
|
+
if (quota === null || typeof quota !== "object") continue;
|
|
43190
|
+
quotas.set(id, quota);
|
|
43191
|
+
}
|
|
43192
|
+
const protocols = /* @__PURE__ */ new Map();
|
|
43193
|
+
for (const [id, protocol] of Object.entries(parsed.protocols ?? {})) {
|
|
43194
|
+
if (typeof protocol !== "string") continue;
|
|
43195
|
+
protocols.set(id, protocol);
|
|
43196
|
+
}
|
|
43197
|
+
return {
|
|
43198
|
+
facts,
|
|
43199
|
+
quotas,
|
|
43200
|
+
protocols,
|
|
43201
|
+
updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : 0
|
|
43202
|
+
};
|
|
43203
|
+
} catch {
|
|
43204
|
+
return void 0;
|
|
43205
|
+
}
|
|
43206
|
+
}
|
|
43207
|
+
function writeSnapshotCache(cacheDir, facts, quotas, protocols) {
|
|
43208
|
+
if (cacheDir === void 0 || facts.size === 0) return;
|
|
43209
|
+
try {
|
|
43210
|
+
mkdirSync4(cacheDir, { recursive: true });
|
|
43211
|
+
const file = {
|
|
43212
|
+
version: CACHE_VERSION4,
|
|
43213
|
+
facts: Object.fromEntries(facts),
|
|
43214
|
+
...quotas.size === 0 ? {} : { quotas: Object.fromEntries(quotas) },
|
|
43215
|
+
...protocols.size === 0 ? {} : { protocols: Object.fromEntries(protocols) },
|
|
43216
|
+
updatedAt: Date.now()
|
|
43217
|
+
};
|
|
43218
|
+
writeFileSync4(join5(cacheDir, CACHE_FILE4), `${JSON.stringify(file)}
|
|
43094
43219
|
`, "utf8");
|
|
43095
43220
|
} catch {
|
|
43096
43221
|
}
|
|
@@ -43158,6 +43283,8 @@ var OpencodeGoCatalog = class {
|
|
|
43158
43283
|
document;
|
|
43159
43284
|
/** Ids that already forced a revalidation, with the time they did. */
|
|
43160
43285
|
probedAt = /* @__PURE__ */ new Map();
|
|
43286
|
+
/** The background refresh, kept so a caller can wait for it to be over. */
|
|
43287
|
+
backgroundRefresh;
|
|
43161
43288
|
constructor(options) {
|
|
43162
43289
|
this.options = options;
|
|
43163
43290
|
const cached = readFactsCache(options.cacheDir);
|
|
@@ -43169,7 +43296,19 @@ var OpencodeGoCatalog = class {
|
|
|
43169
43296
|
}
|
|
43170
43297
|
/**
|
|
43171
43298
|
* The cached snapshot, refreshed when it is older than the configured TTL.
|
|
43172
|
-
*
|
|
43299
|
+
*
|
|
43300
|
+
* A snapshot that is past its TTL is still served — and refreshed behind
|
|
43301
|
+
* it. A caller that asks for a model must not wait for the network to
|
|
43302
|
+
* learn what it already knew, so the answer is the one in hand and the
|
|
43303
|
+
* refresh goes on without anyone watching it. A caller that forces a
|
|
43304
|
+
* refresh — the settings page's button — waits for it, because asking to
|
|
43305
|
+
* refresh and not waiting for the result is asking for nothing.
|
|
43306
|
+
*
|
|
43307
|
+
* A process that has never built a catalog reads the one it wrote down
|
|
43308
|
+
* last time and refreshes that in the background. The first message of a
|
|
43309
|
+
* session therefore names its model at once, instead of after a wait that
|
|
43310
|
+
* belongs to the network rather than to the session.
|
|
43311
|
+
* @param force - revalidate every source, ignoring the cache, and wait.
|
|
43173
43312
|
* @param signal - caller cancellation, forwarded to the documentation read,
|
|
43174
43313
|
* the slowest source and the only one a caller can cancel; a build already
|
|
43175
43314
|
* in flight keeps the signal of the caller that started it, because one
|
|
@@ -43179,6 +43318,19 @@ var OpencodeGoCatalog = class {
|
|
|
43179
43318
|
if (!force && this.served !== void 0 && Date.now() - this.served.fetchedAtMs < this.options.refreshMs) {
|
|
43180
43319
|
return Promise.resolve(this.served);
|
|
43181
43320
|
}
|
|
43321
|
+
if (!force) {
|
|
43322
|
+
if (this.served === void 0) {
|
|
43323
|
+
const remembered = this.readRememberedSnapshot();
|
|
43324
|
+
if (remembered !== void 0) {
|
|
43325
|
+
this.served = remembered;
|
|
43326
|
+
this.refreshInBackground();
|
|
43327
|
+
return Promise.resolve(remembered);
|
|
43328
|
+
}
|
|
43329
|
+
} else {
|
|
43330
|
+
this.refreshInBackground();
|
|
43331
|
+
return Promise.resolve(this.served);
|
|
43332
|
+
}
|
|
43333
|
+
}
|
|
43182
43334
|
this.pending ??= this.build(signal).then((snapshot2) => {
|
|
43183
43335
|
this.served = snapshot2;
|
|
43184
43336
|
return snapshot2;
|
|
@@ -43187,6 +43339,62 @@ var OpencodeGoCatalog = class {
|
|
|
43187
43339
|
});
|
|
43188
43340
|
return this.pending;
|
|
43189
43341
|
}
|
|
43342
|
+
/**
|
|
43343
|
+
* Refresh the catalog behind the snapshot in hand.
|
|
43344
|
+
*
|
|
43345
|
+
* The build is not awaited and its failure is not reported here: the
|
|
43346
|
+
* snapshot in hand stays, and whoever asked for the refresh will see the
|
|
43347
|
+
* next one.
|
|
43348
|
+
*/
|
|
43349
|
+
refreshInBackground() {
|
|
43350
|
+
if (this.pending !== void 0) return;
|
|
43351
|
+
const refresh = this.build().then((snapshot2) => {
|
|
43352
|
+
this.served = snapshot2;
|
|
43353
|
+
return snapshot2;
|
|
43354
|
+
}).finally(() => {
|
|
43355
|
+
this.pending = void 0;
|
|
43356
|
+
});
|
|
43357
|
+
this.pending = refresh;
|
|
43358
|
+
this.backgroundRefresh = refresh;
|
|
43359
|
+
void refresh.catch(() => void 0);
|
|
43360
|
+
}
|
|
43361
|
+
/**
|
|
43362
|
+
* Wait for the background refresh, if one is running.
|
|
43363
|
+
*
|
|
43364
|
+
* The refresh belongs to no caller, so nothing else can wait for it. A
|
|
43365
|
+
* host that is shutting down — or a test that must not have a request
|
|
43366
|
+
* outlive it — needs to know when the quiet is over.
|
|
43367
|
+
* @returns a promise that settles when the background refresh has.
|
|
43368
|
+
*/
|
|
43369
|
+
settled() {
|
|
43370
|
+
const refresh = this.backgroundRefresh;
|
|
43371
|
+
return refresh === void 0 ? Promise.resolve() : refresh.then(() => void 0, () => void 0);
|
|
43372
|
+
}
|
|
43373
|
+
/**
|
|
43374
|
+
* The catalog a previous build produced, read from disk.
|
|
43375
|
+
*
|
|
43376
|
+
* It is presented as what it is: a listing from the past, being brought up
|
|
43377
|
+
* to date in the background. The facts it carries are the ones the last
|
|
43378
|
+
* build produced, so the protocols and the thinking levels are the ones
|
|
43379
|
+
* that were true then.
|
|
43380
|
+
* @returns the remembered catalog, or undefined when there is none.
|
|
43381
|
+
*/
|
|
43382
|
+
readRememberedSnapshot() {
|
|
43383
|
+
const remembered = readSnapshotCache(this.options.cacheDir);
|
|
43384
|
+
if (remembered === void 0) return void 0;
|
|
43385
|
+
const facts = new Map(remembered.facts);
|
|
43386
|
+
return {
|
|
43387
|
+
facts,
|
|
43388
|
+
unavailable: /* @__PURE__ */ new Map(),
|
|
43389
|
+
provider: buildProvider2(this.options.baseURL, [...facts.values()].map((fact) => toPiModel(fact, this.options.baseURL))),
|
|
43390
|
+
// The listing is from the past, and the page says so.
|
|
43391
|
+
live: false,
|
|
43392
|
+
// And a refresh of it is already under way.
|
|
43393
|
+
cached: true,
|
|
43394
|
+
fetchedAtMs: remembered.updatedAt,
|
|
43395
|
+
...remembered.quotas.size === 0 ? {} : { documentQuotas: remembered.quotas }
|
|
43396
|
+
};
|
|
43397
|
+
}
|
|
43190
43398
|
/**
|
|
43191
43399
|
* The snapshot that can serve one exact model.
|
|
43192
43400
|
*
|
|
@@ -43281,13 +43489,20 @@ var OpencodeGoCatalog = class {
|
|
|
43281
43489
|
}
|
|
43282
43490
|
const fromCache = metadata.status !== "fulfilled" && this.lastMetadata === void 0;
|
|
43283
43491
|
const online = metadata.status === "fulfilled" ? metadata.value : this.lastMetadata ?? this.cachedMetadata;
|
|
43284
|
-
|
|
43492
|
+
const snapshot2 = this.assemble(
|
|
43285
43493
|
listing.value,
|
|
43286
43494
|
online,
|
|
43287
43495
|
builtin,
|
|
43288
43496
|
degradations,
|
|
43289
43497
|
fromCache ? this.cachedAt : void 0
|
|
43290
43498
|
);
|
|
43499
|
+
writeSnapshotCache(
|
|
43500
|
+
this.options.cacheDir,
|
|
43501
|
+
snapshot2.facts,
|
|
43502
|
+
this.document?.quotas ?? /* @__PURE__ */ new Map(),
|
|
43503
|
+
this.document?.protocols ?? /* @__PURE__ */ new Map()
|
|
43504
|
+
);
|
|
43505
|
+
return snapshot2;
|
|
43291
43506
|
}
|
|
43292
43507
|
/** Merge the listing with the metadata document and the installed entries. */
|
|
43293
43508
|
assemble(ids, online, builtin, degradations = [], factsCachedAt) {
|
|
@@ -45571,6 +45786,7 @@ function parseCatalogReading(value) {
|
|
|
45571
45786
|
...typeof row["error"] === "string" ? { error: row["error"] } : {},
|
|
45572
45787
|
degradations: parseDegradations(row["degradations"]),
|
|
45573
45788
|
...typeof row["factsCachedAt"] === "number" ? { factsCachedAt: row["factsCachedAt"] } : {},
|
|
45789
|
+
...row["cached"] === true ? { cached: true } : {},
|
|
45574
45790
|
fetchedAtMs: row["fetchedAtMs"],
|
|
45575
45791
|
quotaSource: quotaSource ?? "seed",
|
|
45576
45792
|
counts: {
|
|
@@ -45813,6 +46029,7 @@ function catalogReading(snapshot2, visibility, listingFailure, now) {
|
|
|
45813
46029
|
...!snapshot2.live && listingFailure !== void 0 ? { error: listingFailure } : {},
|
|
45814
46030
|
degradations: snapshot2.degradations ?? [],
|
|
45815
46031
|
...snapshot2.factsCachedAt === void 0 ? {} : { factsCachedAt: snapshot2.factsCachedAt },
|
|
46032
|
+
...snapshot2.cached === void 0 ? {} : { cached: snapshot2.cached },
|
|
45816
46033
|
fetchedAtMs: snapshot2.fetchedAtMs,
|
|
45817
46034
|
quotaSource: documentQuotas === void 0 ? "seed" : "document",
|
|
45818
46035
|
counts: {
|
|
@@ -45981,7 +46198,7 @@ function createUpdateChecker(deps) {
|
|
|
45981
46198
|
}
|
|
45982
46199
|
|
|
45983
46200
|
// src/update/version.ts
|
|
45984
|
-
var PLUGIN_VERSION = true ? "0.1.
|
|
46201
|
+
var PLUGIN_VERSION = true ? "0.1.53" : "0.0.0-dev";
|
|
45985
46202
|
|
|
45986
46203
|
// src/update/service.ts
|
|
45987
46204
|
import { TypertRemoteService as TypertRemoteService2 } from "@deepseek-ai/dsh-typert-protocol";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remembering the provider's document.
|
|
3
|
+
*
|
|
4
|
+
* The document is small — tens of kilobytes — but it is fetched by every
|
|
5
|
+
* process that builds a catalog, and a desktop build fetches over the
|
|
6
|
+
* browser's network stack, where that fetch is measured in hundreds of
|
|
7
|
+
* milliseconds. The registry serves an ETag for it, and a conditional
|
|
8
|
+
* request that comes back `304 Not Modified` carries no body at all, so a
|
|
9
|
+
* process that remembers the ETag can have the document for the price of a
|
|
10
|
+
* round trip.
|
|
11
|
+
*
|
|
12
|
+
* What is remembered is the raw text, not the parsed tables. The parsed
|
|
13
|
+
* form is a reading of a document, and a reading can be taken again; the
|
|
14
|
+
* text is the document.
|
|
15
|
+
*
|
|
16
|
+
* @module @dan-ai-studio/dshopencodego/catalog/go-doc-cache
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The document a previous read produced.
|
|
20
|
+
*
|
|
21
|
+
* A cache that cannot be read is no cache: the caller then reads the
|
|
22
|
+
* document the ordinary way, which is slower and no worse.
|
|
23
|
+
* @param cacheDir - the plugin's cache directory.
|
|
24
|
+
* @returns the remembered document, or undefined.
|
|
25
|
+
*/
|
|
26
|
+
export declare function readDocCache(cacheDir: string | undefined): {
|
|
27
|
+
readonly url: string;
|
|
28
|
+
readonly etag: string | undefined;
|
|
29
|
+
readonly body: string;
|
|
30
|
+
readonly savedAt: number;
|
|
31
|
+
} | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Remember the document a read produced.
|
|
34
|
+
*
|
|
35
|
+
* A cache directory that cannot be written costs a fetch, and nothing else.
|
|
36
|
+
* @param cacheDir - the plugin's cache directory.
|
|
37
|
+
* @param entry - the document, where it came from, and its ETag.
|
|
38
|
+
*/
|
|
39
|
+
export declare function writeDocCache(cacheDir: string | undefined, entry: {
|
|
40
|
+
readonly url: string;
|
|
41
|
+
readonly etag: string | undefined;
|
|
42
|
+
readonly body: string;
|
|
43
|
+
}): void;
|
|
@@ -73,6 +73,15 @@ export interface CatalogSnapshot {
|
|
|
73
73
|
* a user cannot act on.
|
|
74
74
|
*/
|
|
75
75
|
readonly degradations?: readonly CatalogDegradation[];
|
|
76
|
+
/**
|
|
77
|
+
* True when this snapshot was read from disk and is being brought up to
|
|
78
|
+
* date in the background.
|
|
79
|
+
*
|
|
80
|
+
* It is the difference between "here is the last known catalog" and "here
|
|
81
|
+
* is the catalog, and it is current". A page that shows the first must
|
|
82
|
+
* also say that a refresh is under way.
|
|
83
|
+
*/
|
|
84
|
+
readonly cached?: boolean;
|
|
76
85
|
/**
|
|
77
86
|
* When the facts in this snapshot were written, when they came from disk
|
|
78
87
|
* rather than from a live read.
|
|
@@ -149,16 +158,57 @@ export declare class OpencodeGoCatalog {
|
|
|
149
158
|
private document;
|
|
150
159
|
/** Ids that already forced a revalidation, with the time they did. */
|
|
151
160
|
private readonly probedAt;
|
|
161
|
+
/** The background refresh, kept so a caller can wait for it to be over. */
|
|
162
|
+
private backgroundRefresh;
|
|
152
163
|
constructor(options: CatalogOptions);
|
|
153
164
|
/**
|
|
154
165
|
* The cached snapshot, refreshed when it is older than the configured TTL.
|
|
155
|
-
*
|
|
166
|
+
*
|
|
167
|
+
* A snapshot that is past its TTL is still served — and refreshed behind
|
|
168
|
+
* it. A caller that asks for a model must not wait for the network to
|
|
169
|
+
* learn what it already knew, so the answer is the one in hand and the
|
|
170
|
+
* refresh goes on without anyone watching it. A caller that forces a
|
|
171
|
+
* refresh — the settings page's button — waits for it, because asking to
|
|
172
|
+
* refresh and not waiting for the result is asking for nothing.
|
|
173
|
+
*
|
|
174
|
+
* A process that has never built a catalog reads the one it wrote down
|
|
175
|
+
* last time and refreshes that in the background. The first message of a
|
|
176
|
+
* session therefore names its model at once, instead of after a wait that
|
|
177
|
+
* belongs to the network rather than to the session.
|
|
178
|
+
* @param force - revalidate every source, ignoring the cache, and wait.
|
|
156
179
|
* @param signal - caller cancellation, forwarded to the documentation read,
|
|
157
180
|
* the slowest source and the only one a caller can cancel; a build already
|
|
158
181
|
* in flight keeps the signal of the caller that started it, because one
|
|
159
182
|
* shared snapshot cannot carry two different cancellations.
|
|
160
183
|
*/
|
|
161
184
|
snapshot(force?: boolean, signal?: AbortSignal): Promise<CatalogSnapshot>;
|
|
185
|
+
/**
|
|
186
|
+
* Refresh the catalog behind the snapshot in hand.
|
|
187
|
+
*
|
|
188
|
+
* The build is not awaited and its failure is not reported here: the
|
|
189
|
+
* snapshot in hand stays, and whoever asked for the refresh will see the
|
|
190
|
+
* next one.
|
|
191
|
+
*/
|
|
192
|
+
private refreshInBackground;
|
|
193
|
+
/**
|
|
194
|
+
* Wait for the background refresh, if one is running.
|
|
195
|
+
*
|
|
196
|
+
* The refresh belongs to no caller, so nothing else can wait for it. A
|
|
197
|
+
* host that is shutting down — or a test that must not have a request
|
|
198
|
+
* outlive it — needs to know when the quiet is over.
|
|
199
|
+
* @returns a promise that settles when the background refresh has.
|
|
200
|
+
*/
|
|
201
|
+
settled(): Promise<void>;
|
|
202
|
+
/**
|
|
203
|
+
* The catalog a previous build produced, read from disk.
|
|
204
|
+
*
|
|
205
|
+
* It is presented as what it is: a listing from the past, being brought up
|
|
206
|
+
* to date in the background. The facts it carries are the ones the last
|
|
207
|
+
* build produced, so the protocols and the thinking levels are the ones
|
|
208
|
+
* that were true then.
|
|
209
|
+
* @returns the remembered catalog, or undefined when there is none.
|
|
210
|
+
*/
|
|
211
|
+
private readRememberedSnapshot;
|
|
162
212
|
/**
|
|
163
213
|
* The snapshot that can serve one exact model.
|
|
164
214
|
*
|
|
@@ -38,6 +38,16 @@ export interface CatalogReading {
|
|
|
38
38
|
* like a current one.
|
|
39
39
|
*/
|
|
40
40
|
readonly factsCachedAt?: number;
|
|
41
|
+
/**
|
|
42
|
+
* True when the catalog behind this reading was read from disk and is
|
|
43
|
+
* being brought up to date in the background.
|
|
44
|
+
*
|
|
45
|
+
* The reading is usable — the models, the protocols, the levels are all
|
|
46
|
+
* there — but it was true when it was written, and a refresh is under
|
|
47
|
+
* way. The page says so, because a page that looks current and is not is
|
|
48
|
+
* worse than one that admits it.
|
|
49
|
+
*/
|
|
50
|
+
readonly cached?: boolean;
|
|
41
51
|
/** When the underlying snapshot was built. */
|
|
42
52
|
readonly fetchedAtMs: number;
|
|
43
53
|
/**
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remembering the whole catalog.
|
|
3
|
+
*
|
|
4
|
+
* The parsed metadata facts and the provider's document are remembered
|
|
5
|
+
* already, but they are remembered as *evidence* — what to ask about, and
|
|
6
|
+
* what to fall back to when an answer cannot be had. This file remembers
|
|
7
|
+
* the *answer*: the catalog as it stood, so that a process can serve it
|
|
8
|
+
* without waiting for the network at all.
|
|
9
|
+
*
|
|
10
|
+
* That is the difference between a wait and a stale page. A process that
|
|
11
|
+
* starts with nothing must ask the gateway, the metadata document, and the
|
|
12
|
+
* provider's page before it can name a model — and a new session's first
|
|
13
|
+
* message waits for that. A process that starts with a remembered catalog
|
|
14
|
+
* can name a model immediately, and refresh what it remembered afterwards.
|
|
15
|
+
*
|
|
16
|
+
* The listing is the authority on which models exist, so a remembered
|
|
17
|
+
* catalog can only describe the models the gateway advertised when it was
|
|
18
|
+
* written. A model the gateway has since added is missing from it — and a
|
|
19
|
+
* model the gateway has since retired is still in it, which is why the
|
|
20
|
+
* remembered catalog is always presented as being *from* the past.
|
|
21
|
+
*
|
|
22
|
+
* @module @dan-ai-studio/dshopencodego/catalog/snapshot-cache
|
|
23
|
+
*/
|
|
24
|
+
import type { GoQuota } from '../go-limits.ts';
|
|
25
|
+
import type { ModelFacts } from './metadata.ts';
|
|
26
|
+
import type { WireProtocol } from './protocol.ts';
|
|
27
|
+
/**
|
|
28
|
+
* The catalog a previous build produced.
|
|
29
|
+
*
|
|
30
|
+
* A cache that cannot be read is no cache: the caller then builds the
|
|
31
|
+
* catalog the ordinary way, which waits, and no worse.
|
|
32
|
+
* @param cacheDir - the plugin's cache directory.
|
|
33
|
+
* @returns the remembered catalog, or undefined.
|
|
34
|
+
*/
|
|
35
|
+
export declare function readSnapshotCache(cacheDir: string | undefined): {
|
|
36
|
+
readonly facts: ReadonlyMap<string, ModelFacts>;
|
|
37
|
+
readonly quotas: ReadonlyMap<string, GoQuota>;
|
|
38
|
+
readonly protocols: ReadonlyMap<string, WireProtocol>;
|
|
39
|
+
readonly updatedAt: number;
|
|
40
|
+
} | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Remember the catalog a build produced.
|
|
43
|
+
*
|
|
44
|
+
* A cache directory that cannot be written costs a wait, and nothing else.
|
|
45
|
+
* @param cacheDir - the plugin's cache directory.
|
|
46
|
+
* @param facts - the facts the build produced.
|
|
47
|
+
* @param quotas - the provider's allowances.
|
|
48
|
+
* @param protocols - the provider's protocols.
|
|
49
|
+
*/
|
|
50
|
+
export declare function writeSnapshotCache(cacheDir: string | undefined, facts: ReadonlyMap<string, ModelFacts>, quotas: ReadonlyMap<string, GoQuota>, protocols: ReadonlyMap<string, WireProtocol>): void;
|
package/package.json
CHANGED