mohdel 0.123.0 → 0.124.0
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/README.md +3 -1
- package/js/factory/bridge.js +2 -2
- package/package.json +4 -4
- package/src/cli/ask.js +1 -1
- package/src/lib/common.js +3 -4
- package/src/lib/curated-cache.js +5 -0
- package/src/lib/index.js +25 -20
package/README.md
CHANGED
|
@@ -154,7 +154,9 @@ const result = await mo.use('anthropic/claude-sonnet-4-6').answer('Hello')
|
|
|
154
154
|
console.log(result.output, result.cost)
|
|
155
155
|
```
|
|
156
156
|
|
|
157
|
-
No subprocess, no setup beyond your API key. Right for CLI tools (`mo ask`), scripts, tests, and single-process services — which is most projects.
|
|
157
|
+
No subprocess, no setup beyond your API key. Right for CLI tools (`mo ask`), scripts, tests, and single-process services — which is most projects. Pass an `AbortSignal` as `answer(prompt, { signal })` to cancel in flight.
|
|
158
|
+
|
|
159
|
+
The factory reads the catalog from `~/.config/mohdel/curated.json` and keys from the environment, the same defaults a gate session starts from. `mohdel({ models })` replaces the catalog for the process, factory and session runtime alike, the way `set_catalog` replaces it in a gate session. `mohdel({ configurations: { openai: { apiKey } } })` overrides the key per provider.
|
|
158
160
|
|
|
159
161
|
### Client — cross-process (the production gateway)
|
|
160
162
|
|
package/js/factory/bridge.js
CHANGED
|
@@ -35,7 +35,6 @@ import { createRealtimeDeltaBuffer } from '../../src/lib/utils.js'
|
|
|
35
35
|
* Hook that returns `{rpmLimit, tpmLimit}` for a given provider —
|
|
36
36
|
* lets the factory keep its own `providersConfig` as source of
|
|
37
37
|
* truth instead of the module-level `providers.json` loader.
|
|
38
|
-
* @property {AbortSignal} [signal]
|
|
39
38
|
*/
|
|
40
39
|
|
|
41
40
|
/**
|
|
@@ -58,6 +57,7 @@ import { createRealtimeDeltaBuffer } from '../../src/lib/utils.js'
|
|
|
58
57
|
* | `providerOrder` / `providerAllow` / `providerDeny`| envelope.providerOptions.openrouter |
|
|
59
58
|
* | `traceparent` / `baggage` | envelope transport metadata |
|
|
60
59
|
* | `callId` / `authId` | envelope transport metadata |
|
|
60
|
+
* | `signal` | not on the wire — `run()` deps, aborts the call |
|
|
61
61
|
* | `configuration.apiKey` | envelope.auth.key |
|
|
62
62
|
* | **`parentSpan`** | **dropped** — use `traceparent` instead |
|
|
63
63
|
* | **`maybeThrowHandler`** | **dropped** — no factory-side validation hook |
|
|
@@ -90,7 +90,7 @@ export async function runAnswer ({ provider, model, modelKey, configuration, pro
|
|
|
90
90
|
|
|
91
91
|
let terminal
|
|
92
92
|
try {
|
|
93
|
-
for await (const ev of run(envelope, deps)) {
|
|
93
|
+
for await (const ev of run(envelope, { ...deps, signal: options.signal })) {
|
|
94
94
|
if (ev.type === 'delta' && ev.delta) {
|
|
95
95
|
if (deltaBuffer) deltaBuffer.push(ev.delta.type, ev.delta.delta)
|
|
96
96
|
} else if (ev.type === 'done' || ev.type === 'error') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mohdel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.124.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Christophe Le Bars",
|
|
@@ -108,16 +108,16 @@
|
|
|
108
108
|
"@opentelemetry/exporter-trace-otlp-grpc": "^0.222.0",
|
|
109
109
|
"@opentelemetry/sdk-node": "^0.222.0",
|
|
110
110
|
"chalk": "^6.0.0",
|
|
111
|
-
"mohdel-thin-gate-linux-x64-gnu": "0.
|
|
111
|
+
"mohdel-thin-gate-linux-x64-gnu": "0.124.0"
|
|
112
112
|
},
|
|
113
113
|
"dependencies": {
|
|
114
|
-
"@anthropic-ai/sdk": "^0.
|
|
114
|
+
"@anthropic-ai/sdk": "^0.123.0",
|
|
115
115
|
"@cerebras/cerebras_cloud_sdk": "^1.91.0",
|
|
116
116
|
"@google/genai": "^2.20.0",
|
|
117
117
|
"@opentelemetry/api": "^1.9.1",
|
|
118
118
|
"env-paths": "^4.0.0",
|
|
119
119
|
"groq-sdk": "^1.6.0",
|
|
120
|
-
"openai": "^7.
|
|
120
|
+
"openai": "^7.9.0",
|
|
121
121
|
"undici": "^7.29.0"
|
|
122
122
|
},
|
|
123
123
|
"lint-staged": {
|
package/src/cli/ask.js
CHANGED
|
@@ -13,7 +13,7 @@ export const hintsForError = (err, modelId) => {
|
|
|
13
13
|
const provider = modelId.includes('/') ? modelId.split('/')[0] : null
|
|
14
14
|
const hints = []
|
|
15
15
|
|
|
16
|
-
if (/not found in
|
|
16
|
+
if (/not found in catalog/i.test(both)) {
|
|
17
17
|
if (provider) {
|
|
18
18
|
hints.push(`→ run: mo curate ${provider} # add upstream models from this provider`)
|
|
19
19
|
hints.push(`→ or: mo model add ${modelId} # add this one manually`)
|
package/src/lib/common.js
CHANGED
|
@@ -112,12 +112,11 @@ const createFileOperation = (filePath, defaultValue = {}, operationType) => {
|
|
|
112
112
|
const loadHandler = async () => {
|
|
113
113
|
let loadedData
|
|
114
114
|
try {
|
|
115
|
-
if (!existsSync(CONFIG_DIR)) {
|
|
116
|
-
await mkdir(CONFIG_DIR, { recursive: true })
|
|
117
|
-
}
|
|
118
|
-
|
|
119
115
|
if (!existsSync(filePath)) {
|
|
120
116
|
if (defaultValue && Object.keys(defaultValue).length > 0) {
|
|
117
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
118
|
+
await mkdir(CONFIG_DIR, { recursive: true })
|
|
119
|
+
}
|
|
121
120
|
await writeFile(filePath, JSON.stringify(defaultValue, null, 2))
|
|
122
121
|
loadedData = JSON.parse(JSON.stringify(defaultValue))
|
|
123
122
|
} else {
|
package/src/lib/curated-cache.js
CHANGED
|
@@ -69,6 +69,11 @@ const rebuildAliasMap = () => {
|
|
|
69
69
|
|
|
70
70
|
export const loadCuratedCache = ensureCuratedCache
|
|
71
71
|
|
|
72
|
+
export const setCuratedCache = (table) => {
|
|
73
|
+
curatedCache = { ...table }
|
|
74
|
+
aliasMapCache = buildAliasMap(curatedCache)
|
|
75
|
+
}
|
|
76
|
+
|
|
72
77
|
export const getCuratedCacheSnapshot = () => curatedCache
|
|
73
78
|
|
|
74
79
|
export const getAliasMapSnapshot = () => aliasMapCache
|
package/src/lib/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import providers from './providers.js'
|
|
|
7
7
|
import { getAPIKey, loadDefaultEnv, getProvidersConfig, saveProvidersConfig, setLogger as setCommonLogger } from './common.js'
|
|
8
8
|
import {
|
|
9
9
|
loadCuratedCache,
|
|
10
|
+
setCuratedCache,
|
|
10
11
|
getCuratedCacheSnapshot,
|
|
11
12
|
expandModelAliasSync,
|
|
12
13
|
suggestModels,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
} from './curated-cache.js'
|
|
15
16
|
import { createRateLimiter } from '../../js/session/_rate_limiter.js'
|
|
16
17
|
import { createCooldownTracker } from '../../js/session/_cooldown.js'
|
|
18
|
+
import { setCatalog } from '../../js/session/adapters/_catalog.js'
|
|
17
19
|
import { runAnswer, runAnswerImage, runAnswerTranscription } from '../../js/factory/bridge.js'
|
|
18
20
|
import { startSpan, endSpanOk, endSpanError } from './tracing.js'
|
|
19
21
|
import { isValidTag } from './schema.js'
|
|
@@ -200,21 +202,27 @@ export { buildHandlers as _buildHandlersForTests }
|
|
|
200
202
|
* @param {Function} [opts.onFailure] — fired after every failed answer call
|
|
201
203
|
* @param {number} [opts.cooldownThreshold=3] — consecutive provider failures before cooldown
|
|
202
204
|
* @param {number} [opts.cooldownDuration=60000] — cooldown duration in ms
|
|
203
|
-
* @param {object} [opts.models] —
|
|
204
|
-
*
|
|
205
|
+
* @param {object} [opts.models] — catalog `{ [provider/model]: spec }`. Replaces
|
|
206
|
+
* `~/.config/mohdel/curated.json` for this process,
|
|
207
|
+
* factory and session runtime alike.
|
|
208
|
+
* @param {object} [opts.configurations] — `{ [provider]: { apiKey } }`. Overrides the env
|
|
209
|
+
* key per provider; env for the rest.
|
|
205
210
|
*/
|
|
206
211
|
const mohdel = async ({ logger, verbosity: verbosityOpt, onSuccess, onFailure, cooldownThreshold, cooldownDuration, models, configurations } = {}) => {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
212
|
+
loadDefaultEnv()
|
|
213
|
+
|
|
214
|
+
// The session runtime keeps its own catalog cache (the seam thin-gate's
|
|
215
|
+
// `set_catalog` uses) and would otherwise lazy-load curated.json, so a
|
|
216
|
+
// replacement has to reach both caches.
|
|
217
|
+
if (models) {
|
|
218
|
+
setCuratedCache(models)
|
|
219
|
+
setCatalog(models)
|
|
220
|
+
} else {
|
|
213
221
|
await loadCuratedCache()
|
|
214
222
|
}
|
|
215
223
|
|
|
216
224
|
// Provider config: user-specific rate limits per provider (~/.config/mohdel/providers.json)
|
|
217
|
-
const providersConfig =
|
|
225
|
+
const providersConfig = await getProvidersConfig()
|
|
218
226
|
|
|
219
227
|
// Resolve verbosity tier once at init. Factory opt > env var > default.
|
|
220
228
|
// Captured in the answer() closure below to gate per-call log lines.
|
|
@@ -248,7 +256,7 @@ const mohdel = async ({ logger, verbosity: verbosityOpt, onSuccess, onFailure, c
|
|
|
248
256
|
get: (target, prop) => {
|
|
249
257
|
if (prop === 'list') {
|
|
250
258
|
return (tag) => { // Sync
|
|
251
|
-
const catalog =
|
|
259
|
+
const catalog = getCuratedCacheSnapshot()
|
|
252
260
|
let modelEntries = Object.entries(catalog)
|
|
253
261
|
.filter(([, metadata]) => !metadata.deprecated)
|
|
254
262
|
|
|
@@ -267,7 +275,7 @@ const mohdel = async ({ logger, verbosity: verbosityOpt, onSuccess, onFailure, c
|
|
|
267
275
|
|
|
268
276
|
if (prop === 'use') {
|
|
269
277
|
return (modelId) => { // Sync
|
|
270
|
-
const catalog =
|
|
278
|
+
const catalog = getCuratedCacheSnapshot()
|
|
271
279
|
|
|
272
280
|
// Parse optional :outputEffort suffix (e.g. "claude-opus:max").
|
|
273
281
|
// Effort levels vary per spec (`thinkingEffortLevels` keys —
|
|
@@ -284,7 +292,7 @@ const mohdel = async ({ logger, verbosity: verbosityOpt, onSuccess, onFailure, c
|
|
|
284
292
|
// Fallback specs are excluded from that test on purpose:
|
|
285
293
|
// providers that synthesize them resolve any string, which
|
|
286
294
|
// would disable suffix parsing for them entirely.
|
|
287
|
-
const exactId =
|
|
295
|
+
const exactId = expandModelAliasSync(modelId)
|
|
288
296
|
const isCuratedId = !!catalog[exactId]
|
|
289
297
|
|
|
290
298
|
// `@speed` is parsed off first so the two suffixes are read
|
|
@@ -299,7 +307,7 @@ const mohdel = async ({ logger, verbosity: verbosityOpt, onSuccess, onFailure, c
|
|
|
299
307
|
// resolves the same as `x@fast`.
|
|
300
308
|
const colon = base.lastIndexOf(':')
|
|
301
309
|
const probe = colon > 0 ? base.slice(0, colon) : base
|
|
302
|
-
const probeResolved =
|
|
310
|
+
const probeResolved = expandModelAliasSync(probe)
|
|
303
311
|
if (catalog[probeResolved] || createFallbackModelSpec(probeResolved)) {
|
|
304
312
|
aliasSpeed = candidate
|
|
305
313
|
modelId = base
|
|
@@ -311,7 +319,7 @@ const mohdel = async ({ logger, verbosity: verbosityOpt, onSuccess, onFailure, c
|
|
|
311
319
|
if (colonIdx > 0) {
|
|
312
320
|
const candidate = modelId.slice(colonIdx + 1)
|
|
313
321
|
const base = modelId.slice(0, colonIdx)
|
|
314
|
-
const baseResolved =
|
|
322
|
+
const baseResolved = expandModelAliasSync(base)
|
|
315
323
|
const baseSpec = catalog[baseResolved] || createFallbackModelSpec(baseResolved)
|
|
316
324
|
if (baseSpec) {
|
|
317
325
|
aliasOutputEffort = candidate
|
|
@@ -319,17 +327,14 @@ const mohdel = async ({ logger, verbosity: verbosityOpt, onSuccess, onFailure, c
|
|
|
319
327
|
}
|
|
320
328
|
}
|
|
321
329
|
|
|
322
|
-
let resolvedModelId =
|
|
330
|
+
let resolvedModelId = expandModelAliasSync(modelId)
|
|
323
331
|
let modelSpec = catalog[resolvedModelId]
|
|
324
332
|
|
|
325
333
|
if (!modelSpec) {
|
|
326
334
|
modelSpec = createFallbackModelSpec(resolvedModelId)
|
|
327
335
|
if (!modelSpec) {
|
|
328
|
-
if (libraryMode) {
|
|
329
|
-
throw new Error(`Model '${modelId}' not found in provided models.`)
|
|
330
|
-
}
|
|
331
336
|
const suggestions = suggestModels(modelId)
|
|
332
|
-
let msg = `Model '${modelId}' not found in
|
|
337
|
+
let msg = `Model '${modelId}' not found in catalog.`
|
|
333
338
|
if (suggestions.length) {
|
|
334
339
|
msg += ' Did you mean?\n' + suggestions.map(s => ` ${s.id} ${s.label}`).join('\n')
|
|
335
340
|
}
|
|
@@ -777,7 +782,7 @@ const createModelProxy = (resolvedModelId, modelSpec, handlers, aliasOutputEffor
|
|
|
777
782
|
|
|
778
783
|
if (prop === 'info') {
|
|
779
784
|
return () => { // Sync
|
|
780
|
-
const catalog =
|
|
785
|
+
const catalog = getCuratedCacheSnapshot()
|
|
781
786
|
return catalog?.[resolvedModelId] ? { ...catalog[resolvedModelId] } : { ...modelSpec }
|
|
782
787
|
}
|
|
783
788
|
}
|