@dan-ai-studio/dshopencodego 0.1.48 → 0.1.50
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 +8 -0
- package/lib/index.js +116 -21
- package/lib/types/catalog/constants.d.ts +21 -1
- package/lib/types/catalog/index.d.ts +44 -1
- package/lib/types/catalog/metadata-cache.d.ts +41 -0
- package/lib/types/catalog/reading.d.ts +9 -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 **48 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 对上游的断言与实装的上游一致)。共 **48 个测试文件**——这个数字由 `tests/readme-consistency.spec.ts` 对着 `tests/` 目录校验,不会漂移。
|
|
251
251
|
|
|
252
252
|
## 发布(维护者)
|
|
253
253
|
|
package/lib/client.js
CHANGED
|
@@ -128,6 +128,7 @@ function parseCatalogReading(value) {
|
|
|
128
128
|
stale: row["stale"],
|
|
129
129
|
...typeof row["error"] === "string" ? { error: row["error"] } : {},
|
|
130
130
|
degradations: parseDegradations(row["degradations"]),
|
|
131
|
+
...typeof row["factsCachedAt"] === "number" ? { factsCachedAt: row["factsCachedAt"] } : {},
|
|
131
132
|
fetchedAtMs: row["fetchedAtMs"],
|
|
132
133
|
quotaSource: quotaSource ?? "seed",
|
|
133
134
|
counts: {
|
|
@@ -335,6 +336,7 @@ var en = {
|
|
|
335
336
|
catalogDeprecated: "deprecated",
|
|
336
337
|
catalogUnconfigured: "not configurable",
|
|
337
338
|
quotaSeed: "Allowances are the built-in snapshot, transcribed",
|
|
339
|
+
factsCached: "Model metadata is from the last successful reading,",
|
|
338
340
|
refreshLabel: "Catalog refresh (minutes)",
|
|
339
341
|
refreshHint: "How long requests and the model picker reuse one listing.",
|
|
340
342
|
writeRejected: "The settings write was rejected (the namespace was reloaded) \u2014 try again",
|
|
@@ -434,6 +436,7 @@ var zh = {
|
|
|
434
436
|
catalogDeprecated: "\u5DF2\u5F03\u7528",
|
|
435
437
|
catalogUnconfigured: "\u65E0\u6CD5\u914D\u7F6E",
|
|
436
438
|
quotaSeed: "\u989D\u5EA6\u6765\u81EA\u5185\u7F6E\u5FEB\u7167\uFF0C\u8F6C\u5199\u4E8E",
|
|
439
|
+
factsCached: "\u6A21\u578B\u5143\u6570\u636E\u6765\u81EA\u4E0A\u6B21\u6210\u529F\u83B7\u53D6\uFF0C",
|
|
437
440
|
refreshLabel: "\u76EE\u5F55\u5237\u65B0\uFF08\u5206\u949F\uFF09",
|
|
438
441
|
refreshHint: "\u8BF7\u6C42\u4E0E\u6A21\u578B\u9009\u62E9\u5668\u590D\u7528\u540C\u4E00\u4EFD\u5217\u8868\u7684\u65F6\u957F\u3002",
|
|
439
442
|
writeRejected: "\u8BBE\u7F6E\u5199\u5165\u88AB\u62D2\u7EDD\uFF08\u5DF2\u91CD\u65B0\u8F7D\u5165\uFF09\uFF0C\u8BF7\u91CD\u8BD5",
|
|
@@ -1244,6 +1247,11 @@ function Section({ controller, t, getLocale }) {
|
|
|
1244
1247
|
" ",
|
|
1245
1248
|
SEED_TRANSCRIBED
|
|
1246
1249
|
] }),
|
|
1250
|
+
reading !== void 0 && reading.factsCachedAt !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: section_default.hint, children: [
|
|
1251
|
+
t("factsCached"),
|
|
1252
|
+
" ",
|
|
1253
|
+
new Date(reading.factsCachedAt).toISOString().slice(0, 10)
|
|
1254
|
+
] }),
|
|
1247
1255
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: section_default.row, children: [
|
|
1248
1256
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: section_default.hint, htmlFor: "dshopencodego-refresh", children: t("refreshLabel") }),
|
|
1249
1257
|
/* @__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: readFileSync3 } = __require("node:fs");
|
|
17792
|
+
const data = readFileSync3("/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) {
|
|
@@ -42462,8 +42462,6 @@ Check your ChatGPT usage: ${CHATGPT_USAGE_URL}` : errorMessage;
|
|
|
42462
42462
|
});
|
|
42463
42463
|
|
|
42464
42464
|
// src/index.ts
|
|
42465
|
-
import { homedir } from "node:os";
|
|
42466
|
-
import { join as join3 } from "node:path";
|
|
42467
42465
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
42468
42466
|
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
|
|
42469
42467
|
import * as llm2 from "@deepseek-ai/dsh-llm";
|
|
@@ -42530,6 +42528,8 @@ var CONSOLE_BASE_URL = "https://opencode.ai/inference/v1";
|
|
|
42530
42528
|
var MODEL_METADATA_URL = "https://models.dev/api.json";
|
|
42531
42529
|
var MODEL_METADATA_PROVIDER = "opencode-go";
|
|
42532
42530
|
var METADATA_FETCH_TIMEOUT_MS = 1e4;
|
|
42531
|
+
var MODEL_METADATA_TIMEOUT_MS = 3e4;
|
|
42532
|
+
var METADATA_RETRY_DELAY_MS = 1500;
|
|
42533
42533
|
var MODEL_LISTING_MAX_BYTES = 1024 * 1024;
|
|
42534
42534
|
var MODEL_METADATA_MAX_BYTES = 16 * 1024 * 1024;
|
|
42535
42535
|
var UNKNOWN_MODEL_PROBE_COOLDOWN_MS = 5 * 6e4;
|
|
@@ -42551,7 +42551,8 @@ async function readBoundedJson(response, url, maxBytes) {
|
|
|
42551
42551
|
text = await readBoundedText(response, maxBytes);
|
|
42552
42552
|
} catch (error) {
|
|
42553
42553
|
if (error instanceof LlmError) throw error;
|
|
42554
|
-
|
|
42554
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
42555
|
+
throw new LlmError(`could not read ${url}: ${detail}`, "DISCOVERY_FAILED", { cause: error });
|
|
42555
42556
|
}
|
|
42556
42557
|
try {
|
|
42557
42558
|
return JSON.parse(text);
|
|
@@ -43053,6 +43054,43 @@ function toPiModel(facts, baseURL) {
|
|
|
43053
43054
|
};
|
|
43054
43055
|
}
|
|
43055
43056
|
|
|
43057
|
+
// src/catalog/metadata-cache.ts
|
|
43058
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
43059
|
+
import { join as join3 } from "node:path";
|
|
43060
|
+
var CACHE_FILE2 = "models-dev-facts.json";
|
|
43061
|
+
var CACHE_VERSION2 = 1;
|
|
43062
|
+
function readFactsCache(cacheDir) {
|
|
43063
|
+
if (cacheDir === void 0) return void 0;
|
|
43064
|
+
try {
|
|
43065
|
+
const parsed = JSON.parse(readFileSync2(join3(cacheDir, CACHE_FILE2), "utf8"));
|
|
43066
|
+
if (parsed.version !== CACHE_VERSION2 || parsed.facts === null || typeof parsed.facts !== "object") return void 0;
|
|
43067
|
+
const facts = /* @__PURE__ */ new Map();
|
|
43068
|
+
for (const [id, fact] of Object.entries(parsed.facts)) {
|
|
43069
|
+
if (fact === null || typeof fact !== "object") continue;
|
|
43070
|
+
if (typeof fact.id !== "string" || typeof fact.api !== "string") continue;
|
|
43071
|
+
facts.set(id, fact);
|
|
43072
|
+
}
|
|
43073
|
+
if (facts.size === 0) return void 0;
|
|
43074
|
+
return { facts, updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : 0 };
|
|
43075
|
+
} catch {
|
|
43076
|
+
return void 0;
|
|
43077
|
+
}
|
|
43078
|
+
}
|
|
43079
|
+
function writeFactsCache(cacheDir, facts) {
|
|
43080
|
+
if (cacheDir === void 0 || facts.size === 0) return;
|
|
43081
|
+
try {
|
|
43082
|
+
mkdirSync2(cacheDir, { recursive: true });
|
|
43083
|
+
const file = {
|
|
43084
|
+
version: CACHE_VERSION2,
|
|
43085
|
+
facts: Object.fromEntries(facts),
|
|
43086
|
+
updatedAt: Date.now()
|
|
43087
|
+
};
|
|
43088
|
+
writeFileSync2(join3(cacheDir, CACHE_FILE2), `${JSON.stringify(file)}
|
|
43089
|
+
`, "utf8");
|
|
43090
|
+
} catch {
|
|
43091
|
+
}
|
|
43092
|
+
}
|
|
43093
|
+
|
|
43056
43094
|
// src/catalog/index.ts
|
|
43057
43095
|
function reasonText(reason) {
|
|
43058
43096
|
if (reason instanceof Error) return reason.message;
|
|
@@ -43101,12 +43139,27 @@ var OpencodeGoCatalog = class {
|
|
|
43101
43139
|
metadataDocument;
|
|
43102
43140
|
metadataETag;
|
|
43103
43141
|
lastMetadata;
|
|
43142
|
+
/**
|
|
43143
|
+
* The facts a previous successful read produced, read from disk.
|
|
43144
|
+
*
|
|
43145
|
+
* This is what a cold process falls back on when the online metadata cannot
|
|
43146
|
+
* be read. Without it a degraded catalog declares every model unable to
|
|
43147
|
+
* think, and the host refuses the effort the session is already using.
|
|
43148
|
+
*/
|
|
43149
|
+
cachedMetadata;
|
|
43150
|
+
/** When the cached facts were written, for the page to show. */
|
|
43151
|
+
cachedAt;
|
|
43104
43152
|
/** The last parsed documentation; retained across failed fetches. */
|
|
43105
43153
|
document;
|
|
43106
43154
|
/** Ids that already forced a revalidation, with the time they did. */
|
|
43107
43155
|
probedAt = /* @__PURE__ */ new Map();
|
|
43108
43156
|
constructor(options) {
|
|
43109
43157
|
this.options = options;
|
|
43158
|
+
const cached = readFactsCache(options.cacheDir);
|
|
43159
|
+
if (cached !== void 0) {
|
|
43160
|
+
this.cachedMetadata = { models: cached.facts, errors: /* @__PURE__ */ new Map() };
|
|
43161
|
+
this.cachedAt = cached.updatedAt;
|
|
43162
|
+
}
|
|
43110
43163
|
}
|
|
43111
43164
|
/**
|
|
43112
43165
|
* The cached snapshot, refreshed when it is older than the configured TTL.
|
|
@@ -43220,15 +43273,18 @@ var OpencodeGoCatalog = class {
|
|
|
43220
43273
|
...this.documentQuotas()
|
|
43221
43274
|
};
|
|
43222
43275
|
}
|
|
43276
|
+
const fromCache = metadata.status !== "fulfilled" && this.lastMetadata === void 0;
|
|
43277
|
+
const online = metadata.status === "fulfilled" ? metadata.value : this.lastMetadata ?? this.cachedMetadata;
|
|
43223
43278
|
return this.assemble(
|
|
43224
43279
|
listing.value,
|
|
43225
|
-
|
|
43280
|
+
online,
|
|
43226
43281
|
builtin,
|
|
43227
|
-
degradations
|
|
43282
|
+
degradations,
|
|
43283
|
+
fromCache ? this.cachedAt : void 0
|
|
43228
43284
|
);
|
|
43229
43285
|
}
|
|
43230
43286
|
/** Merge the listing with the metadata document and the installed entries. */
|
|
43231
|
-
assemble(ids, online, builtin, degradations = []) {
|
|
43287
|
+
assemble(ids, online, builtin, degradations = [], factsCachedAt) {
|
|
43232
43288
|
const facts = /* @__PURE__ */ new Map();
|
|
43233
43289
|
const unavailable = /* @__PURE__ */ new Map();
|
|
43234
43290
|
for (const id of ids) {
|
|
@@ -43260,6 +43316,7 @@ var OpencodeGoCatalog = class {
|
|
|
43260
43316
|
provider: buildProvider2(this.options.baseURL, [...facts.values()].map((fact) => toPiModel(fact, this.options.baseURL))),
|
|
43261
43317
|
live: true,
|
|
43262
43318
|
...degradations.length === 0 ? {} : { degradations },
|
|
43319
|
+
...factsCachedAt === void 0 ? {} : { factsCachedAt },
|
|
43263
43320
|
fetchedAtMs: Date.now(),
|
|
43264
43321
|
...this.documentQuotas()
|
|
43265
43322
|
};
|
|
@@ -43316,8 +43373,18 @@ var OpencodeGoCatalog = class {
|
|
|
43316
43373
|
releaseDate: void 0
|
|
43317
43374
|
};
|
|
43318
43375
|
}
|
|
43319
|
-
/**
|
|
43320
|
-
|
|
43376
|
+
/**
|
|
43377
|
+
* Read the model metadata document, once.
|
|
43378
|
+
*
|
|
43379
|
+
* The document is megabytes, so its budget is longer than the other two
|
|
43380
|
+
* reads' — and the host that runs this is not always a plain Node process:
|
|
43381
|
+
* the desktop build fetches over the browser's network stack, where the
|
|
43382
|
+
* same document that a shell reads in half a second can take long enough
|
|
43383
|
+
* to outrun a budget set for kilobyte-sized documents.
|
|
43384
|
+
* @param builtin - the installed catalog, for the parse.
|
|
43385
|
+
* @returns the parsed metadata.
|
|
43386
|
+
*/
|
|
43387
|
+
async readMetadataDocument(builtin) {
|
|
43321
43388
|
const response = await fetch(MODEL_METADATA_URL, {
|
|
43322
43389
|
redirect: "error",
|
|
43323
43390
|
headers: {
|
|
@@ -43326,7 +43393,7 @@ var OpencodeGoCatalog = class {
|
|
|
43326
43393
|
"cache-control": "no-cache",
|
|
43327
43394
|
...this.metadataETag === void 0 ? {} : { "if-none-match": this.metadataETag }
|
|
43328
43395
|
},
|
|
43329
|
-
signal: AbortSignal.timeout(
|
|
43396
|
+
signal: AbortSignal.timeout(MODEL_METADATA_TIMEOUT_MS)
|
|
43330
43397
|
});
|
|
43331
43398
|
if (response.status === 304 && this.metadataDocument !== void 0) {
|
|
43332
43399
|
await response.body?.cancel().catch(() => {
|
|
@@ -43353,8 +43420,36 @@ var OpencodeGoCatalog = class {
|
|
|
43353
43420
|
overrides: this.options.overrides
|
|
43354
43421
|
});
|
|
43355
43422
|
this.lastMetadata = parsed;
|
|
43423
|
+
writeFactsCache(this.options.cacheDir, parsed.models);
|
|
43356
43424
|
return parsed;
|
|
43357
43425
|
}
|
|
43426
|
+
/**
|
|
43427
|
+
* Read the model metadata document, retrying once.
|
|
43428
|
+
*
|
|
43429
|
+
* The failure this answers is not a broken network: the same document is
|
|
43430
|
+
* reachable from a browser on the same machine while the host cannot read
|
|
43431
|
+
* it. It is the host being busy — a plugin reload rewrites modules, unpacks
|
|
43432
|
+
* packages, and rebuilds the runtime while the catalog asks for the
|
|
43433
|
+
* document, and the request that loses that race is a timeout, not a
|
|
43434
|
+
* refusal. A second attempt, a moment later, is the same request on a host
|
|
43435
|
+
* that has finished what it was doing.
|
|
43436
|
+
* @param builtin - the installed catalog, for the parse.
|
|
43437
|
+
* @returns the parsed metadata.
|
|
43438
|
+
*/
|
|
43439
|
+
async refreshMetadata(builtin) {
|
|
43440
|
+
try {
|
|
43441
|
+
return await this.readMetadataDocument(builtin);
|
|
43442
|
+
} catch (first) {
|
|
43443
|
+
await new Promise((resolve3) => {
|
|
43444
|
+
setTimeout(resolve3, METADATA_RETRY_DELAY_MS);
|
|
43445
|
+
});
|
|
43446
|
+
try {
|
|
43447
|
+
return await this.readMetadataDocument(builtin);
|
|
43448
|
+
} catch (second) {
|
|
43449
|
+
throw new Error(`${reasonText(first)}; retry: ${reasonText(second)}`);
|
|
43450
|
+
}
|
|
43451
|
+
}
|
|
43452
|
+
}
|
|
43358
43453
|
};
|
|
43359
43454
|
function inferFamily(id) {
|
|
43360
43455
|
const lower = id.toLowerCase();
|
|
@@ -45458,6 +45553,7 @@ function parseCatalogReading(value) {
|
|
|
45458
45553
|
stale: row["stale"],
|
|
45459
45554
|
...typeof row["error"] === "string" ? { error: row["error"] } : {},
|
|
45460
45555
|
degradations: parseDegradations(row["degradations"]),
|
|
45556
|
+
...typeof row["factsCachedAt"] === "number" ? { factsCachedAt: row["factsCachedAt"] } : {},
|
|
45461
45557
|
fetchedAtMs: row["fetchedAtMs"],
|
|
45462
45558
|
quotaSource: quotaSource ?? "seed",
|
|
45463
45559
|
counts: {
|
|
@@ -45699,6 +45795,7 @@ function catalogReading(snapshot2, visibility, listingFailure, now) {
|
|
|
45699
45795
|
stale: !snapshot2.live,
|
|
45700
45796
|
...!snapshot2.live && listingFailure !== void 0 ? { error: listingFailure } : {},
|
|
45701
45797
|
degradations: snapshot2.degradations ?? [],
|
|
45798
|
+
...snapshot2.factsCachedAt === void 0 ? {} : { factsCachedAt: snapshot2.factsCachedAt },
|
|
45702
45799
|
fetchedAtMs: snapshot2.fetchedAtMs,
|
|
45703
45800
|
quotaSource: documentQuotas === void 0 ? "seed" : "document",
|
|
45704
45801
|
counts: {
|
|
@@ -45867,7 +45964,7 @@ function createUpdateChecker(deps) {
|
|
|
45867
45964
|
}
|
|
45868
45965
|
|
|
45869
45966
|
// src/update/version.ts
|
|
45870
|
-
var PLUGIN_VERSION = true ? "0.1.
|
|
45967
|
+
var PLUGIN_VERSION = true ? "0.1.50" : "0.0.0-dev";
|
|
45871
45968
|
|
|
45872
45969
|
// src/update/service.ts
|
|
45873
45970
|
import { TypertRemoteService as TypertRemoteService2 } from "@deepseek-ai/dsh-typert-protocol";
|
|
@@ -46006,15 +46103,12 @@ function isLiveConfig(raw) {
|
|
|
46006
46103
|
}
|
|
46007
46104
|
function resolveCacheDir(ctx) {
|
|
46008
46105
|
const home = ctx.dshHomePath;
|
|
46009
|
-
if (typeof home
|
|
46010
|
-
|
|
46011
|
-
|
|
46012
|
-
|
|
46013
|
-
|
|
46106
|
+
if (typeof home !== "function") return void 0;
|
|
46107
|
+
try {
|
|
46108
|
+
return home("cache", "@dan-ai-studio/dshopencodego");
|
|
46109
|
+
} catch {
|
|
46110
|
+
return void 0;
|
|
46014
46111
|
}
|
|
46015
|
-
const configured = process.env.DSH_HOME?.trim();
|
|
46016
|
-
const root = configured !== void 0 && configured.length > 0 ? configured : join3(homedir(), ".dsh");
|
|
46017
|
-
return join3(root, "cache", "@dan-ai-studio/dshopencodego");
|
|
46018
46112
|
}
|
|
46019
46113
|
function apply(ctx, raw) {
|
|
46020
46114
|
const live = isLiveConfig(raw) ? raw : void 0;
|
|
@@ -46033,10 +46127,11 @@ function apply(ctx, raw) {
|
|
|
46033
46127
|
);
|
|
46034
46128
|
};
|
|
46035
46129
|
const meter = new UsageMeter();
|
|
46130
|
+
const cacheDir = resolveCacheDir(ctx);
|
|
46036
46131
|
const adapter = new OpencodeGoAdapter({
|
|
46037
46132
|
config: current,
|
|
46038
46133
|
resolveApiKey,
|
|
46039
|
-
cacheDir:
|
|
46134
|
+
...cacheDir === void 0 ? {} : { cacheDir },
|
|
46040
46135
|
imageAccess: {
|
|
46041
46136
|
resolveAttachments: () => ctx.get("attachments"),
|
|
46042
46137
|
resolveImageAccess: (attachments, ref) => llm2.resolveImageAttachmentAccess(
|
|
@@ -11,8 +11,28 @@ export declare const CONSOLE_BASE_URL = "https://opencode.ai/inference/v1";
|
|
|
11
11
|
export declare const MODEL_METADATA_URL = "https://models.dev/api.json";
|
|
12
12
|
/** The models.dev provider key this plugin reads. */
|
|
13
13
|
export declare const MODEL_METADATA_PROVIDER = "opencode-go";
|
|
14
|
-
/** Timeout for the two metadata GETs. */
|
|
14
|
+
/** Timeout for the two small metadata GETs. */
|
|
15
15
|
export declare const METADATA_FETCH_TIMEOUT_MS = 10000;
|
|
16
|
+
/**
|
|
17
|
+
* Timeout for the model metadata document.
|
|
18
|
+
*
|
|
19
|
+
* It is the one read measured in megabytes — 5.9 MB for the whole of
|
|
20
|
+
* models.dev, of which this plugin uses one provider — and the host that
|
|
21
|
+
* fetches it is not always a plain Node process. The desktop build fetches
|
|
22
|
+
* over the browser's network stack, where the same document a shell reads in
|
|
23
|
+
* half a second can outrun a budget set for kilobyte-sized documents. The
|
|
24
|
+
* longer budget costs nothing when the read succeeds; it is the difference
|
|
25
|
+
* between a catalog and a degraded one.
|
|
26
|
+
*/
|
|
27
|
+
export declare const MODEL_METADATA_TIMEOUT_MS = 30000;
|
|
28
|
+
/**
|
|
29
|
+
* How long to wait before the one retry of the metadata read.
|
|
30
|
+
*
|
|
31
|
+
* Long enough for the host to finish whatever it was doing — a plugin
|
|
32
|
+
* reload, a package unpack — and short enough that a genuinely unreachable
|
|
33
|
+
* document still fails inside the catalog build.
|
|
34
|
+
*/
|
|
35
|
+
export declare const METADATA_RETRY_DELAY_MS = 1500;
|
|
16
36
|
/** Response caps: the listing is tiny, the metadata document is not. */
|
|
17
37
|
export declare const MODEL_LISTING_MAX_BYTES: number;
|
|
18
38
|
export declare const MODEL_METADATA_MAX_BYTES: number;
|
|
@@ -73,6 +73,15 @@ export interface CatalogSnapshot {
|
|
|
73
73
|
* a user cannot act on.
|
|
74
74
|
*/
|
|
75
75
|
readonly degradations?: readonly CatalogDegradation[];
|
|
76
|
+
/**
|
|
77
|
+
* When the facts in this snapshot were written, when they came from disk
|
|
78
|
+
* rather than from a live read.
|
|
79
|
+
*
|
|
80
|
+
* The listing is always live; the metadata behind it may be older. The page
|
|
81
|
+
* says so, because a capacity that was true last week reads exactly like
|
|
82
|
+
* one that is true now.
|
|
83
|
+
*/
|
|
84
|
+
readonly factsCachedAt?: number;
|
|
76
85
|
readonly fetchedAtMs: number;
|
|
77
86
|
}
|
|
78
87
|
/** Observers the plugin logs through. */
|
|
@@ -126,6 +135,16 @@ export declare class OpencodeGoCatalog {
|
|
|
126
135
|
private metadataDocument;
|
|
127
136
|
private metadataETag;
|
|
128
137
|
private lastMetadata;
|
|
138
|
+
/**
|
|
139
|
+
* The facts a previous successful read produced, read from disk.
|
|
140
|
+
*
|
|
141
|
+
* This is what a cold process falls back on when the online metadata cannot
|
|
142
|
+
* be read. Without it a degraded catalog declares every model unable to
|
|
143
|
+
* think, and the host refuses the effort the session is already using.
|
|
144
|
+
*/
|
|
145
|
+
private cachedMetadata;
|
|
146
|
+
/** When the cached facts were written, for the page to show. */
|
|
147
|
+
private readonly cachedAt;
|
|
129
148
|
/** The last parsed documentation; retained across failed fetches. */
|
|
130
149
|
private document;
|
|
131
150
|
/** Ids that already forced a revalidation, with the time they did. */
|
|
@@ -193,7 +212,31 @@ export declare class OpencodeGoCatalog {
|
|
|
193
212
|
private withDocumentedProtocol;
|
|
194
213
|
/** Facts for an id no source describes, from the ladder and the route defaults. */
|
|
195
214
|
private inferredFacts;
|
|
196
|
-
/**
|
|
215
|
+
/**
|
|
216
|
+
* Read the model metadata document, once.
|
|
217
|
+
*
|
|
218
|
+
* The document is megabytes, so its budget is longer than the other two
|
|
219
|
+
* reads' — and the host that runs this is not always a plain Node process:
|
|
220
|
+
* the desktop build fetches over the browser's network stack, where the
|
|
221
|
+
* same document that a shell reads in half a second can take long enough
|
|
222
|
+
* to outrun a budget set for kilobyte-sized documents.
|
|
223
|
+
* @param builtin - the installed catalog, for the parse.
|
|
224
|
+
* @returns the parsed metadata.
|
|
225
|
+
*/
|
|
226
|
+
private readMetadataDocument;
|
|
227
|
+
/**
|
|
228
|
+
* Read the model metadata document, retrying once.
|
|
229
|
+
*
|
|
230
|
+
* The failure this answers is not a broken network: the same document is
|
|
231
|
+
* reachable from a browser on the same machine while the host cannot read
|
|
232
|
+
* it. It is the host being busy — a plugin reload rewrites modules, unpacks
|
|
233
|
+
* packages, and rebuilds the runtime while the catalog asks for the
|
|
234
|
+
* document, and the request that loses that race is a timeout, not a
|
|
235
|
+
* refusal. A second attempt, a moment later, is the same request on a host
|
|
236
|
+
* that has finished what it was doing.
|
|
237
|
+
* @param builtin - the installed catalog, for the parse.
|
|
238
|
+
* @returns the parsed metadata.
|
|
239
|
+
*/
|
|
197
240
|
private refreshMetadata;
|
|
198
241
|
}
|
|
199
242
|
/**
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeping what the last successful metadata read said.
|
|
3
|
+
*
|
|
4
|
+
* The online metadata is the only source that states which models can think
|
|
5
|
+
* and at which levels. When it cannot be read, the plugin still knows some of
|
|
6
|
+
* that — from the last time it could — and throwing that away turns a
|
|
7
|
+
* degraded catalog into a broken one: a model that does think is declared
|
|
8
|
+
* unable to think, the host's own check refuses the effort the session is
|
|
9
|
+
* already using, and the run fails before it starts.
|
|
10
|
+
*
|
|
11
|
+
* So the parsed facts are written down. What is written is deliberately
|
|
12
|
+
* small: the capabilities, the capacities, the prices — not the document.
|
|
13
|
+
* The listing stays the authority on which models exist, so a stale file can
|
|
14
|
+
* never introduce a model the gateway does not serve; it can only describe
|
|
15
|
+
* the ones it does, with what was true when the file was written.
|
|
16
|
+
*
|
|
17
|
+
* @module @dan-ai-studio/dshopencodego/catalog/metadata-cache
|
|
18
|
+
*/
|
|
19
|
+
import type { ModelFacts } from './metadata.ts';
|
|
20
|
+
/**
|
|
21
|
+
* The facts a previous successful read produced.
|
|
22
|
+
*
|
|
23
|
+
* A cache that cannot be read is no cache: the caller then has nothing to
|
|
24
|
+
* fall back on and says so, rather than pretending the models have no
|
|
25
|
+
* capabilities.
|
|
26
|
+
* @param cacheDir - the plugin's cache directory.
|
|
27
|
+
* @returns the remembered facts and when they were written, or undefined.
|
|
28
|
+
*/
|
|
29
|
+
export declare function readFactsCache(cacheDir: string | undefined): {
|
|
30
|
+
readonly facts: ReadonlyMap<string, ModelFacts>;
|
|
31
|
+
readonly updatedAt: number;
|
|
32
|
+
} | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Remember the facts a successful read produced.
|
|
35
|
+
*
|
|
36
|
+
* A cache directory that cannot be written costs a degraded catalog after the
|
|
37
|
+
* next restart, and nothing else: the run continues with what it has.
|
|
38
|
+
* @param cacheDir - the plugin's cache directory.
|
|
39
|
+
* @param facts - the parsed facts to remember.
|
|
40
|
+
*/
|
|
41
|
+
export declare function writeFactsCache(cacheDir: string | undefined, facts: ReadonlyMap<string, ModelFacts>): void;
|
|
@@ -29,6 +29,15 @@ export interface CatalogReading {
|
|
|
29
29
|
* difference between a user who can act and a user who cannot.
|
|
30
30
|
*/
|
|
31
31
|
readonly degradations: readonly CatalogDegradation[];
|
|
32
|
+
/**
|
|
33
|
+
* When the model metadata behind this reading was written, when it came
|
|
34
|
+
* from disk rather than from a live read.
|
|
35
|
+
*
|
|
36
|
+
* The listing is live; the capacities, prices, and thinking levels behind
|
|
37
|
+
* it may not be. The page shows this, because a stale number reads exactly
|
|
38
|
+
* like a current one.
|
|
39
|
+
*/
|
|
40
|
+
readonly factsCachedAt?: number;
|
|
32
41
|
/** When the underlying snapshot was built. */
|
|
33
42
|
readonly fetchedAtMs: number;
|
|
34
43
|
/**
|
package/package.json
CHANGED