@apinggithub/mydsh-auto-model-router 0.2.2 → 0.2.4
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/auto-adapter.mjs +45 -6
- package/config.mjs +9 -5
- package/index.mjs +4 -3
- package/package.json +1 -1
package/auto-adapter.mjs
CHANGED
|
@@ -107,6 +107,49 @@ function reasoningEfforts(info) {
|
|
|
107
107
|
return FALLBACK_EFFORTS.map((effort) => ({ ...effort }))
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Pick the reasoning effort to send to the routed model.
|
|
112
|
+
*
|
|
113
|
+
* The session's model selection keeps its own `reasoningEffort` and the host's
|
|
114
|
+
* selection listener re-applies it after every waterfall rewrite, so the value
|
|
115
|
+
* that reaches this adapter belongs to the *virtual* model, not to the model it
|
|
116
|
+
* forwards to. Passing it through unchanged sends a thinking parameter to a
|
|
117
|
+
* model with no thinking mode, which the provider rejects outright
|
|
118
|
+
* (`does not support reasoning effort "low"`).
|
|
119
|
+
*
|
|
120
|
+
* @param {object} llm - ctx.llm
|
|
121
|
+
* @param {object} route - the routed provider/model
|
|
122
|
+
* @param {unknown} requested - effort the caller asked for (usually the UI choice)
|
|
123
|
+
* @returns {Promise<string | undefined>} an effort the target advertises, or undefined
|
|
124
|
+
*/
|
|
125
|
+
async function supportedEffort(llm, route, requested) {
|
|
126
|
+
const info = await underlyingInfo(llm, route)
|
|
127
|
+
const efforts = Array.isArray(info?.reasoning?.efforts) ? info.reasoning.efforts : []
|
|
128
|
+
if (efforts.length === 0) return undefined
|
|
129
|
+
const ids = new Set(efforts.map((effort) => effort.id))
|
|
130
|
+
if (typeof route.reasoningEffort === 'string' && ids.has(route.reasoningEffort)) return route.reasoningEffort
|
|
131
|
+
if (typeof requested === 'string' && ids.has(requested)) return requested
|
|
132
|
+
const fallback = info?.reasoning?.defaultEffort
|
|
133
|
+
return typeof fallback === 'string' && ids.has(fallback) ? fallback : undefined
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Request options for the routed model: same call, different provider/model,
|
|
138
|
+
* and only the reasoning effort that model actually supports.
|
|
139
|
+
*
|
|
140
|
+
* @param {object} llm - ctx.llm
|
|
141
|
+
* @param {object} route - the routed provider/model
|
|
142
|
+
* @param {object} options - options the runtime handed to `stream()`
|
|
143
|
+
* @returns {Promise<object>}
|
|
144
|
+
*/
|
|
145
|
+
async function forwardOptions(llm, route, options) {
|
|
146
|
+
const forwarded = { ...options, provider: route.provider, model: route.model }
|
|
147
|
+
delete forwarded.reasoningEffort
|
|
148
|
+
const effort = await supportedEffort(llm, route, options?.reasoningEffort)
|
|
149
|
+
if (effort !== undefined) forwarded.reasoningEffort = effort
|
|
150
|
+
return forwarded
|
|
151
|
+
}
|
|
152
|
+
|
|
110
153
|
/**
|
|
111
154
|
* Build the virtual adapter for one plugin mount.
|
|
112
155
|
*
|
|
@@ -194,13 +237,9 @@ export function createAutoAdapter(resolve = {}) {
|
|
|
194
237
|
// runtime; the adapter contract requires we stream chunks back.
|
|
195
238
|
const llm = readLlm()
|
|
196
239
|
if (typeof llm?.stream === 'function') {
|
|
240
|
+
const forwarded = await forwardOptions(llm, route, options)
|
|
197
241
|
// ctx.llm.stream returns the AsyncIterable directly (not { stream }).
|
|
198
|
-
const stream = llm.stream(
|
|
199
|
-
...options,
|
|
200
|
-
provider: route.provider,
|
|
201
|
-
model: route.model,
|
|
202
|
-
...route.reasoningEffort === undefined ? {} : { reasoningEffort: route.reasoningEffort },
|
|
203
|
-
})
|
|
242
|
+
const stream = llm.stream(forwarded)
|
|
204
243
|
for await (const chunk of stream) yield chunk
|
|
205
244
|
return
|
|
206
245
|
}
|
package/config.mjs
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// fallbackChain: [route, ...] (model-level, appended with the default)
|
|
23
23
|
// maxRetries: per-route retry budget on request-error
|
|
24
24
|
|
|
25
|
-
export const PLUGIN_VERSION = '0.2.
|
|
25
|
+
export const PLUGIN_VERSION = '0.2.4'
|
|
26
26
|
|
|
27
27
|
export const LEVEL_IDS = Object.freeze(['L0', 'L1', 'L2', 'L3'])
|
|
28
28
|
export const COST_MODES = Object.freeze(['cost-first', 'quality-first', 'balanced'])
|
|
@@ -297,9 +297,13 @@ function normalizeLevels(levels) {
|
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
function normalizeLevelRoute(route, where) {
|
|
300
|
+
// `reasoningEffort: null` means "this route has no thinking mode at all":
|
|
301
|
+
// the field is omitted so a provider whose model declares no reasoning
|
|
302
|
+
// metadata (Zhipu/Bailian flash models) is not rejected with
|
|
303
|
+
// UNSUPPORTED_REASONING_EFFORT.
|
|
304
|
+
const omitEffort = route.reasoningEffort === null
|
|
300
305
|
const normalized = normalizeRoute(route, where)
|
|
301
|
-
//
|
|
302
|
-
// thinking mode) so every level spells out its thinking intensity.
|
|
306
|
+
// Every other level carries an explicit effort; absent → 'off' (no thinking).
|
|
303
307
|
const contextWindow = route.contextWindow === undefined || route.contextWindow === null
|
|
304
308
|
? undefined
|
|
305
309
|
: Number(route.contextWindow)
|
|
@@ -308,7 +312,7 @@ function normalizeLevelRoute(route, where) {
|
|
|
308
312
|
}
|
|
309
313
|
return {
|
|
310
314
|
...normalized,
|
|
311
|
-
reasoningEffort: normalized.reasoningEffort ?? 'off',
|
|
315
|
+
...omitEffort ? {} : { reasoningEffort: normalized.reasoningEffort ?? 'off' },
|
|
312
316
|
// A level may declare that its route is a free-quota route. The flag rides
|
|
313
317
|
// along with the route so the router can prefer/demote it.
|
|
314
318
|
...route.freeQuota === true ? { freeQuota: true } : {},
|
|
@@ -328,7 +332,7 @@ function normalizeRoute(route, where) {
|
|
|
328
332
|
`mydsh-auto-model-router: "${where}" needs both provider and model, got ${JSON.stringify(route)}`,
|
|
329
333
|
)
|
|
330
334
|
}
|
|
331
|
-
const reasoningEffort = route.reasoningEffort === undefined
|
|
335
|
+
const reasoningEffort = route.reasoningEffort === undefined || route.reasoningEffort === null
|
|
332
336
|
? undefined
|
|
333
337
|
: String(route.reasoningEffort).trim() || undefined
|
|
334
338
|
return {
|
package/index.mjs
CHANGED
|
@@ -341,9 +341,10 @@ export function apply(ctx, input = {}) {
|
|
|
341
341
|
...proposed,
|
|
342
342
|
provider: route.provider,
|
|
343
343
|
model: route.model,
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
|
|
344
|
+
// A level that omits `reasoningEffort` (configured as `null`) has no
|
|
345
|
+
// thinking mode: sending an effort to a model without reasoning
|
|
346
|
+
// metadata would be rejected with UNSUPPORTED_REASONING_EFFORT.
|
|
347
|
+
...route.reasoningEffort === undefined ? {} : { reasoningEffort: route.reasoningEffort },
|
|
347
348
|
}
|
|
348
349
|
} catch (error) {
|
|
349
350
|
// The router must never be able to fail a user request: hand the host's
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apinggithub/mydsh-auto-model-router",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Hybrid model router for DeepSeek Harness: declarative rules, user-configurable cost control (cost-first / quality-first / balanced), optional LLM task classifier, and failure fallback chains.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.mjs",
|