dsh-any-deepseek 0.0.6 → 0.0.7
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/README.md +29 -0
- package/lib/client.js +125 -6
- package/lib/index.js +68 -9
- package/lib/presets.d.ts +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,6 +98,35 @@ v1 is deliberately manual. A channel that fails reports its error; it does not s
|
|
|
98
98
|
over to another one. Switching invalidates the prompt cache for the running conversation, so
|
|
99
99
|
switch at a natural boundary (or start a new session).
|
|
100
100
|
|
|
101
|
+
The current channel is marked on its row (left colour bar, stronger border, tinted background)
|
|
102
|
+
rather than by a small badge — a single-choice list has to be readable at a glance.
|
|
103
|
+
|
|
104
|
+
### Thinking level
|
|
105
|
+
|
|
106
|
+
Each channel declares its selectable thinking levels, and the level picked in the harness is
|
|
107
|
+
passed through to the relay:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
harness reasoningEffort → pi-ai ThinkingLevel → model.thinkingLevelMap → wire value
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
There is no mapping layer in this plugin: the `thinkingLevelMap` the transport applies **is**
|
|
114
|
+
the mapping, and a second one here would drift from it. A level declared `null` is unsupported
|
|
115
|
+
and is not offered in the picker.
|
|
116
|
+
|
|
117
|
+
### Display names
|
|
118
|
+
|
|
119
|
+
`label` is a free-form display name, and it is what tells two accounts on the same relay apart.
|
|
120
|
+
Rename a channel from the panel; the channel **id** never changes, because the id is the
|
|
121
|
+
channel's identity (its key and its `current` target hang off it).
|
|
122
|
+
|
|
123
|
+
### Image input
|
|
124
|
+
|
|
125
|
+
Not supported yet, and declared as such. A harness `ImageBlock` carries only an
|
|
126
|
+
`attachmentId`; turning that into request bytes requires the durable attachment service, which
|
|
127
|
+
this adapter does not consult. Declaring image support anyway would make the harness stop
|
|
128
|
+
substituting its placeholder text and drop the image silently — worse than declaring text-only.
|
|
129
|
+
|
|
101
130
|
## Usage accounting
|
|
102
131
|
|
|
103
132
|
The ledger is append-only JSONL, split by month, under the harness home:
|
package/lib/client.js
CHANGED
|
@@ -117,7 +117,7 @@ window.__ModuleLoader__.load({
|
|
|
117
117
|
}
|
|
118
118
|
//#endregion
|
|
119
119
|
//#region \0ad-css:src/client/SettingsPage
|
|
120
|
-
const css = "/* Panel styles.\n *\n * 🔴 There are NO `--dsw-alias-*` design tokens on this host (measured 2026-10-21: the document\n * declares 29 custom properties and not one is a colour). This file used to reference a dozen of\n * them, every one falling back to a hardcoded LIGHT value (`rgba(0,0,0,0.12)` and friends) — so\n * on the dark theme the borders and dividers were effectively invisible.\n *\n * ⇒ Derive everything from `currentColor`, which the host sets per theme. `color-mix` gives the\n * low-contrast borders and fills that a token would have provided, and it follows the theme\n * automatically instead of assuming one palette.\n */\n\n.ad-page {\n display: flex;\n flex-direction: column;\n gap: 20px;\n padding: 4px 2px 12px;\n}\n\n/* One place to change the two theme-derived values the whole panel uses. */\n.ad-page {\n --ad-line: color-mix(in srgb, currentColor 18%, transparent);\n --ad-line-strong: color-mix(in srgb, currentColor 32%, transparent);\n --ad-fill-subtle: color-mix(in srgb, currentColor 7%, transparent);\n --ad-danger: #e5534b;\n}\n\n.ad-head {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.ad-title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n}\n\n.ad-intro {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n opacity: 0.72;\n}\n\n.ad-error {\n border: 1px solid var(--ad-danger);\n border-radius: 6px;\n padding: 8px 10px;\n font-size: 12px;\n}\n\n.ad-section {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.ad-sectionTitle {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n opacity: 0.6;\n}\n\n.ad-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.ad-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n border: 1px solid var(--ad-line);\n border-radius: 8px;\n padding: 10px 12px;\n}\n\n.ad-row-current {\n border-color: var(--ad-line-strong);\n}\n\n.ad-rowMain {\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n}\n\n.ad-rowTitleLine {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n font-weight: 500;\n}\n\n.ad-rowTitle {\n white-space: nowrap;\n}\n\n.ad-badge {\n font-size: 10px;\n padding: 1px 6px;\n border-radius: 999px;\n border: 1px solid currentColor;\n opacity: 0.8;\n}\n\n.ad-warn {\n color: #b7791f;\n cursor: help;\n}\n\n.ad-rowMeta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n opacity: 0.66;\n min-width: 0;\n}\n\n.ad-mono {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n word-break: break-all;\n}\n\n.ad-sep {\n opacity: 0.5;\n}\n\n.ad-rowActions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n}\n\n.ad-btn {\n font-size: 11px;\n padding: 4px 10px;\n border-radius: 6px;\n border: 1px solid var(--ad-line);\n background: transparent;\n color: inherit;\n cursor: pointer;\n}\n\n.ad-btn:disabled {\n opacity: 0.45;\n cursor: default;\n}\n\n.ad-btn-quiet {\n opacity: 0.8;\n}\n\n.ad-probe {\n font-size: 11px;\n padding-top: 2px;\n}\n\n.ad-probe-ok {\n color: #2f855a;\n}\n\n.ad-probe-bad {\n color: #c53030;\n word-break: break-word;\n}\n\n.ad-hint,\n.ad-empty,\n.ad-note {\n margin: 0;\n font-size: 11px;\n line-height: 1.6;\n opacity: 0.66;\n}\n\n.ad-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n}\n\n.ad-dot-on {\n background: #2f855a;\n}\n\n.ad-dot-off {\n border: 1px solid currentColor;\n opacity: 0.5;\n}\n\n.ad-dot-unknown {\n background: linear-gradient(90deg, currentColor 50%, transparent 50%);\n opacity: 0.5;\n}\n\n/* The channel row is a radio \"pick\" zone: the whole left side is clickable, and the radio sits\n at the start of the line so \"which one is live\" is readable down a single column. */\n.ad-rowPick {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n flex: 1;\n min-width: 0;\n cursor: pointer;\n}\n\n.ad-rowPick input[type='radio'] {\n flex: none;\n /* Optically centred against the first text line rather than the row box. */\n margin-top: 3px;\n accent-color: var(--ad-line-strong);\n cursor: pointer;\n}\n\n/* The switch is gone: selection is the only axis (see `channels.ts`). Keeping the rules would\n invite someone to bring the second axis back. */\n\n.ad-tabs {\n display: flex;\n gap: 6px;\n}\n\n.ad-tab {\n font-size: 11px;\n padding: 3px 10px;\n border-radius: 999px;\n border: 1px solid var(--ad-line);\n background: transparent;\n color: inherit;\n cursor: pointer;\n}\n\n.ad-tab-on {\n border-color: var(--ad-line-strong);\n font-weight: 600;\n}\n\n.ad-tab-refresh {\n margin-left: auto;\n opacity: 0.75;\n}\n\n.ad-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 11px;\n}\n\n.ad-table th,\n.ad-table td {\n padding: 5px 8px;\n border-bottom: 1px solid var(--ad-line);\n text-align: left;\n}\n\n.ad-table th {\n font-weight: 500;\n opacity: 0.6;\n}\n\n.ad-num {\n text-align: right;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n}\n\n.ad-total td {\n font-weight: 600;\n border-bottom: none;\n}\n\n/* ── add-channel form ─────────────────────────────────────────────────────── */\n\n.ad-addForm {\n display: flex;\n flex-direction: column;\n gap: 8px;\n border: 1px dashed var(--ad-line);\n border-radius: 8px;\n padding: 10px 12px;\n}\n\n.ad-field {\n display: flex;\n flex-direction: column;\n gap: 3px;\n font-size: 11px;\n}\n\n.ad-field > span {\n opacity: 0.7;\n}\n\n.ad-input {\n font-size: 11px;\n padding: 4px 8px;\n border-radius: 6px;\n border: 1px solid var(--ad-line);\n background: transparent;\n color: inherit;\n font-family: inherit;\n}\n\n/*\n * 🔴 A native `<select>` cannot be themed by leaving the background transparent.\n *\n * The popup list is drawn by the browser, not by the closed control's box: the options inherit\n * nothing from it, so `background: transparent` made them render in the platform default (light)\n * palette against this dark theme — reported by the owner and measured on the real host.\n *\n * 🔴 SECOND correction (measured 2026-10-21): this file used `var(--dsw-alias-color-*, #fallback)`\n * throughout, and **none of those variables exist**. The host declares only 29 custom properties\n * in total and not one is a colour token, so every `var()` here was falling back — and a fallback\n * that happens to be set inside a shorthand can be silently dropped (the select's background\n * computed to `rgba(0,0,0,0)`, i.e. still transparent, which is exactly the bug).\n *\n * ⇒ Use only values this file controls, derived from `currentColor` (the host sets a light text\n * colour on both themes) plus explicit translucent layers. No host token is assumed.\n */\nselect.ad-input {\n /* Tells the engine which palette to use for the popup. This part DID work. */\n color-scheme: dark light;\n /*\n * Paint the closed box with a layer that follows the surrounding text colour, so it matches\n * whichever theme is active instead of hardcoding a dark value into a light theme.\n */\n background-color: color-mix(in srgb, currentColor 8%, transparent);\n color: inherit;\n cursor: pointer;\n padding-right: 22px;\n /* Room for the chevron drawn below, so text never runs under it. */\n appearance: none;\n background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),\n linear-gradient(135deg, currentColor 50%, transparent 50%);\n background-position: calc(100% - 12px) calc(50% + 1px), calc(100% - 8px) calc(50% + 1px);\n background-size: 4px 4px, 4px 4px;\n background-repeat: no-repeat;\n}\n\nselect.ad-input:hover:not(:disabled) {\n background-color: color-mix(in srgb, currentColor 14%, transparent);\n}\n\nselect.ad-input:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 1px;\n}\n\n/*\n * The popup itself follows `color-scheme`; these keep the option rows explicit for engines that\n * honour per-option colours. `color-scheme: dark` above is what actually stops the light-on-dark\n * list, because the popup is native chrome rather than a styled element.\n */\nselect.ad-input option {\n background-color: Canvas;\n color: CanvasText;\n}\n\n.ad-fieldError {\n margin: 0;\n font-size: 11px;\n color: #c53030;\n}\n\n/* ── key row ──────────────────────────────────────────────────────────────── */\n\n/*\n * The key line states a FACT and offers the fix beside it: \"configured / not configured\",\n * the reference name the secret is filed under, and one text button that opens the input.\n *\n * The value is never shown — the harness credential seam answers \"configured?\" and refuses to\n * answer anything else, so there is nothing to render even if we wanted to.\n */\n.ad-keyLine {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n padding-top: 2px;\n}\n\n.ad-keyOn {\n color: #2f855a;\n}\n\n.ad-keyOff {\n color: #b7791f;\n}\n\n/* The reference name, not a secret: monospace so it lines up and misreads as a name, not a value. */\n.ad-keyName {\n font-size: 10px;\n padding: 1px 5px;\n border-radius: 4px;\n background: var(--ad-fill-subtle);\n opacity: 0.8;\n}\n\n/* A text-shaped action. Styled as a link because it opens an inline editor, not a dialog. */\n.ad-link {\n font-size: 11px;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 2px;\n opacity: 0.75;\n cursor: pointer;\n}\n\n.ad-link:hover {\n opacity: 1;\n}\n\n/* The inline key editor: one masked field and one button, on the same line as the fact above. */\n.ad-keyForm {\n display: flex;\n align-items: center;\n gap: 6px;\n padding-top: 4px;\n}\n\n.ad-keyForm .ad-input {\n flex: 1;\n min-width: 0;\n}\n\n/* A destructive action is quiet until hovered: it should be findable, not inviting. */\n.ad-btn-danger {\n opacity: 0.6;\n}\n\n.ad-btn-danger:hover:not(:disabled) {\n opacity: 1;\n border-color: var(--ad-danger);\n color: var(--ad-danger);\n}\n";
|
|
120
|
+
const css = "/* Panel styles.\n *\n * 🔴 There are NO `--dsw-alias-*` design tokens on this host (measured 2026-10-21: the document\n * declares 29 custom properties and not one is a colour). This file used to reference a dozen of\n * them, every one falling back to a hardcoded LIGHT value (`rgba(0,0,0,0.12)` and friends) — so\n * on the dark theme the borders and dividers were effectively invisible.\n *\n * ⇒ Derive everything from `currentColor`, which the host sets per theme. `color-mix` gives the\n * low-contrast borders and fills that a token would have provided, and it follows the theme\n * automatically instead of assuming one palette.\n */\n\n.ad-page {\n display: flex;\n flex-direction: column;\n gap: 20px;\n padding: 4px 2px 12px;\n}\n\n/* One place to change the two theme-derived values the whole panel uses. */\n.ad-page {\n --ad-line: color-mix(in srgb, currentColor 18%, transparent);\n --ad-line-strong: color-mix(in srgb, currentColor 32%, transparent);\n --ad-fill-subtle: color-mix(in srgb, currentColor 7%, transparent);\n /* The current row's fill: visible against both themes because it is derived from the text\n colour the host already picked, not from an assumed palette. */\n --ad-fill-current: color-mix(in srgb, currentColor 11%, transparent);\n --ad-danger: #e5534b;\n}\n\n.ad-head {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.ad-title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n}\n\n.ad-intro {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n opacity: 0.72;\n}\n\n.ad-error {\n border: 1px solid var(--ad-danger);\n border-radius: 6px;\n padding: 8px 10px;\n font-size: 12px;\n}\n\n.ad-section {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.ad-sectionTitle {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n opacity: 0.6;\n}\n\n.ad-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.ad-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n border: 1px solid var(--ad-line);\n border-radius: 8px;\n padding: 10px 12px;\n /*\n * The left edge is reserved on EVERY row so the current one does not shift its content when\n * selected — a row that reindents on click makes the list feel like it moved.\n */\n border-left: 3px solid transparent;\n}\n\n/*\n * 🔴 The current channel is marked three ways, not one.\n *\n * It used to be a small badge plus a barely-different border, and the owner could not tell at a\n * glance which relay was live — glanceability is the whole point of a single-choice list. So:\n * a colour bar down the left edge, a stronger border, and a tinted fill. All derived from\n * `currentColor`, because this host ships no colour tokens (measured: 29 custom properties, none\n * of them a colour), so any hardcoded value would assume the wrong theme.\n */\n.ad-row-current {\n border-color: var(--ad-line-strong);\n border-left-color: currentColor;\n background: var(--ad-fill-current);\n}\n\n/* The 当前 badge keeps its place as the textual confirmation, for when colour is not enough. */\n.ad-row-current .ad-badge {\n border-color: currentColor;\n opacity: 1;\n font-weight: 600;\n}\n\n/* A rename button must not look like the row's own selection affordance. */\n.ad-renameBtn {\n margin-left: auto;\n flex: none;\n}\n\n.ad-rowMain {\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n}\n\n.ad-rowTitleLine {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n font-weight: 500;\n}\n\n.ad-rowTitle {\n white-space: nowrap;\n}\n\n.ad-badge {\n font-size: 10px;\n padding: 1px 6px;\n border-radius: 999px;\n border: 1px solid currentColor;\n opacity: 0.8;\n}\n\n.ad-warn {\n color: #b7791f;\n cursor: help;\n}\n\n.ad-rowMeta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n opacity: 0.66;\n min-width: 0;\n}\n\n.ad-mono {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n word-break: break-all;\n}\n\n.ad-sep {\n opacity: 0.5;\n}\n\n.ad-rowActions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n}\n\n.ad-btn {\n font-size: 11px;\n padding: 4px 10px;\n border-radius: 6px;\n border: 1px solid var(--ad-line);\n background: transparent;\n color: inherit;\n cursor: pointer;\n}\n\n.ad-btn:disabled {\n opacity: 0.45;\n cursor: default;\n}\n\n.ad-btn-quiet {\n opacity: 0.8;\n}\n\n.ad-probe {\n font-size: 11px;\n padding-top: 2px;\n}\n\n.ad-probe-ok {\n color: #2f855a;\n}\n\n.ad-probe-bad {\n color: #c53030;\n word-break: break-word;\n}\n\n.ad-hint,\n.ad-empty,\n.ad-note {\n margin: 0;\n font-size: 11px;\n line-height: 1.6;\n opacity: 0.66;\n}\n\n.ad-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n}\n\n.ad-dot-on {\n background: #2f855a;\n}\n\n.ad-dot-off {\n border: 1px solid currentColor;\n opacity: 0.5;\n}\n\n.ad-dot-unknown {\n background: linear-gradient(90deg, currentColor 50%, transparent 50%);\n opacity: 0.5;\n}\n\n/* The channel row is a radio \"pick\" zone: the whole left side is clickable, and the radio sits\n at the start of the line so \"which one is live\" is readable down a single column. */\n.ad-rowPick {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n flex: 1;\n min-width: 0;\n cursor: pointer;\n}\n\n.ad-rowPick input[type='radio'] {\n flex: none;\n /* Optically centred against the first text line rather than the row box. */\n margin-top: 3px;\n accent-color: var(--ad-line-strong);\n cursor: pointer;\n}\n\n/* The switch is gone: selection is the only axis (see `channels.ts`). Keeping the rules would\n invite someone to bring the second axis back. */\n\n.ad-tabs {\n display: flex;\n gap: 6px;\n}\n\n.ad-tab {\n font-size: 11px;\n padding: 3px 10px;\n border-radius: 999px;\n border: 1px solid var(--ad-line);\n background: transparent;\n color: inherit;\n cursor: pointer;\n}\n\n.ad-tab-on {\n border-color: var(--ad-line-strong);\n font-weight: 600;\n}\n\n.ad-tab-refresh {\n margin-left: auto;\n opacity: 0.75;\n}\n\n.ad-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 11px;\n}\n\n.ad-table th,\n.ad-table td {\n padding: 5px 8px;\n border-bottom: 1px solid var(--ad-line);\n text-align: left;\n}\n\n.ad-table th {\n font-weight: 500;\n opacity: 0.6;\n}\n\n.ad-num {\n text-align: right;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n}\n\n.ad-total td {\n font-weight: 600;\n border-bottom: none;\n}\n\n/* ── add-channel form ─────────────────────────────────────────────────────── */\n\n.ad-addForm {\n display: flex;\n flex-direction: column;\n gap: 8px;\n border: 1px dashed var(--ad-line);\n border-radius: 8px;\n padding: 10px 12px;\n}\n\n.ad-field {\n display: flex;\n flex-direction: column;\n gap: 3px;\n font-size: 11px;\n}\n\n.ad-field > span {\n opacity: 0.7;\n}\n\n.ad-input {\n font-size: 11px;\n padding: 4px 8px;\n border-radius: 6px;\n border: 1px solid var(--ad-line);\n background: transparent;\n color: inherit;\n font-family: inherit;\n}\n\n/*\n * 🔴 A native `<select>` cannot be themed by leaving the background transparent.\n *\n * The popup list is drawn by the browser, not by the closed control's box: the options inherit\n * nothing from it, so `background: transparent` made them render in the platform default (light)\n * palette against this dark theme — reported by the owner and measured on the real host.\n *\n * 🔴 SECOND correction (measured 2026-10-21): this file used `var(--dsw-alias-color-*, #fallback)`\n * throughout, and **none of those variables exist**. The host declares only 29 custom properties\n * in total and not one is a colour token, so every `var()` here was falling back — and a fallback\n * that happens to be set inside a shorthand can be silently dropped (the select's background\n * computed to `rgba(0,0,0,0)`, i.e. still transparent, which is exactly the bug).\n *\n * ⇒ Use only values this file controls, derived from `currentColor` (the host sets a light text\n * colour on both themes) plus explicit translucent layers. No host token is assumed.\n */\nselect.ad-input {\n /* Tells the engine which palette to use for the popup. This part DID work. */\n color-scheme: dark light;\n /*\n * Paint the closed box with a layer that follows the surrounding text colour, so it matches\n * whichever theme is active instead of hardcoding a dark value into a light theme.\n */\n background-color: color-mix(in srgb, currentColor 8%, transparent);\n color: inherit;\n cursor: pointer;\n padding-right: 22px;\n /* Room for the chevron drawn below, so text never runs under it. */\n appearance: none;\n background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),\n linear-gradient(135deg, currentColor 50%, transparent 50%);\n background-position: calc(100% - 12px) calc(50% + 1px), calc(100% - 8px) calc(50% + 1px);\n background-size: 4px 4px, 4px 4px;\n background-repeat: no-repeat;\n}\n\nselect.ad-input:hover:not(:disabled) {\n background-color: color-mix(in srgb, currentColor 14%, transparent);\n}\n\nselect.ad-input:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 1px;\n}\n\n/*\n * The popup itself follows `color-scheme`; these keep the option rows explicit for engines that\n * honour per-option colours. `color-scheme: dark` above is what actually stops the light-on-dark\n * list, because the popup is native chrome rather than a styled element.\n */\nselect.ad-input option {\n background-color: Canvas;\n color: CanvasText;\n}\n\n.ad-fieldError {\n margin: 0;\n font-size: 11px;\n color: #c53030;\n}\n\n/* ── key row ──────────────────────────────────────────────────────────────── */\n\n/*\n * The key line states a FACT and offers the fix beside it: \"configured / not configured\",\n * the reference name the secret is filed under, and one text button that opens the input.\n *\n * The value is never shown — the harness credential seam answers \"configured?\" and refuses to\n * answer anything else, so there is nothing to render even if we wanted to.\n */\n.ad-keyLine {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n padding-top: 2px;\n}\n\n.ad-keyOn {\n color: #2f855a;\n}\n\n.ad-keyOff {\n color: #b7791f;\n}\n\n/* The reference name, not a secret: monospace so it lines up and misreads as a name, not a value. */\n.ad-keyName {\n font-size: 10px;\n padding: 1px 5px;\n border-radius: 4px;\n background: var(--ad-fill-subtle);\n opacity: 0.8;\n}\n\n/* A text-shaped action. Styled as a link because it opens an inline editor, not a dialog. */\n.ad-link {\n font-size: 11px;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 2px;\n opacity: 0.75;\n cursor: pointer;\n}\n\n.ad-link:hover {\n opacity: 1;\n}\n\n/* The inline key editor: one masked field and one button, on the same line as the fact above. */\n.ad-keyForm {\n display: flex;\n align-items: center;\n gap: 6px;\n padding-top: 4px;\n}\n\n.ad-keyForm .ad-input {\n flex: 1;\n min-width: 0;\n}\n\n/* A destructive action is quiet until hovered: it should be findable, not inviting. */\n.ad-btn-danger {\n opacity: 0.6;\n}\n\n.ad-btn-danger:hover:not(:disabled) {\n opacity: 1;\n border-color: var(--ad-danger);\n color: var(--ad-danger);\n}\n";
|
|
121
121
|
if (typeof document !== "undefined" && document.querySelector("style[data-ad-css=\"SettingsPage\"]") === null) {
|
|
122
122
|
const tag = document.createElement("style");
|
|
123
123
|
tag.setAttribute("data-ad-css", "SettingsPage");
|
|
@@ -182,12 +182,24 @@ window.__ModuleLoader__.load({
|
|
|
182
182
|
* second store to hold the value) was the design the owner rejected.
|
|
183
183
|
*/
|
|
184
184
|
const [newSecret, setNewSecret] = (0, react.useState)("");
|
|
185
|
+
/**
|
|
186
|
+
* The channel's display name, as typed.
|
|
187
|
+
*
|
|
188
|
+
* Empty means "use the preset's name". It is a real field (`label`) that the runtime has always
|
|
189
|
+
* honoured — the panel simply never exposed it, which made "two accounts on the same relay"
|
|
190
|
+
* impossible to tell apart.
|
|
191
|
+
*/
|
|
192
|
+
const [newLabel, setNewLabel] = (0, react.useState)("");
|
|
185
193
|
/** Which channel currently has its inline key field open (null = none). */
|
|
186
194
|
const [keying, setKeying] = (0, react.useState)(null);
|
|
187
195
|
const [keyDraft, setKeyDraft] = (0, react.useState)("");
|
|
188
196
|
const [keySaving, setKeySaving] = (0, react.useState)(false);
|
|
189
|
-
/**
|
|
197
|
+
/** Which channel currently has its inline RENAME field open (null = none). */
|
|
198
|
+
const [renaming, setRenaming] = (0, react.useState)(null);
|
|
199
|
+
const [renameDraft, setRenameDraft] = (0, react.useState)("");
|
|
200
|
+
/** The channel id and the label the form last derived from a preset (see the select). */
|
|
190
201
|
const lastSuggestedId = (0, react.useRef)("");
|
|
202
|
+
const lastSuggestedLabel = (0, react.useRef)("");
|
|
191
203
|
const refresh = (0, react.useCallback)(async () => {
|
|
192
204
|
const [loaded, statsNow] = await Promise.all([loadChannels(), loadStats(group)]);
|
|
193
205
|
setRows(loaded.channels);
|
|
@@ -216,6 +228,15 @@ window.__ModuleLoader__.load({
|
|
|
216
228
|
* reasons they cannot (transport). Both are facts, and silently discarding them is what makes
|
|
217
229
|
* a panel look alive while nothing is saved.
|
|
218
230
|
*
|
|
231
|
+
* 🔴 **A successful write RE-READS, right here.** The rows the panel renders come from
|
|
232
|
+
* `/any-deepseek/channels`, which is computed from the configuration — not a live push. So a
|
|
233
|
+
* write that does not re-read leaves the panel showing the document as it was BEFORE the write,
|
|
234
|
+
* and the only way to see the result was to close and reopen the panel. Reported by the owner
|
|
235
|
+
* as "adding or removing a channel does not show up until I reopen".
|
|
236
|
+
*
|
|
237
|
+
* Doing it centrally matters: the previous code had `refresh()` at one call site and not at the
|
|
238
|
+
* others, so correctness depended on remembering. A new write path now inherits it.
|
|
239
|
+
*
|
|
219
240
|
* 🔴 **The refusal message must NOT name causes we have not checked.** This used to read
|
|
220
241
|
* "只读部署,或设置服务未启用" — and the one time it actually fired, the real cause was
|
|
221
242
|
* neither: `current` was missing `.volatile()` in the schema, three layers away. Naming two
|
|
@@ -226,8 +247,14 @@ window.__ModuleLoader__.load({
|
|
|
226
247
|
*/
|
|
227
248
|
const write = (run, context = "保存失败") => {
|
|
228
249
|
run().then((accepted) => {
|
|
229
|
-
if (!accepted)
|
|
230
|
-
|
|
250
|
+
if (!accepted) {
|
|
251
|
+
setError(`${context}:宿主拒绝了这个修改`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
setError(null);
|
|
255
|
+
refresh().catch((cause) => {
|
|
256
|
+
setError(`已写入,但重新读取失败:${cause instanceof Error ? cause.message : String(cause)}`);
|
|
257
|
+
});
|
|
231
258
|
}, (cause) => {
|
|
232
259
|
setError(`${context}:${cause instanceof Error ? cause.message : String(cause)}`);
|
|
233
260
|
});
|
|
@@ -259,21 +286,35 @@ window.__ModuleLoader__.load({
|
|
|
259
286
|
* there is no second store to keep in step, so there is no ordering to get wrong and no way for
|
|
260
287
|
* "channel exists but its key does not" to be a reachable state. Pre-filling endpoint fields is
|
|
261
288
|
* still avoided: a preset-backed channel records the preset, so later preset fixes reach it.
|
|
289
|
+
*
|
|
290
|
+
* `label` is only written when the user changed it. Leaving it out lets the preset's display
|
|
291
|
+
* name apply and keep tracking preset changes; writing the same string back would freeze
|
|
292
|
+
* today's name into the document.
|
|
262
293
|
*/
|
|
263
294
|
const addChannel = () => {
|
|
264
295
|
const id = newId.trim();
|
|
265
296
|
if (newPreset === "" || id === "" || newIdProblem !== null || newSecret.trim() === "") return;
|
|
266
|
-
|
|
297
|
+
const label = newLabel.trim();
|
|
298
|
+
const entry = {
|
|
267
299
|
preset: newPreset,
|
|
268
300
|
apiKey: newSecret.trim()
|
|
269
|
-
}
|
|
301
|
+
};
|
|
302
|
+
if (label !== "") entry.label = label;
|
|
303
|
+
write(() => setField(["channels", id], entry), "添加渠道失败");
|
|
270
304
|
if (current === "") write(() => setField(["current"], id), "设为当前渠道失败");
|
|
271
305
|
setNewSecret("");
|
|
272
306
|
setNewId("");
|
|
307
|
+
setNewLabel("");
|
|
273
308
|
setNewPreset("");
|
|
274
309
|
lastSuggestedId.current = "";
|
|
310
|
+
lastSuggestedLabel.current = "";
|
|
275
311
|
setAdding(false);
|
|
276
312
|
};
|
|
313
|
+
/**
|
|
314
|
+
* Remove one channel.
|
|
315
|
+
*
|
|
316
|
+
* The row disappears because `write()` re-reads on success; there is nothing to remember here.
|
|
317
|
+
*/
|
|
277
318
|
const removeChannel = (id) => {
|
|
278
319
|
write(() => removeField(["channels", id]), "移除渠道失败");
|
|
279
320
|
if (current === id) write(() => removeField(["current"]), "清除当前选择失败");
|
|
@@ -312,6 +353,32 @@ window.__ModuleLoader__.load({
|
|
|
312
353
|
setKeySaving(false);
|
|
313
354
|
}
|
|
314
355
|
};
|
|
356
|
+
/**
|
|
357
|
+
* Rename one channel.
|
|
358
|
+
*
|
|
359
|
+
* `label` is the display name and nothing else: the channel's identity is its id (the
|
|
360
|
+
* configuration key), which renaming must never touch — a renamed key would be a different
|
|
361
|
+
* channel, with a different key and a different `current` target.
|
|
362
|
+
*
|
|
363
|
+
* Clearing the name removes the field, which restores the preset's name rather than leaving an
|
|
364
|
+
* empty label in the document.
|
|
365
|
+
* @param row - the channel being renamed.
|
|
366
|
+
*/
|
|
367
|
+
const renameChannel = (row) => {
|
|
368
|
+
const next = renameDraft.trim();
|
|
369
|
+
if (next === "") write(() => removeField([
|
|
370
|
+
"channels",
|
|
371
|
+
row.id,
|
|
372
|
+
"label"
|
|
373
|
+
]), "恢复默认名失败");
|
|
374
|
+
else write(() => setField([
|
|
375
|
+
"channels",
|
|
376
|
+
row.id,
|
|
377
|
+
"label"
|
|
378
|
+
], next), "重命名失败");
|
|
379
|
+
setRenaming(null);
|
|
380
|
+
setRenameDraft("");
|
|
381
|
+
};
|
|
315
382
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
316
383
|
className: "ad-page",
|
|
317
384
|
children: [
|
|
@@ -359,6 +426,8 @@ window.__ModuleLoader__.load({
|
|
|
359
426
|
const probeResult = verdict[row.id];
|
|
360
427
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
361
428
|
className: `ad-row${isCurrent ? " ad-row-current" : ""}`,
|
|
429
|
+
"aria-current": isCurrent ? "true" : void 0,
|
|
430
|
+
"data-current": isCurrent ? "true" : "false",
|
|
362
431
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
363
432
|
className: "ad-rowPick",
|
|
364
433
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
@@ -388,9 +457,45 @@ window.__ModuleLoader__.load({
|
|
|
388
457
|
className: "ad-warn",
|
|
389
458
|
title: row.warnings.join("\n"),
|
|
390
459
|
children: "⚠"
|
|
460
|
+
}),
|
|
461
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
462
|
+
type: "button",
|
|
463
|
+
className: "ad-link ad-renameBtn",
|
|
464
|
+
title: "改显示名(渠道 id 不变)",
|
|
465
|
+
onClick: (event) => {
|
|
466
|
+
event.preventDefault();
|
|
467
|
+
event.stopPropagation();
|
|
468
|
+
setRenaming(renaming === row.id ? null : row.id);
|
|
469
|
+
setRenameDraft(renaming === row.id ? "" : row.label);
|
|
470
|
+
},
|
|
471
|
+
children: renaming === row.id ? "取消" : "改名"
|
|
391
472
|
})
|
|
392
473
|
]
|
|
393
474
|
}),
|
|
475
|
+
renaming === row.id && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
476
|
+
className: "ad-keyForm",
|
|
477
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
478
|
+
className: "ad-input",
|
|
479
|
+
value: renameDraft,
|
|
480
|
+
placeholder: "显示名(留空则用预设默认名)",
|
|
481
|
+
onClick: (event) => event.preventDefault(),
|
|
482
|
+
onChange: (event) => setRenameDraft(event.target.value),
|
|
483
|
+
onKeyDown: (event) => {
|
|
484
|
+
if (event.key === "Enter") {
|
|
485
|
+
event.preventDefault();
|
|
486
|
+
renameChannel(row);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
490
|
+
type: "button",
|
|
491
|
+
className: "ad-btn",
|
|
492
|
+
onClick: (event) => {
|
|
493
|
+
event.preventDefault();
|
|
494
|
+
renameChannel(row);
|
|
495
|
+
},
|
|
496
|
+
children: "保存名字"
|
|
497
|
+
})]
|
|
498
|
+
}),
|
|
394
499
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
395
500
|
className: "ad-rowMeta",
|
|
396
501
|
children: [
|
|
@@ -501,9 +606,13 @@ window.__ModuleLoader__.load({
|
|
|
501
606
|
const next = event.target.value;
|
|
502
607
|
setNewPreset(next);
|
|
503
608
|
const previousId = lastSuggestedId.current;
|
|
609
|
+
const previousLabel = lastSuggestedLabel.current;
|
|
504
610
|
const nextId = suggestId(next, rows);
|
|
611
|
+
const nextLabel = presets.find((preset) => preset.id === next)?.displayName ?? "";
|
|
505
612
|
setNewId((current) => current === "" || current === previousId ? nextId : current);
|
|
613
|
+
setNewLabel((current) => current === "" || current === previousLabel ? nextLabel : current);
|
|
506
614
|
lastSuggestedId.current = nextId;
|
|
615
|
+
lastSuggestedLabel.current = nextLabel;
|
|
507
616
|
},
|
|
508
617
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
509
618
|
value: "",
|
|
@@ -527,6 +636,15 @@ window.__ModuleLoader__.load({
|
|
|
527
636
|
className: "ad-fieldError",
|
|
528
637
|
children: newIdProblem
|
|
529
638
|
}),
|
|
639
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
640
|
+
className: "ad-field",
|
|
641
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "显示名" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
642
|
+
className: "ad-input",
|
|
643
|
+
value: newLabel,
|
|
644
|
+
placeholder: newPreset === "" ? "先选上面的规格" : "例如「Open Design(主号)」",
|
|
645
|
+
onChange: (event) => setNewLabel(event.target.value)
|
|
646
|
+
})]
|
|
647
|
+
}),
|
|
530
648
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
531
649
|
className: "ad-field",
|
|
532
650
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "密钥" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
@@ -573,6 +691,7 @@ window.__ModuleLoader__.load({
|
|
|
573
691
|
onClick: () => {
|
|
574
692
|
setAdding(true);
|
|
575
693
|
lastSuggestedId.current = "";
|
|
694
|
+
lastSuggestedLabel.current = "";
|
|
576
695
|
},
|
|
577
696
|
children: "添加渠道"
|
|
578
697
|
}),
|
package/lib/index.js
CHANGED
|
@@ -161,13 +161,30 @@ const DEFAULT_MAX_TOKENS = 32768;
|
|
|
161
161
|
*
|
|
162
162
|
* Kept as one constant because these are properties of the *model*, not of a relay; a relay
|
|
163
163
|
* that disagrees corrects them in its own preset.
|
|
164
|
+
*
|
|
165
|
+
* ⚠️ `input` is `['text']` **on purpose, for now**. The model is multimodal, but declaring image
|
|
166
|
+
* support is a promise this adapter cannot keep yet: a harness `ImageBlock` carries only an
|
|
167
|
+
* `attachmentId`, and turning that into bytes requires the durable attachment service
|
|
168
|
+
* (`ctx.attachments.readImageRequest`), which the adapter does not consult. Declaring images
|
|
169
|
+
* without that path would be worse than declaring text — the harness would stop substituting its
|
|
170
|
+
* placeholder text and the image would be silently dropped. See SESSION decision 38.
|
|
164
171
|
*/
|
|
165
172
|
const V41_FLASH_CAPS = {
|
|
166
173
|
contextWindow: 1e6,
|
|
167
174
|
maxTokens: 384e3,
|
|
168
175
|
input: ["text"],
|
|
176
|
+
/**
|
|
177
|
+
* Selectable thinking levels, in pi-ai's vocabulary.
|
|
178
|
+
*
|
|
179
|
+
* The VALUES are the wire spellings this model family accepts (`reasoning_effort`); `null`
|
|
180
|
+
* would mark a level as unsupported. pi-ai consumes this map directly as `thinkingLevelMap`,
|
|
181
|
+
* which is why this plugin does not implement a translation layer of its own — a second
|
|
182
|
+
* mapping would inevitably drift from the one the transport actually applies.
|
|
183
|
+
*/
|
|
169
184
|
reasoningEfforts: {
|
|
185
|
+
off: null,
|
|
170
186
|
low: "low",
|
|
187
|
+
medium: "medium",
|
|
171
188
|
high: "high",
|
|
172
189
|
max: "max"
|
|
173
190
|
}
|
|
@@ -184,19 +201,35 @@ const DEEPSEEK_THINKING_COMPAT = {
|
|
|
184
201
|
thinkingFormat: "deepseek"
|
|
185
202
|
};
|
|
186
203
|
/**
|
|
187
|
-
*
|
|
204
|
+
* Headers the OpenCode Go endpoint requires, MINUS the session id.
|
|
205
|
+
*
|
|
206
|
+
* `/zen/go` answers 400 `Request is missing x-opencode-session` without it, and the official
|
|
207
|
+
* documentation adds two requirements this preset has to respect:
|
|
208
|
+
*
|
|
209
|
+
* 1. the session id must be **stable per conversation** — a constant shared by every
|
|
210
|
+
* conversation violates the thing the header exists for (routing and cache affinity);
|
|
211
|
+
* 2. the client must **identify itself with its own user agent** rather than a generic SDK name.
|
|
188
212
|
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
213
|
+
* So the session id is deliberately NOT here: `adapter.ts` injects it per request from the
|
|
214
|
+
* harness's own session identity. What stays here is the static identity of this client.
|
|
215
|
+
*
|
|
216
|
+
* ⚠️ The upstream documentation lists **DeepSeek Harness as a known-problematic client** for this
|
|
217
|
+
* endpoint ("session information arrives on some model paths, but is missing on others"). Even
|
|
218
|
+
* with everything here correct, expect this relay to be stricter than the others.
|
|
192
219
|
*/
|
|
193
220
|
const OPENCODE_HEADERS = {
|
|
194
|
-
"x-opencode-session": "dsh-any-deepseek",
|
|
195
|
-
"X-Session-ID": "dsh-any-deepseek",
|
|
196
221
|
"x-opencode-client": "dsh",
|
|
197
222
|
"x-opencode-project": "global"
|
|
198
223
|
};
|
|
199
224
|
/**
|
|
225
|
+
* The user agent a request to OpenCode Go identifies itself with.
|
|
226
|
+
*
|
|
227
|
+
* Requirement 2 above: a generic SDK name is exactly what they ask clients not to send.
|
|
228
|
+
*/
|
|
229
|
+
const CLIENT_USER_AGENT = "dsh-any-deepseek/1.0";
|
|
230
|
+
/** Header carrying the per-conversation routing id on OpenCode Go. */
|
|
231
|
+
const OPENCODE_SESSION_HEADER = "x-opencode-session";
|
|
232
|
+
/**
|
|
200
233
|
* Presets keyed by a stable id.
|
|
201
234
|
*
|
|
202
235
|
* The id is what a channel entry stores (`preset: opencode-go`), so it is part of the user's
|
|
@@ -590,6 +623,8 @@ const PROVIDER_NAME = "Any DeepSeek";
|
|
|
590
623
|
* @returns a pi-ai chat model describing that relay's view of the model.
|
|
591
624
|
*/
|
|
592
625
|
function buildPiModel(channel) {
|
|
626
|
+
const thinkingLevelMap = channel.reasoningEfforts === void 0 ? void 0 : Object.fromEntries(Object.entries(channel.reasoningEfforts).filter(([, value]) => value !== null));
|
|
627
|
+
const hasThinking = thinkingLevelMap !== void 0 && Object.keys(thinkingLevelMap).length > 0;
|
|
593
628
|
return {
|
|
594
629
|
id: channel.model,
|
|
595
630
|
name: channel.label,
|
|
@@ -605,7 +640,8 @@ function buildPiModel(channel) {
|
|
|
605
640
|
},
|
|
606
641
|
contextWindow: channel.contextWindow,
|
|
607
642
|
maxTokens: channel.maxTokens,
|
|
608
|
-
reasoning:
|
|
643
|
+
reasoning: hasThinking,
|
|
644
|
+
...hasThinking ? { thinkingLevelMap } : {},
|
|
609
645
|
...channel.compat === void 0 ? {} : { compat: channel.compat },
|
|
610
646
|
...channel.headers === void 0 ? {} : { headers: { ...channel.headers } }
|
|
611
647
|
};
|
|
@@ -987,7 +1023,7 @@ var AnyDeepSeekAdapter = class extends LlmAdapter {
|
|
|
987
1023
|
}
|
|
988
1024
|
async resolveModel(provider, model, _signal) {
|
|
989
1025
|
const channel = this.selected();
|
|
990
|
-
const efforts = Object.
|
|
1026
|
+
const efforts = Object.entries(channel?.reasoningEfforts ?? {}).filter(([, wire]) => wire !== null).map(([id]) => id);
|
|
991
1027
|
const reasoning = efforts.length === 0 ? {} : { reasoning: { efforts: efforts.map((id) => ({
|
|
992
1028
|
id: ReasoningEffortId(id),
|
|
993
1029
|
name: id
|
|
@@ -1084,11 +1120,34 @@ var AnyDeepSeekAdapter = class extends LlmAdapter {
|
|
|
1084
1120
|
models: [model],
|
|
1085
1121
|
api: openAICompletionsApi()
|
|
1086
1122
|
}));
|
|
1123
|
+
/**
|
|
1124
|
+
* Per-request headers.
|
|
1125
|
+
*
|
|
1126
|
+
* Two things OpenCode Go requires cannot live in a preset, because neither is a constant:
|
|
1127
|
+
*
|
|
1128
|
+
* · `x-opencode-session` must be STABLE PER CONVERSATION. A single shared literal satisfied
|
|
1129
|
+
* the letter of "send the header" while defeating its purpose (routing and prompt-cache
|
|
1130
|
+
* affinity), which is why the previous preset's fixed value produced 400s. The harness
|
|
1131
|
+
* already stamps every request with `sessionId`; that is the correct source.
|
|
1132
|
+
* · `User-Agent` must identify THIS client rather than the generic SDK. pi-ai sets its own,
|
|
1133
|
+
* so the preset's static headers alone can never satisfy that rule.
|
|
1134
|
+
*/
|
|
1135
|
+
const requestHeaders = {};
|
|
1136
|
+
if (channel.headers !== void 0 && Object.hasOwn(channel.headers, "x-opencode-client")) {
|
|
1137
|
+
const session = options.sessionId === void 0 ? void 0 : String(options.sessionId);
|
|
1138
|
+
if (session !== void 0 && session !== "") {
|
|
1139
|
+
requestHeaders[OPENCODE_SESSION_HEADER] = session;
|
|
1140
|
+
requestHeaders["X-Session-ID"] = session;
|
|
1141
|
+
}
|
|
1142
|
+
requestHeaders["User-Agent"] = CLIENT_USER_AGENT;
|
|
1143
|
+
}
|
|
1087
1144
|
const streamOptions = {
|
|
1088
1145
|
apiKey: key,
|
|
1089
1146
|
signal: options.signal,
|
|
1090
1147
|
...options.temperature === void 0 ? {} : { temperature: options.temperature },
|
|
1091
|
-
...options.maxTokens === void 0 ? {} : { maxTokens: options.maxTokens }
|
|
1148
|
+
...options.maxTokens === void 0 ? {} : { maxTokens: options.maxTokens },
|
|
1149
|
+
...options.reasoningEffort === void 0 ? {} : { reasoning: options.reasoningEffort },
|
|
1150
|
+
...Object.keys(requestHeaders).length === 0 ? {} : { headers: requestHeaders }
|
|
1092
1151
|
};
|
|
1093
1152
|
try {
|
|
1094
1153
|
const events = models.streamSimple(model, toPiContext(options), streamOptions);
|
package/lib/presets.d.ts
CHANGED
|
@@ -81,6 +81,14 @@ export declare const ADVERTISED_MODEL_NAME = "DeepSeek V4.1 Flash";
|
|
|
81
81
|
*/
|
|
82
82
|
export declare const DEFAULT_CONTEXT_WINDOW = 262144;
|
|
83
83
|
export declare const DEFAULT_MAX_TOKENS = 32768;
|
|
84
|
+
/**
|
|
85
|
+
* The user agent a request to OpenCode Go identifies itself with.
|
|
86
|
+
*
|
|
87
|
+
* Requirement 2 above: a generic SDK name is exactly what they ask clients not to send.
|
|
88
|
+
*/
|
|
89
|
+
export declare const CLIENT_USER_AGENT = "dsh-any-deepseek/1.0";
|
|
90
|
+
/** Header carrying the per-conversation routing id on OpenCode Go. */
|
|
91
|
+
export declare const OPENCODE_SESSION_HEADER = "x-opencode-session";
|
|
84
92
|
/**
|
|
85
93
|
* Presets keyed by a stable id.
|
|
86
94
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-any-deepseek",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "One DeepSeek V4.1 Flash provider for DeepSeek Harness: paste a key per channel, switch channels manually, token usage booked per real upstream.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|