@dan-ai-studio/dshopencodego 0.1.22 → 0.1.24
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/lib/client.js +322 -14
- package/lib/index.js +146 -6
- package/lib/types/conversion/stream.js +6 -0
- package/lib/types/index.js +3 -0
- package/lib/types/remotes.js +2 -1
- package/lib/types/update/check.d.ts +74 -0
- package/lib/types/update/check.js +132 -0
- package/lib/types/update/contract.d.ts +23 -0
- package/lib/types/update/contract.js +52 -0
- package/lib/types/update/index.d.ts +12 -0
- package/lib/types/update/index.js +10 -0
- package/lib/types/update/service.d.ts +28 -0
- package/lib/types/update/service.js +28 -0
- package/lib/types/update/version.d.ts +14 -0
- package/lib/types/update/version.js +16 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -152,6 +152,44 @@ var catalogRemote = {
|
|
|
152
152
|
]
|
|
153
153
|
};
|
|
154
154
|
|
|
155
|
+
// src/update/contract.ts
|
|
156
|
+
function parseUpdateStatus(value) {
|
|
157
|
+
if (value === null || typeof value !== "object") throw new Error("invalid update reading");
|
|
158
|
+
const row = value;
|
|
159
|
+
if (typeof row["current"] !== "string") throw new Error("invalid update reading");
|
|
160
|
+
if (row["latest"] !== void 0 && typeof row["latest"] !== "string") throw new Error("invalid update reading");
|
|
161
|
+
if (typeof row["available"] !== "boolean") throw new Error("invalid update reading");
|
|
162
|
+
if (typeof row["checkedAt"] !== "number" || !Number.isFinite(row["checkedAt"])) {
|
|
163
|
+
throw new Error("invalid update reading");
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
current: row["current"],
|
|
167
|
+
latest: typeof row["latest"] === "string" ? row["latest"] : void 0,
|
|
168
|
+
available: row["available"],
|
|
169
|
+
checkedAt: row["checkedAt"]
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
var codec2 = (typeSymbol, parse) => ({
|
|
173
|
+
mode: "strict",
|
|
174
|
+
typeSymbol,
|
|
175
|
+
schema: { parse },
|
|
176
|
+
create: () => ({ parse })
|
|
177
|
+
});
|
|
178
|
+
var updateRemote = {
|
|
179
|
+
package: "@dan-ai-studio/dshopencodego",
|
|
180
|
+
descriptors: [
|
|
181
|
+
{
|
|
182
|
+
id: "@dan-ai-studio/dshopencodego#opencodeGoUpdate/readUpdate",
|
|
183
|
+
service: "opencodeGoUpdate",
|
|
184
|
+
namespace: "opencodeGoUpdate",
|
|
185
|
+
method: "readUpdate",
|
|
186
|
+
invocation: { kind: "direct" },
|
|
187
|
+
parameters: [],
|
|
188
|
+
result: codec2("@dan-ai-studio/dshopencodego#UpdateStatus", parseUpdateStatus)
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
};
|
|
192
|
+
|
|
155
193
|
// src/usage/contract.ts
|
|
156
194
|
function parseWindow(value, key) {
|
|
157
195
|
if (value === null || typeof value !== "object") throw new Error(`invalid usage window "${key}"`);
|
|
@@ -206,7 +244,7 @@ function parseGoMeter(value) {
|
|
|
206
244
|
})
|
|
207
245
|
};
|
|
208
246
|
}
|
|
209
|
-
var
|
|
247
|
+
var codec3 = (typeSymbol, parse) => ({
|
|
210
248
|
mode: "strict",
|
|
211
249
|
typeSymbol,
|
|
212
250
|
schema: { parse },
|
|
@@ -222,7 +260,7 @@ var usageRemote = {
|
|
|
222
260
|
method: "readWindows",
|
|
223
261
|
invocation: { kind: "direct" },
|
|
224
262
|
parameters: [],
|
|
225
|
-
result:
|
|
263
|
+
result: codec3("@dan-ai-studio/dshopencodego#GoUsageWindows", parseUsageWindows)
|
|
226
264
|
},
|
|
227
265
|
{
|
|
228
266
|
id: "@dan-ai-studio/dshopencodego#opencodeGoUsage/readMeter",
|
|
@@ -231,7 +269,7 @@ var usageRemote = {
|
|
|
231
269
|
method: "readMeter",
|
|
232
270
|
invocation: { kind: "direct" },
|
|
233
271
|
parameters: [],
|
|
234
|
-
result:
|
|
272
|
+
result: codec3("@dan-ai-studio/dshopencodego#GoMeter", parseGoMeter)
|
|
235
273
|
}
|
|
236
274
|
]
|
|
237
275
|
};
|
|
@@ -302,7 +340,25 @@ var en = {
|
|
|
302
340
|
usageTokensUnit: "tokens",
|
|
303
341
|
usageCached: "cached read",
|
|
304
342
|
usageLocalHint: "The gateway publishes quota percentages only; token counts are what this process actually spent.",
|
|
305
|
-
usageLoading: "Loading\u2026"
|
|
343
|
+
usageLoading: "Loading\u2026",
|
|
344
|
+
updateTitle: "Plugin update",
|
|
345
|
+
updateChecking: "Checking for updates\u2026",
|
|
346
|
+
updateLatest: "Up to date:",
|
|
347
|
+
updateAvailable: "New version available:",
|
|
348
|
+
updateCurrent: "current",
|
|
349
|
+
updateNow: "Update now",
|
|
350
|
+
updateNotes: "Release notes",
|
|
351
|
+
updateRecheck: "Check again",
|
|
352
|
+
updateConfirmHint: "This removes the running version first, then installs",
|
|
353
|
+
updateConfirm: "Confirm update",
|
|
354
|
+
updateCancel: "Cancel",
|
|
355
|
+
updateApplying: "Updating\u2026 (removing the old version, then installing the new one)",
|
|
356
|
+
updateDone: "Updated to",
|
|
357
|
+
updateRestart: "Restart DSH to finish applying the update.",
|
|
358
|
+
updateFailed: "Update failed:",
|
|
359
|
+
updateStopProfile: "The running version cannot be removed without a restart; restart DSH first, then retry.",
|
|
360
|
+
updateIncompatible: "The new version is incompatible with this DSH build; finish the update from the plugin manager page (a version exemption needs your explicit approval).",
|
|
361
|
+
updatePendingBuilds: "Installation needs build-script approval; approve it on the plugin manager page, then retry."
|
|
306
362
|
};
|
|
307
363
|
var zh = {
|
|
308
364
|
nav: "OpenCode Go",
|
|
@@ -369,12 +425,35 @@ var zh = {
|
|
|
369
425
|
usageTokensUnit: "tokens",
|
|
370
426
|
usageCached: "\u7F13\u5B58\u8BFB\u53D6",
|
|
371
427
|
usageLocalHint: "\u7F51\u5173\u53EA\u516C\u5E03\u989D\u5EA6\u767E\u5206\u6BD4\uFF1Btoken \u6570\u4E3A\u672C\u8FDB\u7A0B\u5B9E\u9645\u6D88\u8017\u3002",
|
|
372
|
-
usageLoading: "\u52A0\u8F7D\u4E2D\u2026"
|
|
428
|
+
usageLoading: "\u52A0\u8F7D\u4E2D\u2026",
|
|
429
|
+
updateTitle: "\u63D2\u4EF6\u66F4\u65B0",
|
|
430
|
+
updateChecking: "\u6B63\u5728\u68C0\u67E5\u66F4\u65B0\u2026",
|
|
431
|
+
updateLatest: "\u5DF2\u662F\u6700\u65B0\u7248\u672C\uFF1A",
|
|
432
|
+
updateAvailable: "\u53D1\u73B0\u65B0\u7248\u672C\uFF1A",
|
|
433
|
+
updateCurrent: "\u5F53\u524D",
|
|
434
|
+
updateNow: "\u7ACB\u5373\u66F4\u65B0",
|
|
435
|
+
updateNotes: "\u66F4\u65B0\u8BF4\u660E",
|
|
436
|
+
updateRecheck: "\u91CD\u65B0\u68C0\u67E5",
|
|
437
|
+
updateConfirmHint: "\u5C06\u5148\u5378\u8F7D\u5F53\u524D\u8FD0\u884C\u7248\u672C\uFF0C\u518D\u5B89\u88C5",
|
|
438
|
+
updateConfirm: "\u786E\u8BA4\u66F4\u65B0",
|
|
439
|
+
updateCancel: "\u53D6\u6D88",
|
|
440
|
+
updateApplying: "\u66F4\u65B0\u4E2D\u2026\uFF08\u5148\u5378\u8F7D\u65E7\u7248\uFF0C\u518D\u5B89\u88C5\u65B0\u7248\uFF09",
|
|
441
|
+
updateDone: "\u5DF2\u66F4\u65B0\u5230",
|
|
442
|
+
updateRestart: "\u8BF7\u91CD\u542F DSH \u4EE5\u5B8C\u6210\u66F4\u65B0\u3002",
|
|
443
|
+
updateFailed: "\u66F4\u65B0\u5931\u8D25\uFF1A",
|
|
444
|
+
updateStopProfile: "\u8FD0\u884C\u4E2D\u7684\u7248\u672C\u9700\u91CD\u542F\u540E\u624D\u80FD\u79FB\u9664\uFF1B\u8BF7\u5148\u91CD\u542F DSH \u518D\u91CD\u8BD5\u3002",
|
|
445
|
+
updateIncompatible: "\u65B0\u7248\u672C\u4E0E\u5F53\u524D DSH \u4E0D\u517C\u5BB9\uFF0C\u8BF7\u524D\u5F80\u63D2\u4EF6\u7BA1\u7406\u9875\u5B8C\u6210\u66F4\u65B0\uFF08\u7248\u672C\u8C41\u514D\u9700\u660E\u786E\u6279\u51C6\uFF09\u3002",
|
|
446
|
+
updatePendingBuilds: "\u5B89\u88C5\u9700\u8981\u6279\u51C6\u6784\u5EFA\u811A\u672C\uFF1B\u8BF7\u524D\u5F80\u63D2\u4EF6\u7BA1\u7406\u9875\u6279\u51C6\u540E\u518D\u91CD\u8BD5\u3002"
|
|
373
447
|
};
|
|
374
448
|
|
|
375
449
|
// src/client/Section.tsx
|
|
376
450
|
var import_react = require("react");
|
|
377
451
|
|
|
452
|
+
// src/update/check.ts
|
|
453
|
+
var PACKAGE_NAME = "@dan-ai-studio/dshopencodego";
|
|
454
|
+
var REGISTRY_LATEST_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`;
|
|
455
|
+
var CHECK_CACHE_MS = 6 * 60 * 60 * 1e3;
|
|
456
|
+
|
|
378
457
|
// src/go-limits.ts
|
|
379
458
|
var SEED_TRANSCRIBED = "2026-09-25";
|
|
380
459
|
function monthlyRequestsRank(quota) {
|
|
@@ -453,16 +532,16 @@ function inputModalityLabels(model, t) {
|
|
|
453
532
|
return (model.inputModalities ?? []).map((modality) => t(MODALITY_KEYS[modality]));
|
|
454
533
|
}
|
|
455
534
|
|
|
456
|
-
// plugin-css
|
|
535
|
+
// plugin-css:/home/runner/work/dshopencodego/dshopencodego/src/client/section.module.css
|
|
457
536
|
var id = "@dan-ai-studio/dshopencodego/section.module.css";
|
|
458
537
|
if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id) + "]")) {
|
|
459
538
|
const style = document.createElement("style");
|
|
460
539
|
style.dataset.plugin = "@dan-ai-studio/dshopencodego";
|
|
461
540
|
style.dataset.pluginCss = id;
|
|
462
|
-
style.textContent = ".
|
|
541
|
+
style.textContent = ".d7lz0W_root{color:var(--dsw-alias-label-primary);flex-direction:column;gap:1.25rem;display:flex}.d7lz0W_title{margin:0;font-size:1.05rem}.d7lz0W_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:.8rem}.d7lz0W_warn{color:var(--dsw-alias-state-warn-primary);margin:0;font-size:.8rem}.d7lz0W_ok{color:var(--dsw-alias-state-success-primary);font-size:.8rem}.d7lz0W_block{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:.6rem;flex-direction:column;gap:.5rem;padding:.85rem;display:flex}.d7lz0W_row{flex-wrap:wrap;align-items:center;gap:.5rem;display:flex}.d7lz0W_row>strong{margin-right:auto}.d7lz0W_input{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);min-width:10rem;color:var(--dsw-alias-label-primary);font:inherit;border-radius:.35rem;flex:14rem;padding:.35rem .5rem;font-size:.85rem}.d7lz0W_button{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;border-radius:.35rem;padding:.35rem .7rem;font-size:.8rem}.d7lz0W_button:disabled{color:var(--dsw-alias-state-idle-primary);cursor:default}.d7lz0W_number{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:5rem;color:var(--dsw-alias-label-primary);font:inherit;border-radius:.35rem;padding:.25rem .4rem;font-size:.8rem}.d7lz0W_select{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:auto;max-width:100%;color:var(--dsw-alias-label-primary);font:inherit;border-radius:.35rem;padding:.25rem .4rem;font-size:.8rem}.d7lz0W_list{flex-direction:column;display:flex}.d7lz0W_item{border-top:1px solid var(--dsw-alias-border-l1);align-items:flex-start;gap:.5rem;padding:.45rem .15rem;display:flex}.d7lz0W_item input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary);margin-top:.15rem}.d7lz0W_itemBody{flex-direction:column;gap:.15rem;min-width:0;display:flex}.d7lz0W_itemMain{flex-wrap:wrap;align-items:baseline;gap:.6rem;display:flex}.d7lz0W_itemMeta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:.75rem;font-size:.75rem;display:flex}.d7lz0W_itemModalities{color:var(--dsw-alias-label-secondary);font-size:.75rem}.d7lz0W_name{color:var(--dsw-alias-label-primary)}.d7lz0W_mono{color:var(--dsw-alias-label-secondary);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.d7lz0W_badge{color:var(--dsw-alias-brand-primary)}";
|
|
463
542
|
document.head.appendChild(style);
|
|
464
543
|
}
|
|
465
|
-
var section_default = { "
|
|
544
|
+
var section_default = { "row": "d7lz0W_row", "itemModalities": "d7lz0W_itemModalities", "number": "d7lz0W_number", "hint": "d7lz0W_hint", "block": "d7lz0W_block", "button": "d7lz0W_button", "list": "d7lz0W_list", "select": "d7lz0W_select", "itemMeta": "d7lz0W_itemMeta", "badge": "d7lz0W_badge", "root": "d7lz0W_root", "title": "d7lz0W_title", "warn": "d7lz0W_warn", "ok": "d7lz0W_ok", "input": "d7lz0W_input", "item": "d7lz0W_item", "itemMain": "d7lz0W_itemMain", "itemBody": "d7lz0W_itemBody", "mono": "d7lz0W_mono", "name": "d7lz0W_name" };
|
|
466
545
|
|
|
467
546
|
// src/client/Section.tsx
|
|
468
547
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -478,7 +557,8 @@ var INITIAL = {
|
|
|
478
557
|
settings: void 0,
|
|
479
558
|
writable: false,
|
|
480
559
|
saving: false,
|
|
481
|
-
filter: INITIAL_FILTER
|
|
560
|
+
filter: INITIAL_FILTER,
|
|
561
|
+
update: { phase: "idle", status: void 0, message: void 0 }
|
|
482
562
|
};
|
|
483
563
|
function settingsOf(value) {
|
|
484
564
|
if (value === null || typeof value !== "object") return void 0;
|
|
@@ -492,7 +572,7 @@ function settingsOf(value) {
|
|
|
492
572
|
refreshMinutes: minutes
|
|
493
573
|
};
|
|
494
574
|
}
|
|
495
|
-
var SectionController = class {
|
|
575
|
+
var SectionController = class _SectionController {
|
|
496
576
|
state = INITIAL;
|
|
497
577
|
listeners = /* @__PURE__ */ new Set();
|
|
498
578
|
services;
|
|
@@ -514,6 +594,7 @@ var SectionController = class {
|
|
|
514
594
|
}
|
|
515
595
|
void this.load(false);
|
|
516
596
|
void this.loadKey();
|
|
597
|
+
void this.checkUpdate(false);
|
|
517
598
|
}
|
|
518
599
|
subscribe = (listener) => {
|
|
519
600
|
this.listeners.add(listener);
|
|
@@ -556,6 +637,117 @@ var SectionController = class {
|
|
|
556
637
|
this.set({ keyConfigured: void 0 });
|
|
557
638
|
}
|
|
558
639
|
}
|
|
640
|
+
/**
|
|
641
|
+
* Ask the Host whether a newer release is published.
|
|
642
|
+
*
|
|
643
|
+
* The Host answers from a cached registry reading and fails silent, so an
|
|
644
|
+
* unreachable registry hides the panel instead of erroring the page. Runs
|
|
645
|
+
* once with the page and again only on explicit refresh.
|
|
646
|
+
*/
|
|
647
|
+
async checkUpdate(force) {
|
|
648
|
+
const previous = this.state.update;
|
|
649
|
+
this.set({ update: { ...previous, phase: "checking", message: void 0 } });
|
|
650
|
+
try {
|
|
651
|
+
const result = await this.services.remote.opencodeGoUpdate.readUpdate(force);
|
|
652
|
+
if (!result.ok) throw result.error;
|
|
653
|
+
const status = result.value;
|
|
654
|
+
this.set({
|
|
655
|
+
update: {
|
|
656
|
+
phase: status.available && status.latest !== void 0 ? "ready" : "current",
|
|
657
|
+
status,
|
|
658
|
+
message: void 0
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
} catch (error) {
|
|
662
|
+
this.set({
|
|
663
|
+
update: {
|
|
664
|
+
...previous,
|
|
665
|
+
phase: previous.phase === "checking" ? "idle" : previous.phase,
|
|
666
|
+
message: error instanceof Error ? error.message : void 0
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
/** Arm the confirm step; the destructive two-step runs only from `applyUpdate`. */
|
|
672
|
+
armUpdate() {
|
|
673
|
+
if (this.state.update.phase === "ready") {
|
|
674
|
+
this.set({ update: { ...this.state.update, phase: "confirming" } });
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
/** Stand down from the confirm step without doing anything. */
|
|
678
|
+
disarmUpdate() {
|
|
679
|
+
if (this.state.update.phase === "confirming") {
|
|
680
|
+
this.set({ update: { ...this.state.update, phase: "ready" } });
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
/** Read one field off an unknown host answer without guessing its shape. */
|
|
684
|
+
static field(value, key) {
|
|
685
|
+
return value !== null && typeof value === "object" ? value[key] : void 0;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* One-click update: inspect, then the host's own remove + install.
|
|
689
|
+
*
|
|
690
|
+
* This mirrors the official plugin-update button exactly — the plugin never
|
|
691
|
+
* touches its own files. `removeBundle` stops the running build first, so
|
|
692
|
+
* the install call that follows is issued by this page (which survives),
|
|
693
|
+
* not by the Host code being removed. Outcomes that need a human —
|
|
694
|
+
* incompatibility (never auto-exempted), pending build approvals, a removal
|
|
695
|
+
* that needs a restart first — surface as guidance, never as silent retries.
|
|
696
|
+
*/
|
|
697
|
+
async applyUpdate() {
|
|
698
|
+
const status = this.state.update.status;
|
|
699
|
+
const latest = status?.latest;
|
|
700
|
+
if (this.state.update.phase !== "confirming" || latest === void 0) return;
|
|
701
|
+
const manager = this.services.remote.pluginManager;
|
|
702
|
+
const spec = `${PACKAGE_NAME}@${latest}`;
|
|
703
|
+
this.set({ update: { phase: "updating", status, message: void 0 } });
|
|
704
|
+
const fail = (message) => {
|
|
705
|
+
this.set({ update: { phase: "failed", status, message } });
|
|
706
|
+
};
|
|
707
|
+
try {
|
|
708
|
+
const inspected = await manager.inspect(spec);
|
|
709
|
+
if (_SectionController.field(inspected, "bundle") === false) {
|
|
710
|
+
fail(`the registry target is not a DSH bundle: ${spec}`);
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
} catch (error) {
|
|
714
|
+
fail(error instanceof Error ? error.message : `could not inspect ${spec}`);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
try {
|
|
718
|
+
const removed = await manager.removeBundle(PACKAGE_NAME);
|
|
719
|
+
if (!removed.ok) throw removed.error;
|
|
720
|
+
const stopCode = _SectionController.field(removed.value, "error");
|
|
721
|
+
const code = typeof stopCode === "object" && stopCode !== null ? _SectionController.field(stopCode, "code") : void 0;
|
|
722
|
+
if (code === "stop-profile") {
|
|
723
|
+
fail("stop-profile");
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
} catch (error) {
|
|
727
|
+
fail(error instanceof Error ? error.message : `could not remove ${PACKAGE_NAME}`);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
try {
|
|
731
|
+
const installed = await manager.installBundle(spec, { saveExact: true });
|
|
732
|
+
if (!installed.ok) throw installed.error;
|
|
733
|
+
const value = installed.value ?? {};
|
|
734
|
+
if (value.error?.code === "incompatible-version") {
|
|
735
|
+
fail("incompatible-version");
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (value.pendingBuilds !== void 0) {
|
|
739
|
+
fail("pending-builds");
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (value.application === "restart-required") {
|
|
743
|
+
this.set({ update: { phase: "done", status, message: "restart-required" } });
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
this.set({ update: { phase: "done", status, message: void 0 } });
|
|
747
|
+
} catch (error) {
|
|
748
|
+
fail(error instanceof Error ? error.message : `could not install ${spec}`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
559
751
|
async saveKey(value) {
|
|
560
752
|
const trimmed = value.trim();
|
|
561
753
|
if (trimmed.length === 0) return;
|
|
@@ -697,6 +889,121 @@ function quotaLabel(model, t) {
|
|
|
697
889
|
const requests = quota.monthlyRequests === "unlimited" ? t("quotaUnlimited") : `${compactCount(quota.monthlyRequests)} ${t("quotaPerMonth")}`;
|
|
698
890
|
return `${usd} \xB7 ${requests}`;
|
|
699
891
|
}
|
|
892
|
+
function UpdateBanner({ controller, t, busy, act, update }) {
|
|
893
|
+
if (update.phase === "idle") return null;
|
|
894
|
+
const status = update.status;
|
|
895
|
+
const notes = `https://www.npmjs.com/package/${encodeURIComponent(PACKAGE_NAME)}`;
|
|
896
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: section_default.block, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: section_default.row, children: [
|
|
897
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: t("updateTitle") }),
|
|
898
|
+
update.phase === "checking" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.hint, children: t("updateChecking") }),
|
|
899
|
+
update.phase === "current" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
900
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.hint, children: `${t("updateLatest")} ${status?.current ?? ""}`.trim() }),
|
|
901
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
902
|
+
"button",
|
|
903
|
+
{
|
|
904
|
+
type: "button",
|
|
905
|
+
className: section_default.button,
|
|
906
|
+
disabled: busy,
|
|
907
|
+
onClick: () => {
|
|
908
|
+
void act(async () => {
|
|
909
|
+
await controller.checkUpdate(true);
|
|
910
|
+
});
|
|
911
|
+
},
|
|
912
|
+
children: t("updateRecheck")
|
|
913
|
+
}
|
|
914
|
+
)
|
|
915
|
+
] }),
|
|
916
|
+
update.phase === "ready" && status?.latest !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
917
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.hint, children: `${t("updateAvailable")} ${status.latest} (${t("updateCurrent")} ${status.current})` }),
|
|
918
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
919
|
+
"button",
|
|
920
|
+
{
|
|
921
|
+
type: "button",
|
|
922
|
+
className: section_default.button,
|
|
923
|
+
disabled: busy,
|
|
924
|
+
onClick: () => {
|
|
925
|
+
controller.armUpdate();
|
|
926
|
+
},
|
|
927
|
+
children: t("updateNow")
|
|
928
|
+
}
|
|
929
|
+
),
|
|
930
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: section_default.hint, href: notes, target: "_blank", rel: "noreferrer", children: t("updateNotes") }),
|
|
931
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
932
|
+
"button",
|
|
933
|
+
{
|
|
934
|
+
type: "button",
|
|
935
|
+
className: section_default.button,
|
|
936
|
+
disabled: busy,
|
|
937
|
+
onClick: () => {
|
|
938
|
+
void act(async () => {
|
|
939
|
+
await controller.checkUpdate(true);
|
|
940
|
+
});
|
|
941
|
+
},
|
|
942
|
+
children: t("updateRecheck")
|
|
943
|
+
}
|
|
944
|
+
)
|
|
945
|
+
] }),
|
|
946
|
+
update.phase === "confirming" && status?.latest !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
947
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.hint, children: `${t("updateConfirmHint")} ${status.latest}` }),
|
|
948
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
949
|
+
"button",
|
|
950
|
+
{
|
|
951
|
+
type: "button",
|
|
952
|
+
className: section_default.button,
|
|
953
|
+
disabled: busy,
|
|
954
|
+
onClick: () => {
|
|
955
|
+
void act(async () => {
|
|
956
|
+
await controller.applyUpdate();
|
|
957
|
+
});
|
|
958
|
+
},
|
|
959
|
+
children: t("updateConfirm")
|
|
960
|
+
}
|
|
961
|
+
),
|
|
962
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
963
|
+
"button",
|
|
964
|
+
{
|
|
965
|
+
type: "button",
|
|
966
|
+
className: section_default.button,
|
|
967
|
+
disabled: busy,
|
|
968
|
+
onClick: () => {
|
|
969
|
+
controller.disarmUpdate();
|
|
970
|
+
},
|
|
971
|
+
children: t("updateCancel")
|
|
972
|
+
}
|
|
973
|
+
)
|
|
974
|
+
] }),
|
|
975
|
+
update.phase === "updating" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.hint, children: t("updateApplying") }),
|
|
976
|
+
update.phase === "done" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
977
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.ok, children: `${t("updateDone")} ${status?.latest ?? ""}`.trim() }),
|
|
978
|
+
update.message === "restart-required" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.warn, children: t("updateRestart") })
|
|
979
|
+
] }),
|
|
980
|
+
update.phase === "failed" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
981
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.warn, children: t("updateFailed") }),
|
|
982
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.hint, children: updateFailureText(update.message, t) }),
|
|
983
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
984
|
+
"button",
|
|
985
|
+
{
|
|
986
|
+
type: "button",
|
|
987
|
+
className: section_default.button,
|
|
988
|
+
disabled: busy,
|
|
989
|
+
onClick: () => {
|
|
990
|
+
void act(async () => {
|
|
991
|
+
await controller.checkUpdate(true);
|
|
992
|
+
});
|
|
993
|
+
},
|
|
994
|
+
children: t("updateRecheck")
|
|
995
|
+
}
|
|
996
|
+
)
|
|
997
|
+
] })
|
|
998
|
+
] }) });
|
|
999
|
+
}
|
|
1000
|
+
function updateFailureText(message, t) {
|
|
1001
|
+
if (message === void 0 || message.length === 0) return "";
|
|
1002
|
+
if (message === "stop-profile") return t("updateStopProfile");
|
|
1003
|
+
if (message === "incompatible-version") return t("updateIncompatible");
|
|
1004
|
+
if (message === "pending-builds") return t("updatePendingBuilds");
|
|
1005
|
+
return message;
|
|
1006
|
+
}
|
|
700
1007
|
function Section({ controller, t, getLocale }) {
|
|
701
1008
|
const state = (0, import_react.useSyncExternalStore)(controller.subscribe, controller.snapshot, controller.snapshot);
|
|
702
1009
|
const [keyDraft, setKeyDraft] = (0, import_react.useState)("");
|
|
@@ -720,6 +1027,7 @@ function Section({ controller, t, getLocale }) {
|
|
|
720
1027
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: section_default.root, children: [
|
|
721
1028
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: section_default.title, children: t("nav") }),
|
|
722
1029
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: section_default.hint, children: t("intro") }),
|
|
1030
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpdateBanner, { controller, t, busy, act, update: state.update }),
|
|
723
1031
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: section_default.block, children: [
|
|
724
1032
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: section_default.row, children: [
|
|
725
1033
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: t("keyTitle") }),
|
|
@@ -954,16 +1262,16 @@ function Section({ controller, t, getLocale }) {
|
|
|
954
1262
|
// src/client/UsagePill.tsx
|
|
955
1263
|
var import_react2 = require("react");
|
|
956
1264
|
|
|
957
|
-
// plugin-css
|
|
1265
|
+
// plugin-css:/home/runner/work/dshopencodego/dshopencodego/src/client/pill.module.css
|
|
958
1266
|
var id2 = "@dan-ai-studio/dshopencodego/pill.module.css";
|
|
959
1267
|
if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id2) + "]")) {
|
|
960
1268
|
const style = document.createElement("style");
|
|
961
1269
|
style.dataset.plugin = "@dan-ai-studio/dshopencodego";
|
|
962
1270
|
style.dataset.pluginCss = id2;
|
|
963
|
-
style.textContent = ".
|
|
1271
|
+
style.textContent = ".mMNkkW_root{align-items:center;display:inline-flex;position:relative}.mMNkkW_trigger{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;white-space:nowrap;border-radius:999px;padding:.35rem .6rem;font-size:.8rem;line-height:1}.mMNkkW_trigger:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2)}.mMNkkW_panel{z-index:20;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-overlay);width:18rem;color:var(--dsw-alias-label-primary);border-radius:.6rem;flex-direction:column;gap:.6rem;padding:.75rem .85rem;display:flex;position:absolute;bottom:calc(100% + .5rem);right:0;box-shadow:0 8px 24px #0000002e}.mMNkkW_window{flex-direction:column;gap:.25rem;display:flex}.mMNkkW_row{justify-content:space-between;gap:.5rem;font-size:.85rem;display:flex}.mMNkkW_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:.75rem}.mMNkkW_progress{width:100%;height:.35rem;accent-color:var(--dsw-alias-brand-primary)}.mMNkkW_limited{color:var(--dsw-alias-state-warn-primary);font-size:.75rem}.mMNkkW_warning{border:1px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-label-primary);border-radius:.4rem;padding:.4rem .5rem;font-size:.75rem}.mMNkkW_retry{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;border-radius:.35rem;align-self:flex-start;padding:.25rem .5rem;font-size:.75rem}.mMNkkW_local{border-top:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:.2rem;padding-top:.5rem;display:flex}";
|
|
964
1272
|
document.head.appendChild(style);
|
|
965
1273
|
}
|
|
966
|
-
var pill_default = { "
|
|
1274
|
+
var pill_default = { "retry": "mMNkkW_retry", "root": "mMNkkW_root", "panel": "mMNkkW_panel", "limited": "mMNkkW_limited", "trigger": "mMNkkW_trigger", "row": "mMNkkW_row", "window": "mMNkkW_window", "hint": "mMNkkW_hint", "progress": "mMNkkW_progress", "warning": "mMNkkW_warning", "local": "mMNkkW_local" };
|
|
967
1275
|
|
|
968
1276
|
// src/client/UsagePill.tsx
|
|
969
1277
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
@@ -1121,7 +1429,7 @@ function apply(ctx) {
|
|
|
1121
1429
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dshopencodego: copy dictionaries");
|
|
1122
1430
|
const mounted = ctx.remote.$mount({
|
|
1123
1431
|
package: usageRemote.package,
|
|
1124
|
-
descriptors: [...usageRemote.descriptors, ...catalogRemote.descriptors]
|
|
1432
|
+
descriptors: [...usageRemote.descriptors, ...catalogRemote.descriptors, ...updateRemote.descriptors]
|
|
1125
1433
|
});
|
|
1126
1434
|
ctx.effect(() => {
|
|
1127
1435
|
let dispose;
|
package/lib/index.js
CHANGED
|
@@ -1178,6 +1178,7 @@ var ACCOUNT_QUOTA_CODE = "ACCOUNT_QUOTA";
|
|
|
1178
1178
|
function classifyPiAiError(message) {
|
|
1179
1179
|
if (/\b(?:401|403)\b/.test(message)) return "AUTH";
|
|
1180
1180
|
if (isQuotaExceededError(message)) return ACCOUNT_QUOTA_CODE;
|
|
1181
|
+
if (/server_error/i.test(message)) return "SERVER";
|
|
1181
1182
|
if (/\b429\b|rate.?limit/i.test(message)) return "RATE_LIMIT";
|
|
1182
1183
|
if (/\b413\b|payload too large|request body too large|length limit exceeded/i.test(message)) return "INVALID_REQUEST";
|
|
1183
1184
|
if (/\b400\b|invalid.?request/i.test(message)) return "INVALID_REQUEST";
|
|
@@ -1766,6 +1767,44 @@ var catalogRemote = {
|
|
|
1766
1767
|
]
|
|
1767
1768
|
};
|
|
1768
1769
|
|
|
1770
|
+
// src/update/contract.ts
|
|
1771
|
+
function parseUpdateStatus(value) {
|
|
1772
|
+
if (value === null || typeof value !== "object") throw new Error("invalid update reading");
|
|
1773
|
+
const row = value;
|
|
1774
|
+
if (typeof row["current"] !== "string") throw new Error("invalid update reading");
|
|
1775
|
+
if (row["latest"] !== void 0 && typeof row["latest"] !== "string") throw new Error("invalid update reading");
|
|
1776
|
+
if (typeof row["available"] !== "boolean") throw new Error("invalid update reading");
|
|
1777
|
+
if (typeof row["checkedAt"] !== "number" || !Number.isFinite(row["checkedAt"])) {
|
|
1778
|
+
throw new Error("invalid update reading");
|
|
1779
|
+
}
|
|
1780
|
+
return {
|
|
1781
|
+
current: row["current"],
|
|
1782
|
+
latest: typeof row["latest"] === "string" ? row["latest"] : void 0,
|
|
1783
|
+
available: row["available"],
|
|
1784
|
+
checkedAt: row["checkedAt"]
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
var codec2 = (typeSymbol, parse) => ({
|
|
1788
|
+
mode: "strict",
|
|
1789
|
+
typeSymbol,
|
|
1790
|
+
schema: { parse },
|
|
1791
|
+
create: () => ({ parse })
|
|
1792
|
+
});
|
|
1793
|
+
var updateRemote = {
|
|
1794
|
+
package: "@dan-ai-studio/dshopencodego",
|
|
1795
|
+
descriptors: [
|
|
1796
|
+
{
|
|
1797
|
+
id: "@dan-ai-studio/dshopencodego#opencodeGoUpdate/readUpdate",
|
|
1798
|
+
service: "opencodeGoUpdate",
|
|
1799
|
+
namespace: "opencodeGoUpdate",
|
|
1800
|
+
method: "readUpdate",
|
|
1801
|
+
invocation: { kind: "direct" },
|
|
1802
|
+
parameters: [],
|
|
1803
|
+
result: codec2("@dan-ai-studio/dshopencodego#UpdateStatus", parseUpdateStatus)
|
|
1804
|
+
}
|
|
1805
|
+
]
|
|
1806
|
+
};
|
|
1807
|
+
|
|
1769
1808
|
// src/usage/contract.ts
|
|
1770
1809
|
function parseWindow(value, key) {
|
|
1771
1810
|
if (value === null || typeof value !== "object") throw new Error(`invalid usage window "${key}"`);
|
|
@@ -1820,7 +1859,7 @@ function parseGoMeter(value) {
|
|
|
1820
1859
|
})
|
|
1821
1860
|
};
|
|
1822
1861
|
}
|
|
1823
|
-
var
|
|
1862
|
+
var codec3 = (typeSymbol, parse) => ({
|
|
1824
1863
|
mode: "strict",
|
|
1825
1864
|
typeSymbol,
|
|
1826
1865
|
schema: { parse },
|
|
@@ -1836,7 +1875,7 @@ var usageRemote = {
|
|
|
1836
1875
|
method: "readWindows",
|
|
1837
1876
|
invocation: { kind: "direct" },
|
|
1838
1877
|
parameters: [],
|
|
1839
|
-
result:
|
|
1878
|
+
result: codec3("@dan-ai-studio/dshopencodego#GoUsageWindows", parseUsageWindows)
|
|
1840
1879
|
},
|
|
1841
1880
|
{
|
|
1842
1881
|
id: "@dan-ai-studio/dshopencodego#opencodeGoUsage/readMeter",
|
|
@@ -1845,7 +1884,7 @@ var usageRemote = {
|
|
|
1845
1884
|
method: "readMeter",
|
|
1846
1885
|
invocation: { kind: "direct" },
|
|
1847
1886
|
parameters: [],
|
|
1848
|
-
result:
|
|
1887
|
+
result: codec3("@dan-ai-studio/dshopencodego#GoMeter", parseGoMeter)
|
|
1849
1888
|
}
|
|
1850
1889
|
]
|
|
1851
1890
|
};
|
|
@@ -1853,7 +1892,7 @@ var usageRemote = {
|
|
|
1853
1892
|
// src/remotes.ts
|
|
1854
1893
|
var contribution = {
|
|
1855
1894
|
package: usageRemote.package,
|
|
1856
|
-
descriptors: [...usageRemote.descriptors, ...catalogRemote.descriptors]
|
|
1895
|
+
descriptors: [...usageRemote.descriptors, ...catalogRemote.descriptors, ...updateRemote.descriptors]
|
|
1857
1896
|
};
|
|
1858
1897
|
function registerRemotes(ctx) {
|
|
1859
1898
|
ctx.inject(["typert"], (scope) => {
|
|
@@ -1948,6 +1987,106 @@ var OpencodeGoCatalogService = class extends TypertRemoteService {
|
|
|
1948
1987
|
}
|
|
1949
1988
|
};
|
|
1950
1989
|
|
|
1990
|
+
// src/update/check.ts
|
|
1991
|
+
var PACKAGE_NAME = "@dan-ai-studio/dshopencodego";
|
|
1992
|
+
var REGISTRY_LATEST_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`;
|
|
1993
|
+
var CHECK_CACHE_MS = 6 * 60 * 60 * 1e3;
|
|
1994
|
+
var CHECK_TIMEOUT_MS = 1e4;
|
|
1995
|
+
function parseVersion(value) {
|
|
1996
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9a-zA-Z.-]+))?(?:\+[0-9a-zA-Z.-]+)?$/.exec(value.trim());
|
|
1997
|
+
if (match === void 0 || match === null) return void 0;
|
|
1998
|
+
const [, major, minor, patch, prerelease] = match;
|
|
1999
|
+
return {
|
|
2000
|
+
core: [Number(major), Number(minor), Number(patch)],
|
|
2001
|
+
prerelease: prerelease === void 0 ? void 0 : prerelease.split(".")
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
function compareVersions(left, right) {
|
|
2005
|
+
const ours = parseVersion(left);
|
|
2006
|
+
const theirs = parseVersion(right);
|
|
2007
|
+
if (ours === void 0 || theirs === void 0) throw new Error(`dshopencodego: cannot compare versions "${left}" and "${right}"`);
|
|
2008
|
+
for (let index = 0; index < 3; index += 1) {
|
|
2009
|
+
const difference = (ours.core[index] ?? 0) - (theirs.core[index] ?? 0);
|
|
2010
|
+
if (difference !== 0) return difference;
|
|
2011
|
+
}
|
|
2012
|
+
if (ours.prerelease === void 0 && theirs.prerelease === void 0) return 0;
|
|
2013
|
+
if (ours.prerelease === void 0) return 1;
|
|
2014
|
+
if (theirs.prerelease === void 0) return -1;
|
|
2015
|
+
const width = Math.max(ours.prerelease.length, theirs.prerelease.length);
|
|
2016
|
+
for (let index = 0; index < width; index += 1) {
|
|
2017
|
+
const oursPart = ours.prerelease[index];
|
|
2018
|
+
const theirsPart = theirs.prerelease[index];
|
|
2019
|
+
if (oursPart === void 0) return -1;
|
|
2020
|
+
if (theirsPart === void 0) return 1;
|
|
2021
|
+
if (oursPart !== theirsPart) return oursPart < theirsPart ? -1 : 1;
|
|
2022
|
+
}
|
|
2023
|
+
return 0;
|
|
2024
|
+
}
|
|
2025
|
+
function isUpdateAvailable(current, latest) {
|
|
2026
|
+
if (latest === void 0) return false;
|
|
2027
|
+
try {
|
|
2028
|
+
return compareVersions(latest, current) > 0;
|
|
2029
|
+
} catch {
|
|
2030
|
+
return false;
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
function parseLatestVersion(body) {
|
|
2034
|
+
if (body === null || typeof body !== "object") return void 0;
|
|
2035
|
+
const version = body["version"];
|
|
2036
|
+
return typeof version === "string" && parseVersion(version) !== void 0 ? version : void 0;
|
|
2037
|
+
}
|
|
2038
|
+
function createUpdateChecker(deps) {
|
|
2039
|
+
const now = deps.now ?? Date.now;
|
|
2040
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
2041
|
+
let cached;
|
|
2042
|
+
let inflight;
|
|
2043
|
+
async function lookup() {
|
|
2044
|
+
const checkedAt = now();
|
|
2045
|
+
try {
|
|
2046
|
+
const response = await fetchImpl(REGISTRY_LATEST_URL, { signal: AbortSignal.timeout(CHECK_TIMEOUT_MS) });
|
|
2047
|
+
if (!response.ok) throw new Error(`registry answered ${response.status}`);
|
|
2048
|
+
const latest = parseLatestVersion(await response.json());
|
|
2049
|
+
return {
|
|
2050
|
+
current: deps.currentVersion,
|
|
2051
|
+
latest,
|
|
2052
|
+
available: isUpdateAvailable(deps.currentVersion, latest),
|
|
2053
|
+
checkedAt
|
|
2054
|
+
};
|
|
2055
|
+
} catch {
|
|
2056
|
+
return { current: deps.currentVersion, latest: void 0, available: false, checkedAt };
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
return {
|
|
2060
|
+
check(force = false) {
|
|
2061
|
+
if (!force && cached !== void 0 && now() - cached.checkedAt < CHECK_CACHE_MS) return Promise.resolve(cached);
|
|
2062
|
+
inflight ??= lookup().then((status) => {
|
|
2063
|
+
cached = status;
|
|
2064
|
+
inflight = void 0;
|
|
2065
|
+
return status;
|
|
2066
|
+
});
|
|
2067
|
+
return inflight;
|
|
2068
|
+
}
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
// src/update/version.ts
|
|
2073
|
+
var PLUGIN_VERSION = true ? "0.1.24" : "0.0.0-dev";
|
|
2074
|
+
|
|
2075
|
+
// src/update/service.ts
|
|
2076
|
+
import { TypertRemoteService as TypertRemoteService2 } from "@deepseek-ai/dsh-typert-protocol";
|
|
2077
|
+
var OpencodeGoUpdateService = class extends TypertRemoteService2 {
|
|
2078
|
+
check;
|
|
2079
|
+
constructor(ctx, options) {
|
|
2080
|
+
super(ctx, "opencodeGoUpdate");
|
|
2081
|
+
const checker = createUpdateChecker({ currentVersion: options.currentVersion });
|
|
2082
|
+
this.check = (force) => checker.check(force);
|
|
2083
|
+
}
|
|
2084
|
+
/** Whether a newer release is published; `force` bypasses the cache. */
|
|
2085
|
+
readUpdate(force) {
|
|
2086
|
+
return this.check(force);
|
|
2087
|
+
}
|
|
2088
|
+
};
|
|
2089
|
+
|
|
1951
2090
|
// src/usage/meter.ts
|
|
1952
2091
|
var EMPTY = {
|
|
1953
2092
|
calls: 0,
|
|
@@ -2053,7 +2192,7 @@ async function readUsageWindows(options) {
|
|
|
2053
2192
|
|
|
2054
2193
|
// src/usage/service.ts
|
|
2055
2194
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2056
|
-
import { RemoteError as RemoteError2, TypertRemoteService as
|
|
2195
|
+
import { RemoteError as RemoteError2, TypertRemoteService as TypertRemoteService3 } from "@deepseek-ai/dsh-typert-protocol";
|
|
2057
2196
|
import { LlmError as LlmError11 } from "@deepseek-ai/dsh-llm";
|
|
2058
2197
|
function usageFailure(error, source) {
|
|
2059
2198
|
const missing = error instanceof LlmError11 && error.code === "MISSING_CREDENTIAL";
|
|
@@ -2064,7 +2203,7 @@ function usageFailure(error, source) {
|
|
|
2064
2203
|
{ cause: error }
|
|
2065
2204
|
);
|
|
2066
2205
|
}
|
|
2067
|
-
var OpencodeGoUsageService = class extends
|
|
2206
|
+
var OpencodeGoUsageService = class extends TypertRemoteService3 {
|
|
2068
2207
|
identity;
|
|
2069
2208
|
options;
|
|
2070
2209
|
constructor(ctx, options) {
|
|
@@ -2157,6 +2296,7 @@ function apply(ctx, raw) {
|
|
|
2157
2296
|
catalog: () => adapter.catalogOf(current()),
|
|
2158
2297
|
visibility: () => current().modelVisibility
|
|
2159
2298
|
});
|
|
2299
|
+
ctx.plugin(OpencodeGoUpdateService, { currentVersion: PLUGIN_VERSION });
|
|
2160
2300
|
let registration;
|
|
2161
2301
|
let visibilityFacts = JSON.stringify(current().modelVisibility);
|
|
2162
2302
|
const applyRoute = (credentialConfigured) => {
|
|
@@ -51,6 +51,12 @@ export function classifyPiAiError(message) {
|
|
|
51
51
|
return 'AUTH';
|
|
52
52
|
if (isQuotaExceededError(message))
|
|
53
53
|
return ACCOUNT_QUOTA_CODE;
|
|
54
|
+
// A 429 typed as `server_error` is the gateway reporting an unavailable
|
|
55
|
+
// upstream, not the caller exceeding a rate limit: the remedy is waiting for
|
|
56
|
+
// the endpoint to recover rather than slowing down, so it classifies as
|
|
57
|
+
// SERVER even though the status code alone reads as a rate limit.
|
|
58
|
+
if (/server_error/i.test(message))
|
|
59
|
+
return 'SERVER';
|
|
54
60
|
if (/\b429\b|rate.?limit/i.test(message))
|
|
55
61
|
return 'RATE_LIMIT';
|
|
56
62
|
if (/\b413\b|payload too large|request body too large|length limit exceeded/i.test(message))
|
package/lib/types/index.js
CHANGED
|
@@ -36,6 +36,8 @@ import { DISPLAY_NAME, PROVIDER_ID, discoverCatalogModels } from "./catalog/inde
|
|
|
36
36
|
import { assertBaseURL, PlainConfig, readConfig } from "./config.js";
|
|
37
37
|
import { registerRemotes } from "./remotes.js";
|
|
38
38
|
import { OpencodeGoCatalogService } from "./catalog/service.js";
|
|
39
|
+
import { OpencodeGoUpdateService } from "./update/index.js";
|
|
40
|
+
import { PLUGIN_VERSION } from "./update/index.js";
|
|
39
41
|
import { OpencodeGoUsageService, UsageMeter } from "./usage/index.js";
|
|
40
42
|
export { OpencodeGoAdapter } from "./adapter.js";
|
|
41
43
|
export { DEFAULT_BASE_URL, CONSOLE_BASE_URL, DISPLAY_NAME, PROVIDER_ID, OpencodeGoCatalog, discoverCatalogModels } from "./catalog/index.js";
|
|
@@ -107,6 +109,7 @@ export function apply(ctx, raw) {
|
|
|
107
109
|
catalog: () => adapter.catalogOf(current()),
|
|
108
110
|
visibility: () => current().modelVisibility,
|
|
109
111
|
});
|
|
112
|
+
ctx.plugin(OpencodeGoUpdateService, { currentVersion: PLUGIN_VERSION });
|
|
110
113
|
let registration;
|
|
111
114
|
let visibilityFacts = JSON.stringify(current().modelVisibility);
|
|
112
115
|
/**
|
package/lib/types/remotes.js
CHANGED
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
* @module @dan-ai-studio/dshopencodego/remotes
|
|
9
9
|
*/
|
|
10
10
|
import { catalogRemote } from "./catalog/contract.js";
|
|
11
|
+
import { updateRemote } from "./update/contract.js";
|
|
11
12
|
import { usageRemote } from "./usage/contract.js";
|
|
12
13
|
/** Every Remote method this package owns, mounted as one contribution. */
|
|
13
14
|
const contribution = {
|
|
14
15
|
package: usageRemote.package,
|
|
15
|
-
descriptors: [...usageRemote.descriptors, ...catalogRemote.descriptors],
|
|
16
|
+
descriptors: [...usageRemote.descriptors, ...catalogRemote.descriptors, ...updateRemote.descriptors],
|
|
16
17
|
};
|
|
17
18
|
/** Register this package's Remote contribution for the mount's lifetime. */
|
|
18
19
|
export function registerRemotes(ctx) {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self update check: is a newer release of this package published?
|
|
3
|
+
*
|
|
4
|
+
* This module only *reads*: it asks the npm registry for the latest published
|
|
5
|
+
* version and compares it with the running build. It never downloads,
|
|
6
|
+
* installs, or replaces anything — the actual update stays with the host's
|
|
7
|
+
* `pluginManager` (`removeBundle` + `installBundle`), driven by an explicit
|
|
8
|
+
* user click on the settings page. Automatic self-replacement would bypass
|
|
9
|
+
* the host's manifest lock, peer-compatibility checks, build approvals, and
|
|
10
|
+
* HMR lifecycle, so it is deliberately not implemented here.
|
|
11
|
+
*
|
|
12
|
+
* The check runs only when the settings page asks (page open or manual
|
|
13
|
+
* refresh), never on the model-request path, and fails silent: an unreachable
|
|
14
|
+
* registry simply hides the banner instead of erroring the page.
|
|
15
|
+
*
|
|
16
|
+
* @module @dan-ai-studio/dshopencodego/update/check
|
|
17
|
+
*/
|
|
18
|
+
/** This package as addressed on the registry. */
|
|
19
|
+
export declare const PACKAGE_NAME = "@dan-ai-studio/dshopencodego";
|
|
20
|
+
/** Document address answering `{ "version": "x.y.z", ... }`. */
|
|
21
|
+
export declare const REGISTRY_LATEST_URL: string;
|
|
22
|
+
/** How long one lookup answers for before the registry is asked again. */
|
|
23
|
+
export declare const CHECK_CACHE_MS: number;
|
|
24
|
+
/** Bound on one registry round trip; expiry fails silent, never throws. */
|
|
25
|
+
export declare const CHECK_TIMEOUT_MS = 10000;
|
|
26
|
+
/** What the settings page renders. */
|
|
27
|
+
export interface UpdateStatus {
|
|
28
|
+
/** Version of the running build. */
|
|
29
|
+
readonly current: string;
|
|
30
|
+
/** Latest published version, when the registry answered. */
|
|
31
|
+
readonly latest: string | undefined;
|
|
32
|
+
/** True when the registry answered and `latest` is newer than `current`. */
|
|
33
|
+
readonly available: boolean;
|
|
34
|
+
/** Milliseconds since the epoch of the lookup backing this reading. */
|
|
35
|
+
readonly checkedAt: number;
|
|
36
|
+
}
|
|
37
|
+
interface ParsedVersion {
|
|
38
|
+
readonly core: readonly number[];
|
|
39
|
+
/** Absent for a release; present (lower precedence) for `1.2.3-tag.4`. */
|
|
40
|
+
readonly prerelease: readonly string[] | undefined;
|
|
41
|
+
}
|
|
42
|
+
/** Split `1.2.3-tag.4+build` into comparable parts; invalid input yields undefined. */
|
|
43
|
+
export declare function parseVersion(value: string): ParsedVersion | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Compare two version strings.
|
|
46
|
+
* @returns negative when `left` is older, positive when newer, zero when equal.
|
|
47
|
+
* @throws {Error} when either side is not a version.
|
|
48
|
+
*/
|
|
49
|
+
export declare function compareVersions(left: string, right: string): number;
|
|
50
|
+
/** True when `latest` is a newer release than `current`. Unparsable input is never an update. */
|
|
51
|
+
export declare function isUpdateAvailable(current: string, latest: string | undefined): boolean;
|
|
52
|
+
/** Read the `version` field out of a registry `latest` document. */
|
|
53
|
+
export declare function parseLatestVersion(body: unknown): string | undefined;
|
|
54
|
+
/** Dependencies the check needs, injectable for tests. */
|
|
55
|
+
export interface UpdateCheckDeps {
|
|
56
|
+
/** Running build's version (build-time constant in production). */
|
|
57
|
+
readonly currentVersion: string;
|
|
58
|
+
/** Fetch implementation; defaults to the global one. */
|
|
59
|
+
readonly fetchImpl?: typeof fetch;
|
|
60
|
+
/** Clock; defaults to `Date.now`. */
|
|
61
|
+
readonly now?: () => number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Ask the registry for the latest published version, cached per process.
|
|
65
|
+
*
|
|
66
|
+
* Concurrent callers share one in-flight lookup. A lookup that fails for any
|
|
67
|
+
* reason — network, timeout, malformed document — resolves as "no update
|
|
68
|
+
* known" rather than rejecting, so the settings page never breaks because
|
|
69
|
+
* the registry is unreachable.
|
|
70
|
+
*/
|
|
71
|
+
export declare function createUpdateChecker(deps: UpdateCheckDeps): {
|
|
72
|
+
check(force?: boolean): Promise<UpdateStatus>;
|
|
73
|
+
};
|
|
74
|
+
export {};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self update check: is a newer release of this package published?
|
|
3
|
+
*
|
|
4
|
+
* This module only *reads*: it asks the npm registry for the latest published
|
|
5
|
+
* version and compares it with the running build. It never downloads,
|
|
6
|
+
* installs, or replaces anything — the actual update stays with the host's
|
|
7
|
+
* `pluginManager` (`removeBundle` + `installBundle`), driven by an explicit
|
|
8
|
+
* user click on the settings page. Automatic self-replacement would bypass
|
|
9
|
+
* the host's manifest lock, peer-compatibility checks, build approvals, and
|
|
10
|
+
* HMR lifecycle, so it is deliberately not implemented here.
|
|
11
|
+
*
|
|
12
|
+
* The check runs only when the settings page asks (page open or manual
|
|
13
|
+
* refresh), never on the model-request path, and fails silent: an unreachable
|
|
14
|
+
* registry simply hides the banner instead of erroring the page.
|
|
15
|
+
*
|
|
16
|
+
* @module @dan-ai-studio/dshopencodego/update/check
|
|
17
|
+
*/
|
|
18
|
+
/** This package as addressed on the registry. */
|
|
19
|
+
export const PACKAGE_NAME = '@dan-ai-studio/dshopencodego';
|
|
20
|
+
/** Document address answering `{ "version": "x.y.z", ... }`. */
|
|
21
|
+
export const REGISTRY_LATEST_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`;
|
|
22
|
+
/** How long one lookup answers for before the registry is asked again. */
|
|
23
|
+
export const CHECK_CACHE_MS = 6 * 60 * 60 * 1000;
|
|
24
|
+
/** Bound on one registry round trip; expiry fails silent, never throws. */
|
|
25
|
+
export const CHECK_TIMEOUT_MS = 10_000;
|
|
26
|
+
/** Split `1.2.3-tag.4+build` into comparable parts; invalid input yields undefined. */
|
|
27
|
+
export function parseVersion(value) {
|
|
28
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9a-zA-Z.-]+))?(?:\+[0-9a-zA-Z.-]+)?$/.exec(value.trim());
|
|
29
|
+
if (match === undefined || match === null)
|
|
30
|
+
return undefined;
|
|
31
|
+
const [, major, minor, patch, prerelease] = match;
|
|
32
|
+
return {
|
|
33
|
+
core: [Number(major), Number(minor), Number(patch)],
|
|
34
|
+
prerelease: prerelease === undefined ? undefined : prerelease.split('.'),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Compare two version strings.
|
|
39
|
+
* @returns negative when `left` is older, positive when newer, zero when equal.
|
|
40
|
+
* @throws {Error} when either side is not a version.
|
|
41
|
+
*/
|
|
42
|
+
export function compareVersions(left, right) {
|
|
43
|
+
const ours = parseVersion(left);
|
|
44
|
+
const theirs = parseVersion(right);
|
|
45
|
+
if (ours === undefined || theirs === undefined)
|
|
46
|
+
throw new Error(`dshopencodego: cannot compare versions "${left}" and "${right}"`);
|
|
47
|
+
for (let index = 0; index < 3; index += 1) {
|
|
48
|
+
const difference = (ours.core[index] ?? 0) - (theirs.core[index] ?? 0);
|
|
49
|
+
if (difference !== 0)
|
|
50
|
+
return difference;
|
|
51
|
+
}
|
|
52
|
+
if (ours.prerelease === undefined && theirs.prerelease === undefined)
|
|
53
|
+
return 0;
|
|
54
|
+
if (ours.prerelease === undefined)
|
|
55
|
+
return 1;
|
|
56
|
+
if (theirs.prerelease === undefined)
|
|
57
|
+
return -1;
|
|
58
|
+
const width = Math.max(ours.prerelease.length, theirs.prerelease.length);
|
|
59
|
+
for (let index = 0; index < width; index += 1) {
|
|
60
|
+
const oursPart = ours.prerelease[index];
|
|
61
|
+
const theirsPart = theirs.prerelease[index];
|
|
62
|
+
if (oursPart === undefined)
|
|
63
|
+
return -1;
|
|
64
|
+
if (theirsPart === undefined)
|
|
65
|
+
return 1;
|
|
66
|
+
if (oursPart !== theirsPart)
|
|
67
|
+
return oursPart < theirsPart ? -1 : 1;
|
|
68
|
+
}
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
/** True when `latest` is a newer release than `current`. Unparsable input is never an update. */
|
|
72
|
+
export function isUpdateAvailable(current, latest) {
|
|
73
|
+
if (latest === undefined)
|
|
74
|
+
return false;
|
|
75
|
+
try {
|
|
76
|
+
return compareVersions(latest, current) > 0;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Read the `version` field out of a registry `latest` document. */
|
|
83
|
+
export function parseLatestVersion(body) {
|
|
84
|
+
if (body === null || typeof body !== 'object')
|
|
85
|
+
return undefined;
|
|
86
|
+
const version = body['version'];
|
|
87
|
+
return typeof version === 'string' && parseVersion(version) !== undefined ? version : undefined;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Ask the registry for the latest published version, cached per process.
|
|
91
|
+
*
|
|
92
|
+
* Concurrent callers share one in-flight lookup. A lookup that fails for any
|
|
93
|
+
* reason — network, timeout, malformed document — resolves as "no update
|
|
94
|
+
* known" rather than rejecting, so the settings page never breaks because
|
|
95
|
+
* the registry is unreachable.
|
|
96
|
+
*/
|
|
97
|
+
export function createUpdateChecker(deps) {
|
|
98
|
+
const now = deps.now ?? Date.now;
|
|
99
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
100
|
+
let cached;
|
|
101
|
+
let inflight;
|
|
102
|
+
async function lookup() {
|
|
103
|
+
const checkedAt = now();
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetchImpl(REGISTRY_LATEST_URL, { signal: AbortSignal.timeout(CHECK_TIMEOUT_MS) });
|
|
106
|
+
if (!response.ok)
|
|
107
|
+
throw new Error(`registry answered ${response.status}`);
|
|
108
|
+
const latest = parseLatestVersion(await response.json());
|
|
109
|
+
return {
|
|
110
|
+
current: deps.currentVersion,
|
|
111
|
+
latest,
|
|
112
|
+
available: isUpdateAvailable(deps.currentVersion, latest),
|
|
113
|
+
checkedAt,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return { current: deps.currentVersion, latest: undefined, available: false, checkedAt };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
check(force = false) {
|
|
122
|
+
if (!force && cached !== undefined && now() - cached.checkedAt < CHECK_CACHE_MS)
|
|
123
|
+
return Promise.resolve(cached);
|
|
124
|
+
inflight ??= lookup().then((status) => {
|
|
125
|
+
cached = status;
|
|
126
|
+
inflight = undefined;
|
|
127
|
+
return status;
|
|
128
|
+
});
|
|
129
|
+
return inflight;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire contract for the update Remote.
|
|
3
|
+
*
|
|
4
|
+
* `readUpdate` answers whether a newer release of this package is published.
|
|
5
|
+
* The reading is advisory — applying the update stays a user click that
|
|
6
|
+
* drives the host's `pluginManager`, never an automatic download.
|
|
7
|
+
*
|
|
8
|
+
* @module @dan-ai-studio/dshopencodego/update/contract
|
|
9
|
+
*/
|
|
10
|
+
import type { RemoteResult, TypertRemoteContribution } from '@deepseek-ai/dsh-typert-protocol';
|
|
11
|
+
import type { UpdateStatus } from './check.ts';
|
|
12
|
+
export type { UpdateStatus } from './check.ts';
|
|
13
|
+
/** Validate an update reading crossing the wire. */
|
|
14
|
+
export declare function parseUpdateStatus(value: unknown): UpdateStatus;
|
|
15
|
+
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
16
|
+
interface TypertRemoteNamespaceMap {
|
|
17
|
+
opencodeGoUpdate: {
|
|
18
|
+
readUpdate(force?: boolean): Promise<RemoteResult<UpdateStatus>>;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Remote methods this package owns, mounted together by the plugin. */
|
|
23
|
+
export declare const updateRemote: TypertRemoteContribution;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire contract for the update Remote.
|
|
3
|
+
*
|
|
4
|
+
* `readUpdate` answers whether a newer release of this package is published.
|
|
5
|
+
* The reading is advisory — applying the update stays a user click that
|
|
6
|
+
* drives the host's `pluginManager`, never an automatic download.
|
|
7
|
+
*
|
|
8
|
+
* @module @dan-ai-studio/dshopencodego/update/contract
|
|
9
|
+
*/
|
|
10
|
+
/** Validate an update reading crossing the wire. */
|
|
11
|
+
export function parseUpdateStatus(value) {
|
|
12
|
+
if (value === null || typeof value !== 'object')
|
|
13
|
+
throw new Error('invalid update reading');
|
|
14
|
+
const row = value;
|
|
15
|
+
if (typeof row['current'] !== 'string')
|
|
16
|
+
throw new Error('invalid update reading');
|
|
17
|
+
if (row['latest'] !== undefined && typeof row['latest'] !== 'string')
|
|
18
|
+
throw new Error('invalid update reading');
|
|
19
|
+
if (typeof row['available'] !== 'boolean')
|
|
20
|
+
throw new Error('invalid update reading');
|
|
21
|
+
if (typeof row['checkedAt'] !== 'number' || !Number.isFinite(row['checkedAt'])) {
|
|
22
|
+
throw new Error('invalid update reading');
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
current: row['current'],
|
|
26
|
+
latest: typeof row['latest'] === 'string' ? row['latest'] : undefined,
|
|
27
|
+
available: row['available'],
|
|
28
|
+
checkedAt: row['checkedAt'],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** Codec shape accepted by both released and source DSH builds. */
|
|
32
|
+
const codec = (typeSymbol, parse) => ({
|
|
33
|
+
mode: 'strict',
|
|
34
|
+
typeSymbol,
|
|
35
|
+
schema: { parse },
|
|
36
|
+
create: () => ({ parse }),
|
|
37
|
+
});
|
|
38
|
+
/** Remote methods this package owns, mounted together by the plugin. */
|
|
39
|
+
export const updateRemote = {
|
|
40
|
+
package: '@dan-ai-studio/dshopencodego',
|
|
41
|
+
descriptors: [
|
|
42
|
+
{
|
|
43
|
+
id: '@dan-ai-studio/dshopencodego#opencodeGoUpdate/readUpdate',
|
|
44
|
+
service: 'opencodeGoUpdate',
|
|
45
|
+
namespace: 'opencodeGoUpdate',
|
|
46
|
+
method: 'readUpdate',
|
|
47
|
+
invocation: { kind: 'direct' },
|
|
48
|
+
parameters: [],
|
|
49
|
+
result: codec('@dan-ai-studio/dshopencodego#UpdateStatus', parseUpdateStatus),
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self update check (advisory only).
|
|
3
|
+
*
|
|
4
|
+
* @module @dan-ai-studio/dshopencodego/update
|
|
5
|
+
*/
|
|
6
|
+
export { PACKAGE_NAME, REGISTRY_LATEST_URL, CHECK_CACHE_MS, CHECK_TIMEOUT_MS } from './check.ts';
|
|
7
|
+
export type { UpdateStatus } from './check.ts';
|
|
8
|
+
export { compareVersions, createUpdateChecker, isUpdateAvailable, parseLatestVersion, parseVersion } from './check.ts';
|
|
9
|
+
export { PLUGIN_VERSION } from './version.ts';
|
|
10
|
+
export { OpencodeGoUpdateService } from './service.ts';
|
|
11
|
+
export type { UpdateServiceOptions } from './service.ts';
|
|
12
|
+
export { parseUpdateStatus, updateRemote } from './contract.ts';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self update check (advisory only).
|
|
3
|
+
*
|
|
4
|
+
* @module @dan-ai-studio/dshopencodego/update
|
|
5
|
+
*/
|
|
6
|
+
export { PACKAGE_NAME, REGISTRY_LATEST_URL, CHECK_CACHE_MS, CHECK_TIMEOUT_MS } from "./check.js";
|
|
7
|
+
export { compareVersions, createUpdateChecker, isUpdateAvailable, parseLatestVersion, parseVersion } from "./check.js";
|
|
8
|
+
export { PLUGIN_VERSION } from "./version.js";
|
|
9
|
+
export { OpencodeGoUpdateService } from "./service.js";
|
|
10
|
+
export { parseUpdateStatus, updateRemote } from "./contract.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host half of the update Remote.
|
|
3
|
+
*
|
|
4
|
+
* One method, one question: is a newer release of this package published?
|
|
5
|
+
* The service answers from the registry through the cached checker in
|
|
6
|
+
* `./check.ts`, so the settings page learns about updates without the model
|
|
7
|
+
* path ever touching the network. Applying an update is intentionally absent
|
|
8
|
+
* here — the page drives the host's own `pluginManager` (`removeBundle` then
|
|
9
|
+
* `installBundle`), which owns the manifest lock, peer checks, build
|
|
10
|
+
* approvals, and the HMR lifecycle a self-replacing plugin would bypass.
|
|
11
|
+
*
|
|
12
|
+
* @module @dan-ai-studio/dshopencodego/update/service
|
|
13
|
+
*/
|
|
14
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
15
|
+
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
16
|
+
import type { UpdateStatus } from './check.ts';
|
|
17
|
+
/** Host inputs for the update service. */
|
|
18
|
+
export interface UpdateServiceOptions {
|
|
19
|
+
/** Running build's version. */
|
|
20
|
+
readonly currentVersion: string;
|
|
21
|
+
}
|
|
22
|
+
/** Update Remote: the cached registry reading for the settings page. */
|
|
23
|
+
export declare class OpencodeGoUpdateService extends TypertRemoteService {
|
|
24
|
+
private readonly check;
|
|
25
|
+
constructor(ctx: Context, options: UpdateServiceOptions);
|
|
26
|
+
/** Whether a newer release is published; `force` bypasses the cache. */
|
|
27
|
+
readUpdate(force?: boolean): Promise<UpdateStatus>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host half of the update Remote.
|
|
3
|
+
*
|
|
4
|
+
* One method, one question: is a newer release of this package published?
|
|
5
|
+
* The service answers from the registry through the cached checker in
|
|
6
|
+
* `./check.ts`, so the settings page learns about updates without the model
|
|
7
|
+
* path ever touching the network. Applying an update is intentionally absent
|
|
8
|
+
* here — the page drives the host's own `pluginManager` (`removeBundle` then
|
|
9
|
+
* `installBundle`), which owns the manifest lock, peer checks, build
|
|
10
|
+
* approvals, and the HMR lifecycle a self-replacing plugin would bypass.
|
|
11
|
+
*
|
|
12
|
+
* @module @dan-ai-studio/dshopencodego/update/service
|
|
13
|
+
*/
|
|
14
|
+
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
15
|
+
import { createUpdateChecker } from "./check.js";
|
|
16
|
+
/** Update Remote: the cached registry reading for the settings page. */
|
|
17
|
+
export class OpencodeGoUpdateService extends TypertRemoteService {
|
|
18
|
+
check;
|
|
19
|
+
constructor(ctx, options) {
|
|
20
|
+
super(ctx, 'opencodeGoUpdate');
|
|
21
|
+
const checker = createUpdateChecker({ currentVersion: options.currentVersion });
|
|
22
|
+
this.check = (force) => checker.check(force);
|
|
23
|
+
}
|
|
24
|
+
/** Whether a newer release is published; `force` bypasses the cache. */
|
|
25
|
+
readUpdate(force) {
|
|
26
|
+
return this.check(force);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The running build's own version.
|
|
3
|
+
*
|
|
4
|
+
* `scripts/build.mjs` replaces `__DSHOPENCODEGO_VERSION__` with the package
|
|
5
|
+
* version at bundle time, so the Host knows what it is without reading its
|
|
6
|
+
* own files back from disk. Outside the bundle (tests, source runs) the
|
|
7
|
+
* constant is absent and the value reads as a development placeholder; the
|
|
8
|
+
* update check treats an unparsable current version as "no update known"
|
|
9
|
+
* rather than erroring.
|
|
10
|
+
*
|
|
11
|
+
* @module @dan-ai-studio/dshopencodego/update/version
|
|
12
|
+
*/
|
|
13
|
+
/** Version of this build, or a placeholder outside the bundle. */
|
|
14
|
+
export declare const PLUGIN_VERSION: string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The running build's own version.
|
|
3
|
+
*
|
|
4
|
+
* `scripts/build.mjs` replaces `__DSHOPENCODEGO_VERSION__` with the package
|
|
5
|
+
* version at bundle time, so the Host knows what it is without reading its
|
|
6
|
+
* own files back from disk. Outside the bundle (tests, source runs) the
|
|
7
|
+
* constant is absent and the value reads as a development placeholder; the
|
|
8
|
+
* update check treats an unparsable current version as "no update known"
|
|
9
|
+
* rather than erroring.
|
|
10
|
+
*
|
|
11
|
+
* @module @dan-ai-studio/dshopencodego/update/version
|
|
12
|
+
*/
|
|
13
|
+
/** Version of this build, or a placeholder outside the bundle. */
|
|
14
|
+
export const PLUGIN_VERSION = typeof __DSHOPENCODEGO_VERSION__ === 'string'
|
|
15
|
+
? __DSHOPENCODEGO_VERSION__
|
|
16
|
+
: '0.0.0-dev';
|
package/package.json
CHANGED