dsh-cline-pass 0.2.3 → 0.2.5
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/lib/adapter.js +69 -14
- package/lib/client.js +205 -2
- package/package.json +3 -2
package/lib/adapter.js
CHANGED
|
@@ -249,31 +249,88 @@ function collectImageParts(blocks, images, target = []) {
|
|
|
249
249
|
/**
|
|
250
250
|
* Convert the harness conversation to wire messages.
|
|
251
251
|
*
|
|
252
|
-
* Tool results become standalone `{ role: 'tool' }` messages
|
|
253
|
-
*
|
|
254
|
-
*
|
|
252
|
+
* Tool results become standalone `{ role: 'tool' }` messages, in both harness
|
|
253
|
+
* shapes: the current one, where the result IS a `tool`-role message carrying
|
|
254
|
+
* `toolCallId`, and the one dsh < 0.2.0 produced, a user message wrapping a
|
|
255
|
+
* `tool-result` block. A user message carrying an image switches to the OpenAI
|
|
256
|
+
* content-part array; a text-only one keeps the plain-string form.
|
|
255
257
|
*
|
|
256
258
|
* An image nested in a tool result (read_image, a screenshot tool) has no
|
|
257
259
|
* OpenAI `tool` wire spelling, so it rides a following user message instead —
|
|
258
|
-
* the same shape the shipped adapters use.
|
|
260
|
+
* the same shape the shipped adapters use. Deferring that message until the
|
|
261
|
+
* next non-tool message keeps a parallel group's answers contiguous.
|
|
262
|
+
*
|
|
263
|
+
* `developer` becomes `system`. The harness uses that role for its own
|
|
264
|
+
* system-level notices — the tool-registry change it appends when the tool set
|
|
265
|
+
* moves — and the newer OpenAI spelling is not accepted by every
|
|
266
|
+
* OpenAI-compatible gateway, while `system` is. Sending it as a user turn (what
|
|
267
|
+
* an unhandled role fell through to) also misrepresents it as something the user
|
|
268
|
+
* said.
|
|
269
|
+
*
|
|
270
|
+
* The group guard below is the same rule stated once: an assistant message
|
|
271
|
+
* carrying `tool_calls` must be followed immediately by a `tool` message per id.
|
|
272
|
+
* Any other message that would land inside that run — a notice, a user turn, the
|
|
273
|
+
* image message a tool-result image needs — is held back until the group closes,
|
|
274
|
+
* because a gateway that validates the rule rejects the whole request with
|
|
275
|
+
* "insufficient tool messages following tool_calls message".
|
|
259
276
|
*/
|
|
260
277
|
function serializeMessages(messages, images) {
|
|
261
278
|
const wire = []
|
|
262
279
|
let pendingToolImages = []
|
|
280
|
+
/** Call ids of the most recent assistant tool_calls still awaiting an answer. */
|
|
281
|
+
let awaiting = []
|
|
282
|
+
/** Messages held back so they cannot split the group; emitted in order. */
|
|
283
|
+
let deferred = []
|
|
284
|
+
|
|
285
|
+
const flushDeferred = () => {
|
|
286
|
+
if (deferred.length === 0) return
|
|
287
|
+
for (const held of deferred) wire.push(held)
|
|
288
|
+
deferred = []
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Emit a non-tool message.
|
|
292
|
+
*
|
|
293
|
+
* A message that is not a tool answer cannot go out while a group is open, so
|
|
294
|
+
* it waits. With no group open this is a plain push, which is every ordinary
|
|
295
|
+
* conversation and keeps that output unchanged.
|
|
296
|
+
*/
|
|
297
|
+
const emit = (message) => {
|
|
298
|
+
if (awaiting.length > 0) deferred.push(message)
|
|
299
|
+
else wire.push(message)
|
|
300
|
+
}
|
|
263
301
|
const flushToolImages = () => {
|
|
264
302
|
if (pendingToolImages.length === 0) return
|
|
265
|
-
|
|
303
|
+
const message = { role: 'user', content: [{ type: 'text', text: TOOL_RESULT_IMAGE_TEXT }, ...pendingToolImages] }
|
|
266
304
|
pendingToolImages = []
|
|
305
|
+
emit(message)
|
|
267
306
|
}
|
|
307
|
+
/** A tool answer: it closes its own id, and the group when it was the last. */
|
|
308
|
+
const answer = (callId, content) => {
|
|
309
|
+
wire.push({ role: 'tool', tool_call_id: callId, content: flattenText(content) || '(no output)' })
|
|
310
|
+
collectImageParts(content, images, pendingToolImages)
|
|
311
|
+
awaiting = awaiting.filter((id) => id !== callId)
|
|
312
|
+
if (awaiting.length === 0) flushDeferred()
|
|
313
|
+
}
|
|
314
|
+
|
|
268
315
|
for (const message of messages) {
|
|
269
|
-
if (message.role === 'system') {
|
|
316
|
+
if (message.role === 'system' || message.role === 'developer') {
|
|
270
317
|
flushToolImages()
|
|
271
|
-
|
|
318
|
+
emit({ role: 'system', content: flattenText(message.content) })
|
|
272
319
|
continue
|
|
273
320
|
}
|
|
274
321
|
if (message.role === 'assistant') {
|
|
275
322
|
flushToolImages()
|
|
323
|
+
// A turn boundary. Anything still held belongs before the new turn, and an
|
|
324
|
+
// unanswered group from the previous turn can no longer be answered.
|
|
325
|
+
flushDeferred()
|
|
276
326
|
wire.push(serializeAssistant(message))
|
|
327
|
+
awaiting = (message.content ?? []).filter((block) => block.type === 'tool-call').map((block) => block.id)
|
|
328
|
+
continue
|
|
329
|
+
}
|
|
330
|
+
// A harness tool result: the call identity is on the message. No flush here,
|
|
331
|
+
// so the answers to one assistant message's parallel calls stay adjacent.
|
|
332
|
+
if (message.role === 'tool') {
|
|
333
|
+
answer(message.toolCallId, message.content)
|
|
277
334
|
continue
|
|
278
335
|
}
|
|
279
336
|
const toolResults = (message.content ?? []).filter((block) => block.type === 'tool-result')
|
|
@@ -286,18 +343,16 @@ function serializeMessages(messages, images) {
|
|
|
286
343
|
const text = parts.filter((part) => part.type === 'text').map((part) => part.text).join('')
|
|
287
344
|
if (hasImage || text.length > 0 || toolResults.length === 0) {
|
|
288
345
|
flushToolImages()
|
|
289
|
-
|
|
346
|
+
emit({ role: 'user', content: hasImage ? parts : text })
|
|
290
347
|
}
|
|
291
348
|
for (const result of toolResults) {
|
|
292
|
-
|
|
293
|
-
role: 'tool',
|
|
294
|
-
tool_call_id: result.toolCallId,
|
|
295
|
-
content: flattenText(result.content) || '(no output)',
|
|
296
|
-
})
|
|
297
|
-
collectImageParts(result.content, images, pendingToolImages)
|
|
349
|
+
answer(result.toolCallId, result.content)
|
|
298
350
|
}
|
|
299
351
|
}
|
|
300
352
|
flushToolImages()
|
|
353
|
+
// A group the history never closed leaves nothing to wait for; whatever was
|
|
354
|
+
// held still belongs in the request, just after the answers that do exist.
|
|
355
|
+
flushDeferred()
|
|
301
356
|
return wire
|
|
302
357
|
}
|
|
303
358
|
|
package/lib/client.js
CHANGED
|
@@ -241,6 +241,25 @@ window.__ModuleLoader__.load({
|
|
|
241
241
|
'.cp-usage-model-tokens{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-tertiary);min-width:52px;text-align:right}',
|
|
242
242
|
'.cp-check{width:14px;height:14px;flex:none;cursor:pointer;accent-color:var(--dsw-alias-brand-primary)}',
|
|
243
243
|
'.cp-tools-label{cursor:pointer;gap:8px}',
|
|
244
|
+
// The usage pill beside the model selector. It borrows the panel's ramp and
|
|
245
|
+
// track treatment so a reading looks the same in both places.
|
|
246
|
+
'.cp-pill{position:relative;display:flex;align-items:center;font-size:12px;line-height:1.4}',
|
|
247
|
+
'.cp-pill-btn{border:0.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:2px 9px;font:inherit;font-size:11.5px;cursor:pointer;white-space:nowrap;font-variant-numeric:tabular-nums}',
|
|
248
|
+
'.cp-pill-btn:hover{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary)}',
|
|
249
|
+
'.cp-pill-btn[aria-expanded=true]{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary)}',
|
|
250
|
+
'.cp-pill-pop{position:absolute;bottom:calc(100% + 8px);right:0;z-index:40;min-width:236px}',
|
|
251
|
+
'.cp-pill-card{background:var(--dsw-alias-bg-layer-1);border:0.5px solid var(--dsw-alias-border-l2);border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.16);padding:12px 14px;display:flex;flex-direction:column;gap:9px}',
|
|
252
|
+
'.cp-pill-title{font-size:12.5px;font-weight:600;color:var(--dsw-alias-label-primary)}',
|
|
253
|
+
'.cp-pill-row{display:flex;flex-direction:column;gap:4px}',
|
|
254
|
+
'.cp-pill-row-head{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:12px;color:var(--dsw-alias-label-primary)}',
|
|
255
|
+
'.cp-pill-percent{font-variant-numeric:tabular-nums;font-weight:600}',
|
|
256
|
+
'.cp-pill-track{height:4px;border-radius:999px;background:var(--dsw-alias-bg-layer-3);overflow:hidden}',
|
|
257
|
+
'.cp-pill-fill{height:100%;border-radius:999px}',
|
|
258
|
+
'.cp-pill-note,.cp-pill-at{font-size:11px;color:var(--dsw-alias-label-tertiary)}',
|
|
259
|
+
'.cp-pill-at{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:0.5px solid var(--dsw-alias-border-l2);padding-top:8px}',
|
|
260
|
+
'.cp-pill-fail{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:12px;color:var(--dsw-alias-label-tertiary)}',
|
|
261
|
+
'.cp-pill-retry{border:0;background:0 0;color:var(--dsw-alias-brand-primary);font:inherit;font-size:11.5px;cursor:pointer;padding:0}',
|
|
262
|
+
'.cp-pill-retry:hover{text-decoration:underline}',
|
|
244
263
|
'.cp-model-hidden .cp-mono,.cp-model-hidden .cp-check{opacity:.55}',
|
|
245
264
|
].join('')
|
|
246
265
|
|
|
@@ -306,6 +325,12 @@ window.__ModuleLoader__.load({
|
|
|
306
325
|
usageHours: '{n} 小时',
|
|
307
326
|
usageMinutes: '{n} 分钟',
|
|
308
327
|
usageRefresh: '刷新',
|
|
328
|
+
usagePillTitle: 'Cline Pass 用量',
|
|
329
|
+
usagePillUnavailable: '用量不可用',
|
|
330
|
+
usagePillRetry: '重试',
|
|
331
|
+
usagePillUpdatedAt: '更新于 {time}',
|
|
332
|
+
usagePillLabel: 'Pass',
|
|
333
|
+
usagePillWeekly: '周',
|
|
309
334
|
usageLoading: '正在读取官方额度…',
|
|
310
335
|
usageUnavailable: '暂时读不到官方额度。',
|
|
311
336
|
usageEmpty: '官方未返回额度窗口。',
|
|
@@ -389,6 +414,12 @@ window.__ModuleLoader__.load({
|
|
|
389
414
|
usageHours: '{n}h',
|
|
390
415
|
usageMinutes: '{n}m',
|
|
391
416
|
usageRefresh: 'Refresh',
|
|
417
|
+
usagePillTitle: 'Cline Pass usage',
|
|
418
|
+
usagePillUnavailable: 'Usage unavailable',
|
|
419
|
+
usagePillRetry: 'Retry',
|
|
420
|
+
usagePillUpdatedAt: 'Updated {time}',
|
|
421
|
+
usagePillLabel: 'Pass',
|
|
422
|
+
usagePillWeekly: 'wk',
|
|
392
423
|
usageLoading: 'Reading the official quota…',
|
|
393
424
|
usageUnavailable: 'The official quota is unavailable right now.',
|
|
394
425
|
usageEmpty: 'The gateway reported no quota windows.',
|
|
@@ -1839,10 +1870,164 @@ window.__ModuleLoader__.load({
|
|
|
1839
1870
|
|
|
1840
1871
|
//#endregion
|
|
1841
1872
|
|
|
1873
|
+
//#region usage pill
|
|
1874
|
+
|
|
1875
|
+
/**
|
|
1876
|
+
* The Cline Pass quota, next to the model selector.
|
|
1877
|
+
*
|
|
1878
|
+
* Only mounted while the selected model belongs to this provider: the
|
|
1879
|
+
* decision the pill informs is "should I use this model right now, and how
|
|
1880
|
+
* much of the window is left", and for any other model the reading is
|
|
1881
|
+
* irrelevant and the request would be wasted. The gate is the whole reason
|
|
1882
|
+
* this is a pill rather than a permanent widget.
|
|
1883
|
+
*/
|
|
1884
|
+
function UsagePill(props) {
|
|
1885
|
+
const state = React.useSyncExternalStore(props.directory.subscribe, props.directory.getSnapshot, props.directory.getSnapshot)
|
|
1886
|
+
const current = state.current
|
|
1887
|
+
if (current === null || current === undefined || String(current.provider) !== String(props.route())) return null
|
|
1888
|
+
return h(UsagePillActive, props)
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
/** The pill itself: a compact reading, expanding into the window breakdown. */
|
|
1892
|
+
function UsagePillActive(props) {
|
|
1893
|
+
const t = props.t
|
|
1894
|
+
const [snapshot, setSnapshot] = React.useState(null)
|
|
1895
|
+
const [failed, setFailed] = React.useState(false)
|
|
1896
|
+
const [open, setOpen] = React.useState(false)
|
|
1897
|
+
const root = React.useRef(null)
|
|
1898
|
+
// Kept across renders so the timer's callback always sees the latest one.
|
|
1899
|
+
const read = React.useRef(() => {})
|
|
1900
|
+
|
|
1901
|
+
React.useEffect(() => {
|
|
1902
|
+
let alive = true
|
|
1903
|
+
let busy = false
|
|
1904
|
+
/**
|
|
1905
|
+
* One reading, shared by the first paint, the timer and the retry.
|
|
1906
|
+
*
|
|
1907
|
+
* A hidden tab is skipped rather than served: a background poll keeps a
|
|
1908
|
+
* request alive for a number nobody is looking at, and the visibility
|
|
1909
|
+
* handler below refreshes the moment the tab comes back.
|
|
1910
|
+
*/
|
|
1911
|
+
const load = async (manual) => {
|
|
1912
|
+
if (busy) return
|
|
1913
|
+
if (manual !== true && typeof document !== 'undefined' && document.visibilityState === 'hidden') return
|
|
1914
|
+
busy = true
|
|
1915
|
+
try {
|
|
1916
|
+
const value = await props.readUsage()
|
|
1917
|
+
if (alive) { setSnapshot({ value, at: Date.now() }); setFailed(false) }
|
|
1918
|
+
} catch {
|
|
1919
|
+
// The last good reading stays on screen — a failed poll is not worth
|
|
1920
|
+
// blanking a number that was true a minute ago.
|
|
1921
|
+
if (alive) setFailed(true)
|
|
1922
|
+
} finally {
|
|
1923
|
+
busy = false
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
read.current = () => { void load(true) }
|
|
1927
|
+
void load(true)
|
|
1928
|
+
const timer = setInterval(() => { void load(false) }, 60000)
|
|
1929
|
+
const onVisible = () => { if (document.visibilityState === 'visible') void load(false) }
|
|
1930
|
+
document.addEventListener('visibilitychange', onVisible)
|
|
1931
|
+
return () => {
|
|
1932
|
+
alive = false
|
|
1933
|
+
read.current = () => {}
|
|
1934
|
+
clearInterval(timer)
|
|
1935
|
+
document.removeEventListener('visibilitychange', onVisible)
|
|
1936
|
+
}
|
|
1937
|
+
}, [props.readUsage])
|
|
1938
|
+
|
|
1939
|
+
// A click anywhere else closes the popover, which is what a one-shot
|
|
1940
|
+
// disclosure is expected to do; the listener only exists while it is open.
|
|
1941
|
+
React.useEffect(() => {
|
|
1942
|
+
if (!open) return undefined
|
|
1943
|
+
const onDown = (event) => {
|
|
1944
|
+
if (root.current !== null && event.target instanceof Node && !root.current.contains(event.target)) setOpen(false)
|
|
1945
|
+
}
|
|
1946
|
+
document.addEventListener('mousedown', onDown)
|
|
1947
|
+
return () => document.removeEventListener('mousedown', onDown)
|
|
1948
|
+
}, [open])
|
|
1949
|
+
|
|
1950
|
+
const accounts = snapshot?.value?.usage?.accounts ?? []
|
|
1951
|
+
const account = accounts.find((entry) => entry.ok === true) ?? accounts[0]
|
|
1952
|
+
const limits = account?.limits ?? []
|
|
1953
|
+
const byType = new Map(limits.map((limit) => [String(limit.type), limit]))
|
|
1954
|
+
const percentOf = (type) => {
|
|
1955
|
+
const limit = byType.get(type)
|
|
1956
|
+
return limit === undefined ? null : Math.max(0, Math.min(100, Math.round(Number(limit.percentUsed ?? 0))))
|
|
1957
|
+
}
|
|
1958
|
+
const five = percentOf('five_hour')
|
|
1959
|
+
const weekly = percentOf('weekly')
|
|
1960
|
+
|
|
1961
|
+
// The collapsed reading names the two windows a user paces against; a
|
|
1962
|
+
// missing one is dropped rather than shown as an empty slot.
|
|
1963
|
+
const parts = []
|
|
1964
|
+
if (five !== null) parts.push(`${t('usageFiveHour')} ${five}%`)
|
|
1965
|
+
if (weekly !== null) parts.push(`${t('usagePillWeekly')} ${weekly}%`)
|
|
1966
|
+
const summary = [t('usagePillLabel'), ...parts].join(' · ')
|
|
1967
|
+
|
|
1968
|
+
const rows = limits.length === 0 ? null : USAGE_WINDOWS
|
|
1969
|
+
.map(([type, key]) => [type, key, byType.get(type)])
|
|
1970
|
+
.filter(([, , limit]) => limit !== undefined)
|
|
1971
|
+
.concat(limits
|
|
1972
|
+
.filter((limit) => !USAGE_WINDOWS.some(([type]) => type === String(limit.type)))
|
|
1973
|
+
.map((limit) => [String(limit.type), '', limit]))
|
|
1974
|
+
|
|
1975
|
+
const body = failed && snapshot === null
|
|
1976
|
+
? h('div', { key: 'fail', className: 'cp-pill-fail' }, [
|
|
1977
|
+
h('span', { key: 'm' }, t('usagePillUnavailable')),
|
|
1978
|
+
h('button', { key: 'r', type: 'button', className: 'cp-pill-retry', onClick: () => read.current() }, t('usagePillRetry')),
|
|
1979
|
+
])
|
|
1980
|
+
: h('div', { key: 'card', className: 'cp-pill-card' }, [
|
|
1981
|
+
h('div', { key: 'title', className: 'cp-pill-title' }, t('usagePillTitle')),
|
|
1982
|
+
rows === null
|
|
1983
|
+
? h('div', { key: 'empty', className: 'cp-pill-fail' }, t('usagePillUnavailable'))
|
|
1984
|
+
: rows.map(([type, key, limit]) => {
|
|
1985
|
+
const percent = Math.max(0, Math.min(100, Number(limit.percentUsed ?? 0)))
|
|
1986
|
+
const left = countdown(t, limit.resetsAt)
|
|
1987
|
+
return h('div', { key: type, className: 'cp-pill-row' }, [
|
|
1988
|
+
h('div', { key: 'head', className: 'cp-pill-row-head' }, [
|
|
1989
|
+
h('span', { key: 'l' }, key === '' ? type : t(key)),
|
|
1990
|
+
h('span', { key: 'v', className: 'cp-pill-percent' }, `${Math.round(percent)}%`),
|
|
1991
|
+
]),
|
|
1992
|
+
h('div', { key: 'track', className: 'cp-pill-track' },
|
|
1993
|
+
h('div', { key: 'fill', className: 'cp-pill-fill', style: { width: `${percent}%`, background: usageColor(percent) } })),
|
|
1994
|
+
left === '' ? null : h('div', { key: 'note', className: 'cp-pill-note' }, fill(t('usageResetsIn'), { t: left })),
|
|
1995
|
+
])
|
|
1996
|
+
}),
|
|
1997
|
+
snapshot === null ? null : h('div', { key: 'at', className: 'cp-pill-at' }, [
|
|
1998
|
+
h('span', { key: 's' }, fill(t('usagePillUpdatedAt'), { time: stamp(snapshot.at) })),
|
|
1999
|
+
h('button', { key: 'r', type: 'button', className: 'cp-pill-retry', onClick: () => read.current() }, t('usageRefresh')),
|
|
2000
|
+
]),
|
|
2001
|
+
])
|
|
2002
|
+
|
|
2003
|
+
return h('div', { key: 'pill', ref: root, className: 'cp-pill' }, [
|
|
2004
|
+
h('button', {
|
|
2005
|
+
key: 'btn',
|
|
2006
|
+
type: 'button',
|
|
2007
|
+
className: 'cp-pill-btn',
|
|
2008
|
+
'aria-expanded': open,
|
|
2009
|
+
onClick: () => setOpen((was) => !was),
|
|
2010
|
+
}, snapshot === null ? t('usagePillTitle') : summary === '' ? t('usagePillTitle') : summary),
|
|
2011
|
+
open ? h('div', { key: 'pop', className: 'cp-pill-pop' }, body) : null,
|
|
2012
|
+
])
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
/** A local timestamp, matching the panel's own reading of "updated at". */
|
|
2016
|
+
function stamp(ms) {
|
|
2017
|
+
const at = new Date(Number(ms ?? 0))
|
|
2018
|
+
const pad = (n) => String(n).padStart(2, '0')
|
|
2019
|
+
return `${at.getFullYear()}/${at.getMonth() + 1}/${at.getDate()} ${pad(at.getHours())}:${pad(at.getMinutes())}:${pad(at.getSeconds())}`
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
//#endregion
|
|
2023
|
+
|
|
1842
2024
|
//#region plugin
|
|
1843
2025
|
|
|
1844
|
-
/**
|
|
1845
|
-
|
|
2026
|
+
/**
|
|
2027
|
+
* The slot registry, the browser RPC transport, and the model-selection
|
|
2028
|
+
* directory the usage pill reads the selected provider from.
|
|
2029
|
+
*/
|
|
2030
|
+
const inject = ['slots', 'connection', 'modelDirectories']
|
|
1846
2031
|
|
|
1847
2032
|
/**
|
|
1848
2033
|
* Mount the Cline Pass configuration surface.
|
|
@@ -1903,6 +2088,24 @@ window.__ModuleLoader__.load({
|
|
|
1903
2088
|
inject: face,
|
|
1904
2089
|
}, renderQuick))
|
|
1905
2090
|
|
|
2091
|
+
// 3. The quota reading beside the model selector. It mounts for every
|
|
2092
|
+
// session but renders nothing unless the selected model is this
|
|
2093
|
+
// provider's, so no other model pays for a usage request.
|
|
2094
|
+
ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
|
|
2095
|
+
name: 'conversation.input.right',
|
|
2096
|
+
id: `${NS}-usage`,
|
|
2097
|
+
order: 100,
|
|
2098
|
+
inject: (sessionId) => ({
|
|
2099
|
+
directory: ctx.modelDirectories.directoryFor(sessionId).store,
|
|
2100
|
+
// The route name is configuration, so it is only known once a state
|
|
2101
|
+
// read has landed. Read it at render time rather than capturing it:
|
|
2102
|
+
// the pill can mount before the first read completes.
|
|
2103
|
+
route: () => controller.store.getSnapshot().data?.provider ?? NS,
|
|
2104
|
+
readUsage: async () => await controller.call('usage', {}),
|
|
2105
|
+
t: translate,
|
|
2106
|
+
}),
|
|
2107
|
+
}, UsagePill))
|
|
2108
|
+
|
|
1906
2109
|
// The first read is lazy (each component asks for it), because the RPC
|
|
1907
2110
|
// transport only exists once Connection has activated.
|
|
1908
2111
|
void controller.refresh()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cline-pass",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Self-contained Cline Pass provider for dsh: register Cline Pass subscription models on the harness LLM seam, enumerate and validate the upstream channels behind each model, pin or exclude them with failover, and drive an account pool. No proxy process and no external cline-pass-switcher install required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
"platform": "web",
|
|
53
53
|
"inject": [
|
|
54
54
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
55
|
-
"@deepseek-ai/dsh-client-connection"
|
|
55
|
+
"@deepseek-ai/dsh-client-connection",
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-model-selection"
|
|
56
57
|
]
|
|
57
58
|
},
|
|
58
59
|
"compatibility": {
|