dsh-router-core 0.3.3 → 0.3.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.
@@ -30,7 +30,6 @@ export default function factory(env: SupplierEnv): SupplierModule {
30
30
  // 模型缓存由 dsh-router 核心统一管;插件每次拉取,失败回退上次成功结果
31
31
  let modelsCache: ModelInfo[] | undefined
32
32
  /** 上次 chatOnce 失败原因(供核心测试模型汇总诊断)。 */
33
- let lastErr: string | undefined
34
33
 
35
34
  async function refreshModels(): Promise<ModelInfo[]> {
36
35
  try {
@@ -81,15 +80,13 @@ export default function factory(env: SupplierEnv): SupplierModule {
81
80
  icon,
82
81
  status: (): SupplierStatusNow => ({ id, name, accounts: [] }),
83
82
  listModels: (force?: boolean): Promise<ModelInfo[]> => allModels(!!force),
84
- getAlias: (): string => id,
85
83
  // testModel 由 dsh-router 核心统一走 chatOnce 路径(无账号,无需回退)
86
- lastError: (): string | undefined => lastErr,
87
84
  /** 无账号供应商:uid 恒为空,只认 free 模型。 */
88
85
  async chatOnce(_uid: string, req: ChatRequest): Promise<ChatOnceResult> {
89
86
  // 只认 free 模型,其他模型交给别的供应商
90
87
  if (!isFreeModel(req.model)) {
91
- lastErr = `unknown free model ${JSON.stringify(req.model)}`
92
- return { ok: false, state: 'no_such_model', message: lastErr }
88
+ const msg = `unknown free model ${JSON.stringify(req.model)}`
89
+ return { ok: false, state: 'no_such_model', message: msg }
93
90
  }
94
91
  // 模型名规范化:剥 alias 前缀,body.model 用裸 id
95
92
  let body = req.rawBody
@@ -115,24 +112,24 @@ export default function factory(env: SupplierEnv): SupplierModule {
115
112
  signal: AbortSignal.timeout(120000),
116
113
  })
117
114
  } catch (err) {
118
- lastErr = `opencode upstream: ${(err as Error).message}`
119
- return { ok: false, state: 'transport', message: lastErr }
115
+ const msg = `opencode upstream: ${(err as Error).message}`
116
+ return { ok: false, state: 'transport', message: msg }
120
117
  }
121
118
 
122
119
  // 非 2xx:按状态归类,核心据此冷却/换号(本供应商无号可换,直接放弃)
123
120
  if (upstream.status < 200 || upstream.status >= 300) {
124
121
  const text = await upstream.text().catch(() => '')
125
- lastErr = `upstream ${upstream.status}: ${text.slice(0, 200)}`
122
+ const msg = `upstream ${upstream.status}: ${text.slice(0, 200)}`
126
123
  const state: AccountState =
127
124
  upstream.status === 429 ? 'rate_limit' : upstream.status === 404 ? 'unavailable' : 'unknown'
128
- return { ok: false, state, message: lastErr }
125
+ return { ok: false, state, message: msg }
129
126
  }
130
127
 
131
128
  // 流式:上游已是 OpenAI SSE,原样交回核心(核心负责写响应)
132
129
  if (req.stream) {
133
130
  if (!upstream.body) {
134
- lastErr = 'opencode upstream: empty stream body'
135
- return { ok: false, state: 'transport', message: lastErr }
131
+ const msg = 'opencode upstream: empty stream body'
132
+ return { ok: false, state: 'transport', message: msg }
136
133
  }
137
134
  return { ok: true, stream: upstream.body }
138
135
  }
@@ -140,8 +137,8 @@ export default function factory(env: SupplierEnv): SupplierModule {
140
137
  // 非流式:透传 JSON(空 body 视为上游异常)
141
138
  const text = await upstream.text().catch(() => '')
142
139
  if (text === '') {
143
- lastErr = 'opencode upstream: empty body'
144
- return { ok: false, state: 'transport', message: lastErr }
140
+ const msg = 'opencode upstream: empty body'
141
+ return { ok: false, state: 'transport', message: msg }
145
142
  }
146
143
  return { ok: true, status: upstream.status, body: text }
147
144
  },
@@ -50,7 +50,6 @@ export default function factory(env: SupplierEnv): SupplierModule {
50
50
  let modelsCache: ModelInfo[] | undefined
51
51
  let freeIds = new Set<string>()
52
52
  /** 上次 chatOnce 失败原因(供核心测试模型汇总诊断)。 */
53
- let lastErr: string | undefined
54
53
  function listKeys(): string[] {
55
54
  return env.credentials.list(id)
56
55
  }
@@ -121,7 +120,6 @@ export default function factory(env: SupplierEnv): SupplierModule {
121
120
  return { id, name, accounts }
122
121
  },
123
122
  listModels: (force?: boolean): Promise<ModelInfo[]> => allModels(!!force),
124
- getAlias: (): string => id,
125
123
  async addApiKey(input: { name: string; apiKey: string }): Promise<{ ok: boolean; error?: string; account?: { uid: string; nickname: string } }> {
126
124
  const key = input.apiKey.trim()
127
125
  if (key === '') return { ok: false, error: 'API key 不能为空' }
@@ -151,18 +149,17 @@ export default function factory(env: SupplierEnv): SupplierModule {
151
149
  env.credentials.remove(id, uid)
152
150
  return true
153
151
  },
154
- lastError: (): string | undefined => lastErr,
155
152
  /** 对单个 key 调一次上游。选号/冷却/换号是核心的活,这里只报结果。 */
156
153
  async chatOnce(uid: string, req: ChatRequest): Promise<ChatOnceResult> {
157
154
  const base = stripAlias(req.model, currentAlias())
158
155
  if (!freeIds.has(base) && !(await allModels(false)).some((m) => m.id === base)) {
159
- lastErr = `unknown free model ${JSON.stringify(req.model)}`
160
- return { ok: false, state: 'no_such_model', message: lastErr }
156
+ const msg = `unknown free model ${JSON.stringify(req.model)}`
157
+ return { ok: false, state: 'no_such_model', message: msg }
161
158
  }
162
159
  const acct = getKey(uid)
163
160
  if (acct === undefined) {
164
- lastErr = `unknown account ${JSON.stringify(uid)}`
165
- return { ok: false, state: 'no_such_model', message: lastErr }
161
+ const msg = `unknown account ${JSON.stringify(uid)}`
162
+ return { ok: false, state: 'no_such_model', message: msg }
166
163
  }
167
164
 
168
165
  let body = req.rawBody
@@ -188,34 +185,34 @@ export default function factory(env: SupplierEnv): SupplierModule {
188
185
  signal: AbortSignal.timeout(120000),
189
186
  })
190
187
  } catch (err) {
191
- lastErr = (err as Error).message
192
- return { ok: false, state: 'transport', message: lastErr }
188
+ const msg = (err as Error).message
189
+ return { ok: false, state: 'transport', message: msg }
193
190
  }
194
191
 
195
192
  if (upstream.status < 200 || upstream.status >= 300) {
196
193
  const text = await upstream.text().catch(() => '')
197
- lastErr = `upstream ${upstream.status}: ${text.slice(0, 120)}`
194
+ const msg = `upstream ${upstream.status}: ${text.slice(0, 120)}`
198
195
  // 401/403 = 这个 key 本身不可用;429 = 限流;其余归为说不清
199
196
  const state: AccountState =
200
197
  upstream.status === 429 ? 'rate_limit'
201
198
  : upstream.status === 401 || upstream.status === 403 ? 'session_dead'
202
199
  : upstream.status === 404 ? 'unavailable'
203
200
  : 'unknown'
204
- return { ok: false, state, message: lastErr }
201
+ return { ok: false, state, message: msg }
205
202
  }
206
203
 
207
204
  // 流式:上游已是 OpenAI SSE,原样交回核心写
208
205
  if (req.stream) {
209
206
  if (!upstream.body) {
210
- lastErr = 'openrouter upstream: empty stream body'
211
- return { ok: false, state: 'transport', message: lastErr }
207
+ const msg = 'openrouter upstream: empty stream body'
208
+ return { ok: false, state: 'transport', message: msg }
212
209
  }
213
210
  return { ok: true, stream: upstream.body }
214
211
  }
215
212
  const text = await upstream.text().catch(() => '')
216
213
  if (text === '') {
217
- lastErr = 'openrouter upstream: empty body'
218
- return { ok: false, state: 'transport', message: lastErr }
214
+ const msg = 'openrouter upstream: empty body'
215
+ return { ok: false, state: 'transport', message: msg }
219
216
  }
220
217
  return { ok: true, status: upstream.status, body: text }
221
218
  },
@@ -327,7 +327,10 @@ export function supplierRoutes(base: string, loaded: LoadedSupplier, store: Supp
327
327
  return
328
328
  }
329
329
  const body = JSON.parse(await readBody(req)) as { uid?: string }
330
- const ok = await s.removeLink(body.uid ?? '')
330
+ const uid = body.uid ?? ''
331
+ const ok = await s.removeLink(uid)
332
+ // 链接没了,它的积分缓存也得跟着走(不然删号再重登会顶着一个旧数字)
333
+ if (ok) store.clearCredits(s.id, uid)
331
334
  writeJson(res, ok ? 200 : 404, ok ? { ok: true } : { ok: false, error: 'link not found' })
332
335
  },
333
336
  })