@goodandready/dsh-ollama-cloud-provider 0.1.11 → 0.1.13

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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.13 — 2026-10-29
4
+
5
+ - Account cards now show per-account balance/remaining credits, purchased
6
+ amount, reset date and the time of the last usage read (or the 5-hour/weekly
7
+ bars for legacy accounts).
8
+ - Added `npm run test:engine`: an isolated boot matrix that installs every
9
+ declared `dshReleases` core, composes the plugin, checks `--dump-config` and
10
+ waits for `/dsh-ollama/health` in a real web host. Supports
11
+ `--versions 0.2.1-rc.4` for a single release.
12
+
13
+ ## 0.1.12 — 2026-10-29
14
+
15
+ - The sidebar quota card and panel now pause polling while the document is
16
+ hidden and refresh immediately when it becomes visible again.
17
+ - Settings gained a **Service status** row: a local, keyless probe of the
18
+ public tag endpoint with latency, checked time and an error state. Ollama
19
+ Cloud has no public status page, so this row reports host-to-gateway
20
+ reachability only.
21
+ - Error mapping now recognizes context-window overflow (using the harness
22
+ `CONTEXT_WINDOW_EXCEEDED_CODE`), model capability rejections for images/tools
23
+ and oversized request bodies, instead of returning a generic 400 message.
24
+
3
25
  ## 0.1.11 — 2026-10-29
4
26
 
5
27
  - Compatibility declarations now use the same-channel range
package/README.md CHANGED
@@ -103,17 +103,17 @@ npm pack
103
103
  Install the published package:
104
104
 
105
105
  ```bash
106
- dsh plugin --profile web add @goodandready/dsh-ollama-cloud-provider@0.1.11
106
+ dsh plugin --profile web add @goodandready/dsh-ollama-cloud-provider@0.1.13
107
107
  # DSH Desktop:
108
- dsh plugin --profile desktop add @goodandready/dsh-ollama-cloud-provider@0.1.11
108
+ dsh plugin --profile desktop add @goodandready/dsh-ollama-cloud-provider@0.1.13
109
109
  ```
110
110
 
111
111
  Or install the packed tarball into a profile:
112
112
 
113
113
  ```bash
114
- dsh plugin --profile web add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.11.tgz
114
+ dsh plugin --profile web add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.13.tgz
115
115
  # DSH Desktop:
116
- dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.11.tgz
116
+ dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.13.tgz
117
117
  ```
118
118
 
119
119
  Restart dsh (or DSH Desktop), then pick **Ollama Cloud** in the model picker.
@@ -194,7 +194,7 @@ Open **Settings → Ollama Cloud**:
194
194
  - **Sidebar** — show or hide the quota card, and an optional plan label (for example `PRO`) shown next to the provider name.
195
195
  - **Plugin update** — current/latest version and **Update now**.
196
196
 
197
- The Host serves `/dsh-ollama/state`, `/health`, `/analytics`, `/usage`, `/balance`, `/config`, `/key`, `/key/clear`, `/refresh`, `/models/refresh`, `/test` and `/update`. Writes require JSON, an `Origin` or `Referer` matching `Host` (the Desktop renderer is identified by its `dsh-auth-*` cookie and desktop user agent when both headers are absent), and are restricted to loopback (`127.0.0.1` / `::1`); a write without any source identity is rejected with 403.
197
+ The Host serves `/dsh-ollama/state`, `/health`, `/status`, `/analytics`, `/usage`, `/balance`, `/config`, `/key`, `/key/clear`, `/refresh`, `/models/refresh`, `/test` and `/update`. `/status` probes the public tag endpoint without a key or quota. Writes require JSON, an `Origin` or `Referer` matching `Host` (the Desktop renderer is identified by its `dsh-auth-*` cookie and desktop user agent when both headers are absent), and are restricted to loopback (`127.0.0.1` / `::1`); a write without any source identity is rejected with 403.
198
198
 
199
199
  ## Command
200
200
 
package/README.ru.md CHANGED
@@ -103,17 +103,17 @@ npm pack
103
103
  Установка опубликованного пакета из npm:
104
104
 
105
105
  ```bash
106
- dsh plugin --profile web add @goodandready/dsh-ollama-cloud-provider@0.1.11
106
+ dsh plugin --profile web add @goodandready/dsh-ollama-cloud-provider@0.1.13
107
107
  # DSH Desktop:
108
- dsh plugin --profile desktop add @goodandready/dsh-ollama-cloud-provider@0.1.11
108
+ dsh plugin --profile desktop add @goodandready/dsh-ollama-cloud-provider@0.1.13
109
109
  ```
110
110
 
111
111
  Либо установка из упакованного tarball:
112
112
 
113
113
  ```bash
114
- dsh plugin --profile web add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.11.tgz
114
+ dsh plugin --profile web add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.13.tgz
115
115
  # DSH Desktop:
116
- dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.11.tgz
116
+ dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.13.tgz
117
117
  ```
118
118
 
119
119
  После установки перезапустите dsh (или DSH Desktop) и выберите **Ollama Cloud** в списке моделей.
package/README.zh.md CHANGED
@@ -102,17 +102,17 @@ npm pack
102
102
  从 npm 安装已发布版本:
103
103
 
104
104
  ```bash
105
- dsh plugin --profile web add @goodandready/dsh-ollama-cloud-provider@0.1.11
105
+ dsh plugin --profile web add @goodandready/dsh-ollama-cloud-provider@0.1.13
106
106
  # DSH 桌面端:
107
- dsh plugin --profile desktop add @goodandready/dsh-ollama-cloud-provider@0.1.11
107
+ dsh plugin --profile desktop add @goodandready/dsh-ollama-cloud-provider@0.1.13
108
108
  ```
109
109
 
110
110
  或通过打包文件安装:
111
111
 
112
112
  ```bash
113
- dsh plugin --profile web add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.11.tgz
113
+ dsh plugin --profile web add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.13.tgz
114
114
  # DSH 桌面端:
115
- dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.11.tgz
115
+ dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.13.tgz
116
116
  ```
117
117
 
118
118
  安装完成后重启 dsh 或 DSH 桌面端,在模型列表中选择 **Ollama Cloud** 即可。
package/lib/client.js CHANGED
@@ -49,6 +49,7 @@ window.__ModuleLoader__.load({
49
49
  tokens: "Tokens",
50
50
  spend7d: "7-day spend",
51
51
  remaining: "remaining",
52
+ until: "until",
52
53
  purchased: "Purchased",
53
54
  range: "Range",
54
55
  last24h: "Last 24 hours",
@@ -118,6 +119,13 @@ window.__ModuleLoader__.load({
118
119
  maxOutputTokens: "Output limit (0 = provider default)",
119
120
  contextWindow: "Context window override",
120
121
  sidebar: "Sidebar",
122
+ serviceStatus: "Service status",
123
+ serviceStatusHint: "Checks the public Ollama Cloud tag endpoint without using your key or quota.",
124
+ serviceOnline: "Reachable",
125
+ serviceOffline: "Unreachable",
126
+ serviceUnknown: "Not checked",
127
+ refreshStatus: "Check now",
128
+ checkingService: "Checking…",
121
129
  showSidebarQuota: "Show the quota card in the sidebar",
122
130
  showSidebarQuotaHint: "The card appears above Settings and opens this panel.",
123
131
  planLabel: "Plan label",
@@ -208,6 +216,7 @@ window.__ModuleLoader__.load({
208
216
  tokens: "Token",
209
217
  spend7d: "7 天费用",
210
218
  remaining: "剩余",
219
+ until: "至",
211
220
  purchased: "已购买",
212
221
  range: "范围",
213
222
  last24h: "最近 24 小时",
@@ -277,6 +286,13 @@ window.__ModuleLoader__.load({
277
286
  maxOutputTokens: "输出上限(0 = 提供方默认)",
278
287
  contextWindow: "上下文窗口覆盖",
279
288
  sidebar: "侧边栏",
289
+ serviceStatus: "服务状态",
290
+ serviceStatusHint: "检查 Ollama Cloud 公共 tag 端点,不使用密钥或额度。",
291
+ serviceOnline: "可访问",
292
+ serviceOffline: "不可访问",
293
+ serviceUnknown: "未检查",
294
+ refreshStatus: "立即检查",
295
+ checkingService: "检查中…",
280
296
  showSidebarQuota: "在侧边栏显示配额卡片",
281
297
  showSidebarQuotaHint: "卡片显示在设置上方,点击可打开此面板。",
282
298
  planLabel: "套餐标签",
@@ -401,6 +417,9 @@ window.__ModuleLoader__.load({
401
417
  function fetchState() {
402
418
  return call("/dsh-ollama/state");
403
419
  }
420
+ function fetchServiceStatus() {
421
+ return call("/dsh-ollama/status");
422
+ }
404
423
  function refreshState() {
405
424
  return call("/dsh-ollama/refresh", {
406
425
  method: "POST",
@@ -990,9 +1009,16 @@ window.__ModuleLoader__.load({
990
1009
  (0, react.useEffect)(() => {
991
1010
  load();
992
1011
  const timer = setInterval(() => {
993
- load();
1012
+ if (document.visibilityState !== "hidden") load();
994
1013
  }, intervalMs);
995
- return () => clearInterval(timer);
1014
+ const onVisibility = () => {
1015
+ if (document.visibilityState !== "hidden") load();
1016
+ };
1017
+ document.addEventListener("visibilitychange", onVisibility);
1018
+ return () => {
1019
+ clearInterval(timer);
1020
+ document.removeEventListener("visibilitychange", onVisibility);
1021
+ };
996
1022
  }, [load, intervalMs]);
997
1023
  return {
998
1024
  state,
@@ -1672,6 +1698,9 @@ window.__ModuleLoader__.load({
1672
1698
  const [testModel, setTestModel] = (0, react.useState)("gpt-oss:120b");
1673
1699
  const [testing, setTesting] = (0, react.useState)(false);
1674
1700
  const [testResult, setTestResult] = (0, react.useState)(null);
1701
+ const [service, setService] = (0, react.useState)(null);
1702
+ const [serviceBusy, setServiceBusy] = (0, react.useState)(false);
1703
+ const [serviceError, setServiceError] = (0, react.useState)(null);
1675
1704
  const load = (0, react.useCallback)(async () => {
1676
1705
  setLoading(true);
1677
1706
  try {
@@ -1733,6 +1762,20 @@ window.__ModuleLoader__.load({
1733
1762
  setError(caught instanceof Error ? caught.message : t("failed"));
1734
1763
  }
1735
1764
  }, [t]);
1765
+ const loadService = (0, react.useCallback)(async () => {
1766
+ setServiceBusy(true);
1767
+ try {
1768
+ setService(await fetchServiceStatus());
1769
+ setServiceError(null);
1770
+ } catch (caught) {
1771
+ setServiceError(caught instanceof Error ? caught.message : t("failed"));
1772
+ } finally {
1773
+ setServiceBusy(false);
1774
+ }
1775
+ }, [t]);
1776
+ (0, react.useEffect)(() => {
1777
+ loadService();
1778
+ }, [loadService]);
1736
1779
  if (loading && state === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1737
1780
  className: "ocl-hint",
1738
1781
  children: t("loading")
@@ -1906,6 +1949,47 @@ window.__ModuleLoader__.load({
1906
1949
  })
1907
1950
  ]
1908
1951
  }),
1952
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1953
+ className: "ocl-card",
1954
+ children: [
1955
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1956
+ className: "ocl-card-title",
1957
+ children: t("serviceStatus")
1958
+ }),
1959
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1960
+ className: "ocl-row ocl-statusRow",
1961
+ children: [
1962
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `ocl-statusDot${service?.ok === true ? " ocl-statusDotOk" : service === null ? "" : " ocl-statusDotBad"}` }),
1963
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: service === null ? serviceBusy ? t("checkingService") : t("serviceUnknown") : service.ok ? t("serviceOnline") : t("serviceOffline") }),
1964
+ service === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1965
+ className: "ocl-hint",
1966
+ children: [
1967
+ service.latencyMs,
1968
+ " ms · ",
1969
+ new Date(service.checkedAt).toLocaleTimeString()
1970
+ ]
1971
+ }),
1972
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1973
+ type: "button",
1974
+ className: "ocl-button",
1975
+ disabled: serviceBusy,
1976
+ onClick: () => {
1977
+ loadService();
1978
+ },
1979
+ children: serviceBusy ? t("checkingService") : t("refreshStatus")
1980
+ })
1981
+ ]
1982
+ }),
1983
+ serviceError === null && service?.error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1984
+ className: "ocl-hint",
1985
+ children: serviceError ?? service?.error
1986
+ }),
1987
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1988
+ className: "ocl-hint",
1989
+ children: t("serviceStatusHint")
1990
+ })
1991
+ ]
1992
+ }),
1909
1993
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1910
1994
  className: "ocl-card",
1911
1995
  children: [
@@ -2023,6 +2107,20 @@ window.__ModuleLoader__.load({
2023
2107
  })
2024
2108
  ]
2025
2109
  }),
2110
+ known?.usage?.report === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2111
+ className: "ocl-hint",
2112
+ children: t("noUsage")
2113
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2114
+ className: "ocl-accountUsage",
2115
+ children: [quotaBars(known.usage.report).map((bar) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2116
+ className: "ocl-badge",
2117
+ title: bar.detail,
2118
+ children: [bar.detail ?? `${bar.label}: ${bar.remainingPercent.toFixed(0)}% ${t("remaining")}`, bar.resetsAt === void 0 ? "" : ` · ${t("until")} ${new Date(bar.resetsAt).toLocaleDateString()}`]
2119
+ }, bar.key)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2120
+ className: "ocl-hint",
2121
+ children: new Date(known.usage.checkedAt).toLocaleTimeString()
2122
+ })]
2123
+ }),
2026
2124
  known?.usage?.report?.windows.d7 === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2027
2125
  className: "ocl-row",
2028
2126
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -2571,6 +2669,7 @@ window.__ModuleLoader__.load({
2571
2669
  .ocl-model-section .ocl-hint { margin: 0 0 4px; }
2572
2670
  .ocl-account { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr) auto; gap: 8px; align-items: center; }
2573
2671
  .ocl-account-block { display: flex; flex-direction: column; gap: 6px; padding: 9px; border: 1px solid var(--ocl-border-soft); border-radius: 8px; background: var(--ocl-fill); }
2672
+ .ocl-accountUsage { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
2574
2673
  .ocl-notice { font-size: 12px; padding: 7px 10px; border-radius: 7px; background: var(--ocl-accent-soft); border: 1px solid var(--ocl-accent-border); }
2575
2674
  .ocl-error { font-size: 12px; padding: 7px 10px; border-radius: 7px; background: var(--ocl-danger-soft); border: 1px solid var(--ocl-danger-border); color: var(--ocl-danger); }
2576
2675
  [class*="_footerActions"]:has(.ocl-foot), [class*="_footerActions"]:has(.ocl-railButton) { flex-direction: column; }
@@ -2595,6 +2694,10 @@ window.__ModuleLoader__.load({
2595
2694
  .ocl-railButton:hover { color: var(--ocl-text); background: var(--ocl-fill-hover); }
2596
2695
  .ocl-railButton:focus-visible { outline: var(--dsw-focus-ring-width, 2px) solid var(--ocl-focus); outline-offset: -2px; }
2597
2696
  .ocl-ring { flex: none; display: inline-flex; }
2697
+ .ocl-statusRow { align-items: center; gap: 8px; flex-wrap: wrap; }
2698
+ .ocl-statusDot { width: 8px; height: 8px; border-radius: 999px; background: var(--ocl-text-secondary); flex: none; }
2699
+ .ocl-statusDotOk { background: var(--ocl-accent); }
2700
+ .ocl-statusDotBad { background: var(--ocl-danger); }
2598
2701
  .ocl-panel { display: flex; flex-direction: column; gap: 14px; padding: 18px; max-width: 760px; margin: 0 auto; height: 100%; max-height: calc(100vh - 56px); min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; }
2599
2702
  .ocl-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
2600
2703
  .ocl-limits { margin-top: 8px; }
package/lib/index.js CHANGED
@@ -3,7 +3,7 @@ import { existsSync, readFileSync, statSync } from "node:fs";
3
3
  import { appendFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
6
- import { LlmAdapter, LlmError, ReasoningEffortId, ToolCallId, attributionHeaders, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
6
+ import { CONTEXT_WINDOW_EXCEEDED_CODE, LlmAdapter, LlmError, ReasoningEffortId, ToolCallId, attributionHeaders, isContextWindowExceededError, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
7
7
  import z from "@deepseek-ai/schemastery";
8
8
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
9
9
  import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
@@ -49,7 +49,7 @@ const DEFAULT_API_BASE = "https://ollama.com";
49
49
  const DEFAULT_OPENAI_BASE = "https://ollama.com/v1";
50
50
  const DEFAULT_USAGE_URL = "https://ollama.com/api/usage";
51
51
  const DEFAULT_BALANCE_URL = "https://ollama.com/api/balance";
52
- const DEFAULT_USER_AGENT = "dsh-ollama-cloud-provider/0.1.11";
52
+ const DEFAULT_USER_AGENT = "dsh-ollama-cloud-provider/0.1.13";
53
53
  const DEFAULT_CONTEXT_WINDOW = 131072;
54
54
  const DEFAULT_MAX_TOKENS = 32768;
55
55
  const DEFAULT_GENERATE_MAX_TOKENS = 131072;
@@ -1042,6 +1042,9 @@ function classifyResponseError(status, text, headers) {
1042
1042
  if (status === 401 || status === 403) return new LlmError(`Ollama Cloud rejected the credential: ${message}. Create a key at https://ollama.com/settings/keys and store it through the credentials service.`, "INVALID_CREDENTIAL", options);
1043
1043
  if (status === 404) return new LlmError(`Ollama Cloud does not serve this model: ${message}. Refresh the catalog and check that the id matches the cloud list exactly.`, "UNSUPPORTED_MODEL", options);
1044
1044
  if (status === 429) return new LlmError(`Ollama Cloud rate-limited the request: ${message}`, "RATE_LIMIT", options);
1045
+ if (isContextWindowExceededError(detail)) return new LlmError(`Ollama Cloud rejected the request: context window exceeded: ${message}`, CONTEXT_WINDOW_EXCEEDED_CODE, options);
1046
+ if (/does not support (?:images?|vision|tools?)|unsupported (?:image|tool)|image (?:input|modality) is not supported|tools? (?:are|is) not supported/i.test(detail)) return new LlmError(`Ollama Cloud model does not accept this request: ${message}`, "UNSUPPORTED_MODEL", options);
1047
+ if (/request (?:body|entity) too large|payload too large|node count cannot exceed|maximum (?:request|body) size/i.test(detail)) return new LlmError(`Ollama Cloud rejected the request body as too large: ${message}`, "PROVIDER_HTTP_ERROR", options);
1045
1048
  if (status === 408 || status === 504) return new LlmError(`Ollama Cloud timed out (HTTP ${status}): ${message}`, "TIMEOUT", options);
1046
1049
  if (status === 503) return new LlmError(`Ollama Cloud is at its concurrency limit or temporarily unavailable: ${message}`, "SERVER", options);
1047
1050
  if (status >= 500) return new LlmError(`Ollama Cloud gateway error (HTTP ${status}): ${message}`, "SERVER", options);
@@ -3588,6 +3591,43 @@ function publicConfig(config) {
3588
3591
  userAgent: config.userAgent
3589
3592
  };
3590
3593
  }
3594
+ // src/status.ts
3595
+ const PROBE_TIMEOUT_MS = 5e3;
3596
+ /**
3597
+ * Probe the public Ollama Cloud tag endpoint. This request carries no key and
3598
+ * does not consume quota; it only answers whether the configured API base is
3599
+ * reachable from this host.
3600
+ */
3601
+ async function probeService(config, fetchImpl = fetch) {
3602
+ const started = Date.now();
3603
+ const controller = new AbortController();
3604
+ const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
3605
+ try {
3606
+ const response = await fetchImpl(tagsUrlFor(config), {
3607
+ method: "GET",
3608
+ signal: controller.signal,
3609
+ headers: { accept: "application/json" }
3610
+ });
3611
+ const latencyMs = Date.now() - started;
3612
+ return {
3613
+ ok: response.ok,
3614
+ status: response.status,
3615
+ latencyMs,
3616
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
3617
+ ...response.ok ? {} : { error: `HTTP ${response.status}` }
3618
+ };
3619
+ } catch (error) {
3620
+ return {
3621
+ ok: false,
3622
+ status: null,
3623
+ latencyMs: Date.now() - started,
3624
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
3625
+ error: controller.signal.aborted ? "timeout" : error instanceof Error ? error.message : String(error)
3626
+ };
3627
+ } finally {
3628
+ clearTimeout(timer);
3629
+ }
3630
+ }
3591
3631
  // src/routes-values.ts
3592
3632
  const TRANSPORTS = /* @__PURE__ */ new Set(["native", "openai-chat"]);
3593
3633
  function cleanAccounts(value) {
@@ -3979,6 +4019,20 @@ function applyRoutes(ctx, web, deps) {
3979
4019
  json(res, 500, { error: error instanceof Error ? error.message : "health failed" });
3980
4020
  }
3981
4021
  });
4022
+ route("/dsh-ollama/status", async (req, res) => {
4023
+ if (!readOnly(req, res)) return;
4024
+ try {
4025
+ json(res, 200, await probeService(deps.config(), fetch));
4026
+ } catch (error) {
4027
+ json(res, 200, {
4028
+ ok: false,
4029
+ status: null,
4030
+ latencyMs: 0,
4031
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
4032
+ error: error instanceof Error ? error.message : "status failed"
4033
+ });
4034
+ }
4035
+ });
3982
4036
  route("/dsh-ollama/analytics", async (req, res) => {
3983
4037
  if (!readOnly(req, res)) return;
3984
4038
  const url = new URL(req.url ?? "/", "http://localhost");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-ollama-cloud-provider",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Ollama Cloud LLM provider plugin for DeepSeek Harness (dsh).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -70,8 +70,9 @@
70
70
  "build": "tsdown",
71
71
  "prepare": "tsdown 1>&2",
72
72
  "typecheck": "tsc --noEmit",
73
- "test": "node --import tsx --test tests/core.test.ts tests/usage.test.ts tests/adapter.test.ts tests/routes.test.ts tests/native.test.ts tests/commands.test.ts tests/tui-settings.test.ts tests/analytics.test.ts tests/apply.test.ts tests/account-rules.test.ts tests/request-log.test.ts tests/image-budget.test.ts tests/tool-schema.test.ts tests/tools.test.ts tests/plugin-updater.test.ts tests/compat.test.ts",
73
+ "test": "node --import tsx --test tests/core.test.ts tests/usage.test.ts tests/adapter.test.ts tests/routes.test.ts tests/native.test.ts tests/commands.test.ts tests/tui-settings.test.ts tests/analytics.test.ts tests/apply.test.ts tests/account-rules.test.ts tests/request-log.test.ts tests/image-budget.test.ts tests/tool-schema.test.ts tests/tools.test.ts tests/plugin-updater.test.ts tests/compat.test.ts tests/status.test.ts",
74
74
  "check": "npm run typecheck && npm test && npm run build",
75
+ "test:engine": "node scripts/test-engines.mjs",
75
76
  "release": "npm run check && npm pack",
76
77
  "sync:models": "node scripts/sync-models.mjs"
77
78
  },