@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 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 packed tarball into a profile:
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.0.tgz
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.0.tgz
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: { "gpt-oss:120b": 131072 }
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, default gpt-oss:120b
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
  ![Ollama Cloud settings](media/visual-verification.png)
220
240
 
package/README.ru.md CHANGED
@@ -1,76 +1,162 @@
1
1
  # 📦 @goodandready/dsh-ollama-cloud-provider
2
2
 
3
- Провайдер **Ollama Cloud** для DeepSeek Harness (dsh).
3
+ <div align="center">
4
4
 
5
- Плагин регистрирует один маршрут `ollama-cloud` (`https://ollama.com`) и два протокола:
5
+ <h3>Плагин-провайдер Ollama Cloud для DeepSeek Harness</h3>
6
6
 
7
- | Транспорт | Endpoint | Когда используется |
8
- |---|---|---|
9
- | `native` (по умолчанию) | `POST /api/chat` | thinking, вызовы инструментов, счётчики usage |
10
- | `openai-chat` | `POST /v1/chat/completions` | OpenAI-совместимые развёртывания и запасной путь |
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
- Ключ разрешается на каждый запрос, поэтому смена credential применяется без перезапуска.
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
- - Текст, reasoning/thinking, tool calls, изображения, usage и finish reasons.
18
- - Живой каталог из `GET /v1/models`, дополненный `capabilities` из `GET /api/tags`.
19
- - Точные метаданные для picker: контекст, модальности, уровни reasoning и уровень по умолчанию, поддержка инструментов, видимость в API.
20
- - Баланс и расход из официальных `/api/balance` и `/api/usage`: включённые и купленные кредиты, расход за 24 часа / 7 дней / 30 дней, расход по моделям.
21
- - Мультиаккаунт с ротацией **до первого токена** на 401/403/429 и исчерпанную квоту; cooldown'ы сохраняются между перезапусками.
22
- - Липкий аккаунт на диалог и правила `модель → аккаунт` по glob.
23
- - Оценка стоимости по published-тарифу Ollama, включая off-peak, с ручными overrides.
24
- - UI: страница **Настройки → Ollama Cloud**, карточка квоты в сайдбаре (по умолчанию выключена и не опрашивает `/state`, пока выключена), панель с расходом, аналитикой и heatmap.
25
- - Команда `/ollama`, секция TUI и обновление плагина в один клик.
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
- dsh plugin --profile web add /absolute/path/to/goodandready-dsh-ollama-cloud-provider-0.1.0.tgz
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.0.tgz
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
- transport: native # native | openai-chat
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`, доступны в настройках DSH.
143
+ Все поля, кроме `apiKey`, доступны для изменения в интерфейсе настроек DSH.
60
144
 
61
- ### Ключи
145
+ ### Получение и хранение ключей
62
146
 
63
- 1. `config.apiKey` — только в composition, `role('secret')`.
64
- 2. Credential-сервис по имени `apiKeyEnv` (по умолчанию `OLLAMA_API_KEY`).
65
- 3. Переменные окружения запуска.
147
+ 1. `config.apiKey` в файле конфигурации (`role('secret')`).
148
+ 2. Системный сервис учетных данных DSH по имени `apiKeyEnv` (по умолчанию `OLLAMA_API_KEY`).
149
+ 3. Переменные окружения процесса, `.env` проекта или `$DSH_HOME/.env`.
66
150
 
67
- Ключ создаётся на <https://ollama.com/settings/keys>, не истекает. Принимается только `Authorization: Bearer`; `x-api-key` cloud отклоняет.
151
+ Ключ создается в личном кабинете: <https://ollama.com/settings/keys>. Ключи бессрочные; облачный API принимает только заголовок `Authorization: Bearer`.
68
152
 
69
- ### Аккаунты и ротация
153
+ ### Пул аккаунтов и ротация
70
154
 
71
- Дополнительный аккаунт добавляется в **Настройки → Ollama Cloud → Аккаунты**: имя + ссылка на credential (например `OLLAMA_API_KEY_2`), затем ключ и «Сохранить». Ротация происходит до стриминга: отклонённый ключ получает cooldown, запрос повторяется следующим ключом. Cooldown'ы лежат в `~/.dsh/storages/dsh-ollama/cooldowns.json` и хранятся по SHA-256 fingerprint — сырые ключи не пишутся.
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 ставит аккаунт первым; остальные остаются fallback.
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` — totals и группы по моделям.
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
- У `/api/usage` лимит **10 запросов в минуту на пользователя**, поэтому баланс кэшируется на 60 с, usage — на 120 с, ошибки — на 30 с. Фонового опроса нет. Недоступность квот не блокирует чат.
174
+ Эндпоинт `/api/usage` ограничен лимитом в **10 запросов в минуту**, поэтому баланс кэшируется на 60 секунд, статистика расхода — на 120 секунд. Фоновых опросов нет; временная недоступность квот не прерывает работу чата.
89
175
 
90
- ## Команда
176
+ ## Страница настроек Web UI
91
177
 
92
- ```
93
- /ollama status
94
- /ollama usage
95
- /ollama balance
96
- /ollama accounts
97
- /ollama models
98
- /ollama rules
99
- /ollama test [model]
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
- ## Проверенные факты API (2026-10-21)
187
+ ## Визуальная верификация
188
+
189
+ Настройки и баланс Ollama Cloud в интерфейсе DSH Desktop (темная тема):
103
190
 
104
- - `GET https://ollama.com/v1/models` публичный и, кроме стандартного `data[]`, отдаёт нестандартный `models[]` с контекстом, модальностями, уровнями reasoning, `visibility` и `supported_in_api`.
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
+ ![Настройки Ollama Cloud](media/visual-verification.png)
110
192
 
111
- ## Разработка
193
+ ## Команды CLI
112
194
 
113
195
  ```bash
114
- npm run typecheck
115
- npm test
116
- npm run build
117
- npm run check
118
- node scripts/probe.mjs
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
- - Embeddings не поддерживаются: ни одна облачная модель не объявляет capability `embedding`.
125
- - Файловые вложения передаются текстовыми handle-заглушками, не байтами.
126
- - Стоимость — оценка по опубликованному тарифу; финальным авторитетом остаётся gateway.
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
- DeepSeek Harness(dsh)的 **Ollama Cloud** 模型提供方插件。
3
+ <div align="center">
4
4
 
5
- 插件注册一个路由 `ollama-cloud`(`https://ollama.com`),支持两种传输协议:
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
- 重启 dsh,然后在模型列表中选择 **Ollama Cloud**。
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
- transport: native # native | openai-chat
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` 外的所有字段都是 volatile,可直接在 dsh 设置中编辑。
60
-
61
- ### 密钥
142
+ 除 `apiKey` 外的所有配置项均可在 DSH 设置页直接修改。
62
143
 
63
- 1. `config.apiKey`:仅用于 composition,`role('secret')`。
64
- 2. dsh 凭据服务中的 `apiKeyEnv`(默认 `OLLAMA_API_KEY`)。
65
- 3. 启动环境变量。
144
+ ### 密钥配置
66
145
 
67
- 在 <https://ollama.com/settings/keys> 创建密钥,密钥不会过期。云端只接受 `Authorization: Bearer`,不接受 `x-api-key`。
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
- 在 **设置 → Ollama Cloud → 账号** 中添加账号:填写名称与凭据引用(如 `OLLAMA_API_KEY_2`),粘贴密钥并保存。轮换发生在流式输出之前;被拒绝的密钥进入冷却,请求由下一个可用密钥继续。冷却信息保存在 `~/.dsh/storages/dsh-ollama/cooldowns.json`,仅使用 SHA-256 指纹,不写入原始密钥。
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
- 第一个匹配的 glob 优先使用指定账号,其余账号仍作后备。
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
- `/api/usage` 限制为**每位用户每分钟 10 次请求**,因此余额缓存 60 秒、用量缓存 120 秒、失败缓存 30 秒,不做后台轮询。配额不可用不影响聊天。
162
+ DSH Desktop(暗黑主题)中的 Ollama Cloud 设置与用量面板:
89
163
 
90
- ## 命令
164
+ ![Ollama Cloud 设置](media/visual-verification.png)
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
- npm run typecheck
115
- npm test
116
- npm run build
117
- npm run check
118
- node scripts/probe.mjs
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;不注册本地 `http://localhost:11434`。
124
- - 不支持 embeddings:实时目录中没有模型声明 `embedding` 能力。
125
- - 文件附件以确定的文本句柄传递,而不是内联字节。
126
- - 费用为公开价目表的估算值,最终以网关为准。
127
- - UI 需要 web profile;TUI 保留 `/ollama` 命令与模型路由。
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)。参见 [LICENSE](./LICENSE) 与 [NOTICE](./NOTICE)。
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 ?? 34;
1009
- const radius = 14;
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 34 34",
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: "17",
1022
- cy: "17",
1025
+ cx: "10",
1026
+ cy: "10",
1023
1027
  r: radius,
1024
1028
  stroke: "var(--ocl-border-strong)",
1025
- strokeWidth: "3"
1029
+ strokeWidth: "2.5"
1026
1030
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1027
- cx: "17",
1028
- cy: "17",
1031
+ cx: "10",
1032
+ cy: "10",
1029
1033
  r: radius,
1030
1034
  stroke,
1031
- strokeWidth: "3",
1035
+ strokeWidth: "2.5",
1032
1036
  strokeLinecap: "round",
1033
1037
  strokeDasharray: `${circumference * filled} ${circumference}`,
1034
- transform: "rotate(-90 17 17)"
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 active = state?.accounts.find((account) => account.active);
1080
- const name = active === void 0 ? t("quotaCardName") : `${t("quotaCardName")} · ${active.label}`;
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.jsxs)("label", {
1591
- className: "ocl-row",
1592
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1593
- type: "checkbox",
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
- model.free ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1603
- className: "ocl-badge",
1604
- title: t("legendFree"),
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.cost.input,
1613
- "/$",
1614
- model.cost.output
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 ocl-model-transport",
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.2";
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-ollama-cloud-provider",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Ollama Cloud LLM provider plugin for DeepSeek Harness (dsh).",
5
5
  "license": "MIT",
6
6
  "type": "module",