@goodandready/dsh-ollama-cloud-provider 0.1.10 → 0.1.12
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 +26 -0
- package/README.md +5 -5
- package/README.ru.md +4 -4
- package/README.zh.md +4 -4
- package/lib/client.js +88 -2
- package/lib/index.js +90 -4
- package/package.json +15 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.12 — 2026-10-29
|
|
4
|
+
|
|
5
|
+
- The sidebar quota card and panel now pause polling while the document is
|
|
6
|
+
hidden and refresh immediately when it becomes visible again.
|
|
7
|
+
- Settings gained a **Service status** row: a local, keyless probe of the
|
|
8
|
+
public tag endpoint with latency, checked time and an error state. Ollama
|
|
9
|
+
Cloud has no public status page, so this row reports host-to-gateway
|
|
10
|
+
reachability only.
|
|
11
|
+
- Error mapping now recognizes context-window overflow (using the harness
|
|
12
|
+
`CONTEXT_WINDOW_EXCEEDED_CODE`), model capability rejections for images/tools
|
|
13
|
+
and oversized request bodies, instead of returning a generic 400 message.
|
|
14
|
+
|
|
15
|
+
## 0.1.11 — 2026-10-29
|
|
16
|
+
|
|
17
|
+
- Compatibility declarations now use the same-channel range
|
|
18
|
+
`0.2.0-rc.2 || >=0.2.1-rc.2 <0.2.2-0` in `engines.dsh`,
|
|
19
|
+
`compatibility.dsh` and all DSH peer dependencies. Future `0.2.1-rc.*`
|
|
20
|
+
releases are admitted without a new plugin release; `dshReleases` remains the
|
|
21
|
+
tested engine matrix. A compatibility test guards the range, release list and
|
|
22
|
+
devDependency versions.
|
|
23
|
+
- The model catalog is stale-while-revalidate: a cached catalog is returned
|
|
24
|
+
immediately after its TTL and refreshed in the background; only the first
|
|
25
|
+
load waits for the network. A failed background refresh keeps the cached rows.
|
|
26
|
+
- Tool schema normalization is memoized per `parameters` object with a
|
|
27
|
+
`WeakMap`, so large MCP tool lists are not re-normalized on every request.
|
|
28
|
+
|
|
3
29
|
## 0.1.10 — 2026-10-28
|
|
4
30
|
|
|
5
31
|
- Stabilized the native stream parser tests: a referenced keep-alive holds the
|
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.
|
|
106
|
+
dsh plugin --profile web add @goodandready/dsh-ollama-cloud-provider@0.1.12
|
|
107
107
|
# DSH Desktop:
|
|
108
|
-
dsh plugin --profile desktop add @goodandready/dsh-ollama-cloud-provider@0.1.
|
|
108
|
+
dsh plugin --profile desktop add @goodandready/dsh-ollama-cloud-provider@0.1.12
|
|
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.
|
|
114
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.12.tgz
|
|
115
115
|
# DSH Desktop:
|
|
116
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.
|
|
116
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.12.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.
|
|
106
|
+
dsh plugin --profile web add @goodandready/dsh-ollama-cloud-provider@0.1.12
|
|
107
107
|
# DSH Desktop:
|
|
108
|
-
dsh plugin --profile desktop add @goodandready/dsh-ollama-cloud-provider@0.1.
|
|
108
|
+
dsh plugin --profile desktop add @goodandready/dsh-ollama-cloud-provider@0.1.12
|
|
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.
|
|
114
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.12.tgz
|
|
115
115
|
# DSH Desktop:
|
|
116
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.
|
|
116
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.12.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.
|
|
105
|
+
dsh plugin --profile web add @goodandready/dsh-ollama-cloud-provider@0.1.12
|
|
106
106
|
# DSH 桌面端:
|
|
107
|
-
dsh plugin --profile desktop add @goodandready/dsh-ollama-cloud-provider@0.1.
|
|
107
|
+
dsh plugin --profile desktop add @goodandready/dsh-ollama-cloud-provider@0.1.12
|
|
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.
|
|
113
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.12.tgz
|
|
114
114
|
# DSH 桌面端:
|
|
115
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.
|
|
115
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.12.tgz
|
|
116
116
|
```
|
|
117
117
|
|
|
118
118
|
安装完成后重启 dsh 或 DSH 桌面端,在模型列表中选择 **Ollama Cloud** 即可。
|
package/lib/client.js
CHANGED
|
@@ -118,6 +118,13 @@ window.__ModuleLoader__.load({
|
|
|
118
118
|
maxOutputTokens: "Output limit (0 = provider default)",
|
|
119
119
|
contextWindow: "Context window override",
|
|
120
120
|
sidebar: "Sidebar",
|
|
121
|
+
serviceStatus: "Service status",
|
|
122
|
+
serviceStatusHint: "Checks the public Ollama Cloud tag endpoint without using your key or quota.",
|
|
123
|
+
serviceOnline: "Reachable",
|
|
124
|
+
serviceOffline: "Unreachable",
|
|
125
|
+
serviceUnknown: "Not checked",
|
|
126
|
+
refreshStatus: "Check now",
|
|
127
|
+
checkingService: "Checking…",
|
|
121
128
|
showSidebarQuota: "Show the quota card in the sidebar",
|
|
122
129
|
showSidebarQuotaHint: "The card appears above Settings and opens this panel.",
|
|
123
130
|
planLabel: "Plan label",
|
|
@@ -277,6 +284,13 @@ window.__ModuleLoader__.load({
|
|
|
277
284
|
maxOutputTokens: "输出上限(0 = 提供方默认)",
|
|
278
285
|
contextWindow: "上下文窗口覆盖",
|
|
279
286
|
sidebar: "侧边栏",
|
|
287
|
+
serviceStatus: "服务状态",
|
|
288
|
+
serviceStatusHint: "检查 Ollama Cloud 公共 tag 端点,不使用密钥或额度。",
|
|
289
|
+
serviceOnline: "可访问",
|
|
290
|
+
serviceOffline: "不可访问",
|
|
291
|
+
serviceUnknown: "未检查",
|
|
292
|
+
refreshStatus: "立即检查",
|
|
293
|
+
checkingService: "检查中…",
|
|
280
294
|
showSidebarQuota: "在侧边栏显示配额卡片",
|
|
281
295
|
showSidebarQuotaHint: "卡片显示在设置上方,点击可打开此面板。",
|
|
282
296
|
planLabel: "套餐标签",
|
|
@@ -401,6 +415,9 @@ window.__ModuleLoader__.load({
|
|
|
401
415
|
function fetchState() {
|
|
402
416
|
return call("/dsh-ollama/state");
|
|
403
417
|
}
|
|
418
|
+
function fetchServiceStatus() {
|
|
419
|
+
return call("/dsh-ollama/status");
|
|
420
|
+
}
|
|
404
421
|
function refreshState() {
|
|
405
422
|
return call("/dsh-ollama/refresh", {
|
|
406
423
|
method: "POST",
|
|
@@ -990,9 +1007,16 @@ window.__ModuleLoader__.load({
|
|
|
990
1007
|
(0, react.useEffect)(() => {
|
|
991
1008
|
load();
|
|
992
1009
|
const timer = setInterval(() => {
|
|
993
|
-
load();
|
|
1010
|
+
if (document.visibilityState !== "hidden") load();
|
|
994
1011
|
}, intervalMs);
|
|
995
|
-
|
|
1012
|
+
const onVisibility = () => {
|
|
1013
|
+
if (document.visibilityState !== "hidden") load();
|
|
1014
|
+
};
|
|
1015
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
1016
|
+
return () => {
|
|
1017
|
+
clearInterval(timer);
|
|
1018
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
1019
|
+
};
|
|
996
1020
|
}, [load, intervalMs]);
|
|
997
1021
|
return {
|
|
998
1022
|
state,
|
|
@@ -1672,6 +1696,9 @@ window.__ModuleLoader__.load({
|
|
|
1672
1696
|
const [testModel, setTestModel] = (0, react.useState)("gpt-oss:120b");
|
|
1673
1697
|
const [testing, setTesting] = (0, react.useState)(false);
|
|
1674
1698
|
const [testResult, setTestResult] = (0, react.useState)(null);
|
|
1699
|
+
const [service, setService] = (0, react.useState)(null);
|
|
1700
|
+
const [serviceBusy, setServiceBusy] = (0, react.useState)(false);
|
|
1701
|
+
const [serviceError, setServiceError] = (0, react.useState)(null);
|
|
1675
1702
|
const load = (0, react.useCallback)(async () => {
|
|
1676
1703
|
setLoading(true);
|
|
1677
1704
|
try {
|
|
@@ -1733,6 +1760,20 @@ window.__ModuleLoader__.load({
|
|
|
1733
1760
|
setError(caught instanceof Error ? caught.message : t("failed"));
|
|
1734
1761
|
}
|
|
1735
1762
|
}, [t]);
|
|
1763
|
+
const loadService = (0, react.useCallback)(async () => {
|
|
1764
|
+
setServiceBusy(true);
|
|
1765
|
+
try {
|
|
1766
|
+
setService(await fetchServiceStatus());
|
|
1767
|
+
setServiceError(null);
|
|
1768
|
+
} catch (caught) {
|
|
1769
|
+
setServiceError(caught instanceof Error ? caught.message : t("failed"));
|
|
1770
|
+
} finally {
|
|
1771
|
+
setServiceBusy(false);
|
|
1772
|
+
}
|
|
1773
|
+
}, [t]);
|
|
1774
|
+
(0, react.useEffect)(() => {
|
|
1775
|
+
loadService();
|
|
1776
|
+
}, [loadService]);
|
|
1736
1777
|
if (loading && state === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1737
1778
|
className: "ocl-hint",
|
|
1738
1779
|
children: t("loading")
|
|
@@ -1906,6 +1947,47 @@ window.__ModuleLoader__.load({
|
|
|
1906
1947
|
})
|
|
1907
1948
|
]
|
|
1908
1949
|
}),
|
|
1950
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1951
|
+
className: "ocl-card",
|
|
1952
|
+
children: [
|
|
1953
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1954
|
+
className: "ocl-card-title",
|
|
1955
|
+
children: t("serviceStatus")
|
|
1956
|
+
}),
|
|
1957
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1958
|
+
className: "ocl-row ocl-statusRow",
|
|
1959
|
+
children: [
|
|
1960
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `ocl-statusDot${service?.ok === true ? " ocl-statusDotOk" : service === null ? "" : " ocl-statusDotBad"}` }),
|
|
1961
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: service === null ? serviceBusy ? t("checkingService") : t("serviceUnknown") : service.ok ? t("serviceOnline") : t("serviceOffline") }),
|
|
1962
|
+
service === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1963
|
+
className: "ocl-hint",
|
|
1964
|
+
children: [
|
|
1965
|
+
service.latencyMs,
|
|
1966
|
+
" ms · ",
|
|
1967
|
+
new Date(service.checkedAt).toLocaleTimeString()
|
|
1968
|
+
]
|
|
1969
|
+
}),
|
|
1970
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1971
|
+
type: "button",
|
|
1972
|
+
className: "ocl-button",
|
|
1973
|
+
disabled: serviceBusy,
|
|
1974
|
+
onClick: () => {
|
|
1975
|
+
loadService();
|
|
1976
|
+
},
|
|
1977
|
+
children: serviceBusy ? t("checkingService") : t("refreshStatus")
|
|
1978
|
+
})
|
|
1979
|
+
]
|
|
1980
|
+
}),
|
|
1981
|
+
serviceError === null && service?.error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1982
|
+
className: "ocl-hint",
|
|
1983
|
+
children: serviceError ?? service?.error
|
|
1984
|
+
}),
|
|
1985
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1986
|
+
className: "ocl-hint",
|
|
1987
|
+
children: t("serviceStatusHint")
|
|
1988
|
+
})
|
|
1989
|
+
]
|
|
1990
|
+
}),
|
|
1909
1991
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1910
1992
|
className: "ocl-card",
|
|
1911
1993
|
children: [
|
|
@@ -2595,6 +2677,10 @@ window.__ModuleLoader__.load({
|
|
|
2595
2677
|
.ocl-railButton:hover { color: var(--ocl-text); background: var(--ocl-fill-hover); }
|
|
2596
2678
|
.ocl-railButton:focus-visible { outline: var(--dsw-focus-ring-width, 2px) solid var(--ocl-focus); outline-offset: -2px; }
|
|
2597
2679
|
.ocl-ring { flex: none; display: inline-flex; }
|
|
2680
|
+
.ocl-statusRow { align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
2681
|
+
.ocl-statusDot { width: 8px; height: 8px; border-radius: 999px; background: var(--ocl-text-secondary); flex: none; }
|
|
2682
|
+
.ocl-statusDotOk { background: var(--ocl-accent); }
|
|
2683
|
+
.ocl-statusDotBad { background: var(--ocl-danger); }
|
|
2598
2684
|
.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
2685
|
.ocl-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
2600
2686
|
.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.
|
|
52
|
+
const DEFAULT_USER_AGENT = "dsh-ollama-cloud-provider/0.1.12";
|
|
53
53
|
const DEFAULT_CONTEXT_WINDOW = 131072;
|
|
54
54
|
const DEFAULT_MAX_TOKENS = 32768;
|
|
55
55
|
const DEFAULT_GENERATE_MAX_TOKENS = 131072;
|
|
@@ -772,6 +772,7 @@ const CATALOG_TTL_MS = 6e5;
|
|
|
772
772
|
const CATALOG_TIMEOUT_MS = 15e3;
|
|
773
773
|
const liveMeta = /* @__PURE__ */ new Map();
|
|
774
774
|
const cache$1 = /* @__PURE__ */ new Map();
|
|
775
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
775
776
|
const FALLBACK_META = {
|
|
776
777
|
id: "",
|
|
777
778
|
name: "",
|
|
@@ -872,11 +873,21 @@ async function fetchJson(url, config, fetchImpl, signal) {
|
|
|
872
873
|
* Refresh the live catalog (public, no key required) and return its ids.
|
|
873
874
|
* Network failures keep the previous live metadata and fall back to the
|
|
874
875
|
* generated snapshot, so the picker always has something to show.
|
|
876
|
+
*
|
|
877
|
+
* A cached catalog is served immediately, even after its TTL: the network
|
|
878
|
+
* refresh runs in the background so the picker never blocks on a stale entry.
|
|
879
|
+
* Only the first load (no cache) waits for the network.
|
|
875
880
|
*/
|
|
876
881
|
async function refreshCatalog(config, fetchImpl = fetch, force = false) {
|
|
877
882
|
const key = config.apiBase;
|
|
878
883
|
const cached = cache$1.get(key);
|
|
879
|
-
if (!force && cached !== void 0
|
|
884
|
+
if (!force && cached !== void 0) {
|
|
885
|
+
if (Date.now() - cached.at >= CATALOG_TTL_MS) refreshInBackground(config, fetchImpl, key);
|
|
886
|
+
return cached.ids;
|
|
887
|
+
}
|
|
888
|
+
return refreshNow(config, fetchImpl, key, cached !== void 0);
|
|
889
|
+
}
|
|
890
|
+
async function refreshNow(config, fetchImpl, key, keepOnFailure) {
|
|
880
891
|
const signal = AbortSignal.timeout(CATALOG_TIMEOUT_MS);
|
|
881
892
|
const [modelsResult, tagsResult] = await Promise.allSettled([fetchJson(modelsUrlFor(config), config, fetchImpl, signal), fetchJson(tagsUrlFor(config), config, fetchImpl, signal)]);
|
|
882
893
|
if (modelsResult.status === "fulfilled") {
|
|
@@ -902,6 +913,10 @@ async function refreshCatalog(config, fetchImpl = fetch, force = false) {
|
|
|
902
913
|
return fromData;
|
|
903
914
|
}
|
|
904
915
|
}
|
|
916
|
+
if (keepOnFailure) {
|
|
917
|
+
const stale = cache$1.get(key);
|
|
918
|
+
if (stale !== void 0) return stale.ids;
|
|
919
|
+
}
|
|
905
920
|
const fallback = Object.keys(GENERATED_MODELS);
|
|
906
921
|
cache$1.set(key, {
|
|
907
922
|
at: Date.now(),
|
|
@@ -909,8 +924,16 @@ async function refreshCatalog(config, fetchImpl = fetch, force = false) {
|
|
|
909
924
|
});
|
|
910
925
|
return fallback;
|
|
911
926
|
}
|
|
927
|
+
function refreshInBackground(config, fetchImpl, key) {
|
|
928
|
+
if (inflight.has(key)) return;
|
|
929
|
+
const task = refreshNow(config, fetchImpl, key, true).catch(() => cache$1.get(key)?.ids ?? Object.keys(GENERATED_MODELS)).finally(() => {
|
|
930
|
+
inflight.delete(key);
|
|
931
|
+
});
|
|
932
|
+
inflight.set(key, task);
|
|
933
|
+
}
|
|
912
934
|
function clearCatalogCache() {
|
|
913
935
|
cache$1.clear();
|
|
936
|
+
inflight.clear();
|
|
914
937
|
}
|
|
915
938
|
function baseMeta(id) {
|
|
916
939
|
const meta = liveMeta.get(id) ?? GENERATED_MODELS[id];
|
|
@@ -1019,6 +1042,9 @@ function classifyResponseError(status, text, headers) {
|
|
|
1019
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);
|
|
1020
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);
|
|
1021
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);
|
|
1022
1048
|
if (status === 408 || status === 504) return new LlmError(`Ollama Cloud timed out (HTTP ${status}): ${message}`, "TIMEOUT", options);
|
|
1023
1049
|
if (status === 503) return new LlmError(`Ollama Cloud is at its concurrency limit or temporarily unavailable: ${message}`, "SERVER", options);
|
|
1024
1050
|
if (status >= 500) return new LlmError(`Ollama Cloud gateway error (HTTP ${status}): ${message}`, "SERVER", options);
|
|
@@ -1245,6 +1271,15 @@ function toolArguments(raw) {
|
|
|
1245
1271
|
return {};
|
|
1246
1272
|
}
|
|
1247
1273
|
}
|
|
1274
|
+
const normalizedParameterCache = /* @__PURE__ */ new WeakMap();
|
|
1275
|
+
function normalizedParameters(parameters) {
|
|
1276
|
+
if (typeof parameters !== "object" || parameters === null) return normalizeToolParameters(parameters);
|
|
1277
|
+
const cached = normalizedParameterCache.get(parameters);
|
|
1278
|
+
if (cached !== void 0) return cached.value;
|
|
1279
|
+
const value = normalizeToolParameters(parameters);
|
|
1280
|
+
normalizedParameterCache.set(parameters, { value });
|
|
1281
|
+
return value;
|
|
1282
|
+
}
|
|
1248
1283
|
function buildTools(tools) {
|
|
1249
1284
|
if (tools === void 0 || tools.length === 0) return void 0;
|
|
1250
1285
|
return tools.map((tool) => ({
|
|
@@ -1252,7 +1287,7 @@ function buildTools(tools) {
|
|
|
1252
1287
|
function: {
|
|
1253
1288
|
name: tool.name,
|
|
1254
1289
|
description: tool.description,
|
|
1255
|
-
parameters:
|
|
1290
|
+
parameters: normalizedParameters(tool.parameters)
|
|
1256
1291
|
}
|
|
1257
1292
|
}));
|
|
1258
1293
|
}
|
|
@@ -3556,6 +3591,43 @@ function publicConfig(config) {
|
|
|
3556
3591
|
userAgent: config.userAgent
|
|
3557
3592
|
};
|
|
3558
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
|
+
}
|
|
3559
3631
|
// src/routes-values.ts
|
|
3560
3632
|
const TRANSPORTS = /* @__PURE__ */ new Set(["native", "openai-chat"]);
|
|
3561
3633
|
function cleanAccounts(value) {
|
|
@@ -3947,6 +4019,20 @@ function applyRoutes(ctx, web, deps) {
|
|
|
3947
4019
|
json(res, 500, { error: error instanceof Error ? error.message : "health failed" });
|
|
3948
4020
|
}
|
|
3949
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
|
+
});
|
|
3950
4036
|
route("/dsh-ollama/analytics", async (req, res) => {
|
|
3951
4037
|
if (!readOnly(req, res)) return;
|
|
3952
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.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "Ollama Cloud LLM provider plugin for DeepSeek Harness (dsh).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -59,18 +59,18 @@
|
|
|
59
59
|
"0.2.1-rc.3": "compatible",
|
|
60
60
|
"0.2.1-rc.4": "compatible"
|
|
61
61
|
},
|
|
62
|
-
"dsh": "0.2.0-rc.2 || 0.2.1-rc.2
|
|
62
|
+
"dsh": "0.2.0-rc.2 || >=0.2.1-rc.2 <0.2.2-0"
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
65
|
"engines": {
|
|
66
66
|
"node": ">=22",
|
|
67
|
-
"dsh": "0.2.0-rc.2 || 0.2.1-rc.2
|
|
67
|
+
"dsh": "0.2.0-rc.2 || >=0.2.1-rc.2 <0.2.2-0"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
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",
|
|
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
75
|
"release": "npm run check && npm pack",
|
|
76
76
|
"sync:models": "node scripts/sync-models.mjs"
|
|
@@ -85,17 +85,17 @@
|
|
|
85
85
|
],
|
|
86
86
|
"peerDependencies": {
|
|
87
87
|
"@deepseek-ai/cordis": "~4.0.4 || ~4.0.5",
|
|
88
|
-
"@deepseek-ai/dsh-attachment": "0.2.0-rc.2 || 0.2.1-rc.2
|
|
89
|
-
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.2 || 0.2.1-rc.2
|
|
90
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2 || 0.2.1-rc.2
|
|
91
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.2 || 0.2.1-rc.2
|
|
92
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2 || 0.2.1-rc.2
|
|
93
|
-
"@deepseek-ai/dsh-commands": "0.2.0-rc.2 || 0.2.1-rc.2
|
|
94
|
-
"@deepseek-ai/dsh-credentials": "0.2.0-rc.2 || 0.2.1-rc.2
|
|
95
|
-
"@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2 || 0.2.1-rc.2
|
|
96
|
-
"@deepseek-ai/dsh-launch-environment": "0.2.0-rc.2 || 0.2.1-rc.2
|
|
97
|
-
"@deepseek-ai/dsh-llm": "0.2.0-rc.2 || 0.2.1-rc.2
|
|
98
|
-
"@deepseek-ai/dsh-settings": "0.2.0-rc.2 || 0.2.1-rc.2
|
|
88
|
+
"@deepseek-ai/dsh-attachment": "0.2.0-rc.2 || >=0.2.1-rc.2 <0.2.2-0",
|
|
89
|
+
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.2 || >=0.2.1-rc.2 <0.2.2-0",
|
|
90
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2 || >=0.2.1-rc.2 <0.2.2-0",
|
|
91
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.2 || >=0.2.1-rc.2 <0.2.2-0",
|
|
92
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2 || >=0.2.1-rc.2 <0.2.2-0",
|
|
93
|
+
"@deepseek-ai/dsh-commands": "0.2.0-rc.2 || >=0.2.1-rc.2 <0.2.2-0",
|
|
94
|
+
"@deepseek-ai/dsh-credentials": "0.2.0-rc.2 || >=0.2.1-rc.2 <0.2.2-0",
|
|
95
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2 || >=0.2.1-rc.2 <0.2.2-0",
|
|
96
|
+
"@deepseek-ai/dsh-launch-environment": "0.2.0-rc.2 || >=0.2.1-rc.2 <0.2.2-0",
|
|
97
|
+
"@deepseek-ai/dsh-llm": "0.2.0-rc.2 || >=0.2.1-rc.2 <0.2.2-0",
|
|
98
|
+
"@deepseek-ai/dsh-settings": "0.2.0-rc.2 || >=0.2.1-rc.2 <0.2.2-0",
|
|
99
99
|
"@deepseek-ai/schemastery": "~3.18.4 || ~3.18.5",
|
|
100
100
|
"react": "^18.2.0"
|
|
101
101
|
},
|