@goodandready/dsh-ollama-cloud-provider 0.1.2 → 0.1.4
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 +14 -0
- package/README.md +29 -9
- package/README.ru.md +157 -67
- package/README.zh.md +134 -77
- package/lib/client.js +80 -64
- package/lib/index.d.ts +3 -0
- package/lib/index.js +21 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.4 — 2026-10-21
|
|
4
|
+
|
|
5
|
+
- Model list rows now use the reference three-column grid: badges live inside
|
|
6
|
+
the name cell, so rows no longer stretch, wrap into empty gaps or truncate the
|
|
7
|
+
model id to a single character (#20).
|
|
8
|
+
- The sidebar quota card uses the reference 16px ring and no longer appends the
|
|
9
|
+
internal `default` account label. An optional `planLabel` (for example `PRO`)
|
|
10
|
+
can be shown next to the provider name instead (#21).
|
|
11
|
+
|
|
12
|
+
## 0.1.3 — 2026-10-21
|
|
13
|
+
|
|
14
|
+
- Aligned README documentation trio with dsh-documentation-standard: added standardized Hero headers, goodandready.app hub badges, support tables, and Mermaid architecture diagrams across English, Russian, and Chinese documentations.
|
|
15
|
+
- Updated installation instructions and package version parity.
|
|
16
|
+
|
|
3
17
|
## 0.1.2 — 2026-10-21
|
|
4
18
|
|
|
5
19
|
- Test connection falls back to the reasoning text when a thinking model returns
|
package/README.md
CHANGED
|
@@ -5,11 +5,23 @@
|
|
|
5
5
|
<h3>Ollama Cloud LLM Provider for DeepSeek Harness</h3>
|
|
6
6
|
|
|
7
7
|
<p>
|
|
8
|
+
<a href="https://goodandready.app/"><img src="https://img.shields.io/badge/🌐_DSH_Hub-goodandready.app-ff4500.svg?style=for-the-badge&labelColor=1a1a2e" alt="GoodAndReady Showcase"></a>
|
|
8
9
|
<a href="https://www.npmjs.com/package/@goodandready/dsh-ollama-cloud-provider"><img src="https://img.shields.io/npm/v/@goodandready/dsh-ollama-cloud-provider?style=for-the-badge&color=crimson&logo=npm" alt="npm version"></a>
|
|
10
|
+
<a href="https://github.com/GooDAnDReaDY/dsh-ollama-cloud-provider/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge" alt="License"></a>
|
|
9
11
|
</p>
|
|
10
12
|
|
|
11
13
|
</div>
|
|
12
14
|
|
|
15
|
+
<table align="center">
|
|
16
|
+
<tr>
|
|
17
|
+
<td align="center">
|
|
18
|
+
⭐ <strong>If you like this plugin, please star it on GitHub</strong> — it shows me that the plugin is useful to you and motivates me to keep developing it.
|
|
19
|
+
<br><br>
|
|
20
|
+
🐛 <strong>If you find a bug or would like to request a feature</strong>, open a GitHub issue in any language — I will review your proposal and implement useful suggestions in a future plugin version.
|
|
21
|
+
</td>
|
|
22
|
+
</tr>
|
|
23
|
+
</table>
|
|
24
|
+
|
|
13
25
|
---
|
|
14
26
|
|
|
15
27
|
Ollama Cloud provider plugin for [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) (dsh).
|
|
@@ -72,7 +84,7 @@ graph LR
|
|
|
72
84
|
- **Price estimates** from the published Ollama pricing table, including off-peak rates, with per-model overrides.
|
|
73
85
|
- **Browser UI**:
|
|
74
86
|
- **Settings → Ollama Cloud**: key, balance, accounts, rules, model allowlist, per-model transport, connection settings.
|
|
75
|
-
- **Sidebar quota card** (off by default, and it does not poll `/state` while disabled): a ring with the highest used window and rows with remaining credits or legacy windows.
|
|
87
|
+
- **Sidebar quota card** (off by default, and it does not poll `/state` while disabled): a ring with the highest used window and rows with remaining credits or legacy windows; an optional `planLabel` is shown next to the provider name.
|
|
76
88
|
- **Center quota panel**: spend windows, spend by model, accounts, routing trace, usage analytics and a request heatmap.
|
|
77
89
|
- **`/ollama` command** for status, usage, balance, accounts, models, rules and `test [model]`.
|
|
78
90
|
- **TUI settings section** for terminal profiles.
|
|
@@ -88,12 +100,20 @@ npm run build
|
|
|
88
100
|
npm pack
|
|
89
101
|
```
|
|
90
102
|
|
|
91
|
-
Install the
|
|
103
|
+
Install the published package:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
dsh plugin --profile web add @goodandready/dsh-ollama-cloud-provider@0.1.4
|
|
107
|
+
# DSH Desktop:
|
|
108
|
+
dsh plugin --profile desktop add @goodandready/dsh-ollama-cloud-provider@0.1.4
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Or install the packed tarball into a profile:
|
|
92
112
|
|
|
93
113
|
```bash
|
|
94
|
-
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.4.tgz
|
|
95
115
|
# DSH Desktop:
|
|
96
|
-
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.4.tgz
|
|
97
117
|
```
|
|
98
118
|
|
|
99
119
|
Restart dsh (or DSH Desktop), then pick **Ollama Cloud** in the model picker.
|
|
@@ -114,7 +134,7 @@ Restart dsh (or DSH Desktop), then pick **Ollama Cloud** in the model picker.
|
|
|
114
134
|
streamIdleTimeoutMs: 300000
|
|
115
135
|
visibleModels: []
|
|
116
136
|
modelTransports: {}
|
|
117
|
-
# contextWindowOverrides: { "
|
|
137
|
+
# contextWindowOverrides: { "model-id": 131072 }
|
|
118
138
|
# maxOutputTokens: 32768
|
|
119
139
|
accounts:
|
|
120
140
|
- label: Work
|
|
@@ -122,6 +142,7 @@ Restart dsh (or DSH Desktop), then pick **Ollama Cloud** in the model picker.
|
|
|
122
142
|
activeAccount: '' # label or credential name to pin
|
|
123
143
|
stickySession: true
|
|
124
144
|
showSidebarQuota: false
|
|
145
|
+
planLabel: '' # optional, e.g. PRO
|
|
125
146
|
```
|
|
126
147
|
|
|
127
148
|
Every field except `apiKey` is volatile and appears in dsh settings.
|
|
@@ -170,7 +191,7 @@ Open **Settings → Ollama Cloud**:
|
|
|
170
191
|
- **Routing** — sticky session per conversation.
|
|
171
192
|
- **Models** — search, refresh the live catalog, hide models, override the wire transport per model, see price, context and capabilities.
|
|
172
193
|
- **Connection** — native and OpenAI bases, usage and balance endpoints, transport, timeouts, output cap and context override.
|
|
173
|
-
- **Sidebar** — show or hide the quota card.
|
|
194
|
+
- **Sidebar** — show or hide the quota card, and an optional plan label (for example `PRO`) shown next to the provider name.
|
|
174
195
|
- **Plugin update** — current/latest version and **Update now**.
|
|
175
196
|
|
|
176
197
|
The Host serves `/dsh-ollama/state`, `/health`, `/analytics`, `/usage`, `/balance`, `/config`, `/key`, `/key/clear`, `/refresh`, `/models/refresh`, `/test` and `/update`. Writes require JSON, are refused for cross-origin requests, and are restricted to loopback (`127.0.0.1` / `::1`).
|
|
@@ -184,7 +205,7 @@ The Host serves `/dsh-ollama/state`, `/health`, `/analytics`, `/usage`, `/balanc
|
|
|
184
205
|
/ollama accounts # labels, source, active/cooling, 7-day spend
|
|
185
206
|
/ollama models # ids, price, context, capabilities
|
|
186
207
|
/ollama rules # model → account rules
|
|
187
|
-
/ollama test [model] # minimal real request
|
|
208
|
+
/ollama test [model] # minimal real request
|
|
188
209
|
```
|
|
189
210
|
|
|
190
211
|
## Verified API facts
|
|
@@ -213,8 +234,7 @@ node scripts/probe.mjs
|
|
|
213
234
|
|
|
214
235
|
## Visual verification
|
|
215
236
|
|
|
216
|
-
The Ollama Cloud settings page in DSH Desktop (dark theme): configured
|
|
217
|
-
credential, balance and usage, and the connection probe.
|
|
237
|
+
The Ollama Cloud settings page in DSH Desktop (dark theme): configured credential, balance and usage, and the connection probe.
|
|
218
238
|
|
|
219
239
|

|
|
220
240
|
|
package/README.ru.md
CHANGED
|
@@ -1,76 +1,162 @@
|
|
|
1
1
|
# 📦 @goodandready/dsh-ollama-cloud-provider
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div align="center">
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<h3>Плагин-провайдер Ollama Cloud для DeepSeek Harness</h3>
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
<p>
|
|
8
|
+
<a href="https://goodandready.app/"><img src="https://img.shields.io/badge/🌐_DSH_Hub-goodandready.app-ff4500.svg?style=for-the-badge&labelColor=1a1a2e" alt="Витрина GoodAndReady"></a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/@goodandready/dsh-ollama-cloud-provider"><img src="https://img.shields.io/npm/v/@goodandready/dsh-ollama-cloud-provider?style=for-the-badge&color=crimson&logo=npm" alt="npm версия"></a>
|
|
10
|
+
<a href="https://github.com/GooDAnDReaDY/dsh-ollama-cloud-provider/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge" alt="Лицензия"></a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<table align="center">
|
|
16
|
+
<tr>
|
|
17
|
+
<td align="center">
|
|
18
|
+
⭐ <strong>Если вам нравится этот плагин, поставьте ему Star на GitHub</strong> — это покажет мне, что плагин полезен, и добавит мотивации продолжать его развитие.
|
|
19
|
+
<br><br>
|
|
20
|
+
🐛 <strong>Если вы нашли баг или хотите предложить новую функцию</strong>, создайте Issue на GitHub на любом языке — я рассмотрю предложение и реализую полезные улучшения в одной из следующих версий плагина.
|
|
21
|
+
</td>
|
|
22
|
+
</tr>
|
|
23
|
+
</table>
|
|
24
|
+
|
|
25
|
+
---
|
|
11
26
|
|
|
12
|
-
|
|
27
|
+
Плагин-провайдер **Ollama Cloud** для [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) (dsh).
|
|
13
28
|
|
|
14
|
-
|
|
29
|
+
Плагин регистрирует один нативный LLM-маршрут `ollama-cloud` (`https://ollama.com`) и поддерживает два протокола передачи данных:
|
|
30
|
+
|
|
31
|
+
| Транспорт | Эндпоинт | Назначение |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `native` (по умолчанию) | `POST /api/chat` | thinking, вызовы инструментов (tool calls), счётчики usage (NDJSON) |
|
|
34
|
+
| `openai-chat` | `POST /v1/chat/completions` | Совместимость с OpenAI API и резервный транспорт (SSE) |
|
|
35
|
+
|
|
36
|
+
Ключи разрешаются динамически на каждый вызов: смена credential применяется мгновенно без перезапуска сервера.
|
|
37
|
+
|
|
38
|
+
## Архитектура
|
|
39
|
+
|
|
40
|
+
```mermaid
|
|
41
|
+
graph LR
|
|
42
|
+
subgraph DSH["Ядро DeepSeek Harness"]
|
|
43
|
+
Agent["DSH Агент / Чат"]
|
|
44
|
+
Creds["Сервис учетных данных / окружение"]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
subgraph Plugin["dsh-ollama-cloud-provider"]
|
|
48
|
+
Router{"Маршрут: ollama-cloud"}
|
|
49
|
+
Pool["Пул аккаунтов<br/>ротация, cooldown, sticky"]
|
|
50
|
+
Catalog["Живой каталог<br/>/v1/models + /api/tags"]
|
|
51
|
+
Usage["Баланс и расход<br/>/api/balance + /api/usage"]
|
|
52
|
+
|
|
53
|
+
subgraph Transports["Протоколы передачи"]
|
|
54
|
+
T1["Native /api/chat (NDJSON)"]
|
|
55
|
+
T2["OpenAI /v1/chat/completions (SSE)"]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
subgraph Ollama["Ollama Cloud"]
|
|
60
|
+
Chat["ollama.com/api/chat"]
|
|
61
|
+
OpenAI["ollama.com/v1/chat/completions"]
|
|
62
|
+
Billing["/api/balance, /api/usage"]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
Agent --> Router
|
|
66
|
+
Creds -.-> Pool
|
|
67
|
+
Router --> Pool
|
|
68
|
+
Pool --> Transports
|
|
69
|
+
T1 --> Chat
|
|
70
|
+
T2 --> OpenAI
|
|
71
|
+
Catalog --> Router
|
|
72
|
+
Usage --> Billing
|
|
73
|
+
```
|
|
15
74
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
75
|
+
## Полный обзор возможностей
|
|
76
|
+
|
|
77
|
+
- **Два транспорта**, выбираемые индивидуально для каждой модели: нативный протокол Ollama API (NDJSON) и совместимый шлюз OpenAI (SSE).
|
|
78
|
+
- **Поддержка всех режимов**: текст, reasoning/thinking, вызовы функций/инструментов, изображения (vision), подсчёт токенов и finish reasons на обоих транспортах.
|
|
79
|
+
- **Живой каталог моделей**: автоматическое чтение `GET /v1/models`, дополненное флагами возможностей `capabilities` (`tools`, `thinking`, `vision`) из `GET /api/tags`.
|
|
80
|
+
- **Точные лимиты и метаданные**: контекстное окно, входные модальности, уровни reasoning и значение по умолчанию, статус поддержки инструментов и видимость в API.
|
|
81
|
+
- **Баланс и аналитика расхода**: интеграция с официальными облачными эндпоинтами `/api/balance` и `/api/usage` — включённые и купленные кредиты, расход за 24 часа / 7 дней / 30 дней, а также группировка по моделям.
|
|
82
|
+
- **Пул аккаунтов с авто-ротацией до первого токена**: при получении 401, 403, 429 или ответа об исчерпании квоты ключ переводится в cooldown, а запрос автоматически перенаправляется на следующий доступный аккаунт. Состояния cooldown сохраняются между перезапусками по SHA-256 fingerprint.
|
|
83
|
+
- **Sticky sessions**: закрепление одного аккаунта за конкретным диалогом, плюс гибкие правила `модель ➔ аккаунт` по glob-паттернам.
|
|
84
|
+
- **Оценка стоимости**: тарификация по официальному прайс-листу Ollama Cloud, включая ночные скидки (off-peak rates) и возможность ручного переопределения цен на модель.
|
|
85
|
+
- **Интерфейс в браузере (Web UI)**:
|
|
86
|
+
- **Настройки → Ollama Cloud**: управление ключами, отображение баланса, пул аккаунтов, правила маршрутизации, allowlist моделей, выбор транспорта.
|
|
87
|
+
- **Карточка квоты в сайдбаре**: кольцевой индикатор с наиболее загруженным окном и детализацией остатка кредитов (по умолчанию выключена и не нагружает сеть фоновыми опросами; рядом с названием можно показать `planLabel`, например `PRO`).
|
|
88
|
+
- **Центральная панель квот**: окна расходов, разбивка по моделям, трейс маршрутизации, аналитика токенов и интерактивный heatmap запросов.
|
|
89
|
+
- **Команда `/ollama`**: статус провайдера, квоты, баланс, список аккаунтов, моделей, правил и запуск тестового запроса `test [model]`.
|
|
90
|
+
- **Интеграция с терминалом (TUI)**: специальная секция настроек в терминальном режиме.
|
|
91
|
+
- **One-Click обновление плагина**: прямая проверка npm registry и кнопка обновления в Web UI.
|
|
26
92
|
|
|
27
93
|
## Установка
|
|
28
94
|
|
|
95
|
+
Сборка и упаковка из исходного кода:
|
|
96
|
+
|
|
29
97
|
```bash
|
|
30
98
|
npm install
|
|
31
99
|
npm run build
|
|
32
100
|
npm pack
|
|
33
|
-
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Установка опубликованного пакета из npm:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
dsh plugin --profile web add @goodandready/dsh-ollama-cloud-provider@0.1.4
|
|
107
|
+
# DSH Desktop:
|
|
108
|
+
dsh plugin --profile desktop add @goodandready/dsh-ollama-cloud-provider@0.1.4
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Либо установка из упакованного tarball:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.4.tgz
|
|
34
115
|
# DSH Desktop:
|
|
35
|
-
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.4.tgz
|
|
36
117
|
```
|
|
37
118
|
|
|
38
|
-
После установки перезапустите dsh и выберите **Ollama Cloud** в списке моделей.
|
|
119
|
+
После установки перезапустите dsh (или DSH Desktop) и выберите **Ollama Cloud** в списке моделей.
|
|
39
120
|
|
|
40
|
-
##
|
|
121
|
+
## Конфигурация
|
|
41
122
|
|
|
42
123
|
```yaml
|
|
43
124
|
- id: llm-ollama-cloud
|
|
44
125
|
name: "@goodandready/dsh-ollama-cloud-provider"
|
|
45
126
|
config:
|
|
46
|
-
apiKeyEnv: OLLAMA_API_KEY
|
|
47
|
-
|
|
127
|
+
apiKeyEnv: OLLAMA_API_KEY # имя переменной или credential
|
|
128
|
+
# apiKey: ... # только в composition, не коммитить в git
|
|
129
|
+
transport: native # native | openai-chat
|
|
48
130
|
requestTimeoutMs: 300000
|
|
49
131
|
streamIdleTimeoutMs: 300000
|
|
50
132
|
visibleModels: []
|
|
133
|
+
modelTransports: {}
|
|
51
134
|
accounts:
|
|
52
135
|
- label: Work
|
|
53
136
|
apiKeyEnv: OLLAMA_API_KEY_2
|
|
54
|
-
activeAccount: ''
|
|
137
|
+
activeAccount: '' # метка для принудительного закрепления
|
|
55
138
|
stickySession: true
|
|
56
139
|
showSidebarQuota: false
|
|
140
|
+
planLabel: '' # необязательно, например PRO
|
|
57
141
|
```
|
|
58
142
|
|
|
59
|
-
Все поля, кроме `apiKey`, доступны в
|
|
143
|
+
Все поля, кроме `apiKey`, доступны для изменения в интерфейсе настроек DSH.
|
|
60
144
|
|
|
61
|
-
###
|
|
145
|
+
### Получение и хранение ключей
|
|
62
146
|
|
|
63
|
-
1. `config.apiKey`
|
|
64
|
-
2.
|
|
65
|
-
3. Переменные окружения
|
|
147
|
+
1. `config.apiKey` в файле конфигурации (`role('secret')`).
|
|
148
|
+
2. Системный сервис учетных данных DSH по имени `apiKeyEnv` (по умолчанию `OLLAMA_API_KEY`).
|
|
149
|
+
3. Переменные окружения процесса, `.env` проекта или `$DSH_HOME/.env`.
|
|
66
150
|
|
|
67
|
-
Ключ
|
|
151
|
+
Ключ создается в личном кабинете: <https://ollama.com/settings/keys>. Ключи бессрочные; облачный API принимает только заголовок `Authorization: Bearer`.
|
|
68
152
|
|
|
69
|
-
###
|
|
153
|
+
### Пул аккаунтов и ротация
|
|
70
154
|
|
|
71
|
-
Дополнительный аккаунт добавляется в **Настройки → Ollama Cloud → Аккаунты**: имя
|
|
155
|
+
Дополнительный аккаунт добавляется в **Настройки → Ollama Cloud → Аккаунты**: укажите имя метки и credential reference (например, `OLLAMA_API_KEY_2`), вставьте токен и сохраните. Ротация происходит **строго до начала генерации потока**: отклонённый ключ получает cooldown, а запрос повторяется со следующим готовым аккаунтом.
|
|
72
156
|
|
|
73
|
-
|
|
157
|
+
Состояния cooldown сохраняются в `~/.dsh/storages/dsh-ollama/cooldowns.json` по SHA-256 хешу ключа.
|
|
158
|
+
|
|
159
|
+
### Правила привязки «модель ➔ аккаунт»
|
|
74
160
|
|
|
75
161
|
```yaml
|
|
76
162
|
modelAccountRules:
|
|
@@ -78,53 +164,57 @@ modelAccountRules:
|
|
|
78
164
|
account: "Work"
|
|
79
165
|
```
|
|
80
166
|
|
|
81
|
-
Первый совпавший glob
|
|
167
|
+
Первый совпавший glob-паттерн направляет модель на указанный аккаунт, остальные аккаунты остаются в резерве.
|
|
82
168
|
|
|
83
|
-
## Баланс и
|
|
169
|
+
## Баланс и мониторинг расхода
|
|
84
170
|
|
|
85
|
-
- `GET /api/balance` — новый формат (`included.balance_usd`, `allowance_usd`, `period`, `purchased.balance_usd`) и legacy-формат (`session` / `weekly` с `remaining_percent`).
|
|
86
|
-
- `GET /api/usage?range=24h|7d|30d&group_by=model` —
|
|
171
|
+
- `GET /api/balance` — поддерживает новый формат (`included.balance_usd`, `allowance_usd`, `period`, `purchased.balance_usd`) и legacy-формат (`session` / `weekly` с `remaining_percent`).
|
|
172
|
+
- `GET /api/usage?range=24h|7d|30d&group_by=model` — суммарные показатели и разбивка по моделям.
|
|
87
173
|
|
|
88
|
-
|
|
174
|
+
Эндпоинт `/api/usage` ограничен лимитом в **10 запросов в минуту**, поэтому баланс кэшируется на 60 секунд, статистика расхода — на 120 секунд. Фоновых опросов нет; временная недоступность квот не прерывает работу чата.
|
|
89
175
|
|
|
90
|
-
##
|
|
176
|
+
## Страница настроек Web UI
|
|
91
177
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
```
|
|
178
|
+
В разделе **Настройки → Ollama Cloud**:
|
|
179
|
+
- **Key**: статус ключа, вставка, очистка, кнопка **Test connection**.
|
|
180
|
+
- **Balance & Usage**: шкалы кредитов, окна расходов, предупреждения при 80% и 95%.
|
|
181
|
+
- **Accounts**: управление пулом дополнительных учетных записей.
|
|
182
|
+
- **Model rules**: настройка правил ротации по моделям.
|
|
183
|
+
- **Models**: поиск, принудительное обновление каталога моделей, allowlist, переопределение транспорта.
|
|
184
|
+
- **Connection**: базовые адреса, таймауты, лимиты токенов.
|
|
185
|
+
- **Plugin update**: проверка обновлений и кнопка **Update now**.
|
|
101
186
|
|
|
102
|
-
##
|
|
187
|
+
## Визуальная верификация
|
|
188
|
+
|
|
189
|
+
Настройки и баланс Ollama Cloud в интерфейсе DSH Desktop (темная тема):
|
|
103
190
|
|
|
104
|
-
|
|
105
|
-
- `GET https://ollama.com/api/tags` публичный и единственный источник `capabilities` по моделям.
|
|
106
|
-
- Live-каталог вернул 23 облачные модели.
|
|
107
|
-
- Нативный `/api/chat` — NDJSON, счётчики и `done_reason` в финальной строке.
|
|
108
|
-
- OpenAI `/v1/chat/completions` поддерживает стриминг, tools, vision, `reasoning_effort` и `include_usage`.
|
|
109
|
-
- Anthropic-совместимость (`/v1/messages`) частичная и плагином не используется.
|
|
191
|
+

|
|
110
192
|
|
|
111
|
-
##
|
|
193
|
+
## Команды CLI
|
|
112
194
|
|
|
113
195
|
```bash
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
196
|
+
/ollama status # статус провайдера, квоты, модели
|
|
197
|
+
/ollama usage # окна расходов и статистика по моделям
|
|
198
|
+
/ollama balance # баланс кредитов
|
|
199
|
+
/ollama accounts # список аккаунтов и их расход
|
|
200
|
+
/ollama models # каталог моделей и параметры
|
|
201
|
+
/ollama rules # правила модель ➔ аккаунт
|
|
202
|
+
/ollama test [model] # проверочный запрос к модели
|
|
119
203
|
```
|
|
120
204
|
|
|
205
|
+
## Проверенные факты API (2026-10-21)
|
|
206
|
+
|
|
207
|
+
- `GET https://ollama.com/v1/models` возвращает стандартный `data[]` и расширенный массив `models[]` с метаданными контекста и модальностей.
|
|
208
|
+
- `GET https://ollama.com/api/tags` служит источником флагов `capabilities` по моделям.
|
|
209
|
+
- Нативный `/api/chat` передает NDJSON с подробной статистикой токенов в финальном объекте.
|
|
210
|
+
- Совместимый эндпоинт OpenAI `/v1/chat/completions` поддерживает стриминг, инструменты, зрение (vision) и `reasoning_effort`.
|
|
211
|
+
|
|
121
212
|
## Ограничения
|
|
122
213
|
|
|
123
|
-
- Только Ollama Cloud; локальный `http://localhost:11434` не регистрируется.
|
|
124
|
-
-
|
|
125
|
-
- Файловые вложения передаются текстовыми handle
|
|
126
|
-
-
|
|
127
|
-
- UI требует web-профиль; в TUI остаются команда `/ollama` и маршрут модели.
|
|
214
|
+
- Только Ollama Cloud; локальный сервис `http://localhost:11434` данным плагином не регистрируется.
|
|
215
|
+
- Эмбеддинги (embeddings) в облачном API не предоставляются.
|
|
216
|
+
- Файловые вложения передаются текстовыми дескрипторами (handle), а не бинарными байтами.
|
|
217
|
+
- Интерфейс требует web-профиль; в терминальном TUI доступны команда `/ollama` и маршрут модели.
|
|
128
218
|
|
|
129
219
|
## Лицензия
|
|
130
220
|
|
package/README.zh.md
CHANGED
|
@@ -1,76 +1,155 @@
|
|
|
1
1
|
# 📦 @goodandready/dsh-ollama-cloud-provider
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div align="center">
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<h3>面向 DeepSeek Harness 的 Ollama Cloud 模型提供商插件</h3>
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
<p>
|
|
8
|
+
<a href="https://goodandready.app/"><img src="https://img.shields.io/badge/🌐_DSH_Hub-goodandready.app-ff4500.svg?style=for-the-badge&labelColor=1a1a2e" alt="GoodAndReady 展厅"></a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/@goodandready/dsh-ollama-cloud-provider"><img src="https://img.shields.io/npm/v/@goodandready/dsh-ollama-cloud-provider?style=for-the-badge&color=crimson&logo=npm" alt="npm 版本"></a>
|
|
10
|
+
<a href="https://github.com/GooDAnDReaDY/dsh-ollama-cloud-provider/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge" alt="许可证"></a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<table align="center">
|
|
16
|
+
<tr>
|
|
17
|
+
<td align="center">
|
|
18
|
+
⭐ <strong>如果您喜欢这个插件,请在 GitHub 上为它点亮 Star</strong> — 这能让我知道插件对您有用,并鼓励我继续开发和维护它。
|
|
19
|
+
<br><br>
|
|
20
|
+
🐛 <strong>如果您发现 Bug 或希望增加功能</strong>,请使用任意语言在 GitHub 上提交 Issue — 我会评估您的建议,并在后续版本中实现有价值的改进。
|
|
21
|
+
</td>
|
|
22
|
+
</tr>
|
|
23
|
+
</table>
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
面向 [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/)(dsh)的 **Ollama Cloud** 模型提供商插件。
|
|
28
|
+
|
|
29
|
+
插件注册一个原生 LLM 路由 `ollama-cloud`(`https://ollama.com`),支持两种底层传输协议:
|
|
30
|
+
|
|
31
|
+
| 传输协议 | 端点 | 用途 |
|
|
8
32
|
|---|---|---|
|
|
9
|
-
| `native`(默认) | `POST /api/chat` | thinking、工具调用、usage
|
|
10
|
-
| `openai-chat` | `POST /v1/chat/completions` | OpenAI
|
|
33
|
+
| `native`(默认) | `POST /api/chat` | thinking、工具调用、usage 计数(NDJSON) |
|
|
34
|
+
| `openai-chat` | `POST /v1/chat/completions` | OpenAI 兼容部署与备用网关(SSE) |
|
|
35
|
+
|
|
36
|
+
密钥在每次请求时动态解析:在设置中更换凭据无需重启即可立即生效。
|
|
37
|
+
|
|
38
|
+
## 架构示意
|
|
39
|
+
|
|
40
|
+
```mermaid
|
|
41
|
+
graph LR
|
|
42
|
+
subgraph DSH["DeepSeek Harness 核心"]
|
|
43
|
+
Agent["DSH 智能体 / 聊天"]
|
|
44
|
+
Creds["凭据服务 / 环境变量"]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
subgraph Plugin["dsh-ollama-cloud-provider"]
|
|
48
|
+
Router{"路由: ollama-cloud"}
|
|
49
|
+
Pool["多账号连接池<br/>轮换、冷却、粘性会话"]
|
|
50
|
+
Catalog["实时模型目录<br/>/v1/models + /api/tags"]
|
|
51
|
+
Usage["余额与用量监控<br/>/api/balance + /api/usage"]
|
|
52
|
+
|
|
53
|
+
subgraph Transports["底层传输协议"]
|
|
54
|
+
T1["Native /api/chat (NDJSON)"]
|
|
55
|
+
T2["OpenAI /v1/chat/completions (SSE)"]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
subgraph Ollama["Ollama Cloud 云端网关"]
|
|
60
|
+
Chat["ollama.com/api/chat"]
|
|
61
|
+
OpenAI["ollama.com/v1/chat/completions"]
|
|
62
|
+
Billing["/api/balance, /api/usage"]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
Agent --> Router
|
|
66
|
+
Creds -.-> Pool
|
|
67
|
+
Router --> Pool
|
|
68
|
+
Pool --> Transports
|
|
69
|
+
T1 --> Chat
|
|
70
|
+
T2 --> OpenAI
|
|
71
|
+
Catalog --> Router
|
|
72
|
+
Usage --> Billing
|
|
73
|
+
```
|
|
11
74
|
|
|
12
|
-
|
|
75
|
+
## 功能全貌
|
|
13
76
|
|
|
14
|
-
|
|
77
|
+
- **两种底层传输协议**:按模型选择 Ollama 原生协议(NDJSON)或 OpenAI 兼容协议(SSE)。
|
|
78
|
+
- **全要素流式交互**:文本、深度思考(thinking)、工具调用、多模态图像、Token 统计与 finish reasons。
|
|
79
|
+
- **实时模型目录**:自动从 `GET /v1/models` 获取模型列表,并结合 `GET /api/tags` 补全能力标记(`tools`、`thinking`、`vision`)。
|
|
80
|
+
- **精准模型元数据**:上下文长度、输入模态、推理等级与默认配置、工具支持状态及 API 开放性。
|
|
81
|
+
- **官方余额与用量分析**:集成官方 `/api/balance` 与 `/api/usage`,呈现可用额度、已购额度、24小时/7天/30天支出及按模型细分报表。
|
|
82
|
+
- **多账号智能轮换**:在输出首个 Token 之前,遇到 401/403/429 或额度耗尽自动切换至备用账号。冷却时间通过 SHA-256 哈希安全持久化。
|
|
83
|
+
- **会话粘性与路由规则**:支持单会话锁定同一账号,以及基于 Glob 通配符的 `模型 → 账号` 绑定规则。
|
|
84
|
+
- **成本透明估算**:依据官方发布的价目表(包含闲时优惠折扣)进行费用估算,支持按模型覆盖价格。
|
|
85
|
+
- **Web UI 与交互卡片**:
|
|
86
|
+
- **设置 → Ollama Cloud**:密钥配置、余额显示、账号池管理、模型白名单与协议切换。
|
|
87
|
+
- **侧边栏配额指示卡片**:环形展示最高额度窗口(默认关闭且不产生后台轮询;可在名称旁显示 `planLabel`,例如 `PRO`)。
|
|
88
|
+
- **中央用量分析面板**:支出窗口、请求热力图与用量走势。
|
|
89
|
+
- **`/ollama` 快捷指令**:快速查询状态、额度、余额、账号列表与执行联通性测试。
|
|
90
|
+
- **一键热更新**:在 DSH 设置页直接检测 npm 最新版本并快速安装更新。
|
|
15
91
|
|
|
16
|
-
|
|
17
|
-
- 文本、thinking、工具调用、图像、usage 和结束原因。
|
|
18
|
-
- 从 `GET /v1/models` 读取实时模型目录,并用 `GET /api/tags` 的 `capabilities` 补充能力。
|
|
19
|
-
- 模型选择器中的准确元数据:上下文窗口、输入模态、推理等级及默认值、工具支持、API 可见性。
|
|
20
|
-
- 从官方 `/api/balance` 和 `/api/usage` 读取余额与用量:包含额度、购买额度、24 小时 / 7 天 / 30 天支出、按模型支出。
|
|
21
|
-
- 多账号轮换在**首个 token 之前**完成:401/403/429 与配额耗尽会标记密钥并切换到下一个。冷却状态重启后保留。
|
|
22
|
-
- 每个会话的粘性账号,以及 `模型 → 账号` 的 glob 规则。
|
|
23
|
-
- 按 Ollama 公开价目表估算费用,包含 off-peak 价格,并支持按模型覆盖。
|
|
24
|
-
- 浏览器界面:**设置 → Ollama Cloud**、侧边栏配额卡片(默认关闭,关闭时不会轮询 `/state`)、含用量分析、热力图和路由记录的中央面板。
|
|
25
|
-
- `/ollama` 命令、TUI 设置区、一键插件更新。
|
|
92
|
+
## 安装指南
|
|
26
93
|
|
|
27
|
-
|
|
94
|
+
从源码构建与打包:
|
|
28
95
|
|
|
29
96
|
```bash
|
|
30
97
|
npm install
|
|
31
98
|
npm run build
|
|
32
99
|
npm pack
|
|
33
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.0.tgz
|
|
34
|
-
# DSH Desktop:
|
|
35
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.0.tgz
|
|
36
100
|
```
|
|
37
101
|
|
|
38
|
-
|
|
102
|
+
从 npm 安装已发布版本:
|
|
39
103
|
|
|
40
|
-
|
|
104
|
+
```bash
|
|
105
|
+
dsh plugin --profile web add @goodandready/dsh-ollama-cloud-provider@0.1.4
|
|
106
|
+
# DSH 桌面端:
|
|
107
|
+
dsh plugin --profile desktop add @goodandready/dsh-ollama-cloud-provider@0.1.4
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
或通过打包文件安装:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.4.tgz
|
|
114
|
+
# DSH 桌面端:
|
|
115
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.4.tgz
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
安装完成后重启 dsh 或 DSH 桌面端,在模型列表中选择 **Ollama Cloud** 即可。
|
|
119
|
+
|
|
120
|
+
## 配置示例
|
|
41
121
|
|
|
42
122
|
```yaml
|
|
43
123
|
- id: llm-ollama-cloud
|
|
44
124
|
name: "@goodandready/dsh-ollama-cloud-provider"
|
|
45
125
|
config:
|
|
46
|
-
apiKeyEnv: OLLAMA_API_KEY
|
|
47
|
-
|
|
126
|
+
apiKeyEnv: OLLAMA_API_KEY # 环境变量名或 DSH 凭据名
|
|
127
|
+
# apiKey: ... # 仅本地调试,严禁提交代码库
|
|
128
|
+
transport: native # native | openai-chat
|
|
48
129
|
requestTimeoutMs: 300000
|
|
49
130
|
streamIdleTimeoutMs: 300000
|
|
50
131
|
visibleModels: []
|
|
132
|
+
modelTransports: {}
|
|
51
133
|
accounts:
|
|
52
134
|
- label: Work
|
|
53
135
|
apiKeyEnv: OLLAMA_API_KEY_2
|
|
54
|
-
activeAccount: ''
|
|
136
|
+
activeAccount: '' # 固定指定活跃账号
|
|
55
137
|
stickySession: true
|
|
56
138
|
showSidebarQuota: false
|
|
139
|
+
planLabel: '' # 可选,例如 PRO
|
|
57
140
|
```
|
|
58
141
|
|
|
59
|
-
除 `apiKey`
|
|
60
|
-
|
|
61
|
-
### 密钥
|
|
142
|
+
除 `apiKey` 外的所有配置项均可在 DSH 设置页直接修改。
|
|
62
143
|
|
|
63
|
-
|
|
64
|
-
2. dsh 凭据服务中的 `apiKeyEnv`(默认 `OLLAMA_API_KEY`)。
|
|
65
|
-
3. 启动环境变量。
|
|
144
|
+
### 密钥配置
|
|
66
145
|
|
|
67
|
-
|
|
146
|
+
1. DSH 配置中的 `config.apiKey`。
|
|
147
|
+
2. DSH 凭据服务中的 `apiKeyEnv`(默认 `OLLAMA_API_KEY`)。
|
|
148
|
+
3. 环境变量或 `$DSH_HOME/.env`。
|
|
68
149
|
|
|
69
|
-
|
|
150
|
+
密钥可在官方后台生成:<https://ollama.com/settings/keys>。云端网关仅接受 `Authorization: Bearer` 请求头。
|
|
70
151
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
### 模型 → 账号规则
|
|
152
|
+
### 模型到账号路由规则
|
|
74
153
|
|
|
75
154
|
```yaml
|
|
76
155
|
modelAccountRules:
|
|
@@ -78,54 +157,32 @@ modelAccountRules:
|
|
|
78
157
|
account: "Work"
|
|
79
158
|
```
|
|
80
159
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
## 余额与用量
|
|
84
|
-
|
|
85
|
-
- `GET /api/balance`:新格式(`included.balance_usd`、`allowance_usd`、`period`、`purchased.balance_usd`)与旧格式(`session` / `weekly` 的 `remaining_percent`)均支持。
|
|
86
|
-
- `GET /api/usage?range=24h|7d|30d&group_by=model`:总计与按模型分组。
|
|
160
|
+
## 界面与视觉验收
|
|
87
161
|
|
|
88
|
-
|
|
162
|
+
DSH Desktop(暗黑主题)中的 Ollama Cloud 设置与用量面板:
|
|
89
163
|
|
|
90
|
-
|
|
164
|
+

|
|
91
165
|
|
|
92
|
-
|
|
93
|
-
/ollama status
|
|
94
|
-
/ollama usage
|
|
95
|
-
/ollama balance
|
|
96
|
-
/ollama accounts
|
|
97
|
-
/ollama models
|
|
98
|
-
/ollama rules
|
|
99
|
-
/ollama test [model]
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
## 已验证的 API 事实(2026-10-21)
|
|
103
|
-
|
|
104
|
-
- `GET https://ollama.com/v1/models` 公开,除标准 `data[]` 外还返回非标准 `models[]`,包含上下文、模态、推理等级、`visibility` 和 `supported_in_api`。
|
|
105
|
-
- `GET https://ollama.com/api/tags` 公开,是按模型获取 `capabilities` 的唯一来源。
|
|
106
|
-
- 实时目录返回 23 个云端模型。
|
|
107
|
-
- 原生 `/api/chat` 使用 NDJSON,最后一行包含计数与 `done_reason`。
|
|
108
|
-
- OpenAI `/v1/chat/completions` 支持流式、工具、图像、`reasoning_effort` 与 `include_usage`。
|
|
109
|
-
- Anthropic 兼容接口(`/v1/messages`)仅部分支持,本插件未使用。
|
|
110
|
-
|
|
111
|
-
## 开发
|
|
166
|
+
## 常用指令
|
|
112
167
|
|
|
113
168
|
```bash
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
169
|
+
/ollama status # 查看服务商状态、配额与模型数
|
|
170
|
+
/ollama usage # 7 天支出与按模型细分
|
|
171
|
+
/ollama balance # 包含额度与充值额度
|
|
172
|
+
/ollama accounts # 账号池健康与支出
|
|
173
|
+
/ollama models # 模型目录与参数能力
|
|
174
|
+
/ollama rules # 模型到账号规则
|
|
175
|
+
/ollama test [model] # 发送连通性测试请求
|
|
119
176
|
```
|
|
120
177
|
|
|
121
|
-
##
|
|
178
|
+
## 限制与注意事项
|
|
122
179
|
|
|
123
|
-
- 仅支持 Ollama Cloud
|
|
124
|
-
-
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
180
|
+
- 仅支持 Ollama Cloud 云端模型;不会注册本地 `http://localhost:11434` 路由。
|
|
181
|
+
- 云端 API 暂不提供向量嵌入(Embeddings)模型。
|
|
182
|
+
- 文件附件以文本句柄(handle)形式转发。
|
|
183
|
+
- `/api/usage` 限制为每分钟 10 次请求,插件已内置本地缓存以确保顺畅使用。
|
|
184
|
+
- 完整功能需要 Web 配置文件;TUI 终端环境保留 `/ollama` 命令与模型推理。
|
|
128
185
|
|
|
129
186
|
## 许可证
|
|
130
187
|
|
|
131
|
-
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
|
188
|
+
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)。详见 [LICENSE](./LICENSE) 与 [NOTICE](./NOTICE)。
|
package/lib/client.js
CHANGED
|
@@ -120,6 +120,8 @@ window.__ModuleLoader__.load({
|
|
|
120
120
|
sidebar: "Sidebar",
|
|
121
121
|
showSidebarQuota: "Show the quota card in the sidebar",
|
|
122
122
|
showSidebarQuotaHint: "The card appears above Settings and opens this panel.",
|
|
123
|
+
planLabel: "Plan label",
|
|
124
|
+
planLabelHint: "Optional label shown on the sidebar card, for example PRO. Ollama does not expose the plan name via API.",
|
|
123
125
|
usageAnalytics: "Usage analytics",
|
|
124
126
|
analyticsEmpty: "No Ollama Cloud sessions in this window.",
|
|
125
127
|
analyticsHint: "Aggregated from the local session projection cache: numeric metadata only, no prompt text.",
|
|
@@ -277,6 +279,8 @@ window.__ModuleLoader__.load({
|
|
|
277
279
|
sidebar: "侧边栏",
|
|
278
280
|
showSidebarQuota: "在侧边栏显示配额卡片",
|
|
279
281
|
showSidebarQuotaHint: "卡片显示在设置上方,点击可打开此面板。",
|
|
282
|
+
planLabel: "套餐标签",
|
|
283
|
+
planLabelHint: "侧边栏卡片上显示的可选标签,例如 PRO。Ollama API 不提供套餐名称。",
|
|
280
284
|
usageAnalytics: "用量分析",
|
|
281
285
|
analyticsEmpty: "此窗口内没有 Ollama Cloud 会话。",
|
|
282
286
|
analyticsHint: "来自本地会话投影缓存,仅包含数值元数据,不读取提示词。",
|
|
@@ -1005,8 +1009,8 @@ window.__ModuleLoader__.load({
|
|
|
1005
1009
|
};
|
|
1006
1010
|
}
|
|
1007
1011
|
function MiniRing(props) {
|
|
1008
|
-
const size = props.size ??
|
|
1009
|
-
const radius =
|
|
1012
|
+
const size = props.size ?? 16;
|
|
1013
|
+
const radius = 7;
|
|
1010
1014
|
const circumference = 2 * Math.PI * radius;
|
|
1011
1015
|
const filled = Math.max(0, Math.min(100, props.percent)) / 100;
|
|
1012
1016
|
const stroke = props.percent >= 90 ? "var(--ocl-danger)" : props.percent >= 75 ? "var(--ocl-warning)" : "var(--ocl-accent)";
|
|
@@ -1014,24 +1018,24 @@ window.__ModuleLoader__.load({
|
|
|
1014
1018
|
className: "ocl-ring",
|
|
1015
1019
|
width: size,
|
|
1016
1020
|
height: size,
|
|
1017
|
-
viewBox: "0 0
|
|
1021
|
+
viewBox: "0 0 20 20",
|
|
1018
1022
|
fill: "none",
|
|
1019
1023
|
"aria-hidden": "true",
|
|
1020
1024
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1021
|
-
cx: "
|
|
1022
|
-
cy: "
|
|
1025
|
+
cx: "10",
|
|
1026
|
+
cy: "10",
|
|
1023
1027
|
r: radius,
|
|
1024
1028
|
stroke: "var(--ocl-border-strong)",
|
|
1025
|
-
strokeWidth: "
|
|
1029
|
+
strokeWidth: "2.5"
|
|
1026
1030
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1027
|
-
cx: "
|
|
1028
|
-
cy: "
|
|
1031
|
+
cx: "10",
|
|
1032
|
+
cy: "10",
|
|
1029
1033
|
r: radius,
|
|
1030
1034
|
stroke,
|
|
1031
|
-
strokeWidth: "
|
|
1035
|
+
strokeWidth: "2.5",
|
|
1032
1036
|
strokeLinecap: "round",
|
|
1033
1037
|
strokeDasharray: `${circumference * filled} ${circumference}`,
|
|
1034
|
-
transform: "rotate(-90
|
|
1038
|
+
transform: "rotate(-90 10 10)"
|
|
1035
1039
|
})]
|
|
1036
1040
|
});
|
|
1037
1041
|
}
|
|
@@ -1076,8 +1080,8 @@ window.__ModuleLoader__.load({
|
|
|
1076
1080
|
const report = state?.usage?.report;
|
|
1077
1081
|
const bars = quotaBars(report);
|
|
1078
1082
|
const worst = worstUsedPercent(report);
|
|
1079
|
-
const
|
|
1080
|
-
const name =
|
|
1083
|
+
const plan = state?.config.planLabel?.trim() ?? "";
|
|
1084
|
+
const name = plan === "" ? t("quotaCardName") : `${t("quotaCardName")} · ${plan}`;
|
|
1081
1085
|
const detail = bars.map((entry) => `${entry.label}: ${entry.remainingPercent.toFixed(0)}% ${t("remaining")}`).join(" · ");
|
|
1082
1086
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1083
1087
|
type: "button",
|
|
@@ -1441,6 +1445,7 @@ window.__ModuleLoader__.load({
|
|
|
1441
1445
|
maxOutputTokens: state.config.maxOutputTokens === null ? "" : String(state.config.maxOutputTokens),
|
|
1442
1446
|
contextWindow: state.config.contextWindow === null ? "" : String(state.config.contextWindow),
|
|
1443
1447
|
showSidebarQuota: state.config.showSidebarQuota,
|
|
1448
|
+
planLabel: state.config.planLabel,
|
|
1444
1449
|
activeAccount: state.config.activeAccount,
|
|
1445
1450
|
accounts: state.config.accounts.map((account) => ({ ...account })),
|
|
1446
1451
|
modelAccountRules: state.config.modelAccountRules.map((rule) => ({ ...rule })),
|
|
@@ -1471,6 +1476,7 @@ window.__ModuleLoader__.load({
|
|
|
1471
1476
|
maxOutputTokens: numberOrNull(draft.maxOutputTokens),
|
|
1472
1477
|
contextWindow: numberOrNull(draft.contextWindow),
|
|
1473
1478
|
showSidebarQuota: draft.showSidebarQuota,
|
|
1479
|
+
planLabel: draft.planLabel.trim(),
|
|
1474
1480
|
activeAccount: draft.activeAccount.trim(),
|
|
1475
1481
|
accounts: draft.accounts.map((account) => ({
|
|
1476
1482
|
label: account.label.trim(),
|
|
@@ -1587,63 +1593,67 @@ window.__ModuleLoader__.load({
|
|
|
1587
1593
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1588
1594
|
className: "ocl-model",
|
|
1589
1595
|
children: [
|
|
1590
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
checked: visible,
|
|
1595
|
-
onChange: (event) => onVisible(event.target.checked)
|
|
1596
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1597
|
-
className: "ocl-model-name",
|
|
1598
|
-
title: `${model.name} · ${model.description}`,
|
|
1599
|
-
children: model.id
|
|
1600
|
-
})]
|
|
1596
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1597
|
+
type: "checkbox",
|
|
1598
|
+
checked: visible,
|
|
1599
|
+
onChange: (event) => onVisible(event.target.checked)
|
|
1601
1600
|
}),
|
|
1602
|
-
|
|
1603
|
-
className: "ocl-
|
|
1604
|
-
title:
|
|
1605
|
-
children: t("free")
|
|
1606
|
-
}) : null,
|
|
1607
|
-
!model.free && model.cost.priced ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1608
|
-
className: "ocl-badge",
|
|
1609
|
-
title: t("legendPrice"),
|
|
1601
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1602
|
+
className: "ocl-model-name",
|
|
1603
|
+
title: `${model.name} · ${model.description}`,
|
|
1610
1604
|
children: [
|
|
1611
|
-
|
|
1612
|
-
model.
|
|
1613
|
-
|
|
1614
|
-
|
|
1605
|
+
model.id,
|
|
1606
|
+
model.free ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1607
|
+
className: "ocl-badge",
|
|
1608
|
+
title: t("legendFree"),
|
|
1609
|
+
children: [" ", t("free")]
|
|
1610
|
+
}) : null,
|
|
1611
|
+
!model.free && model.cost.priced ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1612
|
+
className: "ocl-badge",
|
|
1613
|
+
title: t("legendPrice"),
|
|
1614
|
+
children: [
|
|
1615
|
+
" $",
|
|
1616
|
+
model.cost.input,
|
|
1617
|
+
"/$",
|
|
1618
|
+
model.cost.output
|
|
1619
|
+
]
|
|
1620
|
+
}) : null,
|
|
1621
|
+
!model.free && !model.cost.priced ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1622
|
+
className: "ocl-badge",
|
|
1623
|
+
title: t("legendUnknownPrice"),
|
|
1624
|
+
children: [" ", t("priceUnknown")]
|
|
1625
|
+
}) : null,
|
|
1626
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1627
|
+
className: "ocl-badge",
|
|
1628
|
+
title: t("legendContext"),
|
|
1629
|
+
children: [
|
|
1630
|
+
" ",
|
|
1631
|
+
Math.round(model.contextWindow / 1024),
|
|
1632
|
+
"K"
|
|
1633
|
+
]
|
|
1634
|
+
}),
|
|
1635
|
+
model.tools ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1636
|
+
className: "ocl-badge",
|
|
1637
|
+
title: t("legendTools"),
|
|
1638
|
+
children: [" ", t("tools")]
|
|
1639
|
+
}) : null,
|
|
1640
|
+
model.input.includes("image") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1641
|
+
className: "ocl-badge",
|
|
1642
|
+
children: [" ", t("vision")]
|
|
1643
|
+
}) : null,
|
|
1644
|
+
model.reasoning.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1645
|
+
className: "ocl-badge",
|
|
1646
|
+
title: model.reasoning.join(", "),
|
|
1647
|
+
children: [" ", t("reasoning")]
|
|
1648
|
+
}) : null,
|
|
1649
|
+
model.available ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1650
|
+
className: "ocl-badge ocl-badge-warn",
|
|
1651
|
+
children: [" ", t("unavailable")]
|
|
1652
|
+
})
|
|
1615
1653
|
]
|
|
1616
|
-
}) : null,
|
|
1617
|
-
!model.free && !model.cost.priced ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1618
|
-
className: "ocl-badge",
|
|
1619
|
-
title: t("legendUnknownPrice"),
|
|
1620
|
-
children: t("priceUnknown")
|
|
1621
|
-
}) : null,
|
|
1622
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1623
|
-
className: "ocl-badge",
|
|
1624
|
-
title: t("legendContext"),
|
|
1625
|
-
children: [Math.round(model.contextWindow / 1024), "K"]
|
|
1626
|
-
}),
|
|
1627
|
-
model.tools ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1628
|
-
className: "ocl-badge",
|
|
1629
|
-
title: t("legendTools"),
|
|
1630
|
-
children: t("tools")
|
|
1631
|
-
}) : null,
|
|
1632
|
-
model.input.includes("image") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1633
|
-
className: "ocl-badge",
|
|
1634
|
-
children: t("vision")
|
|
1635
|
-
}) : null,
|
|
1636
|
-
model.reasoning.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1637
|
-
className: "ocl-badge",
|
|
1638
|
-
title: model.reasoning.join(", "),
|
|
1639
|
-
children: t("reasoning")
|
|
1640
|
-
}) : null,
|
|
1641
|
-
model.available ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1642
|
-
className: "ocl-badge ocl-badge-warn",
|
|
1643
|
-
children: t("unavailable")
|
|
1644
1654
|
}),
|
|
1645
1655
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1646
|
-
className: "ocl-select
|
|
1656
|
+
className: "ocl-select",
|
|
1647
1657
|
value: transport === "" ? model.transport : transport,
|
|
1648
1658
|
"aria-label": t("transport"),
|
|
1649
1659
|
onChange: (event) => onTransport(event.target.value),
|
|
@@ -2415,6 +2425,12 @@ window.__ModuleLoader__.load({
|
|
|
2415
2425
|
type: "number",
|
|
2416
2426
|
value: draft.contextWindow,
|
|
2417
2427
|
onChange: (value) => update({ contextWindow: value })
|
|
2428
|
+
}),
|
|
2429
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
2430
|
+
label: t("planLabel"),
|
|
2431
|
+
hint: t("planLabelHint"),
|
|
2432
|
+
value: draft.planLabel,
|
|
2433
|
+
onChange: (value) => update({ planLabel: value })
|
|
2418
2434
|
})
|
|
2419
2435
|
]
|
|
2420
2436
|
})]
|
package/lib/index.d.ts
CHANGED
|
@@ -66,6 +66,8 @@ interface Config {
|
|
|
66
66
|
activeAccount?: string;
|
|
67
67
|
/** Show the quota card in the sidebar. */
|
|
68
68
|
showSidebarQuota?: boolean;
|
|
69
|
+
/** Optional plan label shown on the sidebar card, for example PRO. */
|
|
70
|
+
planLabel?: string;
|
|
69
71
|
/** Per-model price overrides in USD per 1M tokens. */
|
|
70
72
|
priceOverrides?: Record<string, ModelPriceOverride>;
|
|
71
73
|
/** Product user agent sent to Ollama Cloud. */
|
|
@@ -94,6 +96,7 @@ interface ResolvedConfig {
|
|
|
94
96
|
stickySession: boolean;
|
|
95
97
|
activeAccount: string;
|
|
96
98
|
showSidebarQuota: boolean;
|
|
99
|
+
planLabel: string;
|
|
97
100
|
priceOverrides: Readonly<Record<string, ModelPriceOverride>>;
|
|
98
101
|
userAgent: string;
|
|
99
102
|
}
|
package/lib/index.js
CHANGED
|
@@ -51,7 +51,7 @@ const DEFAULT_API_BASE = "https://ollama.com";
|
|
|
51
51
|
const DEFAULT_OPENAI_BASE = "https://ollama.com/v1";
|
|
52
52
|
const DEFAULT_USAGE_URL = "https://ollama.com/api/usage";
|
|
53
53
|
const DEFAULT_BALANCE_URL = "https://ollama.com/api/balance";
|
|
54
|
-
const DEFAULT_USER_AGENT = "dsh-ollama-cloud-provider/0.1.
|
|
54
|
+
const DEFAULT_USER_AGENT = "dsh-ollama-cloud-provider/0.1.4";
|
|
55
55
|
const DEFAULT_CONTEXT_WINDOW = 131072;
|
|
56
56
|
const DEFAULT_MAX_TOKENS = 32768;
|
|
57
57
|
const DEFAULT_GENERATE_MAX_TOKENS = 131072;
|
|
@@ -88,6 +88,7 @@ const Config = z.object({
|
|
|
88
88
|
stickySession: markVolatile(z.boolean().default(true)),
|
|
89
89
|
activeAccount: markVolatile(z.string().default("")),
|
|
90
90
|
showSidebarQuota: markVolatile(z.boolean().default(false)),
|
|
91
|
+
planLabel: markVolatile(z.string().default("")),
|
|
91
92
|
priceOverrides: markVolatile(z.dict(z.object({
|
|
92
93
|
input: z.number().min(0),
|
|
93
94
|
output: z.number().min(0),
|
|
@@ -178,6 +179,7 @@ function resolveConfig(config) {
|
|
|
178
179
|
stickySession: config.stickySession !== false,
|
|
179
180
|
activeAccount: typeof config.activeAccount === "string" ? config.activeAccount : "",
|
|
180
181
|
showSidebarQuota: config.showSidebarQuota === true,
|
|
182
|
+
planLabel: typeof config.planLabel === "string" ? config.planLabel.trim() : "",
|
|
181
183
|
priceOverrides: priceRecord(config.priceOverrides),
|
|
182
184
|
userAgent: typeof config.userAgent === "string" && config.userAgent !== "" ? config.userAgent : DEFAULT_USER_AGENT
|
|
183
185
|
};
|
|
@@ -3573,6 +3575,7 @@ function publicConfig(config) {
|
|
|
3573
3575
|
stickySession: config.stickySession,
|
|
3574
3576
|
activeAccount: config.activeAccount,
|
|
3575
3577
|
showSidebarQuota: config.showSidebarQuota,
|
|
3578
|
+
planLabel: config.planLabel,
|
|
3576
3579
|
priceOverrides: config.priceOverrides,
|
|
3577
3580
|
userAgent: config.userAgent
|
|
3578
3581
|
};
|
|
@@ -3777,6 +3780,7 @@ function configOps(patch) {
|
|
|
3777
3780
|
if ("stickySession" in patch) set("stickySession", patch.stickySession === true);
|
|
3778
3781
|
if ("activeAccount" in patch) set("activeAccount", patch.activeAccount);
|
|
3779
3782
|
if ("showSidebarQuota" in patch) set("showSidebarQuota", patch.showSidebarQuota === true);
|
|
3783
|
+
if ("planLabel" in patch) set("planLabel", patch.planLabel);
|
|
3780
3784
|
if ("priceOverrides" in patch) set("priceOverrides", cleanPrices(patch.priceOverrides));
|
|
3781
3785
|
if ("userAgent" in patch) set("userAgent", patch.userAgent);
|
|
3782
3786
|
return ops;
|
|
@@ -4258,6 +4262,18 @@ function buildOllamaTuiSection(deps) {
|
|
|
4258
4262
|
value: text.trim() === "true"
|
|
4259
4263
|
})
|
|
4260
4264
|
},
|
|
4265
|
+
{
|
|
4266
|
+
path: ["planLabel"],
|
|
4267
|
+
group: "sidebar",
|
|
4268
|
+
kind: "text",
|
|
4269
|
+
label: "Plan label",
|
|
4270
|
+
hint: "Optional label shown on the sidebar card, for example PRO.",
|
|
4271
|
+
format: (value) => String(value === void 0 ? deps.planLabel() : value),
|
|
4272
|
+
parse: (text) => text.trim() === "" ? { kind: "clear" } : {
|
|
4273
|
+
kind: "set",
|
|
4274
|
+
value: text.trim()
|
|
4275
|
+
}
|
|
4276
|
+
},
|
|
4261
4277
|
{
|
|
4262
4278
|
path: ["activeAccount"],
|
|
4263
4279
|
group: "advanced",
|
|
@@ -4328,7 +4344,8 @@ function applyOllamaTuiSettings(ctx, deps) {
|
|
|
4328
4344
|
visibleModels: deps.visibleModels(),
|
|
4329
4345
|
activeAccount: deps.activeAccount(),
|
|
4330
4346
|
stickySession: deps.stickySession(),
|
|
4331
|
-
showSidebarQuota: deps.showSidebarQuota()
|
|
4347
|
+
showSidebarQuota: deps.showSidebarQuota(),
|
|
4348
|
+
planLabel: deps.planLabel()
|
|
4332
4349
|
});
|
|
4333
4350
|
let lastSignature = signature();
|
|
4334
4351
|
const refresh = () => {
|
|
@@ -4626,7 +4643,8 @@ function apply(ctx, config) {
|
|
|
4626
4643
|
visibleModels: () => current().visibleModels,
|
|
4627
4644
|
activeAccount: () => current().activeAccount,
|
|
4628
4645
|
stickySession: () => current().stickySession,
|
|
4629
|
-
showSidebarQuota: () => current().showSidebarQuota
|
|
4646
|
+
showSidebarQuota: () => current().showSidebarQuota,
|
|
4647
|
+
planLabel: () => current().planLabel
|
|
4630
4648
|
});
|
|
4631
4649
|
});
|
|
4632
4650
|
ctx.inject(["webServer"], (webCtx) => {
|