@apinggithub/mydsh-auto-model-router 0.2.1 → 0.2.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/auto-adapter.mjs CHANGED
@@ -26,6 +26,25 @@ import { routesEqual } from './router.mjs'
26
26
  export const AUTO_PROVIDER = 'auto'
27
27
  export const AUTO_MODEL = 'auto'
28
28
 
29
+ /**
30
+ * Reasoning efforts advertised when the routed model's own metadata cannot be
31
+ * read: the four values the DeepSeek catalog accepts.
32
+ */
33
+ const FALLBACK_EFFORTS = Object.freeze([
34
+ { id: 'off', name: 'Off' },
35
+ { id: 'low', name: 'Low' },
36
+ { id: 'high', name: 'High' },
37
+ { id: 'max', name: 'Max' },
38
+ ])
39
+
40
+ /**
41
+ * Context window advertised when the routed model does not report one.
42
+ * DeepSeek's 0.2.x catalog tops out at 1M tokens; assuming the largest window
43
+ * merely keeps compaction from firing early — the routed provider still
44
+ * rejects an oversized request and the fallback chain takes over.
45
+ */
46
+ const FALLBACK_CONTEXT_WINDOW = 1_000_000
47
+
29
48
  /** Provider route metadata shown in the selector. */
30
49
  export function autoProviderInfo(provider) {
31
50
  return { id: provider, name: 'Auto Router' }
@@ -41,6 +60,53 @@ export function autoResolveModel(provider, model) {
41
60
  return { provider, id: model, name: model }
42
61
  }
43
62
 
63
+ /**
64
+ * The route the virtual model should borrow its capabilities from.
65
+ *
66
+ * The host asks `resolveModel("auto", "auto")` before it assembles a request,
67
+ * long before any session decision exists, so this picks the configured
68
+ * default level (falling back to the first configured one).
69
+ *
70
+ * @param {object | undefined} router
71
+ * @returns {{ provider: string, model: string, reasoningEffort?: string } | undefined}
72
+ */
73
+ function representativeRoute(router) {
74
+ try {
75
+ const level = router?.config?.costControl?.defaultLevel
76
+ const route = (level === undefined ? undefined : router?.routeForLevel?.(level))
77
+ ?? router?.firstConfiguredRoute?.()
78
+ if (route === undefined || route === null) return undefined
79
+ if (typeof route.provider !== 'string' || route.provider === '') return undefined
80
+ if (typeof route.model !== 'string' || route.model === '') return undefined
81
+ return route
82
+ } catch {
83
+ return undefined
84
+ }
85
+ }
86
+
87
+ /** Read the routed model's own metadata, tolerating a host without it. */
88
+ async function underlyingInfo(llm, route) {
89
+ if (route === undefined || typeof llm?.resolveModelInfo !== 'function') return undefined
90
+ try {
91
+ return await llm.resolveModelInfo(route.provider, route.model)
92
+ } catch {
93
+ return undefined
94
+ }
95
+ }
96
+
97
+ /** Copy the routed model's efforts, or the DeepSeek defaults. */
98
+ function reasoningEfforts(info) {
99
+ const efforts = info?.reasoning?.efforts
100
+ if (Array.isArray(efforts) && efforts.length > 0) {
101
+ return efforts.map((effort) => ({
102
+ id: effort.id,
103
+ name: effort.name,
104
+ ...effort.description === undefined ? {} : { description: effort.description },
105
+ }))
106
+ }
107
+ return FALLBACK_EFFORTS.map((effort) => ({ ...effort }))
108
+ }
109
+
44
110
  /**
45
111
  * Build the virtual adapter for one plugin mount.
46
112
  *
@@ -64,7 +130,43 @@ export function createAutoAdapter(resolve = {}) {
64
130
  return undefined
65
131
  },
66
132
  listModels: async () => autoListModels(),
67
- resolveModel: async (provider, model) => autoResolveModel(provider, model),
133
+ /**
134
+ * Capability metadata for the virtual model.
135
+ *
136
+ * The host validates a request against the exact model *before* any
137
+ * provider I/O and assembles the conversation with what it learns here:
138
+ * without a context window it cannot budget compaction, and without
139
+ * reasoning efforts it treats the route as non-thinking and stops echoing
140
+ * the `thinking` blocks a thinking-mode provider requires on every turn.
141
+ * Borrowing the routed model's own metadata keeps the virtual provider
142
+ * indistinguishable from the model it forwards to.
143
+ */
144
+ async resolveModel(provider, model) {
145
+ const route = representativeRoute(readRouter())
146
+ const info = await underlyingInfo(readLlm(), route)
147
+ const efforts = reasoningEfforts(info)
148
+ const requested = route?.reasoningEffort
149
+ const wanted = efforts.some((effort) => effort.id === requested)
150
+ ? requested
151
+ : info?.reasoning?.defaultEffort
152
+ const defaultEffort = efforts.some((effort) => effort.id === wanted) ? wanted : efforts[0]?.id
153
+ const contextWindow = Number.isInteger(info?.context?.contextWindow) && info.context.contextWindow > 0
154
+ ? info.context.contextWindow
155
+ : FALLBACK_CONTEXT_WINDOW
156
+ return {
157
+ provider,
158
+ id: model,
159
+ name: 'Auto',
160
+ context: { contextWindow },
161
+ systemPromptUpdate: info?.systemPromptUpdate ?? 'in-history',
162
+ ...info?.defaultMaxTokens === undefined ? {} : { defaultMaxTokens: info.defaultMaxTokens },
163
+ ...info?.inputModalities === undefined ? {} : { inputModalities: info.inputModalities },
164
+ reasoning: {
165
+ efforts,
166
+ ...defaultEffort === undefined ? {} : { defaultEffort },
167
+ },
168
+ }
169
+ },
68
170
  /**
69
171
  * The 0.2.x adapter contract: the runtime validates a request against the
70
172
  * model's capabilities and binds it to one concrete adapter generation
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.1'
25
+ export const PLUGIN_VERSION = '0.2.2'
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'])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apinggithub/mydsh-auto-model-router",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
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",