@goodandready/dsh-subscriptions 0.7.20 → 0.7.22
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-manager.js +1 -0
- package/lib/adapter.js +10 -1
- package/lib/alerts.js +13 -0
- package/lib/client.css +2 -0
- package/lib/client.js +53 -23
- package/lib/images.js +32 -0
- package/lib/import-auth.js +1 -1
- package/lib/instructions.js +2 -2
- package/lib/messages.js +36 -9
- package/lib/vendors/claude-cli.js +28 -5
- package/lib/vendors/claude.js +9 -7
- package/package.json +1 -1
package/lib/adapter-manager.js
CHANGED
|
@@ -68,6 +68,7 @@ export function createAdapterManager({
|
|
|
68
68
|
recordHistory,
|
|
69
69
|
fetchImpl: fetch,
|
|
70
70
|
fetchForRef,
|
|
71
|
+
attachments: () => (typeof ctx.get === 'function' ? ctx.get('attachments') : null),
|
|
71
72
|
hideDeprecatedModels: () => Boolean(live().hideDeprecatedModels),
|
|
72
73
|
cascadingFallback: (...args) => cascadeFallbackStream(...args),
|
|
73
74
|
ollamaFallback: typeof ollamaFallback === 'function' ? ollamaFallback : async function* ({ err }) { yield { type: 'finish', reason: { kind: 'error', failure: { code: err?.code || 'RATE_LIMIT', message: err?.message || 'all subscription accounts exhausted' } } } },
|
package/lib/adapter.js
CHANGED
|
@@ -13,6 +13,7 @@ import { pickFetch } from './proxy.js'
|
|
|
13
13
|
import { quotaSnapshot, parseBody } from './ratelimit.js'
|
|
14
14
|
import { toTokenUsage } from './wire.js'
|
|
15
15
|
import { isSwitchableError } from './rotate.js'
|
|
16
|
+
import { hydrateAttachmentImages } from './images.js'
|
|
16
17
|
|
|
17
18
|
// #94: isDeprecatedId imported from ./models-catalog.js
|
|
18
19
|
|
|
@@ -156,8 +157,16 @@ export class SubscriptionAdapter extends LlmAdapter {
|
|
|
156
157
|
const sessionId = options.sessionId || options.session_id || options.conversationId
|
|
157
158
|
const effectiveReasoning = resolveEffectiveReasoning(sessionId, options)
|
|
158
159
|
const decisionTrace = {}
|
|
159
|
-
const
|
|
160
|
+
const hydratedOptions = {
|
|
160
161
|
...options,
|
|
162
|
+
messages: await hydrateAttachmentImages(
|
|
163
|
+
options.messages,
|
|
164
|
+
typeof deps.attachments === 'function' ? deps.attachments() : null,
|
|
165
|
+
options.signal,
|
|
166
|
+
),
|
|
167
|
+
}
|
|
168
|
+
const streamOptions = {
|
|
169
|
+
...hydratedOptions,
|
|
161
170
|
...(provider === 'codex' && getSpeedMode(sessionId) === 'fast' ? { service_tier: 'priority' } : {}),
|
|
162
171
|
...(effectiveReasoning.effort ? { reasoningEffort: effectiveReasoning.effort } : {}),
|
|
163
172
|
...(effectiveReasoning.budgetTokens ? { thinkingBudget: effectiveReasoning.budgetTokens } : {}),
|
package/lib/alerts.js
CHANGED
|
@@ -56,11 +56,19 @@ export function toNotifyPayload(body) {
|
|
|
56
56
|
}
|
|
57
57
|
const DEDUP_WINDOW_MS = 10 * 60 * 1000 // 10 minutes cooldown per (ref + type)
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Last billing status announced per account. A condition that simply
|
|
61
|
+
* persists — the provider page still says `past_due` — is news the first
|
|
62
|
+
* time and noise on every later dedupe window.
|
|
63
|
+
*/
|
|
64
|
+
const lastBillingStatus = new Map()
|
|
65
|
+
|
|
59
66
|
const alertBuffer = []
|
|
60
67
|
const lastAlertTime = new Map()
|
|
61
68
|
|
|
62
69
|
export function clearAlerts() {
|
|
63
70
|
alertBuffer.length = 0
|
|
71
|
+
lastBillingStatus.clear()
|
|
64
72
|
lastAlertTime.clear()
|
|
65
73
|
}
|
|
66
74
|
|
|
@@ -309,6 +317,11 @@ export function notifyBillingAlert({
|
|
|
309
317
|
? `Billing alert: ${provider || 'Account'} (${ref || 'slot'}) has payment issue (${status}).`
|
|
310
318
|
: `Subscription renewal alert: ${provider || 'Account'} (${ref || 'slot'}) renewal is overdue.`
|
|
311
319
|
|
|
320
|
+
// Announce a billing state once, and re-arm when it clears.
|
|
321
|
+
const billingKey = `${provider || "account"}:${ref || "slot"}`
|
|
322
|
+
if (lastBillingStatus.get(billingKey) === status) return null
|
|
323
|
+
lastBillingStatus.set(billingKey, status)
|
|
324
|
+
|
|
312
325
|
const alert = recordAlert({
|
|
313
326
|
type: 'billing_status',
|
|
314
327
|
ref,
|
package/lib/client.css
CHANGED
|
@@ -5,3 +5,5 @@
|
|
|
5
5
|
.dsub-tabs{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 12px}.dsub-tab{appearance:none;font:inherit;font-size:12px;font-weight:600;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:5px 12px}.dsub-tab:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-tertiary)}.dsub-tabActive{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary-foreground)}.dsub-wrap[data-tab=overview]>:not([data-pane=overview]):not(.dsub-header-bar):not(.dsub-tabs):not(.dsub-foot):not(.dsub-modalWrap),.dsub-wrap[data-tab=accounts]>:not([data-pane=accounts]):not(.dsub-header-bar):not(.dsub-tabs):not(.dsub-foot):not(.dsub-modalWrap),.dsub-wrap[data-tab=routing]>:not([data-pane=routing]):not(.dsub-header-bar):not(.dsub-tabs):not(.dsub-foot):not(.dsub-modalWrap),.dsub-wrap[data-tab=models]>:not([data-pane=models]):not(.dsub-header-bar):not(.dsub-tabs):not(.dsub-foot):not(.dsub-modalWrap),.dsub-wrap[data-tab=vault]>:not([data-pane=vault]):not(.dsub-header-bar):not(.dsub-tabs):not(.dsub-foot):not(.dsub-modalWrap),.dsub-wrap[data-tab=notifications]>:not([data-pane=notifications]):not(.dsub-header-bar):not(.dsub-tabs):not(.dsub-foot):not(.dsub-modalWrap),.dsub-wrap[data-tab=advanced]>:not([data-pane=advanced]):not(.dsub-header-bar):not(.dsub-tabs):not(.dsub-foot):not(.dsub-modalWrap){display:none}
|
|
6
6
|
|
|
7
7
|
.dsub-wrap[data-tab=overview]>[data-pane]:not([data-pane=overview]),.dsub-wrap[data-tab=accounts]>[data-pane]:not([data-pane=accounts]),.dsub-wrap[data-tab=routing]>[data-pane]:not([data-pane=routing]),.dsub-wrap[data-tab=models]>[data-pane]:not([data-pane=models]),.dsub-wrap[data-tab=vault]>[data-pane]:not([data-pane=vault]),.dsub-wrap[data-tab=notifications]>[data-pane]:not([data-pane=notifications]),.dsub-wrap[data-tab=advanced]>[data-pane]:not([data-pane=advanced]),.dsub-wrap[data-tab=overview] [data-pane]:not([data-pane=overview]),.dsub-wrap[data-tab=accounts] [data-pane]:not([data-pane=accounts]),.dsub-wrap[data-tab=routing] [data-pane]:not([data-pane=routing]),.dsub-wrap[data-tab=models] [data-pane]:not([data-pane=models]),.dsub-wrap[data-tab=vault] [data-pane]:not([data-pane=vault]),.dsub-wrap[data-tab=notifications] [data-pane]:not([data-pane=notifications]),.dsub-wrap[data-tab=advanced] [data-pane]:not([data-pane=advanced]),.dsub-wrap[data-tab=overview]>.dsub-block:not([data-pane]),.dsub-wrap[data-tab=accounts]>.dsub-block:not([data-pane]){display:none}
|
|
8
|
+
|
|
9
|
+
.dsub-accountCardAction{cursor:pointer}.dsub-accountCardAction:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}
|
package/lib/client.js
CHANGED
|
@@ -3585,6 +3585,7 @@ h('div', { className: 'dsub-row' },
|
|
|
3585
3585
|
const [state, setState] = React.useState({ logged: [], usage: 0, accounts: [], open: false, active: null, loading: false })
|
|
3586
3586
|
const [toasts, setToasts] = React.useState([])
|
|
3587
3587
|
const lastAlertTsRef = React.useRef(Date.now() - 30000)
|
|
3588
|
+
const lastErrorRef = React.useRef({ text: '', at: 0 })
|
|
3588
3589
|
|
|
3589
3590
|
const dismissToast = (id) => {
|
|
3590
3591
|
setToasts((prev) => prev.filter((t) => t.id !== id))
|
|
@@ -3592,11 +3593,23 @@ h('div', { className: 'dsub-row' },
|
|
|
3592
3593
|
const reportError = (message) => {
|
|
3593
3594
|
const text = cleanErrorMessage(message)
|
|
3594
3595
|
if (!text) return
|
|
3596
|
+
const now = Date.now()
|
|
3597
|
+
const last = lastErrorRef.current
|
|
3598
|
+
if (last.text === text && now - last.at < 60000) return
|
|
3599
|
+
lastErrorRef.current = { text: text, at: now }
|
|
3600
|
+
const id = 'err_' + now
|
|
3595
3601
|
setToasts((prev) => prev.some((toast) => toast.message === text)
|
|
3596
3602
|
? prev
|
|
3597
|
-
: [{ id:
|
|
3603
|
+
: [{ id: id, message: text, cls: 'dsub-toast-bad' }, ...prev].slice(0, 3))
|
|
3604
|
+
setTimeout(() => dismissToast(id), 12000)
|
|
3598
3605
|
}
|
|
3599
3606
|
|
|
3607
|
+
const hostDown = (() => { let last = 0; return (msg) => { const now = Date.now(); if (now - last < 120000) return; last = now; reportError(msg) } })()
|
|
3608
|
+
const fetchConfig = (attempt) => fetch('/dsh-subscriptions/config', { cache: 'no-store' }).catch((e) => {
|
|
3609
|
+
if ((attempt || 0) < 1) return new Promise((res) => setTimeout(res, 700)).then(() => fetchConfig((attempt || 0) + 1))
|
|
3610
|
+
throw e
|
|
3611
|
+
})
|
|
3612
|
+
|
|
3600
3613
|
const pollAlerts = () => {
|
|
3601
3614
|
fetch('/dsh-subscriptions/alerts?limit=5', { cache: 'no-store' })
|
|
3602
3615
|
.then((res) => res.json())
|
|
@@ -3615,25 +3628,25 @@ h('div', { className: 'dsub-row' },
|
|
|
3615
3628
|
}
|
|
3616
3629
|
}
|
|
3617
3630
|
})
|
|
3618
|
-
.catch((e) =>
|
|
3631
|
+
.catch((e) => hostDown(t('hostOffline') + ': ' + String(e && e.message || e)))
|
|
3619
3632
|
}
|
|
3620
3633
|
|
|
3621
3634
|
const pull = () => {
|
|
3622
3635
|
setState((s) => Object.assign({}, s, { loading: true }))
|
|
3623
3636
|
refreshLoggedIn().then((r) => {
|
|
3624
|
-
if (r.error)
|
|
3637
|
+
if (r.error) hostDown(t('hostOffline') + ': ' + r.error)
|
|
3625
3638
|
const logged = r.logged || []
|
|
3626
|
-
|
|
3639
|
+
fetchConfig(0)
|
|
3627
3640
|
.then((res) => res.json())
|
|
3628
3641
|
.then((cfg) => {
|
|
3629
3642
|
setState((s) => Object.assign({}, s, { logged, usage: r.usage, accounts: (cfg && cfg.accounts) || [], active: r.active, loading: false }))
|
|
3630
3643
|
})
|
|
3631
3644
|
.catch((e) => {
|
|
3632
|
-
|
|
3645
|
+
hostDown(t('hostOffline') + ': ' + String(e && e.message || e))
|
|
3633
3646
|
setState((s) => Object.assign({}, s, { logged, usage: r.usage, accounts: [], active: r.active, loading: false }))
|
|
3634
3647
|
})
|
|
3635
3648
|
}).catch((e) => {
|
|
3636
|
-
|
|
3649
|
+
hostDown(t('hostOffline') + ': ' + String(e && e.message || e))
|
|
3637
3650
|
setState((s) => Object.assign({}, s, { loading: false }))
|
|
3638
3651
|
})
|
|
3639
3652
|
}
|
|
@@ -3747,6 +3760,25 @@ h('div', { className: 'dsub-row' },
|
|
|
3747
3760
|
}, [state.accounts, now])
|
|
3748
3761
|
|
|
3749
3762
|
// Accounts list
|
|
3763
|
+
const openSettings = () => {
|
|
3764
|
+
setState((s) => Object.assign({}, s, { open: false }))
|
|
3765
|
+
const clientCtx = props.ctx || window.__dshCtx
|
|
3766
|
+
|
|
3767
|
+
try {
|
|
3768
|
+
const nav = (clientCtx && clientCtx.pluginNavigation) || (window.__dshCtx && window.__dshCtx.pluginNavigation)
|
|
3769
|
+
if (nav && typeof nav.openBundle === 'function') {
|
|
3770
|
+
nav.openBundle(PKG)
|
|
3771
|
+
}
|
|
3772
|
+
} catch { /* bestEffort */ }
|
|
3773
|
+
|
|
3774
|
+
try {
|
|
3775
|
+
const layout = (clientCtx && clientCtx.layout) || (window.__dshCtx && window.__dshCtx.layout)
|
|
3776
|
+
if (layout && typeof layout.selectPanel === 'function') {
|
|
3777
|
+
layout.selectPanel('plugins')
|
|
3778
|
+
}
|
|
3779
|
+
} catch { /* bestEffort */ }
|
|
3780
|
+
}
|
|
3781
|
+
|
|
3750
3782
|
const rows = state.accounts.map((a, i) => {
|
|
3751
3783
|
const isLogged = state.logged.includes(a.provider)
|
|
3752
3784
|
const cooled = a.cooldownUntil && a.cooldownUntil > now
|
|
@@ -3771,7 +3803,20 @@ h('div', { className: 'dsub-row' },
|
|
|
3771
3803
|
else { statusClass = 'dsub-statusOk'; statusText = (pct != null ? pct + '% · ' : '') + t('subsLogged') }
|
|
3772
3804
|
}
|
|
3773
3805
|
|
|
3774
|
-
return h('div', {
|
|
3806
|
+
return h('div', {
|
|
3807
|
+
className: 'dsub-accountCard dsub-accountCardAction',
|
|
3808
|
+
key: i,
|
|
3809
|
+
role: 'button',
|
|
3810
|
+
tabIndex: 0,
|
|
3811
|
+
title: t('subsOpenSettings'),
|
|
3812
|
+
onClick: openSettings,
|
|
3813
|
+
onKeyDown: (e) => {
|
|
3814
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
3815
|
+
e.preventDefault()
|
|
3816
|
+
openSettings()
|
|
3817
|
+
}
|
|
3818
|
+
},
|
|
3819
|
+
},
|
|
3775
3820
|
h('div', { className: 'dsub-brandBadge ' + b.cls }, b.icon),
|
|
3776
3821
|
h('div', { className: 'dsub-accInfo' },
|
|
3777
3822
|
h('div', { className: 'dsub-accNameRow' },
|
|
@@ -3856,22 +3901,7 @@ h('div', { className: 'dsub-row' },
|
|
|
3856
3901
|
href: '#',
|
|
3857
3902
|
onClick: (e) => {
|
|
3858
3903
|
e.preventDefault()
|
|
3859
|
-
|
|
3860
|
-
const clientCtx = props.ctx || window.__dshCtx
|
|
3861
|
-
|
|
3862
|
-
try {
|
|
3863
|
-
const nav = (clientCtx && clientCtx.pluginNavigation) || (window.__dshCtx && window.__dshCtx.pluginNavigation)
|
|
3864
|
-
if (nav && typeof nav.openBundle === 'function') {
|
|
3865
|
-
nav.openBundle(PKG)
|
|
3866
|
-
}
|
|
3867
|
-
} catch { /* bestEffort */ }
|
|
3868
|
-
|
|
3869
|
-
try {
|
|
3870
|
-
const layout = (clientCtx && clientCtx.layout) || (window.__dshCtx && window.__dshCtx.layout)
|
|
3871
|
-
if (layout && typeof layout.selectPanel === 'function') {
|
|
3872
|
-
layout.selectPanel('plugins')
|
|
3873
|
-
}
|
|
3874
|
-
} catch { /* bestEffort */ }
|
|
3904
|
+
openSettings()
|
|
3875
3905
|
},
|
|
3876
3906
|
}, t('subsOpenSettings')),
|
|
3877
3907
|
),
|
package/lib/images.js
CHANGED
|
@@ -174,6 +174,38 @@ export function normalizeImage(item) {
|
|
|
174
174
|
return null
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
export async function hydrateAttachmentImages(messages, attachments, signal) {
|
|
178
|
+
if (!attachments || typeof attachments.readImageRequest !== 'function' || !Array.isArray(messages)) return messages
|
|
179
|
+
return Promise.all(messages.map(async (message) => {
|
|
180
|
+
if (!message || !Array.isArray(message.content)) return message
|
|
181
|
+
const content = await Promise.all(message.content.map(async (block) => {
|
|
182
|
+
if (!block || block.type !== 'image' || !block.attachment) return block
|
|
183
|
+
try {
|
|
184
|
+
const ref = block.attachment
|
|
185
|
+
const version = await attachments.readImageRequest(ref, {
|
|
186
|
+
width: Number(ref.width) || 1568,
|
|
187
|
+
height: Number(ref.height) || 1568,
|
|
188
|
+
maxBytes: 5 * 1024 * 1024,
|
|
189
|
+
}, signal)
|
|
190
|
+
return {
|
|
191
|
+
type: 'image',
|
|
192
|
+
source: {
|
|
193
|
+
type: 'base64',
|
|
194
|
+
media_type: version.mediaType || ref.mediaType || 'image/jpeg',
|
|
195
|
+
data: Buffer.from(version.data).toString('base64'),
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
} catch {
|
|
199
|
+
return {
|
|
200
|
+
type: 'text',
|
|
201
|
+
text: '[image attachment unavailable: ' + String(block.attachment.attachmentId || block.attachment.name || 'unknown') + ']',
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}))
|
|
205
|
+
return { ...message, content }
|
|
206
|
+
}))
|
|
207
|
+
}
|
|
208
|
+
|
|
177
209
|
export function extractImages(content) {
|
|
178
210
|
if (!Array.isArray(content)) {
|
|
179
211
|
const norm = normalizeImage(content)
|
package/lib/import-auth.js
CHANGED
|
@@ -255,7 +255,7 @@ async function discoverLocalCliSessionsInner() {
|
|
|
255
255
|
const claudeEntry = {
|
|
256
256
|
provider: 'claude',
|
|
257
257
|
source: 'file',
|
|
258
|
-
path: homeFile('.claude', 'credentials.json'),
|
|
258
|
+
path: claudeCreds.sourcePath || homeFile('.claude', '.credentials.json'),
|
|
259
259
|
email: claudeCreds.email || 'Claude Code CLI User',
|
|
260
260
|
hasRefreshToken: !!claudeCreds.refreshToken,
|
|
261
261
|
}
|
package/lib/instructions.js
CHANGED
|
@@ -17,12 +17,12 @@ export const INSTRUCTIONS = {
|
|
|
17
17
|
title: "Claude Code",
|
|
18
18
|
stepsEn: [
|
|
19
19
|
"1. Run \"claude login\" in server console.",
|
|
20
|
-
"2. Click \"📥 From CLI\" — plugin will import ~/.claude
|
|
20
|
+
"2. Click \"📥 From CLI\" — plugin will import ~/.claude/.credentials.json.",
|
|
21
21
|
"3. Or paste your Session Token / API Key from Anthropic Console and click \"Save token\".",
|
|
22
22
|
],
|
|
23
23
|
stepsZh: [
|
|
24
24
|
"1. 在服务器控制台运行 \"claude login\"。",
|
|
25
|
-
"2. 点击 \"📥 从 CLI 导入\" — 插件将自动导入 ~/.claude
|
|
25
|
+
"2. 点击 \"📥 从 CLI 导入\" — 插件将自动导入 ~/.claude/.credentials.json。",
|
|
26
26
|
"3. 或者从 Anthropic 控制台复制您的 Session Token 或 API Key 并点击 \"保存令牌\"。",
|
|
27
27
|
],
|
|
28
28
|
link: "https://console.anthropic.com/settings/keys",
|
package/lib/messages.js
CHANGED
|
@@ -338,14 +338,24 @@ export function anthropicPayload(options, extraSystem) {
|
|
|
338
338
|
input: safeJson(call.arguments),
|
|
339
339
|
})
|
|
340
340
|
}
|
|
341
|
-
messages.push({ role: "assistant", content: blocks.length ? blocks : [{ type: "text", text: "" }] })
|
|
341
|
+
messages.push({ role: "assistant", content: blocks.length ? blocks : [{ type: "text", text: " " }] })
|
|
342
342
|
continue
|
|
343
343
|
}
|
|
344
344
|
if (message.role === "tool") {
|
|
345
345
|
const toolUseId = String(message.toolCallId || message.tool_call_id || message.tool_use_id || message.id || "")
|
|
346
|
-
const
|
|
346
|
+
const images = extractImages(message.content)
|
|
347
|
+
let content = typeof message.content === "string"
|
|
347
348
|
? message.content
|
|
348
349
|
: (flattenText(message.content) || JSON.stringify(message.content || ""))
|
|
350
|
+
if (images.length) {
|
|
351
|
+
const blocks = []
|
|
352
|
+
if (content) blocks.push({ type: "text", text: content })
|
|
353
|
+
for (const img of images) {
|
|
354
|
+
const blk = toAnthropicImage(img)
|
|
355
|
+
if (blk) blocks.push(blk)
|
|
356
|
+
}
|
|
357
|
+
if (blocks.length) content = blocks
|
|
358
|
+
}
|
|
349
359
|
messages.push({
|
|
350
360
|
role: "user",
|
|
351
361
|
content: [{
|
|
@@ -375,7 +385,7 @@ export function anthropicPayload(options, extraSystem) {
|
|
|
375
385
|
content: content || "",
|
|
376
386
|
})
|
|
377
387
|
}
|
|
378
|
-
messages.push({ role: "user", content: blocks.length ? blocks : [{ type: "text", text: "" }] })
|
|
388
|
+
messages.push({ role: "user", content: blocks.length ? blocks : [{ type: "text", text: " " }] })
|
|
379
389
|
}
|
|
380
390
|
|
|
381
391
|
const useCacheControl = options.cacheControl !== false
|
|
@@ -415,11 +425,26 @@ export function anthropicPayload(options, extraSystem) {
|
|
|
415
425
|
const targetIdx = userTurnIndices[userTurnIndices.length - 2]
|
|
416
426
|
const targetMsg = messages[targetIdx]
|
|
417
427
|
if (Array.isArray(targetMsg.content) && targetMsg.content.length > 0) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
428
|
+
// Anthropic rejects cache_control on an empty text block, so stamp
|
|
429
|
+
// the last block that can carry it: non-empty text, or an image.
|
|
430
|
+
let targetBlockIdx = -1
|
|
431
|
+
for (let i = targetMsg.content.length - 1; i >= 0; i -= 1) {
|
|
432
|
+
const block = targetMsg.content[i]
|
|
433
|
+
if (block === null || block === undefined) continue
|
|
434
|
+
if (block.type === "image") {
|
|
435
|
+
targetBlockIdx = i
|
|
436
|
+
break
|
|
437
|
+
}
|
|
438
|
+
if (block.type === "text" && typeof block.text === "string" && block.text.trim() !== "") {
|
|
439
|
+
targetBlockIdx = i
|
|
440
|
+
break
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (targetBlockIdx >= 0) {
|
|
444
|
+
targetMsg.content[targetBlockIdx] = {
|
|
445
|
+
...targetMsg.content[targetBlockIdx],
|
|
446
|
+
cache_control: { type: "ephemeral" },
|
|
447
|
+
}
|
|
423
448
|
}
|
|
424
449
|
}
|
|
425
450
|
}
|
|
@@ -432,7 +457,9 @@ export function anthropicPayload(options, extraSystem) {
|
|
|
432
457
|
...(system ? { system } : {}),
|
|
433
458
|
messages,
|
|
434
459
|
...(tools && tools.length ? { tools } : {}),
|
|
435
|
-
|
|
460
|
+
// temperature is deliberately not sent to Anthropic: the API accepts
|
|
461
|
+
// only 1 while extended thinking is enabled, so forwarding the session
|
|
462
|
+
// value turns every other setting into a 400.
|
|
436
463
|
}
|
|
437
464
|
}
|
|
438
465
|
|
|
@@ -3,13 +3,20 @@ import { homedir } from 'node:os'
|
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Credential file names Claude Code has used, newest first. Current releases
|
|
7
|
+
* write `.credentials.json` (dotted); the undotted name is kept as a fallback so
|
|
8
|
+
* machines with an older or hand-made file keep working.
|
|
8
9
|
*/
|
|
9
|
-
export
|
|
10
|
-
|
|
10
|
+
export const CLAUDE_CREDENTIAL_FILES = ['.credentials.json', 'credentials.json']
|
|
11
|
+
|
|
12
|
+
/** Absolute candidate paths for the Claude Code OAuth grant, newest first. */
|
|
13
|
+
export function claudeCredentialsPaths(home) {
|
|
14
|
+
return CLAUDE_CREDENTIAL_FILES.map((file) => join(home || homedir(), '.claude', file))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function readGrantFrom(file) {
|
|
11
18
|
try {
|
|
12
|
-
const text = await readFile(
|
|
19
|
+
const text = await readFile(file, 'utf8')
|
|
13
20
|
const json = JSON.parse(text)
|
|
14
21
|
if (!json) return null
|
|
15
22
|
const oauth = json.claudeAiOauth || json.oauth || json
|
|
@@ -20,6 +27,7 @@ export async function readClaudeCredentials(home) {
|
|
|
20
27
|
expiresAt: oauth.expiresAt || (oauth.expires_in ? Date.now() + oauth.expires_in * 1000 : Date.now() + 3600000),
|
|
21
28
|
subscriptionType: oauth.subscriptionType || 'Claude Code',
|
|
22
29
|
email: oauth.email || json.email || 'Claude Code CLI User',
|
|
30
|
+
sourcePath: file,
|
|
23
31
|
}
|
|
24
32
|
}
|
|
25
33
|
return null
|
|
@@ -27,3 +35,18 @@ export async function readClaudeCredentials(home) {
|
|
|
27
35
|
return null
|
|
28
36
|
}
|
|
29
37
|
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Read Claude Code CLI credentials. `home` is a test seam: when omitted the
|
|
41
|
+
* real user home is used, so production behaviour is unchanged.
|
|
42
|
+
*
|
|
43
|
+
* Candidates are tried newest first, because Claude Code moved from
|
|
44
|
+
* `~/.claude/credentials.json` to the dotted `~/.claude/.credentials.json`.
|
|
45
|
+
*/
|
|
46
|
+
export async function readClaudeCredentials(home) {
|
|
47
|
+
for (const file of claudeCredentialsPaths(home)) {
|
|
48
|
+
const grant = await readGrantFrom(file)
|
|
49
|
+
if (grant) return grant
|
|
50
|
+
}
|
|
51
|
+
return null
|
|
52
|
+
}
|
package/lib/vendors/claude.js
CHANGED
|
@@ -73,10 +73,10 @@ export function defaults() {
|
|
|
73
73
|
redirectUri: 'https://console.anthropic.com/oauth/code/callback',
|
|
74
74
|
claudeCcVersion: '2.1.289.7e2',
|
|
75
75
|
models: [
|
|
76
|
-
{ id: 'claude-opus-5', name: 'Claude Opus 5' },
|
|
77
|
-
{ id: 'claude-sonnet-5', name: 'Claude Sonnet 5' },
|
|
78
|
-
{ id: 'claude-fable-5', name: 'Claude Fable 5' },
|
|
79
|
-
{ id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5' },
|
|
76
|
+
{ id: 'claude-opus-5', name: 'Claude Opus 5', inputModalities: ['text', 'image'] },
|
|
77
|
+
{ id: 'claude-sonnet-5', name: 'Claude Sonnet 5', inputModalities: ['text', 'image'] },
|
|
78
|
+
{ id: 'claude-fable-5', name: 'Claude Fable 5', inputModalities: ['text', 'image'] },
|
|
79
|
+
{ id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5', inputModalities: ['text', 'image'] },
|
|
80
80
|
],
|
|
81
81
|
systemPrefix: IDENTITY,
|
|
82
82
|
}
|
|
@@ -189,9 +189,10 @@ export async function listModels(blob, cfg, fetchImpl) {
|
|
|
189
189
|
const rawDefault = (cfg && cfg.models && cfg.models.length) ? cfg.models : defaults().models
|
|
190
190
|
const defaultCatalog = modelCatalog(id, rawDefault.map((entry) => {
|
|
191
191
|
const model = typeof entry === "string" ? { id: entry, name: entry } : { ...entry }
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
const withVision = { ...model, inputModalities: model.inputModalities || ['text', 'image'] }
|
|
193
|
+
const levels = effortLevelsForModel(withVision.id || withVision.name)
|
|
194
|
+
if (!levels) return withVision
|
|
195
|
+
return { ...withVision, reasoning: { efforts: levels.map((level) => ({ id: level, name: level })) } }
|
|
195
196
|
}))
|
|
196
197
|
|
|
197
198
|
if (!blob || !blob.accessToken) return defaultCatalog
|
|
@@ -223,6 +224,7 @@ export async function listModels(blob, cfg, fetchImpl) {
|
|
|
223
224
|
id: modelId,
|
|
224
225
|
name: item.display_name || modelId,
|
|
225
226
|
...(levels ? { reasoning: { efforts: levels.map((level) => ({ id: level, name: level })) } } : {}),
|
|
227
|
+
inputModalities: ['text', 'image'],
|
|
226
228
|
})
|
|
227
229
|
}
|
|
228
230
|
if (!rows.length) return defaultCatalog
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.22",
|
|
4
4
|
"description": "Use ChatGPT Codex, Claude, Grok, Antigravity, Kimi, GLM, Cursor, Kiro, Copilot, Qwen, ERNIE, Spark, JetBrains, Perplexity, Replit, and Cody subscriptions as DeepSeek Harness LLM providers via OAuth.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|