@dan-ai-studio/dshopencodego 0.1.46 → 0.1.47
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 +29 -1
- package/lib/index.js +68 -11
- package/lib/types/catalog/constants.d.ts +16 -0
- package/lib/types/catalog/go-doc.d.ts +13 -0
- package/lib/types/catalog/index.d.ts +49 -1
- package/lib/types/catalog/reading.d.ts +10 -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 **45 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 对上游的断言与实装的上游一致)。共 **45 个测试文件**——这个数字由 `tests/readme-consistency.spec.ts` 对着 `tests/` 目录校验,不会漂移。
|
|
251
251
|
|
|
252
252
|
## 发布(维护者)
|
|
253
253
|
|
package/lib/client.js
CHANGED
|
@@ -29,6 +29,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
29
29
|
// src/catalog/constants.ts
|
|
30
30
|
var MODEL_LISTING_MAX_BYTES = 1024 * 1024;
|
|
31
31
|
var MODEL_METADATA_MAX_BYTES = 16 * 1024 * 1024;
|
|
32
|
+
var UNKNOWN_MODEL_PROBE_COOLDOWN_MS = 5 * 6e4;
|
|
32
33
|
|
|
33
34
|
// src/catalog/metadata.ts
|
|
34
35
|
var INPUT_MODALITIES = ["text", "image", "audio", "video", "pdf"];
|
|
@@ -84,12 +85,27 @@ function parseModel(value) {
|
|
|
84
85
|
...typeof row["releaseDate"] === "string" ? { releaseDate: row["releaseDate"] } : {},
|
|
85
86
|
...row["goQuota"] === void 0 ? {} : { goQuota: parseGoQuota(row["goQuota"]) },
|
|
86
87
|
...row["cost"] === void 0 ? {} : { cost: parseCost(row["cost"]) },
|
|
87
|
-
|
|
88
|
+
// `document` is the provider's own endpoint table — the most authoritative
|
|
89
|
+
// evidence there is — so it must survive the wire. Dropping it made a
|
|
90
|
+
// documented protocol indistinguishable from an unknown one on the page.
|
|
91
|
+
...row["protocolSource"] === "builtin" || row["protocolSource"] === "document" || row["protocolSource"] === "online" || row["protocolSource"] === "inferred" || row["protocolSource"] === "override" ? { protocolSource: row["protocolSource"] } : {},
|
|
88
92
|
...typeof row["assumedLimits"] === "boolean" ? { assumedLimits: row["assumedLimits"] } : {},
|
|
89
93
|
...parseModalities(row["inputModalities"]),
|
|
90
94
|
...typeof row["configurationMissing"] === "string" ? { configurationMissing: row["configurationMissing"] } : {}
|
|
91
95
|
};
|
|
92
96
|
}
|
|
97
|
+
function parseDegradations(value) {
|
|
98
|
+
if (!Array.isArray(value)) return [];
|
|
99
|
+
const kept = [];
|
|
100
|
+
for (const item of value) {
|
|
101
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) continue;
|
|
102
|
+
const row = item;
|
|
103
|
+
if (row["source"] !== "metadata" && row["source"] !== "document") continue;
|
|
104
|
+
if (typeof row["reason"] !== "string") continue;
|
|
105
|
+
kept.push({ source: row["source"], reason: row["reason"] });
|
|
106
|
+
}
|
|
107
|
+
return kept;
|
|
108
|
+
}
|
|
93
109
|
function parseCatalogReading(value) {
|
|
94
110
|
if (value === null || typeof value !== "object") throw new Error("invalid catalog reading");
|
|
95
111
|
const row = value;
|
|
@@ -111,6 +127,7 @@ function parseCatalogReading(value) {
|
|
|
111
127
|
models: row["models"].map(parseModel),
|
|
112
128
|
stale: row["stale"],
|
|
113
129
|
...typeof row["error"] === "string" ? { error: row["error"] } : {},
|
|
130
|
+
degradations: parseDegradations(row["degradations"]),
|
|
114
131
|
fetchedAtMs: row["fetchedAtMs"],
|
|
115
132
|
quotaSource: quotaSource ?? "seed",
|
|
116
133
|
counts: {
|
|
@@ -309,6 +326,9 @@ var en = {
|
|
|
309
326
|
catalogSelectAll: "Select all",
|
|
310
327
|
catalogSelectNone: "Select none",
|
|
311
328
|
catalogStale: "Showing the last successful listing",
|
|
329
|
+
catalogDegraded: "Catalog degraded",
|
|
330
|
+
catalogDegradedMetadata: "model metadata (models.dev) unavailable",
|
|
331
|
+
catalogDegradedDocument: "provider documentation unavailable",
|
|
312
332
|
catalogCountUnit: "models",
|
|
313
333
|
catalogInferred: "protocol inferred",
|
|
314
334
|
catalogAssumed: "default capacities",
|
|
@@ -405,6 +425,9 @@ var zh = {
|
|
|
405
425
|
catalogSelectAll: "\u5168\u9009",
|
|
406
426
|
catalogSelectNone: "\u5168\u4E0D\u9009",
|
|
407
427
|
catalogStale: "\u5F53\u524D\u663E\u793A\u7684\u662F\u4E0A\u6B21\u6210\u529F\u83B7\u53D6\u7684\u5217\u8868",
|
|
428
|
+
catalogDegraded: "\u76EE\u5F55\u5DF2\u964D\u7EA7",
|
|
429
|
+
catalogDegradedMetadata: "\u6A21\u578B\u5143\u6570\u636E\uFF08models.dev\uFF09\u4E0D\u53EF\u7528",
|
|
430
|
+
catalogDegradedDocument: "\u5382\u5546\u6587\u6863\u4E0D\u53EF\u7528",
|
|
408
431
|
catalogCountUnit: "\u4E2A\u6A21\u578B",
|
|
409
432
|
catalogInferred: "\u534F\u8BAE\u4E3A\u63A8\u65AD",
|
|
410
433
|
catalogAssumed: "\u5BB9\u91CF\u4E3A\u9ED8\u8BA4\u503C",
|
|
@@ -1211,6 +1234,11 @@ function Section({ controller, t, getLocale }) {
|
|
|
1211
1234
|
state.failure
|
|
1212
1235
|
] }),
|
|
1213
1236
|
reading !== void 0 && reading.stale && state.failure === void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: section_default.warn, children: t("catalogStale") }),
|
|
1237
|
+
reading !== void 0 && reading.degradations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: section_default.warn, children: [
|
|
1238
|
+
t("catalogDegraded"),
|
|
1239
|
+
": ",
|
|
1240
|
+
reading.degradations.map((degradation) => `${t(degradation.source === "metadata" ? "catalogDegradedMetadata" : "catalogDegradedDocument")} \u2014 ${degradation.reason}`).join("; ")
|
|
1241
|
+
] }),
|
|
1214
1242
|
reading !== void 0 && reading.quotaSource === "seed" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: section_default.hint, children: [
|
|
1215
1243
|
t("quotaSeed"),
|
|
1216
1244
|
" ",
|
package/lib/index.js
CHANGED
|
@@ -42530,6 +42530,7 @@ var MODEL_METADATA_PROVIDER = "opencode-go";
|
|
|
42530
42530
|
var METADATA_FETCH_TIMEOUT_MS = 1e4;
|
|
42531
42531
|
var MODEL_LISTING_MAX_BYTES = 1024 * 1024;
|
|
42532
42532
|
var MODEL_METADATA_MAX_BYTES = 16 * 1024 * 1024;
|
|
42533
|
+
var UNKNOWN_MODEL_PROBE_COOLDOWN_MS = 5 * 6e4;
|
|
42533
42534
|
|
|
42534
42535
|
// src/catalog/gateway.ts
|
|
42535
42536
|
import { attributionHeaders, LlmError as LlmError2 } from "@deepseek-ai/dsh-llm";
|
|
@@ -42626,8 +42627,8 @@ async function fetchModelIds(baseURL, signal) {
|
|
|
42626
42627
|
// src/catalog/go-doc.ts
|
|
42627
42628
|
import { attributionHeaders as attributionHeaders2, LlmError as LlmError3 } from "@deepseek-ai/dsh-llm";
|
|
42628
42629
|
var GO_DOC_URLS = [
|
|
42629
|
-
"https://raw.githubusercontent.com/anomalyco/opencode/dev/
|
|
42630
|
-
"https://cdn.jsdelivr.net/gh/anomalyco/opencode@dev/
|
|
42630
|
+
"https://raw.githubusercontent.com/anomalyco/opencode/dev/services/www/src/docs/content/console/go.mdx",
|
|
42631
|
+
"https://cdn.jsdelivr.net/gh/anomalyco/opencode@dev/services/www/src/docs/content/console/go.mdx"
|
|
42631
42632
|
];
|
|
42632
42633
|
var GO_DOC_MAX_BYTES = 1024 * 1024;
|
|
42633
42634
|
var ENDPOINT_PROTOCOLS = {
|
|
@@ -42716,7 +42717,9 @@ async function readSource(url, signal) {
|
|
|
42716
42717
|
return { failure: `${url} answered HTTP ${response.status}` };
|
|
42717
42718
|
}
|
|
42718
42719
|
const document = parseGoDocument(await readBoundedText(response, GO_DOC_MAX_BYTES));
|
|
42719
|
-
if (document.quotas.size === 0
|
|
42720
|
+
if (document.quotas.size === 0 && document.protocols.size === 0) {
|
|
42721
|
+
return { failure: `${url} carried neither an allowance table nor an endpoint table` };
|
|
42722
|
+
}
|
|
42720
42723
|
return { document };
|
|
42721
42724
|
} catch (error) {
|
|
42722
42725
|
return { failure: `${url}: ${error instanceof Error ? error.message : String(error)}` };
|
|
@@ -42949,6 +42952,10 @@ function toPiModel(facts, baseURL) {
|
|
|
42949
42952
|
}
|
|
42950
42953
|
|
|
42951
42954
|
// src/catalog/index.ts
|
|
42955
|
+
function reasonText(reason) {
|
|
42956
|
+
if (reason instanceof Error) return reason.message;
|
|
42957
|
+
return typeof reason === "string" ? reason : String(reason);
|
|
42958
|
+
}
|
|
42952
42959
|
function builtinModels(baseURL) {
|
|
42953
42960
|
return new Map(Object.values(OPENCODE_GO_MODELS).map((model) => [model.id, {
|
|
42954
42961
|
...model,
|
|
@@ -42994,6 +43001,8 @@ var OpencodeGoCatalog = class {
|
|
|
42994
43001
|
lastMetadata;
|
|
42995
43002
|
/** The last parsed documentation; retained across failed fetches. */
|
|
42996
43003
|
document;
|
|
43004
|
+
/** Ids that already forced a revalidation, with the time they did. */
|
|
43005
|
+
probedAt = /* @__PURE__ */ new Map();
|
|
42997
43006
|
constructor(options) {
|
|
42998
43007
|
this.options = options;
|
|
42999
43008
|
}
|
|
@@ -43030,7 +43039,8 @@ var OpencodeGoCatalog = class {
|
|
|
43030
43039
|
*/
|
|
43031
43040
|
async forModel(id, signal) {
|
|
43032
43041
|
let snapshot2 = await this.snapshot();
|
|
43033
|
-
if (!snapshot2.facts.has(id) && !snapshot2.unavailable.has(id)) {
|
|
43042
|
+
if (!snapshot2.facts.has(id) && !snapshot2.unavailable.has(id) && this.mayProbe(id)) {
|
|
43043
|
+
this.probedAt.set(id, Date.now());
|
|
43034
43044
|
snapshot2 = await this.snapshot(true, signal);
|
|
43035
43045
|
}
|
|
43036
43046
|
const reason = snapshot2.unavailable.get(id);
|
|
@@ -43042,6 +43052,22 @@ var OpencodeGoCatalog = class {
|
|
|
43042
43052
|
}
|
|
43043
43053
|
return snapshot2;
|
|
43044
43054
|
}
|
|
43055
|
+
/**
|
|
43056
|
+
* Whether this id may revalidate the catalog again.
|
|
43057
|
+
*
|
|
43058
|
+
* The revalidation is what makes a newly published model usable without a
|
|
43059
|
+
* restart, so it cannot simply be dropped. It is also the most expensive
|
|
43060
|
+
* thing this route does — the metadata document is megabytes — and the host
|
|
43061
|
+
* resolves a model on every list and on every new session. An id that never
|
|
43062
|
+
* resolves would therefore pay that cost forever, which is how a plugin
|
|
43063
|
+
* makes a host slow down over time rather than start slow.
|
|
43064
|
+
* @param id - the model id that was not found.
|
|
43065
|
+
* @returns true when the id may force a revalidation now.
|
|
43066
|
+
*/
|
|
43067
|
+
mayProbe(id) {
|
|
43068
|
+
const last = this.probedAt.get(id);
|
|
43069
|
+
return last === void 0 || Date.now() - last >= UNKNOWN_MODEL_PROBE_COOLDOWN_MS;
|
|
43070
|
+
}
|
|
43045
43071
|
/**
|
|
43046
43072
|
* Revalidate every source, reusing the metadata document when it is unchanged.
|
|
43047
43073
|
* @param signal - caller cancellation, forwarded to the documentation read.
|
|
@@ -43054,7 +43080,9 @@ var OpencodeGoCatalog = class {
|
|
|
43054
43080
|
this.refreshMetadata(builtin),
|
|
43055
43081
|
readDocument(signal)
|
|
43056
43082
|
]);
|
|
43083
|
+
const degradations = [];
|
|
43057
43084
|
if (metadata.status === "rejected") {
|
|
43085
|
+
degradations.push({ source: "metadata", reason: reasonText(metadata.reason) });
|
|
43058
43086
|
this.options.observers?.onFallback?.({
|
|
43059
43087
|
url: MODEL_METADATA_URL,
|
|
43060
43088
|
error: metadata.reason,
|
|
@@ -43064,6 +43092,7 @@ var OpencodeGoCatalog = class {
|
|
|
43064
43092
|
if (documentation.status === "fulfilled") {
|
|
43065
43093
|
this.document = documentation.value;
|
|
43066
43094
|
} else {
|
|
43095
|
+
degradations.push({ source: "document", reason: reasonText(documentation.reason) });
|
|
43067
43096
|
this.options.observers?.onFallback?.({
|
|
43068
43097
|
url: GO_DOC_URLS[0] ?? "the Go documentation",
|
|
43069
43098
|
error: documentation.reason,
|
|
@@ -43084,6 +43113,7 @@ var OpencodeGoCatalog = class {
|
|
|
43084
43113
|
provider: buildProvider2(this.options.baseURL, [...facts.values()].map((fact) => toPiModel(fact, this.options.baseURL))),
|
|
43085
43114
|
live: false,
|
|
43086
43115
|
listingFailure: listing.reason,
|
|
43116
|
+
...degradations.length === 0 ? {} : { degradations },
|
|
43087
43117
|
fetchedAtMs: Date.now(),
|
|
43088
43118
|
...this.documentQuotas()
|
|
43089
43119
|
};
|
|
@@ -43091,17 +43121,18 @@ var OpencodeGoCatalog = class {
|
|
|
43091
43121
|
return this.assemble(
|
|
43092
43122
|
listing.value,
|
|
43093
43123
|
metadata.status === "fulfilled" ? metadata.value : this.lastMetadata,
|
|
43094
|
-
builtin
|
|
43124
|
+
builtin,
|
|
43125
|
+
degradations
|
|
43095
43126
|
);
|
|
43096
43127
|
}
|
|
43097
43128
|
/** Merge the listing with the metadata document and the installed entries. */
|
|
43098
|
-
assemble(ids, online, builtin) {
|
|
43129
|
+
assemble(ids, online, builtin, degradations = []) {
|
|
43099
43130
|
const facts = /* @__PURE__ */ new Map();
|
|
43100
43131
|
const unavailable = /* @__PURE__ */ new Map();
|
|
43101
43132
|
for (const id of ids) {
|
|
43102
43133
|
const parsed = online?.models.get(id);
|
|
43103
43134
|
if (parsed !== void 0) {
|
|
43104
|
-
facts.set(id, parsed);
|
|
43135
|
+
facts.set(id, this.withDocumentedProtocol(parsed));
|
|
43105
43136
|
continue;
|
|
43106
43137
|
}
|
|
43107
43138
|
const reason = online?.errors.get(id);
|
|
@@ -43126,13 +43157,22 @@ var OpencodeGoCatalog = class {
|
|
|
43126
43157
|
unavailable,
|
|
43127
43158
|
provider: buildProvider2(this.options.baseURL, [...facts.values()].map((fact) => toPiModel(fact, this.options.baseURL))),
|
|
43128
43159
|
live: true,
|
|
43160
|
+
...degradations.length === 0 ? {} : { degradations },
|
|
43129
43161
|
fetchedAtMs: Date.now(),
|
|
43130
43162
|
...this.documentQuotas()
|
|
43131
43163
|
};
|
|
43132
43164
|
}
|
|
43133
|
-
/**
|
|
43165
|
+
/**
|
|
43166
|
+
* The document allowances as a snapshot field, absent until one fetch lands.
|
|
43167
|
+
*
|
|
43168
|
+
* A document that carries no allowance table leaves the field absent rather
|
|
43169
|
+
* than empty, so the frozen seed in `go-limits.ts` answers instead. An empty
|
|
43170
|
+
* map would look like "the provider states no allowance for any model", and
|
|
43171
|
+
* that is a different claim from "this build has no live allowances".
|
|
43172
|
+
*/
|
|
43134
43173
|
documentQuotas() {
|
|
43135
|
-
|
|
43174
|
+
if (this.document === void 0 || this.document.quotas.size === 0) return {};
|
|
43175
|
+
return { documentQuotas: this.document.quotas };
|
|
43136
43176
|
}
|
|
43137
43177
|
/**
|
|
43138
43178
|
* Prefer the documented protocol over anything below the installed catalog.
|
|
@@ -45272,12 +45312,27 @@ function parseModel(value) {
|
|
|
45272
45312
|
...typeof row["releaseDate"] === "string" ? { releaseDate: row["releaseDate"] } : {},
|
|
45273
45313
|
...row["goQuota"] === void 0 ? {} : { goQuota: parseGoQuota(row["goQuota"]) },
|
|
45274
45314
|
...row["cost"] === void 0 ? {} : { cost: parseCost(row["cost"]) },
|
|
45275
|
-
|
|
45315
|
+
// `document` is the provider's own endpoint table — the most authoritative
|
|
45316
|
+
// evidence there is — so it must survive the wire. Dropping it made a
|
|
45317
|
+
// documented protocol indistinguishable from an unknown one on the page.
|
|
45318
|
+
...row["protocolSource"] === "builtin" || row["protocolSource"] === "document" || row["protocolSource"] === "online" || row["protocolSource"] === "inferred" || row["protocolSource"] === "override" ? { protocolSource: row["protocolSource"] } : {},
|
|
45276
45319
|
...typeof row["assumedLimits"] === "boolean" ? { assumedLimits: row["assumedLimits"] } : {},
|
|
45277
45320
|
...parseModalities(row["inputModalities"]),
|
|
45278
45321
|
...typeof row["configurationMissing"] === "string" ? { configurationMissing: row["configurationMissing"] } : {}
|
|
45279
45322
|
};
|
|
45280
45323
|
}
|
|
45324
|
+
function parseDegradations(value) {
|
|
45325
|
+
if (!Array.isArray(value)) return [];
|
|
45326
|
+
const kept = [];
|
|
45327
|
+
for (const item of value) {
|
|
45328
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) continue;
|
|
45329
|
+
const row = item;
|
|
45330
|
+
if (row["source"] !== "metadata" && row["source"] !== "document") continue;
|
|
45331
|
+
if (typeof row["reason"] !== "string") continue;
|
|
45332
|
+
kept.push({ source: row["source"], reason: row["reason"] });
|
|
45333
|
+
}
|
|
45334
|
+
return kept;
|
|
45335
|
+
}
|
|
45281
45336
|
function parseCatalogReading(value) {
|
|
45282
45337
|
if (value === null || typeof value !== "object") throw new Error("invalid catalog reading");
|
|
45283
45338
|
const row = value;
|
|
@@ -45299,6 +45354,7 @@ function parseCatalogReading(value) {
|
|
|
45299
45354
|
models: row["models"].map(parseModel),
|
|
45300
45355
|
stale: row["stale"],
|
|
45301
45356
|
...typeof row["error"] === "string" ? { error: row["error"] } : {},
|
|
45357
|
+
degradations: parseDegradations(row["degradations"]),
|
|
45302
45358
|
fetchedAtMs: row["fetchedAtMs"],
|
|
45303
45359
|
quotaSource: quotaSource ?? "seed",
|
|
45304
45360
|
counts: {
|
|
@@ -45539,6 +45595,7 @@ function catalogReading(snapshot2, visibility, listingFailure, now) {
|
|
|
45539
45595
|
models: sortModels(models, now),
|
|
45540
45596
|
stale: !snapshot2.live,
|
|
45541
45597
|
...!snapshot2.live && listingFailure !== void 0 ? { error: listingFailure } : {},
|
|
45598
|
+
degradations: snapshot2.degradations ?? [],
|
|
45542
45599
|
fetchedAtMs: snapshot2.fetchedAtMs,
|
|
45543
45600
|
quotaSource: documentQuotas === void 0 ? "seed" : "document",
|
|
45544
45601
|
counts: {
|
|
@@ -45707,7 +45764,7 @@ function createUpdateChecker(deps) {
|
|
|
45707
45764
|
}
|
|
45708
45765
|
|
|
45709
45766
|
// src/update/version.ts
|
|
45710
|
-
var PLUGIN_VERSION = true ? "0.1.
|
|
45767
|
+
var PLUGIN_VERSION = true ? "0.1.47" : "0.0.0-dev";
|
|
45711
45768
|
|
|
45712
45769
|
// src/update/service.ts
|
|
45713
45770
|
import { TypertRemoteService as TypertRemoteService2 } from "@deepseek-ai/dsh-typert-protocol";
|
|
@@ -16,3 +16,19 @@ export declare const METADATA_FETCH_TIMEOUT_MS = 10000;
|
|
|
16
16
|
/** Response caps: the listing is tiny, the metadata document is not. */
|
|
17
17
|
export declare const MODEL_LISTING_MAX_BYTES: number;
|
|
18
18
|
export declare const MODEL_METADATA_MAX_BYTES: number;
|
|
19
|
+
/**
|
|
20
|
+
* How long one unknown model id waits before it may revalidate the catalog
|
|
21
|
+
* again.
|
|
22
|
+
*
|
|
23
|
+
* An id the catalog has never seen forces one revalidation, because "this
|
|
24
|
+
* build has not seen it" and "it was just published" look the same from here.
|
|
25
|
+
* Without a cooldown, an id that will never resolve — a mistyped id, a model
|
|
26
|
+
* the gateway retired, a stale entry in a session — makes every single model
|
|
27
|
+
* resolution refetch the whole metadata document, which is megabytes. The
|
|
28
|
+
* host resolves a model on every list and on every new session, so that cost
|
|
29
|
+
* is paid over and over for an id whose answer will not change.
|
|
30
|
+
*
|
|
31
|
+
* Five minutes is short enough that a genuinely new model still shows up
|
|
32
|
+
* without a restart, and long enough that a bad id costs one fetch.
|
|
33
|
+
*/
|
|
34
|
+
export declare const UNKNOWN_MODEL_PROBE_COOLDOWN_MS: number;
|
|
@@ -20,6 +20,13 @@ import type { WireProtocol } from './protocol.ts';
|
|
|
20
20
|
*
|
|
21
21
|
* The repository file is the source of truth; the jsDelivr entry caches the
|
|
22
22
|
* same path and is tried when GitHub itself is unreachable.
|
|
23
|
+
*
|
|
24
|
+
* The path is the provider's own, and it moves: `go.mdx` used to live at
|
|
25
|
+
* `packages/web/src/content/docs/go.mdx` and the `packages/web` tree was
|
|
26
|
+
* restructured, after which every request answered 404. A document that cannot
|
|
27
|
+
* be read costs the endpoint table — the provider's own statement of each
|
|
28
|
+
* model's protocol — so the path is asserted against the live repository by
|
|
29
|
+
* `tests/go-doc-url.spec.ts` rather than left to rot unnoticed.
|
|
23
30
|
*/
|
|
24
31
|
export declare const GO_DOC_URLS: readonly string[];
|
|
25
32
|
/** What one parse of the document yields. */
|
|
@@ -37,6 +44,12 @@ export interface GoDoc {
|
|
|
37
44
|
* endpoint table and the other two attach by normalised display name. A row
|
|
38
45
|
* that names no known model, or states no usable number, is skipped rather than
|
|
39
46
|
* guessed; a tiered model whose rows disagree is dropped entirely.
|
|
47
|
+
*
|
|
48
|
+
* The two tables are read independently because the provider's page has
|
|
49
|
+
* carried them at different times: the endpoint table names each model's
|
|
50
|
+
* protocol, and the allowance table states the monthly allowance. A document
|
|
51
|
+
* that keeps one and drops the other is still worth reading — the one it
|
|
52
|
+
* keeps is evidence the other source cannot supply.
|
|
40
53
|
* @param markdown - the raw document text.
|
|
41
54
|
* @returns the parsed allowances and protocols; both may be empty when the
|
|
42
55
|
* document does not carry the expected tables.
|
|
@@ -29,6 +29,22 @@ export { readOnlineMetadata, toPiModel, modelBaseURL } from './metadata.ts';
|
|
|
29
29
|
export type { ModelFacts, OnlineMetadata, ModelDefaults } from './metadata.ts';
|
|
30
30
|
export { decideProtocol, inferProtocol, protocolOfNpm } from './protocol.ts';
|
|
31
31
|
export type { ProtocolDecision, ProtocolSource, WireProtocol } from './protocol.ts';
|
|
32
|
+
/**
|
|
33
|
+
* One source the catalog could not read.
|
|
34
|
+
*
|
|
35
|
+
* A degradation is not a failure: the snapshot still answers, from the
|
|
36
|
+
* installed catalog and the frozen seed. But it is not the same answer, and
|
|
37
|
+
* the difference is invisible unless it is said out loud — a model whose
|
|
38
|
+
* protocol was inferred reads exactly like one whose protocol the provider
|
|
39
|
+
* documented, and a thinking level that the online metadata would have
|
|
40
|
+
* supplied is simply absent.
|
|
41
|
+
*/
|
|
42
|
+
export interface CatalogDegradation {
|
|
43
|
+
/** Which source could not be read. */
|
|
44
|
+
readonly source: 'metadata' | 'document';
|
|
45
|
+
/** Why it failed, in the source's own words. */
|
|
46
|
+
readonly reason: string;
|
|
47
|
+
}
|
|
32
48
|
/** One resolved view of the gateway's models. */
|
|
33
49
|
export interface CatalogSnapshot {
|
|
34
50
|
/** Models that can be called, by id, with the evidence behind each decision. */
|
|
@@ -47,6 +63,16 @@ export interface CatalogSnapshot {
|
|
|
47
63
|
readonly documentQuotas?: ReadonlyMap<string, GoQuota>;
|
|
48
64
|
/** Retained for explicit discovery when the listing failed. */
|
|
49
65
|
readonly listingFailure?: unknown;
|
|
66
|
+
/**
|
|
67
|
+
* Sources this build could not read.
|
|
68
|
+
*
|
|
69
|
+
* Empty when every source answered. Otherwise the reading is degraded: the
|
|
70
|
+
* protocol of a model no installed entry describes falls back to a guess,
|
|
71
|
+
* and the thinking levels the online metadata carries are missing. The page
|
|
72
|
+
* shows this, because a degraded reading that looks whole is the one state
|
|
73
|
+
* a user cannot act on.
|
|
74
|
+
*/
|
|
75
|
+
readonly degradations?: readonly CatalogDegradation[];
|
|
50
76
|
readonly fetchedAtMs: number;
|
|
51
77
|
}
|
|
52
78
|
/** Observers the plugin logs through. */
|
|
@@ -94,6 +120,8 @@ export declare class OpencodeGoCatalog {
|
|
|
94
120
|
private lastMetadata;
|
|
95
121
|
/** The last parsed documentation; retained across failed fetches. */
|
|
96
122
|
private document;
|
|
123
|
+
/** Ids that already forced a revalidation, with the time they did. */
|
|
124
|
+
private readonly probedAt;
|
|
97
125
|
constructor(options: CatalogOptions);
|
|
98
126
|
/**
|
|
99
127
|
* The cached snapshot, refreshed when it is older than the configured TTL.
|
|
@@ -116,6 +144,19 @@ export declare class OpencodeGoCatalog {
|
|
|
116
144
|
* the id but this build cannot configure it, naming the reason.
|
|
117
145
|
*/
|
|
118
146
|
forModel(id: string, signal?: AbortSignal): Promise<CatalogSnapshot>;
|
|
147
|
+
/**
|
|
148
|
+
* Whether this id may revalidate the catalog again.
|
|
149
|
+
*
|
|
150
|
+
* The revalidation is what makes a newly published model usable without a
|
|
151
|
+
* restart, so it cannot simply be dropped. It is also the most expensive
|
|
152
|
+
* thing this route does — the metadata document is megabytes — and the host
|
|
153
|
+
* resolves a model on every list and on every new session. An id that never
|
|
154
|
+
* resolves would therefore pay that cost forever, which is how a plugin
|
|
155
|
+
* makes a host slow down over time rather than start slow.
|
|
156
|
+
* @param id - the model id that was not found.
|
|
157
|
+
* @returns true when the id may force a revalidation now.
|
|
158
|
+
*/
|
|
159
|
+
private mayProbe;
|
|
119
160
|
/**
|
|
120
161
|
* Revalidate every source, reusing the metadata document when it is unchanged.
|
|
121
162
|
* @param signal - caller cancellation, forwarded to the documentation read.
|
|
@@ -123,7 +164,14 @@ export declare class OpencodeGoCatalog {
|
|
|
123
164
|
private build;
|
|
124
165
|
/** Merge the listing with the metadata document and the installed entries. */
|
|
125
166
|
private assemble;
|
|
126
|
-
/**
|
|
167
|
+
/**
|
|
168
|
+
* The document allowances as a snapshot field, absent until one fetch lands.
|
|
169
|
+
*
|
|
170
|
+
* A document that carries no allowance table leaves the field absent rather
|
|
171
|
+
* than empty, so the frozen seed in `go-limits.ts` answers instead. An empty
|
|
172
|
+
* map would look like "the provider states no allowance for any model", and
|
|
173
|
+
* that is a different claim from "this build has no live allowances".
|
|
174
|
+
*/
|
|
127
175
|
private documentQuotas;
|
|
128
176
|
/**
|
|
129
177
|
* Prefer the documented protocol over anything below the installed catalog.
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { ModelSummary } from '../models.ts';
|
|
13
13
|
import type { CatalogSnapshot } from './index.ts';
|
|
14
|
+
import type { CatalogDegradation } from './index.ts';
|
|
14
15
|
/** One settings-page reading of the catalog. */
|
|
15
16
|
export interface CatalogReading {
|
|
16
17
|
/** Every advertised model, new first, deprecated last. */
|
|
@@ -19,6 +20,15 @@ export interface CatalogReading {
|
|
|
19
20
|
readonly stale: boolean;
|
|
20
21
|
/** Why the reading is stale, when it is. */
|
|
21
22
|
readonly error?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Sources this reading could not read.
|
|
25
|
+
*
|
|
26
|
+
* Empty when every source answered. Otherwise the page is showing a
|
|
27
|
+
* degraded catalog: a protocol that no source describes is a guess, and the
|
|
28
|
+
* thinking levels the online metadata carries are missing. Saying so is the
|
|
29
|
+
* difference between a user who can act and a user who cannot.
|
|
30
|
+
*/
|
|
31
|
+
readonly degradations: readonly CatalogDegradation[];
|
|
22
32
|
/** When the underlying snapshot was built. */
|
|
23
33
|
readonly fetchedAtMs: number;
|
|
24
34
|
/**
|
package/package.json
CHANGED