dsh-kenari-plugin 0.1.1
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/LICENSE +21 -0
- package/README.en.md +281 -0
- package/README.md +283 -0
- package/assets/kenari-favicon-128.png +0 -0
- package/assets/kenari-favicon-256.png +0 -0
- package/client/index.js +1998 -0
- package/cordis.patch.yml +137 -0
- package/lib/billing.d.ts +120 -0
- package/lib/billing.d.ts.map +1 -0
- package/lib/billing.js +209 -0
- package/lib/billing.js.map +1 -0
- package/lib/catalog-view.d.ts +78 -0
- package/lib/catalog-view.d.ts.map +1 -0
- package/lib/catalog-view.js +113 -0
- package/lib/catalog-view.js.map +1 -0
- package/lib/catalog.d.ts +239 -0
- package/lib/catalog.d.ts.map +1 -0
- package/lib/catalog.js +445 -0
- package/lib/catalog.js.map +1 -0
- package/lib/default-route.d.ts +35 -0
- package/lib/default-route.d.ts.map +1 -0
- package/lib/default-route.js +111 -0
- package/lib/default-route.js.map +1 -0
- package/lib/errors.d.ts +29 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +74 -0
- package/lib/errors.js.map +1 -0
- package/lib/favicon.d.ts +24 -0
- package/lib/favicon.d.ts.map +1 -0
- package/lib/favicon.js +68 -0
- package/lib/favicon.js.map +1 -0
- package/lib/http.d.ts +104 -0
- package/lib/http.d.ts.map +1 -0
- package/lib/http.js +247 -0
- package/lib/http.js.map +1 -0
- package/lib/index.d.ts +83 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +285 -0
- package/lib/index.js.map +1 -0
- package/lib/llm/adapter.d.ts +78 -0
- package/lib/llm/adapter.d.ts.map +1 -0
- package/lib/llm/adapter.js +429 -0
- package/lib/llm/adapter.js.map +1 -0
- package/lib/llm/candidates.d.ts +57 -0
- package/lib/llm/candidates.d.ts.map +1 -0
- package/lib/llm/candidates.js +86 -0
- package/lib/llm/candidates.js.map +1 -0
- package/lib/llm/recovery.d.ts +48 -0
- package/lib/llm/recovery.d.ts.map +1 -0
- package/lib/llm/recovery.js +118 -0
- package/lib/llm/recovery.js.map +1 -0
- package/lib/llm/retry.d.ts +35 -0
- package/lib/llm/retry.d.ts.map +1 -0
- package/lib/llm/retry.js +53 -0
- package/lib/llm/retry.js.map +1 -0
- package/lib/plans.d.ts +59 -0
- package/lib/plans.d.ts.map +1 -0
- package/lib/plans.js +118 -0
- package/lib/plans.js.map +1 -0
- package/lib/session-title.d.ts +63 -0
- package/lib/session-title.d.ts.map +1 -0
- package/lib/session-title.js +280 -0
- package/lib/session-title.js.map +1 -0
- package/lib/settings.d.ts +35 -0
- package/lib/settings.d.ts.map +1 -0
- package/lib/settings.js +78 -0
- package/lib/settings.js.map +1 -0
- package/lib/tools/account.d.ts +11 -0
- package/lib/tools/account.d.ts.map +1 -0
- package/lib/tools/account.js +132 -0
- package/lib/tools/account.js.map +1 -0
- package/lib/tools/billing.d.ts +10 -0
- package/lib/tools/billing.d.ts.map +1 -0
- package/lib/tools/billing.js +100 -0
- package/lib/tools/billing.js.map +1 -0
- package/lib/tools/count-tokens.d.ts +11 -0
- package/lib/tools/count-tokens.d.ts.map +1 -0
- package/lib/tools/count-tokens.js +84 -0
- package/lib/tools/count-tokens.js.map +1 -0
- package/lib/tools/data.d.ts +12 -0
- package/lib/tools/data.d.ts.map +1 -0
- package/lib/tools/data.js +205 -0
- package/lib/tools/data.js.map +1 -0
- package/lib/tools/docs.d.ts +13 -0
- package/lib/tools/docs.d.ts.map +1 -0
- package/lib/tools/docs.js +209 -0
- package/lib/tools/docs.js.map +1 -0
- package/lib/tools/documents.d.ts +10 -0
- package/lib/tools/documents.d.ts.map +1 -0
- package/lib/tools/documents.js +118 -0
- package/lib/tools/documents.js.map +1 -0
- package/lib/tools/media.d.ts +28 -0
- package/lib/tools/media.d.ts.map +1 -0
- package/lib/tools/media.js +599 -0
- package/lib/tools/media.js.map +1 -0
- package/lib/tools/shared.d.ts +89 -0
- package/lib/tools/shared.d.ts.map +1 -0
- package/lib/tools/shared.js +128 -0
- package/lib/tools/shared.js.map +1 -0
- package/lib/tools/x-search.d.ts +11 -0
- package/lib/tools/x-search.d.ts.map +1 -0
- package/lib/tools/x-search.js +129 -0
- package/lib/tools/x-search.js.map +1 -0
- package/lib/web/fallback.d.ts +39 -0
- package/lib/web/fallback.d.ts.map +1 -0
- package/lib/web/fallback.js +79 -0
- package/lib/web/fallback.js.map +1 -0
- package/lib/web/fetch.d.ts +19 -0
- package/lib/web/fetch.d.ts.map +1 -0
- package/lib/web/fetch.js +41 -0
- package/lib/web/fetch.js.map +1 -0
- package/lib/web/search.d.ts +21 -0
- package/lib/web/search.d.ts.map +1 -0
- package/lib/web/search.js +47 -0
- package/lib/web/search.js.map +1 -0
- package/package.json +84 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ryan-yuan-dev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# dsh-kenari-plugin
|
|
2
|
+
|
|
3
|
+
English | [中文](README.md)
|
|
4
|
+
|
|
5
|
+
Connects [Kenari](https://kenari.id) to the DeepSeek Harness (dsh):
|
|
6
|
+
|
|
7
|
+
- Kenari models work as dsh session models, and the default route follows the plan attached to your key
|
|
8
|
+
- Web search and fetch go to Kenari first, falling back to the dsh providers on failure
|
|
9
|
+
- 21 REST capabilities registered as agent tools: catalog, account, search, OCR, images, audio, video, embeddings, rerank, moderation, billing, token counting
|
|
10
|
+
- New session titles carry a local-time prefix, with a template you can change or turn off
|
|
11
|
+
- Settings → Kenari holds everything in one page: credential status, runtime parameters, model catalog
|
|
12
|
+
|
|
13
|
+
Every capability registers through dsh's bundle patch layer and its public seams (`ctx.web`, `ctx.settings`, `ctx.tools`). Nothing in dsh is modified.
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
You need dsh `0.1.5-rc.1` (installed globally, `dsh` on PATH), Node.js >= 22, and pnpm (`dsh plugin` forwards to it). dsh subpackages must match the main version, so pin the version when installing one; npm's `latest` is not necessarily the right one:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
dsh plugin --profile web add @deepseek-ai/dsh-web-fetch-http@0.1.5-rc.1
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
dsh plugin --profile web add dsh-kenari-plugin
|
|
27
|
+
dsh --profile web --dump-config | grep -A3 dsh-kenari-plugin # confirm the patch layer applied
|
|
28
|
+
dsh --profile web
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
To work on the plugin source, install from a clone instead: `pnpm install && pnpm build`, then `dsh plugin --profile web add "$PWD"`. The profile links to that directory, so after installing you only rerun `pnpm build` for source changes.
|
|
32
|
+
|
|
33
|
+
## Set up your key
|
|
34
|
+
|
|
35
|
+
In the Kenari dashboard, go to API keys → Create key and copy the `kn-...` value (shown once). Then either:
|
|
36
|
+
|
|
37
|
+
- Open **Settings → Models → Kenari** and paste it into the password field
|
|
38
|
+
- Or write a `.env` file, which the credential layer can write:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
# ~/.dsh/.env
|
|
42
|
+
KENARI_API_KEY=kn-...
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Do not put it in `cordis.patch.yml`. The settings document stores the reference name only, and the value never reaches logs, the UI, or config.
|
|
46
|
+
|
|
47
|
+
## Session models
|
|
48
|
+
|
|
49
|
+
Installing the plugin adds a Kenari route to Settings → Models.
|
|
50
|
+
|
|
51
|
+
### Default route
|
|
52
|
+
|
|
53
|
+
The default models come from the plan attached to your key. On load, the plugin reads `GET /v1/account/quota` for the plan name, takes that plan's `free_cache_models` (models whose cache reads do not count against the plan), and writes them into the route. It writes once, then the layer is yours.
|
|
54
|
+
|
|
55
|
+
When the plan cannot be read (no key, a share-page key that gets 403, an account without a plan), this fallback preset stands. It happens to be the free-cache list of the Kreator and Studio plans:
|
|
56
|
+
|
|
57
|
+
| Model id | Display name | Context | Max output | Notes |
|
|
58
|
+
| --- | --- | --- | --- | --- |
|
|
59
|
+
| `deepseek-v4-flash` | DeepSeek v4 Flash | 1M | 64K | reasoning low/high/max |
|
|
60
|
+
| `glm-5-3-flash` | GLM 5.3 Flash | 1M | 64K | vision, reasoning low/high/max |
|
|
61
|
+
| `gpt-5-6-luna` | GPT 5.6 Luna | 872k | 64K | vision + PDF, 6 reasoning levels |
|
|
62
|
+
| `mimo-v2-5` | MiMo v2.5 | 1.05M | 64K | vision + audio + video |
|
|
63
|
+
|
|
64
|
+
None of the four is free, so **a new account with no balance hits 402 on its first session**. Add a `:free` model or top up first. `:free` models stay out of the route by default; filter by "free" in the picker to add them.
|
|
65
|
+
|
|
66
|
+
Display names and output caps are derived by the plugin, since the catalog publishes neither. A name is reconstructed from the id, and the output cap is a quarter of the context window, clamped to [1K, 64K]; models without a published window get no cap field. The name is presentation only.
|
|
67
|
+
|
|
68
|
+
Once you edit the list yourself, your config replaces the preset as a whole (pi-ai's semantics) and the plugin stops touching it.
|
|
69
|
+
|
|
70
|
+
### Picking models by capability and plan
|
|
71
|
+
|
|
72
|
+
Under **Settings → Models → Kenari → Edit → Model catalog**, "Add model" and "Get available models" are the same entry point, and either opens this picker. It uses dsh's own dialog components, with extra filter dimensions:
|
|
73
|
+
|
|
74
|
+
- Each row shows the model id and capability tags (`image` `audio` `video` `pdf` `embedding`). Paid models also carry an "in plan" tag meaning a subscription plan covers them; free models are recognisable by the `:free` suffix
|
|
75
|
+
- Filters are `in plan`, `free`, and the capability tags. "In plan" and "free" are mutually exclusive, and `embedding` is exclusive on its own (it has no chat endpoint, so mixing it with anything returns an empty list). The rest combine
|
|
76
|
+
- "Add selected" **appends** to the current entries, and rows already in the route cannot be selected
|
|
77
|
+
|
|
78
|
+
The write is immediate with no save step, and the list inside that card refreshes only after you collapse and reopen Edit. The Host computes the data and serves it at `GET /api/kenari.models`, the same facts as `kenari_list_models`, and no key is needed.
|
|
79
|
+
|
|
80
|
+
### Three protocol lines and the compat preset
|
|
81
|
+
|
|
82
|
+
Kenari serves three lines with different base URL shapes. A wrong shape answers 405, not 404:
|
|
83
|
+
|
|
84
|
+
| pi-ai `api` | Base URL |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `openai-completions` | `https://kenari.id/v1` |
|
|
87
|
+
| `openai-responses` | `https://kenari.id/v1` |
|
|
88
|
+
| `anthropic-messages` | `https://kenari.id` (no `/v1`) |
|
|
89
|
+
|
|
90
|
+
Copy these four compat settings when you build your own route. They live in `cordis.patch.yml`:
|
|
91
|
+
|
|
92
|
+
- `maxTokensField: max_tokens`. Kenari reads `max_tokens` only; sending `max_completion_tokens` returns 200 and silently ignores it
|
|
93
|
+
- `supportsDeveloperRole: false`, so system prompts keep the `system` role
|
|
94
|
+
- `supportsUsageInStreaming: true`, since the last streaming frame carries the `usage` that `cached_tokens` accounting relies on
|
|
95
|
+
- `reasoningEfforts` keys must come from `off|minimal|low|medium|high|xhigh|max`. Kenari's `none` maps to `off`. A wrong key stops dsh from starting, so run `--dump-config` after editing
|
|
96
|
+
|
|
97
|
+
## Web search and fetch
|
|
98
|
+
|
|
99
|
+
Both `searchProvider` and `fetchProvider` are pinned to `kenari-fallback`: Kenari first, then `deepseek-official` and the anonymous local `http` on failure (401/402/403 and network errors included). The log records direction, reason, and elapsed time.
|
|
100
|
+
|
|
101
|
+
The `available()` contract forbids network calls, so a Kenari outage only surfaces when a call fails and the user pays one extra round trip. To drop this layer, set `fallbackEnabled` to `false` (restart required).
|
|
102
|
+
|
|
103
|
+
## Automatic recovery on model failure
|
|
104
|
+
|
|
105
|
+
A failed session model call on a Kenari route recovers on its own:
|
|
106
|
+
|
|
107
|
+
1. Retry 5 times at a fixed 5 second interval. dsh's own `dsh-llm-retry` does this and leaves an `llm/retry` record
|
|
108
|
+
2. When retries run out, switch to a model with at least as large a window, preferring the smallest adequate one in the same provider before crossing providers
|
|
109
|
+
3. If that also fails, fall back to dsh's default provider and model
|
|
110
|
+
|
|
111
|
+
Only when all three fail does the turn error out. Retry and switching happen inside the same step, so the task is never interrupted and the plugin sends no "continue" message. A switched model does not inherit `reasoningEffort` (levels are per model); the switch injects a notice you can disable with `modelSwitchNoticeEnabled`, and picking a model yourself with `/model` clears the automatic switch.
|
|
112
|
+
|
|
113
|
+
`CONTEXT_WINDOW_EXCEEDED` is deliberately not retryable. Retrying the same context on the same model can only fail again, so it goes straight to the model switch, which is often where recovery works.
|
|
114
|
+
|
|
115
|
+
Two limits:
|
|
116
|
+
|
|
117
|
+
- **Retry timing on the `llm-pi-ai` route comes from `providers.kenari.retryPolicy` in `cordis.patch.yml`, not from the plugin config.** dsh freezes a route's retry policy when the adapter registers, and the plugin cannot change it. The defaults match the plugin config, so change both together.
|
|
118
|
+
- **A `TRANSPORT` failure may have completed and been billed on the server**, so retrying can bill twice. Narrow `modelRetryableCodes` if that matters. This is separate from the no-retry-on-timeout rule for generation endpoints.
|
|
119
|
+
|
|
120
|
+
Field defaults are in "Configuration reference".
|
|
121
|
+
|
|
122
|
+
## Session title prefix
|
|
123
|
+
|
|
124
|
+
New session titles carry a prefix like `20260911174258-`. The timestamp is the time of the session's first human message, in local time. The plugin writes it into the `session/title` event, so Web, TUI, and headless render the same string.
|
|
125
|
+
|
|
126
|
+
The timestamp does not come from `session.header.createdAt` (that records session creation, which dsh may have done hours earlier when it reused a blank session). It is the fallback for a session with no human message at all.
|
|
127
|
+
|
|
128
|
+
The prefix is part of the title text, not a separate display layer, so the setting controls whether one is written at generation time: with it off, new titles have no prefix, and a title written while it was off contains no prefix in its text. Existing sessions are never rewritten, by the switch or the template.
|
|
129
|
+
|
|
130
|
+
Every source gets the same treatment: LLM titles, the deterministic fallback, and manual renames. When a fork inherits its parent's title, the prefix becomes the child's own start time, ignoring the inherited history.
|
|
131
|
+
|
|
132
|
+
Three things to keep in mind:
|
|
133
|
+
|
|
134
|
+
- `sessionTitleMaxBytes` must match `maxTitleBytes` on the `session-title` row in `cordis.patch.yml`. The plugin writes title events directly and dsh will not truncate them
|
|
135
|
+
- The template has no escaping, so substrings like `yyyy` and `MM` are always replaced, and stripping an old prefix replays the template against the session's start time. A template changed between parent and fork, or a parent no longer in the store, can leave a double prefix
|
|
136
|
+
- A prefix already stored in the sidebar is a cached value; restart dsh and open that session and it corrects itself
|
|
137
|
+
|
|
138
|
+
Field defaults are in "Configuration reference".
|
|
139
|
+
|
|
140
|
+
## Billing and budget
|
|
141
|
+
|
|
142
|
+
Each tool call returns a cost line: `cost_micro_idr` when the response carries it, otherwise an estimate from the catalog's `pricing_lines` multiplied by quantity for images, video, and speech. Non-token units round up to whole units (a 21-character TTS call counts as one 1k-character unit).
|
|
143
|
+
|
|
144
|
+
`kenari_billing` reports per-session totals, a breakdown by tool and model, `cached_tokens` hit rate, remaining budget, and wallet balance; below `lowBalanceAlertRp` the cost line gains a warning. `budgetCapRp` defaults to 0, meaning no cap, and spending tools are refused once it is reached. The pre-check only sees recorded spending, so it stops new billed calls rather than guaranteeing a total. 402, 401, 405, and 403 all carry actionable guidance.
|
|
145
|
+
|
|
146
|
+
## Settings page
|
|
147
|
+
|
|
148
|
+
Settings → **Kenari** (namespace `kenari`). The groups follow the order you use them:
|
|
149
|
+
|
|
150
|
+
| Group | Contents |
|
|
151
|
+
| --- | --- |
|
|
152
|
+
| Key | Status and names only (configured or missing, source, writability, a read-only reference name). No input field |
|
|
153
|
+
| Models | Models the current route can use: id, display name, context. Only the first 3 show by default |
|
|
154
|
+
| Session titles | Switch, prefix format, byte ceiling |
|
|
155
|
+
| Advanced (collapsed) | API base URL, timeouts, retry counts, cache lifetimes, low-balance threshold |
|
|
156
|
+
| Restart required (collapsed) | Switches fixed at load time (whether to register search/fetch/tools, budget cap, the built-in adapter). Read-only; edit the matching settings section and restart |
|
|
157
|
+
| Balance and usage | Numbers that move with usage. No stored snapshot, just a question you can ask as-is |
|
|
158
|
+
|
|
159
|
+
Copy exists in Chinese and English and follows dsh's language setting; `pnpm build` asserts the two key sets match.
|
|
160
|
+
|
|
161
|
+
## Tools
|
|
162
|
+
|
|
163
|
+
| Category | Tools |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| Catalog and docs | `kenari_list_models` (free grouping, pricing, context, sunset and beta warnings; no key needed), `kenari_search_docs` (also no key) |
|
|
166
|
+
| Account | `kenari_balance`, `kenari_usage`, `kenari_quota` (share-page keys get 403) |
|
|
167
|
+
| Search | `kenari_x_search` |
|
|
168
|
+
| Documents | `kenari_ocr` (`reuse_id` reuses free) |
|
|
169
|
+
| Media | `kenari_image_generate`, `kenari_image_edit`, `kenari_speech`, `kenari_transcribe`, `kenari_music`, `kenari_video_generate`, `kenari_video_extend`, `kenari_video_status`, `kenari_video_content` |
|
|
170
|
+
| Data | `kenari_embed`, `kenari_rerank`, `kenari_moderate` |
|
|
171
|
+
| Context and billing | `kenari_count_tokens`, `kenari_billing` |
|
|
172
|
+
|
|
173
|
+
Images, audio, and video land on disk through `ctx.attachments` as image or file blocks rather than going into JSON.
|
|
174
|
+
|
|
175
|
+
The public catalog has no music or moderation model, so `kenari_music` and `kenari_moderate` only return 400. `kenari_speech` works with `mimo-v2-5-tts`, while `kokoro-tts` and `gemini-3-1-flash-tts` return an upstream 400.
|
|
176
|
+
|
|
177
|
+
## Optional: the plugin's own LlmAdapter
|
|
178
|
+
|
|
179
|
+
The `llm-pi-ai` preset already runs Kenari as a session model. Setting `nativeAdapterEnabled` to `true` (restart required) adds pricing in the model picker, token accounting and `cached_tokens` hit rate in `kenari_billing`, and reasoning levels exposed exactly as the catalog reports them, `none` included.
|
|
180
|
+
|
|
181
|
+
Its route is named `kenari-direct` by default, distinct from the preset's `kenari`, so both coexist and turning the switch off reverts. Compared with the preset it does not replay thinking blocks, inject `file-parser` (file blocks project to explanatory text, so use `kenari_ocr` to read documents), or map `web_search_options`; image input works.
|
|
182
|
+
|
|
183
|
+
```yaml
|
|
184
|
+
- id: kenari
|
|
185
|
+
config:
|
|
186
|
+
nativeAdapterEnabled: true
|
|
187
|
+
nativeProviderId: kenari-direct
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Troubleshooting
|
|
191
|
+
|
|
192
|
+
| Symptom | Cause and fix |
|
|
193
|
+
| --- | --- |
|
|
194
|
+
| 401 `invalid api key` | The key is dead; dsh was not restarted after a `.env` edit; the credential reference name is wrong |
|
|
195
|
+
| 402 `insufficient_balance` | A paid model with a zero balance. Switch to a `:free` model or top up |
|
|
196
|
+
| 405 | Wrong base URL shape, see "Three protocol lines" |
|
|
197
|
+
| Startup fails with `invalid config ... reasoningEfforts` | That model's `reasoningEfforts` key is outside `off..max`. Revert it or drop the model from the preset |
|
|
198
|
+
| 403 on balance, usage, or quota | The key came from a share page, and those tools read the key owner's account data |
|
|
199
|
+
| The Kenari card in settings is blank | The client bundle was not built, or the service name is wrong. Run `pnpm build` and restart |
|
|
200
|
+
| The nav row still shows a gear | The image did not load. Check that the package contains `assets/kenari-favicon-128.png` and that `GET /api/kenari.favicon` responds |
|
|
201
|
+
| The model list is empty | The preset did not apply. Check `llm-pi-ai` in `dsh --profile web --dump-config` |
|
|
202
|
+
|
|
203
|
+
## Optional: MCP without the plugin
|
|
204
|
+
|
|
205
|
+
Kenari ships a Streamable HTTP MCP server with 8 tools, named with the `mcp__kenari__` prefix:
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{ "mcpServers": { "kenari": { "url": "https://kenari.id/mcp", "headers": { "Authorization": "Bearer kn-..." } } } }
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The trade is prefixed tool names, no cost visibility or balance warnings, and one more runtime dependency. MCP is for a quick trial.
|
|
212
|
+
|
|
213
|
+
## Uninstall
|
|
214
|
+
|
|
215
|
+
```sh
|
|
216
|
+
dsh plugin --profile web remove dsh-kenari-plugin
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Everything the plugin registers hangs off a Cordis fiber and is reclaimed on unload; no dsh package or config was changed.
|
|
220
|
+
|
|
221
|
+
## Configuration reference
|
|
222
|
+
|
|
223
|
+
Most fields can be edited under Settings → Kenari. The ones marked (restart) belong in the matching settings section followed by a restart.
|
|
224
|
+
|
|
225
|
+
| Field | Default | Notes |
|
|
226
|
+
| --- | --- | --- |
|
|
227
|
+
| `apiKeyEnv` | `KENARI_API_KEY` | Credential reference holding the key |
|
|
228
|
+
| `baseURL` | `https://kenari.id` | Kenari REST base URL used by the plugin |
|
|
229
|
+
| `timeoutMs` | `30000` | Per-request REST timeout |
|
|
230
|
+
| `generationTimeoutMs` | `180000` | Per-attempt timeout for generation calls (image/audio/music/OCR) |
|
|
231
|
+
| `maxRetries` | `2` | REST retries for transient failures |
|
|
232
|
+
| `searchEnabled` | `true` | Register the search provider (restart) |
|
|
233
|
+
| `fetchEnabled` | `true` | Register the fetch provider (restart) |
|
|
234
|
+
| `fallbackEnabled` | `true` | Fall back to the dsh defaults when Kenari fails (restart) |
|
|
235
|
+
| `toolsEnabled` | `true` | Register the 21 tools (restart) |
|
|
236
|
+
| `docsCacheTtlMs` | `3600000` | Cache lifetime for `/llms-full.txt` |
|
|
237
|
+
| `catalogCacheTtlMs` | `3600000` | Cache lifetime for `/v1/models` |
|
|
238
|
+
| `modelAliases` | `{}` | Model aliases as `{ alias: 'exact-model-id' }` |
|
|
239
|
+
| `lowBalanceAlertRp` | `5000` | Warn below this many Rupiah, 0 disables |
|
|
240
|
+
| `balanceCacheTtlMs` | `300000` | Cache lifetime for the wallet balance |
|
|
241
|
+
| `budgetCapRp` | `0` | Per-session spend ceiling, 0 means no cap |
|
|
242
|
+
| `nativeAdapterEnabled` | `false` | Register the plugin's own LlmAdapter (restart) |
|
|
243
|
+
| `nativeProviderId` | `kenari-direct` | Route id for that adapter |
|
|
244
|
+
| `modelRecoveryEnabled` | `true` | Master switch for automatic recovery |
|
|
245
|
+
| `modelRecoveryProviders` | `['kenari','kenari-direct']` | Routes covered by recovery |
|
|
246
|
+
| `modelRetryMaxRetries` | `5` | Extra retries (`kenari-direct`) |
|
|
247
|
+
| `modelRetryDelayMs` | `5000` | Fixed wait before each retry |
|
|
248
|
+
| `modelRetryableCodes` | `EMPTY_RESPONSE` `RATE_LIMIT` `SERVER` `TIMEOUT` `TRANSPORT` | Retryable failure codes, must not be empty |
|
|
249
|
+
| `modelSwitchEnabled` | `true` | Whether to switch models |
|
|
250
|
+
| `modelSwitchDelayMs` | `5000` | Wait before a switch or fallback |
|
|
251
|
+
| `modelSwitchSkipCodes` | `AUTH` `INVALID_CREDENTIAL` `MISSING_CREDENTIAL` `QUOTA` | Codes that skip straight to the provider fallback |
|
|
252
|
+
| `modelSwitchNoticeEnabled` | `true` | Inject a notice on switch |
|
|
253
|
+
| `sessionTitlePrefixEnabled` | `true` | Session title prefix switch |
|
|
254
|
+
| `sessionTitlePrefix` | `yyyyMMddHHmmss-` | Prefix template |
|
|
255
|
+
| `sessionTitleMaxBytes` | `96` | Byte ceiling for prefix plus body |
|
|
256
|
+
|
|
257
|
+
## Development
|
|
258
|
+
|
|
259
|
+
```sh
|
|
260
|
+
pnpm build # tsc to lib/, plus the client bundle self-check
|
|
261
|
+
node test/real-harness.mjs # real dsh harness (needs a real key)
|
|
262
|
+
node test/llm-adapter.mjs # local fake gateway, no network and no spend
|
|
263
|
+
# offline suites: session-title / catalog-view / default-route / favicon, one .mjs each
|
|
264
|
+
node test/billed-media.mjs # spends real money: TTS + STT, about Rp 500
|
|
265
|
+
KENARI_ALLOW_VIDEO=1 node test/billed-video.mjs # spends real money: 4s video, about Rp 1,400
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
`scripts/check-client.mjs` runs as part of `pnpm build`. It checks the client bundle's registration format, export surface, dependency declarations, and namespace, and renders the settings card for real (the bundle is hand-written with no bundler, so these checks are its compile step). Design and measurement notes live under `docs/`, written for people changing this plugin.
|
|
269
|
+
|
|
270
|
+
## How this was built
|
|
271
|
+
|
|
272
|
+
The plugin was written in **ZCode**, with session models from opencode's DeepSeek V4.1 Flash and Kenari's `deepseek-v4-flash`. Building it meant reading dsh's source repeatedly and editing across files, so both were picked for holding a long context.
|
|
273
|
+
|
|
274
|
+
If you want a similar setup, these are the two I actually use:
|
|
275
|
+
|
|
276
|
+
- **[Kenari](https://kenari.id/code/KNR-KKRNAJ)**: one key reaches models from several vendors, more than 70 in the catalog, from DeepSeek, GLM and GPT through speech, image, video, OCR, embeddings and rerank. It speaks OpenAI-compatible, Anthropic-compatible and Responses, so an existing client needs only a new base URL. Billing comes both per use and as a monthly plan, in Rupiah, with a low top-up minimum (QRIS from Rp 1,000). The catalog and docs endpoints are public, so you can see what you get before paying.
|
|
277
|
+
- **[opencode Go](https://opencode.ai/go?ref=343F5JW4RA)**: $10 a month with quota per 5 hour window instead of per-token billing, and you can top up credit when the quota runs short. It works with any agent, which is why it runs the dsh session model here.
|
|
278
|
+
|
|
279
|
+
## License
|
|
280
|
+
|
|
281
|
+
MIT. See [LICENSE](LICENSE).
|
package/README.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# dsh-kenari-plugin
|
|
2
|
+
|
|
3
|
+
[English](README.en.md) | 中文
|
|
4
|
+
|
|
5
|
+
把 [Kenari](https://kenari.id) 接进 DeepSeek Harness(dsh):
|
|
6
|
+
|
|
7
|
+
- Kenari 的模型可以当 dsh 会话模型,默认路由按 key 的套餐自动决定
|
|
8
|
+
- 搜索与抓取优先走 Kenari,失败回退 dsh 自带的 provider
|
|
9
|
+
- 21 个 REST 能力注册成 agent 工具:目录、账户、搜索、OCR、图像、音频、视频、嵌入、重排、审核、计费、token 计数
|
|
10
|
+
- 新会话标题带本机时间前缀,模板可改、可关
|
|
11
|
+
- Settings → Kenari 一页管完:凭据状态、运行参数、模型目录
|
|
12
|
+
|
|
13
|
+
所有能力通过 dsh 的 bundle patch 层与公开 seam(`ctx.web`、`ctx.settings`、`ctx.tools`)注册,没有改动 dsh 的任何代码或配置。
|
|
14
|
+
|
|
15
|
+
## 前提
|
|
16
|
+
|
|
17
|
+
需要 dsh `0.1.5-rc.1`(全局安装,`dsh` 在 PATH)、Node.js >= 22,以及 pnpm(`dsh plugin` 底层转发给它)。dsh 子包必须与主版本对齐,装的时候显式带版本号,npm 的 `latest` 未必对得上:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
dsh plugin --profile web add @deepseek-ai/dsh-web-fetch-http@0.1.5-rc.1
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
dsh plugin --profile web add dsh-kenari-plugin
|
|
27
|
+
dsh --profile web --dump-config | grep -A3 dsh-kenari-plugin # 确认 patch 层生效
|
|
28
|
+
dsh --profile web
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
改插件源码时换成 clone 安装:`pnpm install && pnpm build`,再 `dsh plugin --profile web add "$PWD"`。profile 里是 pnpm link,之后改源码只需重新 `pnpm build`。
|
|
32
|
+
|
|
33
|
+
## 配置密钥
|
|
34
|
+
|
|
35
|
+
在 Kenari 面板的 API keys → Create key 生成 `kn-...`(只显示一次),然后二选一:
|
|
36
|
+
|
|
37
|
+
- 打开 **Settings → 模型 → Kenari 卡片**,在密码框里填入
|
|
38
|
+
- 写 `.env`(凭据层可写):
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
# ~/.dsh/.env
|
|
42
|
+
KENARI_API_KEY=kn-...
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
不要写进 `cordis.patch.yml`。设置文档只存引用名,值不进日志、界面与配置。
|
|
46
|
+
|
|
47
|
+
## 会话模型
|
|
48
|
+
|
|
49
|
+
装上后 Settings → Models 里会多出 Kenari 路由。
|
|
50
|
+
|
|
51
|
+
### 默认路由
|
|
52
|
+
|
|
53
|
+
默认模型由 key 的套餐决定。插件加载时读 `GET /v1/account/quota` 拿套餐名,取该套餐的 `free_cache_models`(缓存读取不占套餐额度)写进路由。只写一次,之后这层归你。
|
|
54
|
+
|
|
55
|
+
读不到套餐时(没配 key、分享页 key 被 403、账户没有套餐)用内置兜底清单,它正好是 Kreator 与 Studio 两档的免缓存清单:
|
|
56
|
+
|
|
57
|
+
| 模型 id | 显示名称 | 上下文 | 最大输出 | 备注 |
|
|
58
|
+
| --- | --- | --- | --- | --- |
|
|
59
|
+
| `deepseek-v4-flash` | DeepSeek v4 Flash | 1M | 64K | reasoning low/high/max |
|
|
60
|
+
| `glm-5-3-flash` | GLM 5.3 Flash | 1M | 64K | 视觉,reasoning low/high/max |
|
|
61
|
+
| `gpt-5-6-luna` | GPT 5.6 Luna | 872k | 64K | 视觉 + PDF,6 档 reasoning |
|
|
62
|
+
| `mimo-v2-5` | MiMo v2.5 | 1.05M | 64K | 视觉 + 音频 + 视频 |
|
|
63
|
+
|
|
64
|
+
四个都不是免费模型,**没有余额的新账户第一次会话会拿到 402**,先加一个 `:free` 模型或充值。`:free` 模型默认不进路由,用挑选器里的「免费」筛一下再加。
|
|
65
|
+
|
|
66
|
+
显示名称与最大输出由插件推导(目录里都没有):名称还原自 id,最大输出取窗口的 1/4 并夹在 [1K, 64K],不公布窗口的模型不写这个字段。名称只用于展示。
|
|
67
|
+
|
|
68
|
+
你手动改过列表之后,用户配置整份覆盖预设(pi-ai 的语义),插件不再改。
|
|
69
|
+
|
|
70
|
+
### 按能力和套餐挑模型
|
|
71
|
+
|
|
72
|
+
**Settings → 模型 → Kenari → 编辑 → 模型目录**里,「添加模型」和「获取可用模型」是同一个入口,都开这台挑选器。它用 dsh 自己的对话框组件,只多了筛选维度:
|
|
73
|
+
|
|
74
|
+
- 每行显示模型 id 与能力标签(`image` `audio` `video` `pdf` `embedding`),付费模型另带「套餐内」标签,表示被某个订阅套餐覆盖。免费模型看 id 的 `:free` 后缀
|
|
75
|
+
- 过滤片有 `套餐内` `免费` 与各能力标签。「套餐内」与「免费」互斥,`embedding` 独占(它没有 chat 端点,混选只会得到空列表),其余可多选
|
|
76
|
+
- 「添加所选」**追加**到现有条目之后,已在路由里的行不能再勾
|
|
77
|
+
|
|
78
|
+
写入是即时的,没有保存步骤;同一张卡里的列表要收起再展开才会刷新。数据由 Host 侧算好(`GET /api/kenari.models`),与 `kenari_list_models` 同源,没有 key 也能看。
|
|
79
|
+
|
|
80
|
+
### 三条协议线与兼容预设
|
|
81
|
+
|
|
82
|
+
Kenari 有三条线,base URL 形状不同,写错返回 405 而不是 404:
|
|
83
|
+
|
|
84
|
+
| pi-ai 的 `api` | base URL |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `openai-completions` | `https://kenari.id/v1` |
|
|
87
|
+
| `openai-responses` | `https://kenari.id/v1` |
|
|
88
|
+
| `anthropic-messages` | `https://kenari.id`(不带 `/v1`) |
|
|
89
|
+
|
|
90
|
+
自建路由时照抄这四条兼容设置(已写在 `cordis.patch.yml`):
|
|
91
|
+
|
|
92
|
+
- `maxTokensField: max_tokens`。Kenari 只读 `max_tokens`,发 `max_completion_tokens` 会返回 200 但静默忽略
|
|
93
|
+
- `supportsDeveloperRole: false`,系统提示词继续用 `system` 角色
|
|
94
|
+
- `supportsUsageInStreaming: true`,流式最后一帧带 `usage`,`cached_tokens` 计量靠它
|
|
95
|
+
- `reasoningEfforts` 的键只能用 `off|minimal|low|medium|high|xhigh|max`,Kenari 的 `none` 映射成 `off`。键写错会让 dsh 启动失败,改完先 `--dump-config`
|
|
96
|
+
|
|
97
|
+
## web 搜索与抓取
|
|
98
|
+
|
|
99
|
+
`searchProvider` 与 `fetchProvider` 都钉到 `kenari-fallback`:先打 Kenari,失败(含 401/402/403 与网络错误)回退 `deepseek-official` 和本地匿名 `http`,日志给出方向、原因与耗时。
|
|
100
|
+
|
|
101
|
+
`available()` 的契约禁止发网络请求,所以 Kenari 不可用只能等调用失败才发现,用户会多等一次。不想要这层替换,把 `fallbackEnabled` 设为 `false`(需重启)。
|
|
102
|
+
|
|
103
|
+
## 模型调用失败自动恢复
|
|
104
|
+
|
|
105
|
+
Kenari 路由上的会话模型调用失败后自动恢复:
|
|
106
|
+
|
|
107
|
+
1. 按固定 5 秒间隔重试 5 次,由 dsh 自带的 `dsh-llm-retry` 执行,会话里留 `llm/retry` 记录
|
|
108
|
+
2. 用尽后换一个窗口不小于当前模型的模型,先在同一个 provider 内挑最小的够用者,没有候选再跨 provider
|
|
109
|
+
3. 仍失败则回退 dsh 的默认 provider 与模型
|
|
110
|
+
|
|
111
|
+
三步都失败这一轮才报错。重试与换模型都在同一个 step 内,任务不中断,插件不会再发「继续」消息。换模型不继承 `reasoningEffort`(档位按模型定义),换完注入一条说明,可用 `modelSwitchNoticeEnabled` 关掉;手动 `/model` 会清除自动切换。
|
|
112
|
+
|
|
113
|
+
`CONTEXT_WINDOW_EXCEEDED` 刻意不在可重试集里:同模型同上下文重试必然再失败,直接进换模型阶段,而这一步往往正是恢复生效的地方。
|
|
114
|
+
|
|
115
|
+
两处限制:
|
|
116
|
+
|
|
117
|
+
- **`llm-pi-ai` 路由的重试参数取自 `cordis.patch.yml` 的 `providers.kenari.retryPolicy`,不是插件配置。** dsh 在适配器注册时就冻结了路由的重试策略,插件改不了。默认值与插件配置一致,改一个记得改另一个。
|
|
118
|
+
- **`TRANSPORT` 类失败在服务端可能已经完成并计费**,重试会重复扣费。介意就把 `modelRetryableCodes` 调小。这与生成类端点的「超时不重试」是两回事。
|
|
119
|
+
|
|
120
|
+
字段与默认值见「配置项」。
|
|
121
|
+
|
|
122
|
+
## 会话标题前缀
|
|
123
|
+
|
|
124
|
+
新会话标题默认带 `20260911174258-` 形式的前缀,时间取本会话第一条人类消息的时间(本机时区)。前缀由插件写进 `session/title` 事件,Web、TUI、headless 看到同一个字符串。
|
|
125
|
+
|
|
126
|
+
时间不用 `session.header.createdAt`(它记的是会话记录创建时间,dsh 复用空白会话时可能比真正开口早几个小时),完全没有人类消息时才退回它。
|
|
127
|
+
|
|
128
|
+
前缀是标题文本的一部分,不是独立的显示层。所以开关控制的是生成时写不写:关掉后新标题不带前缀,关闭期间落盘的标题文本本身就不含前缀;已有会话不会被回溯修改,改开关或模板都不会。
|
|
129
|
+
|
|
130
|
+
LLM 标题、确定性 fallback、手动 rename 一视同仁。fork 继承父标题时,前缀换成子会话自己的开始时间(父会话历史不计)。
|
|
131
|
+
|
|
132
|
+
三点注意:
|
|
133
|
+
|
|
134
|
+
- `sessionTitleMaxBytes` 必须与 `cordis.patch.yml` 里 `session-title` 行的 `maxTitleBytes` 一致。插件直接写标题事件,dsh 不会替它截断
|
|
135
|
+
- 模板没有转义,`yyyy`、`MM` 这类子串一律替换;剥旧前缀按「当前模板 + 对应会话的开始时间」重放。模板在父会话与 fork 之间被改过、或父会话已不在 store 里,会留下双前缀
|
|
136
|
+
- 侧边栏里已存的旧前缀是缓存值,重启 dsh 并打开那个会话,它会自动校正
|
|
137
|
+
|
|
138
|
+
字段与默认值见「配置项」。
|
|
139
|
+
|
|
140
|
+
## 计费与预算
|
|
141
|
+
|
|
142
|
+
工具每次带一行费用回显:响应里有 `cost_micro_idr` 就写实际扣费,没有的(图像、视频、语音)按目录 `pricing_lines` 单价乘数量写预估。非 token 单位按整单位上取(21 字符的 TTS 计 1 个「1k 字符」单位)。
|
|
143
|
+
|
|
144
|
+
`kenari_billing` 给会话累计、分工具与分模型、`cached_tokens` 命中率、预算余量、钱包余额;余额低于 `lowBalanceAlertRp` 时费用回显附一条告警。`budgetCapRp` 默认 0(不封顶),到顶后花费型工具会被拒绝。预检只看已记录的花费,所以它是「不再新增花钱调用」,不是「保证总额不超」。402、401、405、403 都带了可执行建议。
|
|
145
|
+
|
|
146
|
+
## 设置页
|
|
147
|
+
|
|
148
|
+
Settings → **Kenari**(命名空间 `kenari`)。分组顺序就是使用顺序:
|
|
149
|
+
|
|
150
|
+
| 分组 | 内容 |
|
|
151
|
+
| --- | --- |
|
|
152
|
+
| 密钥 | 只显示状态与名字(已配置/缺失、来源、可修改性、只读引用名),没有输入框 |
|
|
153
|
+
| 模型 | 当前路由可用的模型:ID、显示名称、上下文。默认只列前 3 个 |
|
|
154
|
+
| 会话标题 | 开关、前缀格式、长度上限 |
|
|
155
|
+
| 高级设置(默认折叠) | API 地址、超时、重试次数、缓存时长、余额提醒阈值 |
|
|
156
|
+
| 重启后生效(默认折叠) | 加载时定型的开关(是否注册搜索/抓取/工具、预算封顶、自带适配器)。只读,改它们要编辑对应设置节并重启 dsh |
|
|
157
|
+
| 余额与用量 | 数字随用量变化,页面不存快照,只给一句可以直接照着问的话 |
|
|
158
|
+
|
|
159
|
+
文案有中英两套,跟随 dsh 的语言设置切换;`pnpm build` 会断言两边键集一致。
|
|
160
|
+
|
|
161
|
+
## 工具一览
|
|
162
|
+
|
|
163
|
+
| 类别 | 工具 |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| 目录与文档 | `kenari_list_models`(免费分组、价格、上下文、下线与 beta 告警;不需要 key)、`kenari_search_docs`(同样不需要 key) |
|
|
166
|
+
| 账户 | `kenari_balance`、`kenari_usage`、`kenari_quota`(分享页 key 会 403) |
|
|
167
|
+
| 搜索 | `kenari_x_search` |
|
|
168
|
+
| 文档 | `kenari_ocr`(`reuse_id` 复用免费) |
|
|
169
|
+
| 媒体 | `kenari_image_generate`、`kenari_image_edit`、`kenari_speech`、`kenari_transcribe`、`kenari_music`、`kenari_video_generate`、`kenari_video_extend`、`kenari_video_status`、`kenari_video_content` |
|
|
170
|
+
| 数据 | `kenari_embed`、`kenari_rerank`、`kenari_moderate` |
|
|
171
|
+
| 上下文与计费 | `kenari_count_tokens`、`kenari_billing` |
|
|
172
|
+
|
|
173
|
+
图像、音频、视频经 `ctx.attachments` 落盘成 image/file block,不塞进 JSON。
|
|
174
|
+
|
|
175
|
+
公开目录里没有 music 与 moderation 模型,所以 `kenari_music` 和 `kenari_moderate` 只会返回 400;`kenari_speech` 用 `mimo-v2-5-tts` 可用,`kokoro-tts` 与 `gemini-3-1-flash-tts` 是上游 400。
|
|
176
|
+
|
|
177
|
+
## 可选:插件自带的 LlmAdapter
|
|
178
|
+
|
|
179
|
+
`llm-pi-ai` 预设已经能把 Kenari 当会话模型用。把 `nativeAdapterEnabled` 设为 `true`(需重启)还能得到:模型选择器里直接看到价格、token 计量与 `cached_tokens` 命中率进 `kenari_billing`、推理档位按目录原样暴露(含 `none`)。
|
|
180
|
+
|
|
181
|
+
它注册的路由默认叫 `kenari-direct`,与预设的 `kenari` 不同名,两条可以并存,关掉开关即回退。相比预设,它不回放思考块、不注入 `file-parser`(文件块投影成说明文本,读文档请用 `kenari_ocr`)、不映射 `web_search_options`;图像输入是支持的。
|
|
182
|
+
|
|
183
|
+
```yaml
|
|
184
|
+
- id: kenari
|
|
185
|
+
config:
|
|
186
|
+
nativeAdapterEnabled: true
|
|
187
|
+
nativeProviderId: kenari-direct
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## 故障排查
|
|
191
|
+
|
|
192
|
+
| 症状 | 原因与处置 |
|
|
193
|
+
| --- | --- |
|
|
194
|
+
| 401 `invalid api key` | key 失效;`.env` 改完没重启 dsh;凭据引用名写错 |
|
|
195
|
+
| 402 `insufficient_balance` | 用了付费模型但余额为 0。切 `:free` 模型或充值 |
|
|
196
|
+
| 405 | base URL 形状错,见「三条协议线」 |
|
|
197
|
+
| 启动报 `invalid config ... reasoningEfforts` | 该模型的 `reasoningEfforts` 键不在 `off..max` 里,改回或从预设删掉该模型 |
|
|
198
|
+
| 403(balance / usage / quota) | key 来自分享页,这三个工具读 key 所有者的账户数据,所以被拒 |
|
|
199
|
+
| 设置页 Kenari 卡片空白 | 客户端 bundle 没构建或服务名写错。跑 `pnpm build` 再重启 |
|
|
200
|
+
| 导航里 Kenari 那行还是齿轮图标 | 图片没加载成功。确认包内含 `assets/kenari-favicon-128.png`,且 `GET /api/kenari.favicon` 能通 |
|
|
201
|
+
| 模型列表是空的 | 预设未生效,`dsh --profile web --dump-config` 看 `llm-pi-ai` 段是否存在 |
|
|
202
|
+
|
|
203
|
+
## 可选:只用 MCP,不装插件
|
|
204
|
+
|
|
205
|
+
Kenari 自带 Streamable HTTP MCP server(8 个工具,名字带 `mcp__kenari__` 前缀),配置如下:
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{ "mcpServers": { "kenari": { "url": "https://kenari.id/mcp", "headers": { "Authorization": "Bearer kn-..." } } } }
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
代价是工具名带前缀、没有计费可视与余额预警、多一个运行时依赖。MCP 只适合先试一下。
|
|
212
|
+
|
|
213
|
+
## 卸载
|
|
214
|
+
|
|
215
|
+
```sh
|
|
216
|
+
dsh plugin --profile web remove dsh-kenari-plugin
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
注册的一切都挂在 Cordis fiber 上,卸载即回收;dsh 的包与配置没有被改过。
|
|
220
|
+
|
|
221
|
+
## 配置项
|
|
222
|
+
|
|
223
|
+
多数可以在 Settings → Kenari 里改,标(重启)的要改对应设置节并重启。
|
|
224
|
+
|
|
225
|
+
| 字段 | 默认 | 说明 |
|
|
226
|
+
| --- | --- | --- |
|
|
227
|
+
| `apiKeyEnv` | `KENARI_API_KEY` | 存放 key 的凭据引用名 |
|
|
228
|
+
| `baseURL` | `https://kenari.id` | 插件调用 REST API 的地址 |
|
|
229
|
+
| `timeoutMs` | `30000` | 单次 REST 请求超时 |
|
|
230
|
+
| `generationTimeoutMs` | `180000` | 生成类调用(图像/音频/音乐/OCR)的单次超时 |
|
|
231
|
+
| `maxRetries` | `2` | 瞬时失败的 REST 重试次数 |
|
|
232
|
+
| `searchEnabled` | `true` | 注册搜索 provider(重启) |
|
|
233
|
+
| `fetchEnabled` | `true` | 注册抓取 provider(重启) |
|
|
234
|
+
| `fallbackEnabled` | `true` | Kenari 失败时回退 dsh 默认(重启) |
|
|
235
|
+
| `toolsEnabled` | `true` | 注册 21 个工具(重启) |
|
|
236
|
+
| `docsCacheTtlMs` | `3600000` | `/llms-full.txt` 缓存时长 |
|
|
237
|
+
| `catalogCacheTtlMs` | `3600000` | `/v1/models` 缓存时长 |
|
|
238
|
+
| `modelAliases` | `{}` | 模型别名,`{ 别名: '精确模型 id' }` |
|
|
239
|
+
| `lowBalanceAlertRp` | `5000` | 低于这个卢比数就告警,0 关闭 |
|
|
240
|
+
| `balanceCacheTtlMs` | `300000` | 余额缓存时长 |
|
|
241
|
+
| `budgetCapRp` | `0` | 单会话花费上限,0 不封顶 |
|
|
242
|
+
| `nativeAdapterEnabled` | `false` | 注册自带 LlmAdapter(重启) |
|
|
243
|
+
| `nativeProviderId` | `kenari-direct` | 自带适配器的路由 id |
|
|
244
|
+
| `modelRecoveryEnabled` | `true` | 失败自动恢复总开关 |
|
|
245
|
+
| `modelRecoveryProviders` | `['kenari','kenari-direct']` | 参与恢复的路由 |
|
|
246
|
+
| `modelRetryMaxRetries` | `5` | 额外重试次数(`kenari-direct`) |
|
|
247
|
+
| `modelRetryDelayMs` | `5000` | 每次重试前的固定等待 |
|
|
248
|
+
| `modelRetryableCodes` | `EMPTY_RESPONSE` `RATE_LIMIT` `SERVER` `TIMEOUT` `TRANSPORT` | 可重试的失败码,不能为空 |
|
|
249
|
+
| `modelSwitchEnabled` | `true` | 是否换模型 |
|
|
250
|
+
| `modelSwitchDelayMs` | `5000` | 换模型或回退前的等待 |
|
|
251
|
+
| `modelSwitchSkipCodes` | `AUTH` `INVALID_CREDENTIAL` `MISSING_CREDENTIAL` `QUOTA` | 跳过同 provider 换模型,直接回退 |
|
|
252
|
+
| `modelSwitchNoticeEnabled` | `true` | 换模型时注入通知 |
|
|
253
|
+
| `sessionTitlePrefixEnabled` | `true` | 标题前缀开关 |
|
|
254
|
+
| `sessionTitlePrefix` | `yyyyMMddHHmmss-` | 前缀模板 |
|
|
255
|
+
| `sessionTitleMaxBytes` | `96` | 前缀加正文的字节上限 |
|
|
256
|
+
|
|
257
|
+
## 开发
|
|
258
|
+
|
|
259
|
+
```sh
|
|
260
|
+
pnpm build # tsc → lib/,并跑客户端 bundle 自检
|
|
261
|
+
node test/real-harness.mjs # 真实 dsh harness(需要真 key)
|
|
262
|
+
node test/llm-adapter.mjs # 本地假网关,不联网不花钱
|
|
263
|
+
# 离线套件:session-title / catalog-view / default-route / favicon,各一个 .mjs
|
|
264
|
+
node test/billed-media.mjs # 会真花钱:TTS + STT,约 Rp 500
|
|
265
|
+
KENARI_ALLOW_VIDEO=1 node test/billed-video.mjs # 会真花钱:4s 视频,约 Rp 1.400
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
`scripts/check-client.mjs` 随 `pnpm build` 运行,校验客户端 bundle 的注册格式、导出面、依赖声明与命名空间,并真实渲染一次卡片组件(手写的 bundle 没有打包器,这些就是它的编译期检查)。设计与实测记录在 `docs/` 下,面向要改这个插件的人。
|
|
269
|
+
|
|
270
|
+
## 开发环境与致谢
|
|
271
|
+
|
|
272
|
+
这个插件在 **ZCode** 里写的,会话模型主要用 opencode 的 DeepSeek V4.1 Flash 和 Kenari 的 `deepseek-v4-flash`。写它要反复读 dsh 的源码并跨文件改,选这两个是因为都扛得住长上下文。
|
|
273
|
+
|
|
274
|
+
如果你也想搭一套类似的组合,可以参考我实际在用的这两个:
|
|
275
|
+
|
|
276
|
+
- **[Kenari](https://kenari.id/code/KNR-KKRNAJ)**:一个 key 打通多个厂商的模型,目录七十多个,从 DeepSeek、GLM、GPT 到语音、图像、视频、OCR、嵌入和重排;OpenAI 兼容、Anthropic 兼容与 Responses 三条协议线,现成客户端改个 base URL 就能接。按量付费与包月套餐都有,账单是印尼盾,充值门槛低(QRIS 最低 Rp 1.000)。目录与文档是公开端点,没有 key 也能先看清有什么。
|
|
277
|
+
- **[opencode Go](https://opencode.ai/go?ref=343F5JW4RA)**:$10 一个月,按 5 小时窗口给额度,不用逐 token 算钱,不够还能单独充值。它能配任何 agent,所以我拿它跑 dsh 的会话模型。
|
|
278
|
+
|
|
279
|
+
两个都支持支付宝扫码支付。
|
|
280
|
+
|
|
281
|
+
## License
|
|
282
|
+
|
|
283
|
+
MIT,见 [LICENSE](LICENSE)。
|
|
Binary file
|
|
Binary file
|