@goodandready/dsh-clinebot 0.6.0 → 0.6.2

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/client.js CHANGED
@@ -206,19 +206,9 @@ const en = {
206
206
  'stats.confirm_reset': 'Confirm Reset?',
207
207
  'stats.cancel_reset': 'Cancel',
208
208
  'diag.title': 'Diagnostics & Auto-Registration',
209
- 'diag.desc': 'ClineBot registers into DSH Models automatically when key is configured. You can run connection smoke test or force re-sync.',
209
+ 'diag.desc': 'ClineBot registers into DSH Models automatically when a key is configured. You can run a connection smoke test here.',
210
210
  'diag.smoke_btn': 'Run Smoke Test (Ping)',
211
211
  'diag.smoke_testing': 'Testing…',
212
- 'diag.resync_btn': 'Force Re-sync to DSH',
213
- 'diag.resyncing': 'Syncing…',
214
- 'diag.resynced_msg': 'Provider re-synced to DSH Models ({count} models)',
215
- 'diag.unregister_btn': 'Unregister from DSH',
216
- 'diag.unregistering': 'Removing…',
217
- 'diag.unregistered_msg': 'Provider removed from DSH Models',
218
- 'diag.proxy_title': 'Transparent Loopback Proxy',
219
- 'diag.proxy_desc': 'Enables multi-account failover, sticky sessions, and telemetry via local loopback proxy. Disable to connect directly to upstream endpoint.',
220
- 'diag.proxy_enabled': 'Proxy Mode Enabled (Failover & Telemetry Active)',
221
- 'diag.proxy_disabled': 'Direct Mode (Direct Upstream, No Proxy)',
222
212
  'update.available': 'Update available: v{latestVersion} (current: v{currentVersion})',
223
213
  'update.btn': 'Update Now',
224
214
  'update.updating': 'Updating…',
@@ -241,7 +231,9 @@ const en = {
241
231
  'dashboard.account_pool': 'Active Routing Account',
242
232
  'dashboard.credits': 'Credit Balance',
243
233
  'dashboard.updated_at': 'Updated at',
244
- 'composer.cost_title': 'Estimated Session Cost',
234
+ 'composer.cost_title': 'ClineBot Session Cost',
235
+ 'composer.last_request': 'Last request',
236
+ 'composer.free': 'Free',
245
237
  'composer.cost_breakdown': 'Token & Cost Breakdown',
246
238
  'composer.prompt_tok': 'Prompt Tokens',
247
239
  'composer.completion_tok': 'Completion Tokens',
@@ -260,13 +252,11 @@ const en = {
260
252
  'settings.web_search': 'Serve web search with ClineBot', 'settings.web_search_hint': 'Use the ClinePass endpoint for the DSH web_search tool.',
261
253
  'settings.sidebar_quota': 'Show quota meter in the sidebar', 'settings.sidebar_quota_hint': 'Display the compact plan and rolling-window meter above Settings.',
262
254
  'settings.composer_cost': 'Show session cost in the composer', 'settings.composer_cost_hint': 'Display the estimated token cost beside the composer.',
263
- 'settings.proxy_mode': 'Transparent loopback proxy', 'settings.proxy_mode_hint': 'Legacy transport only: route requests through the local failover proxy.',
264
- 'settings.advanced': 'Advanced', 'settings.advanced_desc': 'Connection, timeouts, storage paths and the temporary transport switch.',
255
+ 'settings.advanced': 'Advanced', 'settings.advanced_desc': 'Connection, timeouts and storage paths.',
265
256
  'settings.base_url': 'API base URL', 'settings.base_url_hint': 'ClinePass API root. Remote endpoints must use https://.',
266
257
  'settings.timeout': 'Connect timeout (ms)', 'settings.timeout_hint': 'Maximum wait for the upstream response headers.', 'settings.smoke_timeout': 'Smoke-test timeout (ms)', 'settings.smoke_timeout_hint': 'Maximum wait for the diagnostic chat completion.',
267
258
  'settings.stream_idle': 'Stream idle timeout (ms)', 'settings.stream_idle_hint': 'Maximum gap between SSE chunks before the stream is treated as dead.', 'settings.max_payload': 'Max multimodal payload (MB)', 'settings.max_payload_hint': 'Oldest images are evicted once the request exceeds this budget.',
268
259
  'settings.stats_path': 'Stats file path', 'settings.stats_path_hint': 'Persistent JSON telemetry path (supports ~/).', 'settings.models_cache_path': 'Models cache path', 'settings.models_cache_path_hint': 'Offline model catalogue cache (supports ~/).',
269
- 'settings.transport': 'Transport', 'settings.transport_hint': 'Native ctx.llm adapter (recommended) or the legacy llm-pi-ai + loopback proxy.',
270
260
  'providerCard.title': 'ClineBot (ClinePass)',
271
261
  'providerCard.hint': 'Paste a ClinePass API key or manage the full account pool in Settings → ClineBot.',
272
262
  'providerCard.keyPresent': 'Key configured',
@@ -493,19 +483,9 @@ const zh = {
493
483
  'stats.confirm_reset': '确认重置?',
494
484
  'stats.cancel_reset': '取消',
495
485
  'diag.title': '诊断工具与服务接入',
496
- 'diag.desc': '配置 API 密钥后,ClineBot 会自动向 DSH 注册模型服务。您可以在此进行连通性测试或强制重新同步。',
486
+ 'diag.desc': '配置 API 密钥后,ClineBot 会自动向 DSH 注册模型服务。您可以在此进行连通性测试。',
497
487
  'diag.smoke_btn': '执行连通性冒烟测试 (Ping)',
498
488
  'diag.smoke_testing': '正在测试…',
499
- 'diag.resync_btn': '强制重新同步到 DSH',
500
- 'diag.resyncing': '正在同步…',
501
- 'diag.resynced_msg': '模型服务已重新同步到 DSH ({count} 个模型)',
502
- 'diag.unregister_btn': '从 DSH 注销该服务',
503
- 'diag.unregistering': '正在注销…',
504
- 'diag.unregistered_msg': '已从 DSH 模型注册表中移除',
505
- 'diag.proxy_title': '本地透明回环代理',
506
- 'diag.proxy_desc': '通过本地回环代理支持多账户故障转移、会话保持与实时统计。关闭后直连上游端点。',
507
- 'diag.proxy_enabled': '代理模式已开启(支持故障转移与遥测)',
508
- 'diag.proxy_disabled': '直连模式(直接连接上游,无本地代理)',
509
489
  'update.available': '发现新版本 v{latestVersion}(当前版本 v{currentVersion})',
510
490
  'update.btn': '立即更新',
511
491
  'update.updating': '正在更新…',
@@ -528,7 +508,9 @@ const zh = {
528
508
  'dashboard.account_pool': '当前承载账户',
529
509
  'dashboard.credits': '点数余额',
530
510
  'dashboard.updated_at': '更新于',
531
- 'composer.cost_title': '预估会话成本',
511
+ 'composer.cost_title': 'ClineBot 会话成本',
512
+ 'composer.last_request': '最近一次请求',
513
+ 'composer.free': '免费',
532
514
  'composer.cost_breakdown': 'Token 与费用明细',
533
515
  'composer.prompt_tok': '提示词 Token',
534
516
  'composer.completion_tok': '补全 Token',
@@ -547,13 +529,11 @@ const zh = {
547
529
  'settings.web_search': '使用 ClineBot 提供网页搜索', 'settings.web_search_hint': '为 DSH web_search 工具使用 ClinePass 端点。',
548
530
  'settings.sidebar_quota': '在侧边栏显示配额', 'settings.sidebar_quota_hint': '在设置上方显示紧凑的套餐与滚动窗口配额。',
549
531
  'settings.composer_cost': '在输入框显示会话成本', 'settings.composer_cost_hint': '在输入框旁显示预估 token 成本。',
550
- 'settings.proxy_mode': '透明回环代理', 'settings.proxy_mode_hint': '仅旧传输:通过本地故障转移代理发送请求。',
551
- 'settings.advanced': '高级', 'settings.advanced_desc': '连接、超时、存储路径和临时传输开关。',
532
+ 'settings.advanced': '高级', 'settings.advanced_desc': '连接、超时和存储路径。',
552
533
  'settings.base_url': 'API 基础地址', 'settings.base_url_hint': 'ClinePass API 根地址。远程端点必须使用 https://。',
553
534
  'settings.timeout': '连接超时(毫秒)', 'settings.timeout_hint': '等待上游响应头的最大时间。', 'settings.smoke_timeout': '烟雾测试超时(毫秒)', 'settings.smoke_timeout_hint': '等待诊断聊天补全的最大时间。',
554
535
  'settings.stream_idle': '流空闲超时(毫秒)', 'settings.stream_idle_hint': 'SSE 数据块之间的最大间隔,超过后视为断流。', 'settings.max_payload': '多模态负载上限(MB)', 'settings.max_payload_hint': '请求超过预算时会淘汰最旧的图片。',
555
536
  'settings.stats_path': '统计文件路径', 'settings.stats_path_hint': '持久化 JSON 遥测路径(支持 ~/)。', 'settings.models_cache_path': '模型缓存路径', 'settings.models_cache_path_hint': '离线模型目录缓存(支持 ~/)。',
556
- 'settings.transport': '传输方式', 'settings.transport_hint': '原生 ctx.llm 适配器(推荐)或旧版 llm-pi-ai + 回环代理。',
557
537
  'providerCard.title': 'ClineBot(ClinePass)',
558
538
  'providerCard.hint': '粘贴 ClinePass API 密钥,或前往“设置 → ClineBot”管理完整账户池。',
559
539
  'providerCard.keyPresent': '密钥已配置',
@@ -1633,7 +1613,7 @@ function IntegrationToggle({ id, label, hint, checked, onChange }) {
1633
1613
  })
1634
1614
  }
1635
1615
 
1636
- function IntegrationsSection({ draft, onStage, transport, t }) {
1616
+ function IntegrationsSection({ draft, onStage, t }) {
1637
1617
  const current = draft || {}
1638
1618
  return React.createElement(
1639
1619
  SettingsGroup,
@@ -1662,16 +1642,7 @@ function IntegrationsSection({ draft, onStage, transport, t }) {
1662
1642
  hint: t('settings.composer_cost_hint'),
1663
1643
  checked: current.showComposerCost !== false,
1664
1644
  onChange: (value) => onStage({ showComposerCost: value }),
1665
- }),
1666
- transport === 'pi-ai'
1667
- ? React.createElement(IntegrationToggle, {
1668
- id: 'cb-proxy-mode',
1669
- label: t('settings.proxy_mode'),
1670
- hint: t('settings.proxy_mode_hint'),
1671
- checked: current.proxyMode !== false,
1672
- onChange: (value) => onStage({ proxyMode: value }),
1673
- })
1674
- : null
1645
+ })
1675
1646
  )
1676
1647
  }
1677
1648
 
@@ -1692,7 +1663,7 @@ function AdvancedField({ id, label, hint, value, type, onChange }) {
1692
1663
  })
1693
1664
  }
1694
1665
 
1695
- function AdvancedSection({ draft, onStage, transport, t }) {
1666
+ function AdvancedSection({ draft, onStage, t }) {
1696
1667
  const current = draft || {}
1697
1668
  const numeric = (key, minimum) => (raw) => {
1698
1669
  if (raw === '') return
@@ -1759,22 +1730,6 @@ function AdvancedSection({ draft, onStage, transport, t }) {
1759
1730
  hint: t('settings.models_cache_path_hint'),
1760
1731
  value: current.modelsCachePath,
1761
1732
  onChange: (value) => onStage({ modelsCachePath: value }),
1762
- }),
1763
- React.createElement(SettingRow, {
1764
- title: t('settings.transport'),
1765
- description: t('settings.transport_hint'),
1766
- htmlFor: 'cb-transport',
1767
- control: React.createElement(
1768
- 'select',
1769
- {
1770
- id: 'cb-transport',
1771
- className: 'cb-select',
1772
- value: current.transport === 'pi-ai' ? 'pi-ai' : 'adapter',
1773
- onChange: (event) => onStage({ transport: event.target.value }),
1774
- },
1775
- React.createElement('option', { value: 'adapter' }, 'Native adapter'),
1776
- React.createElement('option', { value: 'pi-ai' }, 'Legacy llm-pi-ai + proxy')
1777
- ),
1778
1733
  })
1779
1734
  )
1780
1735
  }
@@ -3601,42 +3556,12 @@ function StatsSection({ status, onResetStats, busy, t }) {
3601
3556
  }
3602
3557
 
3603
3558
  // --- src/client/components/diag-section.js ---
3604
- function DiagSection({
3605
- keyPresent,
3606
- isRegistered,
3607
- busy,
3608
- smokeResult,
3609
- handleSmoke,
3610
- handleUnregister,
3611
- handleRegister,
3612
- proxyMode,
3613
- onToggleProxy,
3614
- t,
3615
- }) {
3559
+ function DiagSection({ keyPresent, busy, smokeResult, handleSmoke, t }) {
3616
3560
  return React.createElement(
3617
3561
  'section',
3618
3562
  { className: 'cb-section-card' },
3619
3563
  React.createElement('div', { className: 'cb-section-title' }, t('diag.title')),
3620
3564
  React.createElement('div', { className: 'cb-section-desc' }, t('diag.desc')),
3621
- React.createElement(SettingRow, {
3622
- title: t('diag.proxy_title'),
3623
- description: t('diag.proxy_desc'),
3624
- control: React.createElement(
3625
- React.Fragment,
3626
- null,
3627
- React.createElement(
3628
- 'span',
3629
- { className: proxyMode ? 'cb-state-ok' : 'cb-muted' },
3630
- proxyMode ? t('diag.proxy_enabled') : t('diag.proxy_disabled')
3631
- ),
3632
- React.createElement(BooleanControl, {
3633
- checked: !!proxyMode,
3634
- disabled: !!busy,
3635
- label: t('diag.proxy_title'),
3636
- onChange: (next) => onToggleProxy && onToggleProxy(next),
3637
- })
3638
- ),
3639
- }),
3640
3565
  React.createElement(
3641
3566
  'div',
3642
3567
  { className: 'cb-row cb-row-actions' },
@@ -3644,34 +3569,11 @@ function DiagSection({
3644
3569
  'button',
3645
3570
  {
3646
3571
  type: 'button',
3647
- className: 'cb-btn',
3572
+ className: 'cb-btn cb-btn-primary',
3648
3573
  disabled: !!busy || !keyPresent,
3649
3574
  onClick: handleSmoke,
3650
3575
  },
3651
3576
  busy === 'smoke' ? t('diag.smoke_testing') : t('diag.smoke_btn')
3652
- ),
3653
- isRegistered
3654
- ? React.createElement(
3655
- 'button',
3656
- {
3657
- type: 'button',
3658
- className: 'cb-btn cb-btn-danger',
3659
- disabled: !!busy,
3660
- onClick: handleUnregister,
3661
- },
3662
- busy === 'unregister' ? t('diag.unregistering') : t('diag.unregister_btn')
3663
- )
3664
- : null,
3665
- React.createElement('span', { className: 'cb-settings-spacer' }),
3666
- React.createElement(
3667
- 'button',
3668
- {
3669
- type: 'button',
3670
- className: 'cb-btn cb-btn-primary',
3671
- disabled: !!busy,
3672
- onClick: handleRegister,
3673
- },
3674
- busy === 'register' ? t('diag.resyncing') : t('diag.resync_btn')
3675
3577
  )
3676
3578
  ),
3677
3579
  smokeResult
@@ -4710,10 +4612,12 @@ function ComposerCostEntry(props) {
4710
4612
  const configSnapshot = useConfigFormsSnapshot(props?.ctx)
4711
4613
  const [stats, setStats] = React.useState(null)
4712
4614
  const [prices, setPrices] = React.useState([])
4615
+ const [sessionStats, setSessionStats] = React.useState(null)
4713
4616
  const markerRef = React.useRef(null)
4714
4617
 
4715
4618
  const projectedTokens = props.useProjection('tokenUsage')
4716
4619
  const selection = props.useProjection('modelSelection')
4620
+ const sessionId = String(props?.sessionId || '')
4717
4621
  const priceOverridesKey = JSON.stringify(configSnapshot?.value?.priceOverrides || {})
4718
4622
 
4719
4623
  React.useEffect(() => {
@@ -4728,7 +4632,40 @@ function ComposerCostEntry(props) {
4728
4632
  }, [priceOverridesKey])
4729
4633
 
4730
4634
  React.useEffect(() => {
4731
- if (projectedTokens) return undefined
4635
+ if (!sessionId) {
4636
+ setSessionStats(null)
4637
+ return undefined
4638
+ }
4639
+ let active = true
4640
+ const loadSession = async () => {
4641
+ try {
4642
+ const res = await fetchWithTimeout(
4643
+ `${ROUTE_PREFIX}/stats/session?sessionId=${encodeURIComponent(sessionId)}`,
4644
+ { cache: 'no-store' },
4645
+ )
4646
+ const data = await res.json().catch(() => null)
4647
+ if (active && data?.ok) setSessionStats(data.session || null)
4648
+ } catch {
4649
+ /* quiet */
4650
+ }
4651
+ }
4652
+ loadSession()
4653
+ let timer = null
4654
+ const schedule = () => {
4655
+ timer = setTimeout(async () => {
4656
+ await loadSession()
4657
+ if (active) schedule()
4658
+ }, 10000)
4659
+ }
4660
+ schedule()
4661
+ return () => {
4662
+ active = false
4663
+ if (timer) clearTimeout(timer)
4664
+ }
4665
+ }, [sessionId, projectedTokens])
4666
+
4667
+ React.useEffect(() => {
4668
+ if (projectedTokens || sessionId) return undefined
4732
4669
  let active = true
4733
4670
  const loadStats = async () => {
4734
4671
  try {
@@ -4744,7 +4681,7 @@ function ComposerCostEntry(props) {
4744
4681
  loadStats()
4745
4682
  const timer = setTimeout(loadStats, 30000)
4746
4683
  return () => { active = false; clearTimeout(timer) }
4747
- }, [projectedTokens])
4684
+ }, [projectedTokens, sessionId])
4748
4685
 
4749
4686
  const selected = selection?.lastUsed
4750
4687
  ?? selection?.next
@@ -4754,32 +4691,65 @@ function ComposerCostEntry(props) {
4754
4691
  const isClineBot = (provider === '' || provider === 'clinebot' || provider === 'llm-clinebot')
4755
4692
  && (provider === 'clinebot' || provider === 'llm-clinebot' || modelId.startsWith('cline-pass/'))
4756
4693
 
4757
- const rawTokens = projectedTokens || {
4694
+ // ClineBot requests are recorded in the plugin's own per-session bucket. That
4695
+ // bucket is provider-scoped, unlike the host's session-wide tokenUsage, so a
4696
+ // session that also used OpenCode cannot inflate the ClineBot read-out.
4697
+ const session = sessionStats && Number(sessionStats.requests || 0) > 0 ? sessionStats : null
4698
+ const sessionTokens = session
4699
+ ? {
4700
+ promptTokens: Number(session.promptTokens || 0),
4701
+ completionTokens: Number(session.completionTokens || 0),
4702
+ reasoningTokens: Number(session.reasoningTokens || 0),
4703
+ cacheReadTokens: Number(session.cacheReadTokens || 0),
4704
+ cacheWriteTokens: Number(session.cacheWriteTokens || 0),
4705
+ }
4706
+ : null
4707
+ const rawTokens = sessionTokens || (sessionId ? null : (projectedTokens || {
4758
4708
  promptTokens: stats?.promptTokens || 0,
4759
4709
  completionTokens: stats?.completionTokens || 0,
4760
4710
  reasoningTokens: stats?.reasoningTokens || 0,
4761
4711
  cacheReadTokens: stats?.cacheReadTokens || 0,
4762
4712
  cacheWriteTokens: stats?.cacheWriteTokens || 0,
4763
- }
4764
- const cacheRead = Number(rawTokens.cacheReadTokens || 0)
4765
- const cacheWrite = Number(rawTokens.cacheWriteTokens || 0)
4766
- const uncachedInput = Number(rawTokens.uncachedInputTokens)
4767
- const promptTokens = Number.isFinite(uncachedInput)
4768
- ? uncachedInput + cacheRead + cacheWrite
4769
- : Number(rawTokens.promptTokens || rawTokens.inputTokens || 0)
4713
+ }))
4714
+ const cacheRead = Number(rawTokens?.cacheReadTokens || 0)
4715
+ const cacheWrite = Number(rawTokens?.cacheWriteTokens || 0)
4716
+ const uncachedInput = Number(rawTokens?.uncachedInputTokens)
4717
+ const promptTokens = rawTokens
4718
+ ? (Number.isFinite(uncachedInput)
4719
+ ? uncachedInput + cacheRead + cacheWrite
4720
+ : Number(rawTokens.promptTokens || rawTokens.inputTokens || 0))
4721
+ : 0
4770
4722
  const tokens = {
4771
4723
  promptTokens,
4772
- completionTokens: Number(rawTokens.completionTokens || rawTokens.outputTokens || 0),
4773
- reasoningTokens: Number(rawTokens.reasoningTokens || 0),
4724
+ completionTokens: Number(rawTokens?.completionTokens || rawTokens?.outputTokens || 0),
4725
+ reasoningTokens: Number(rawTokens?.reasoningTokens || 0),
4774
4726
  cacheReadTokens: cacheRead,
4775
4727
  cacheWriteTokens: cacheWrite,
4776
4728
  }
4777
- const { cost, priced, source, pTok, cTok, rTok, total } = calculateCost(tokens, modelId, prices)
4778
-
4779
- const costValue = priced ? (cost < 0.0001 && cost > 0 ? '< $0.0001' : `$${cost.toFixed(4)}`) : t('composer.unpriced')
4780
- const costDisplay = priced && source === 'estimate' ? `≈ ${costValue}` : costValue
4729
+ const calculated = calculateCost(tokens, session?.model || modelId, prices)
4730
+ const cost = session ? Number(session.costUsd || 0) : calculated.cost
4731
+ const priced = session ? Number(session.pricedRequests || 0) > 0 : calculated.priced
4732
+ const source = session
4733
+ ? (findPrice(session.model || modelId, prices)?.source || 'estimate')
4734
+ : calculated.source
4735
+ const { pTok, cTok, rTok } = calculated
4736
+ const total = session ? Number(session.totalTokens || pTok + cTok) : calculated.total
4737
+ const last = session?.last || null
4738
+ const lastCost = last ? Number(last.costUsd || 0) : 0
4739
+ const lastPriced = Boolean(last?.priced)
4740
+
4741
+ const costValue = priced
4742
+ ? (cost === 0 ? t('composer.free') : (cost < 0.0001 ? '< $0.0001' : `$${cost.toFixed(4)}`))
4743
+ : t('composer.unpriced')
4744
+ const costDisplay = priced && source === 'estimate' && cost > 0 ? `≈ ${costValue}` : costValue
4745
+ const lastCostValue = last
4746
+ ? (lastPriced
4747
+ ? (lastCost === 0 ? t('composer.free') : (lastCost < 0.0001 ? '< $0.0001' : `$${lastCost.toFixed(4)}`))
4748
+ : t('composer.unpriced'))
4749
+ : ''
4781
4750
  const title = [
4782
4751
  `${t('composer.cost_title')} ${costDisplay}`,
4752
+ ...(last ? [`${t('composer.last_request')} ${lastCostValue}`] : []),
4783
4753
  `${t('composer.prompt_tok')} ${formatTokens(pTok)}`,
4784
4754
  `${t('composer.completion_tok')} ${formatTokens(cTok)}`,
4785
4755
  ...(rTok > 0 ? [`${t('composer.reasoning_tok')} ${formatTokens(rTok)}`] : []),
@@ -5051,27 +5021,6 @@ function SettingsPage(props) {
5051
5021
  })
5052
5022
  }
5053
5023
 
5054
- async function handleRegister() {
5055
- await performAction('register', async () => {
5056
- const res = await fetchWithTimeout(`${ROUTE_PREFIX}/register`, { method: 'POST' })
5057
- const data = await res.json().catch(() => ({}))
5058
- if (!res.ok || !data.ok) throw new Error(data.error || `HTTP ${res.status}`)
5059
- const count = status?.availableModels?.filter((m) => !(draft?.disabledModels || []).includes(m.id)).length || 0
5060
- setMsg(t('diag.resynced_msg', { count }))
5061
- await load()
5062
- })
5063
- }
5064
-
5065
- async function handleUnregister() {
5066
- await performAction('unregister', async () => {
5067
- const res = await fetchWithTimeout(`${ROUTE_PREFIX}/unregister`, { method: 'POST' })
5068
- const data = await res.json().catch(() => ({}))
5069
- if (!res.ok || !data.ok) throw new Error(data.error || `HTTP ${res.status}`)
5070
- setMsg(t('diag.unregistered_msg'))
5071
- await load()
5072
- })
5073
- }
5074
-
5075
5024
  async function handleSmoke() {
5076
5025
  setSmokeResult(null)
5077
5026
  await performAction('smoke', async () => {
@@ -5300,28 +5249,21 @@ function SettingsPage(props) {
5300
5249
  React.createElement(IntegrationsSection, {
5301
5250
  draft,
5302
5251
  onStage: staged.stage,
5303
- transport: draft?.transport || status?.config?.transport || 'adapter',
5304
5252
  t,
5305
5253
  }),
5306
5254
 
5307
5255
  // Diagnostics & Sync with DSH
5308
5256
  React.createElement(DiagSection, {
5309
5257
  keyPresent,
5310
- isRegistered,
5311
5258
  busy,
5312
5259
  smokeResult,
5313
5260
  handleSmoke,
5314
- handleUnregister,
5315
- handleRegister,
5316
- proxyMode: draft?.proxyMode !== false,
5317
- onToggleProxy: (value) => staged.stage({ proxyMode: value }),
5318
5261
  t,
5319
5262
  }),
5320
5263
 
5321
5264
  React.createElement(AdvancedSection, {
5322
5265
  draft,
5323
5266
  onStage: staged.stage,
5324
- transport: draft?.transport || status?.config?.transport || 'adapter',
5325
5267
  t,
5326
5268
  }),
5327
5269
 
@@ -18,9 +18,7 @@ import {
18
18
  DEFAULT_MODEL_ID,
19
19
  PROVIDER_ID,
20
20
  PROVIDER_DISPLAY_NAME,
21
- getAllModels,
22
21
  parsePlanIncludedModels,
23
- formatModelDescription,
24
22
  } from './models.js'
25
23
 
26
24
  export const DEFAULT_BASE_URL = 'https://api.cline.bot/api/v1'
@@ -487,103 +485,3 @@ export async function smokeChat(baseUrl, apiKey, {
487
485
  cancel()
488
486
  }
489
487
  }
490
-
491
- /**
492
- * Shape for llm-pi-ai.providers.clinebot (openai-completions).
493
- */
494
- export function buildPiAiProvider({
495
- baseUrl = DEFAULT_BASE_URL,
496
- apiKeyEnv = DEFAULT_API_KEY_ENV,
497
- apiKey,
498
- dynamicModels = [],
499
- modelContextOverrides = [],
500
- models = [],
501
- customModels = [],
502
- existingModels = [],
503
- displayName = PROVIDER_DISPLAY_NAME,
504
- } = {}) {
505
- const allAvailable = getAllModels(dynamicModels, modelContextOverrides, customModels)
506
- const customModelsMap = new Map((Array.isArray(customModels) ? customModels : []).filter(Boolean).map(cm => [cm.id, cm]))
507
- const existingMap = new Map((Array.isArray(existingModels) ? existingModels : []).filter(Boolean).map(m => [m.id, m]))
508
- const explicitOverridesSet = new Set((Array.isArray(modelContextOverrides) ? modelContextOverrides : []).map(o => o?.modelId))
509
- const hasWildcardOverride = explicitOverridesSet.has('*')
510
-
511
- const modelList = (Array.isArray(models) && models.length ? models : allAvailable).map((m) => {
512
- let item
513
- if (typeof m === 'string') {
514
- const customMatch = customModelsMap.get(m)
515
- const baseMatch = allAvailable.find((x) => x.id === m)
516
- item = customMatch ? { ...baseMatch, ...customMatch } : (baseMatch || { id: m, name: m })
517
- } else {
518
- item = m
519
- }
520
- const hasImage = item.input?.includes('image') || item.input?.includes('vision')
521
-
522
- // Preserve manual provider edits if user configured contextWindow or maxTokens in llm-pi-ai without explicit override in clinebot
523
- const existingEntry = existingMap.get(item.id)
524
- let ctxWin = Number(item.contextLength || item.contextWindow) || 200000
525
- let maxTok = Number(item.maxTokens) || 8192
526
- if (existingEntry && !explicitOverridesSet.has(item.id) && !hasWildcardOverride) {
527
- if (typeof existingEntry.contextWindow === 'number' && existingEntry.contextWindow > 0) {
528
- ctxWin = existingEntry.contextWindow
529
- }
530
- if (typeof existingEntry.maxTokens === 'number' && existingEntry.maxTokens > 0) {
531
- maxTok = existingEntry.maxTokens
532
- }
533
- }
534
-
535
- const res = {
536
- id: item.id,
537
- name: item.name || item.id,
538
- description: formatModelDescription(item),
539
- contextWindow: ctxWin,
540
- maxTokens: maxTok,
541
- input: hasImage ? ['text', 'image'] : ['text'],
542
- provider: PROVIDER_ID,
543
- }
544
- const validEfforts = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']
545
- let efforts = null
546
- if (Array.isArray(item.reasoningEfforts) && item.reasoningEfforts.length) {
547
- efforts = {}
548
- for (const effort of item.reasoningEfforts) {
549
- const key = String(effort).trim().toLowerCase()
550
- if (validEfforts.includes(key)) efforts[key] = key === 'off' ? null : key
551
- }
552
- } else if (item.reasoningEfforts && typeof item.reasoningEfforts === 'object' && !Array.isArray(item.reasoningEfforts)) {
553
- efforts = {}
554
- for (const [k, v] of Object.entries(item.reasoningEfforts)) {
555
- const key = String(k).trim().toLowerCase()
556
- if (validEfforts.includes(key)) efforts[key] = key === 'off' ? (v === undefined ? null : v) : (typeof v === 'string' && v.trim() ? v.trim() : key)
557
- }
558
- }
559
- const modelCompat = {}
560
- if (efforts && Object.keys(efforts).length) {
561
- if (!('off' in efforts)) efforts.off = null
562
- res.reasoningEfforts = efforts
563
- modelCompat.supportsReasoningEffort = true
564
- } else {
565
- res.reasoningEfforts = false
566
- }
567
- if (Object.keys(modelCompat).length > 0) {
568
- res.compat = modelCompat
569
- }
570
- return res
571
- })
572
-
573
- return {
574
- displayName,
575
- api: 'openai-completions',
576
- baseURL: normalizeBaseUrl(baseUrl),
577
- apiKeyEnv: apiKeyEnv || DEFAULT_API_KEY_ENV,
578
- cacheRetention: 'long',
579
- compat: {
580
- supportsReasoningEffort: true,
581
- supportsLongCacheRetention: true,
582
- },
583
- models: modelList,
584
- }
585
- }
586
-
587
-
588
-
589
-
package/lib/config.js CHANGED
@@ -29,8 +29,6 @@ export const LLM_PI_AI_NS = 'llm-pi-ai'
29
29
  export const Config = z.object({
30
30
  enabled: z.boolean().default(true)
31
31
  .description('When true, ClineBot is registered as a model provider in DSH.').volatile(),
32
- transport: z.union(['adapter', 'pi-ai']).default('adapter')
33
- .description('Provider transport: native ctx.llm adapter (recommended) or legacy llm-pi-ai + loopback proxy.').volatile(),
34
32
  baseUrl: z.string().default(DEFAULT_BASE_URL)
35
33
  .description('Base API URL (default: https://api.cline.bot/api/v1).').volatile(),
36
34
  apiKeyEnv: z.string().default(DEFAULT_API_KEY_ENV)
@@ -76,8 +74,6 @@ export const Config = z.object({
76
74
  .description('Models automatically discovered from the official ClinePass subscription plan.'),
77
75
  planSyncedAt: z.number().default(0)
78
76
  .description('Timestamp when models were last synchronized with active subscription plan.'),
79
- legacyMigratedAt: z.number().default(0)
80
- .description('Internal timestamp of the one-time llm-pi-ai provider migration.'),
81
77
  modelContextOverrides: z.array(z.object({
82
78
  modelId: z.string(),
83
79
  contextLength: z.number().default(200000),
@@ -101,8 +97,6 @@ export const Config = z.object({
101
97
  isReasoning: z.boolean().default(false),
102
98
  })).default([])
103
99
  .description('User-defined custom gateway models.').volatile(),
104
- proxyMode: z.boolean().default(true)
105
- .description('When true, registers local transparent loopback proxy for failover and token tracking.').volatile(),
106
100
  statsPath: z.string().default('~/.dsh/clinebot-stats.json')
107
101
  .description('Local on-disk persistent path for token usage analytics.').volatile(),
108
102
  timeoutMs: z.number().default(DEFAULT_TIMEOUT_MS)
@@ -229,7 +223,6 @@ export function publicConfig(cfg) {
229
223
 
230
224
  return {
231
225
  enabled: !!cfg?.enabled,
232
- transport: cfg?.transport === 'pi-ai' ? 'pi-ai' : 'adapter',
233
226
  baseUrl: normalizeBaseUrl(cfg?.baseUrl),
234
227
  apiKeyEnv: cfg?.apiKeyEnv || DEFAULT_API_KEY_ENV,
235
228
  defaultModel,
@@ -244,7 +237,6 @@ export function publicConfig(cfg) {
244
237
  modelReasoningDefaults: cfg?.modelReasoningDefaults && typeof cfg.modelReasoningDefaults === 'object' ? cfg.modelReasoningDefaults : {},
245
238
  modelRoutes: cfg?.modelRoutes && typeof cfg.modelRoutes === 'object' ? cfg.modelRoutes : {},
246
239
  customModels: custom,
247
- proxyMode: cfg?.proxyMode !== false,
248
240
  statsPath: String(cfg?.statsPath || '~/.dsh/clinebot-stats.json'),
249
241
  disabledModels: disabledList,
250
242
  filterModelsByPlan,