mohdel 0.90.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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +377 -0
  3. package/config/benchmarks.json +39 -0
  4. package/js/client/call.js +75 -0
  5. package/js/client/call_image.js +82 -0
  6. package/js/client/gate-binary.js +72 -0
  7. package/js/client/index.js +16 -0
  8. package/js/client/ndjson.js +29 -0
  9. package/js/client/transport.js +48 -0
  10. package/js/core/envelope.js +141 -0
  11. package/js/core/errors.js +75 -0
  12. package/js/core/events.js +96 -0
  13. package/js/core/image.js +58 -0
  14. package/js/core/index.js +10 -0
  15. package/js/core/status.js +48 -0
  16. package/js/factory/bridge.js +372 -0
  17. package/js/session/_cooldown.js +114 -0
  18. package/js/session/_logger.js +138 -0
  19. package/js/session/_rate_limiter.js +77 -0
  20. package/js/session/_tracing.js +58 -0
  21. package/js/session/adapters/_cancelled.js +44 -0
  22. package/js/session/adapters/_catalog.js +58 -0
  23. package/js/session/adapters/_chat_completions.js +439 -0
  24. package/js/session/adapters/_errors.js +85 -0
  25. package/js/session/adapters/_images.js +60 -0
  26. package/js/session/adapters/_lazy_json_cache.js +76 -0
  27. package/js/session/adapters/_pricing.js +67 -0
  28. package/js/session/adapters/_providers.js +60 -0
  29. package/js/session/adapters/_tools.js +185 -0
  30. package/js/session/adapters/_videos.js +283 -0
  31. package/js/session/adapters/anthropic.js +397 -0
  32. package/js/session/adapters/cerebras.js +28 -0
  33. package/js/session/adapters/deepseek.js +32 -0
  34. package/js/session/adapters/echo.js +51 -0
  35. package/js/session/adapters/fake.js +262 -0
  36. package/js/session/adapters/fireworks.js +46 -0
  37. package/js/session/adapters/gemini.js +381 -0
  38. package/js/session/adapters/groq.js +23 -0
  39. package/js/session/adapters/image/fake.js +55 -0
  40. package/js/session/adapters/image/index.js +40 -0
  41. package/js/session/adapters/image/novita.js +135 -0
  42. package/js/session/adapters/image/openai.js +50 -0
  43. package/js/session/adapters/index.js +53 -0
  44. package/js/session/adapters/mistral.js +31 -0
  45. package/js/session/adapters/novita.js +29 -0
  46. package/js/session/adapters/openai.js +381 -0
  47. package/js/session/adapters/openrouter.js +66 -0
  48. package/js/session/adapters/xai.js +27 -0
  49. package/js/session/bin.js +54 -0
  50. package/js/session/driver.js +160 -0
  51. package/js/session/index.js +18 -0
  52. package/js/session/run.js +393 -0
  53. package/js/session/run_image.js +61 -0
  54. package/package.json +107 -0
  55. package/src/cli/ask.js +160 -0
  56. package/src/cli/backup.js +107 -0
  57. package/src/cli/bench.js +262 -0
  58. package/src/cli/check.js +123 -0
  59. package/src/cli/colored-logger.js +67 -0
  60. package/src/cli/colors.js +13 -0
  61. package/src/cli/default.js +39 -0
  62. package/src/cli/index.js +150 -0
  63. package/src/cli/json-output.js +60 -0
  64. package/src/cli/model.js +571 -0
  65. package/src/cli/onboard.js +232 -0
  66. package/src/cli/rank.js +176 -0
  67. package/src/cli/ratelimit.js +160 -0
  68. package/src/cli/tag.js +105 -0
  69. package/src/lib/assets/alibaba.svg +1 -0
  70. package/src/lib/assets/anthropic.svg +5 -0
  71. package/src/lib/assets/deepseek.svg +1 -0
  72. package/src/lib/assets/gemini.svg +1 -0
  73. package/src/lib/assets/google.svg +2 -0
  74. package/src/lib/assets/kwaipilot.svg +1 -0
  75. package/src/lib/assets/meta.svg +1 -0
  76. package/src/lib/assets/minimax.svg +9 -0
  77. package/src/lib/assets/moonshotai.svg +4 -0
  78. package/src/lib/assets/openai.svg +5 -0
  79. package/src/lib/assets/xai.svg +1 -0
  80. package/src/lib/assets/xiaomi.svg +2 -0
  81. package/src/lib/assets/zai.svg +219 -0
  82. package/src/lib/benchmark-score.js +215 -0
  83. package/src/lib/benchmark-truth.js +68 -0
  84. package/src/lib/cache.js +76 -0
  85. package/src/lib/common.js +208 -0
  86. package/src/lib/cooldown.js +63 -0
  87. package/src/lib/creators.js +71 -0
  88. package/src/lib/curated-cache.js +146 -0
  89. package/src/lib/errors.js +126 -0
  90. package/src/lib/index.js +726 -0
  91. package/src/lib/logger.js +29 -0
  92. package/src/lib/providers.js +87 -0
  93. package/src/lib/rank.js +390 -0
  94. package/src/lib/rate-limiter.js +50 -0
  95. package/src/lib/schema.js +150 -0
  96. package/src/lib/select.js +474 -0
  97. package/src/lib/tracing.js +62 -0
  98. package/src/lib/utils.js +85 -0
@@ -0,0 +1,32 @@
1
+ /**
2
+ * DeepSeek adapter — OpenAI-compatible chat completions against
3
+ * api.deepseek.com. DeepSeek models occasionally emit tool calls as
4
+ * DSML XML-style blocks in `content` rather than the native
5
+ * `tool_calls` array; the shared core handles the fallback parse
6
+ * when `parseDsml: true`.
7
+ *
8
+ * @module session/adapters/deepseek
9
+ */
10
+
11
+ import OpenAI from 'openai'
12
+
13
+ import { runChatCompletions } from './_chat_completions.js'
14
+
15
+ const BASE_URL = 'https://api.deepseek.com'
16
+
17
+ /**
18
+ * @param {import('#core/envelope.js').CallEnvelope} envelope
19
+ * @param {{client?: any, signal?: AbortSignal, log?: any, span?: any}} [deps]
20
+ * @returns {AsyncGenerator<import('#core/events.js').Event>}
21
+ */
22
+ export async function * deepseek (envelope, deps = {}) {
23
+ const client = deps.client ?? new OpenAI({ apiKey: envelope.auth.key, baseURL: envelope.auth.baseURL || BASE_URL })
24
+ yield * runChatCompletions(envelope, client, {
25
+ provider: 'deepseek',
26
+ parseDsml: true
27
+ }, {
28
+ signal: deps.signal,
29
+ log: deps.log,
30
+ span: deps.span
31
+ })
32
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Echo adapter — deterministic stub. Emits two message deltas and a
3
+ * `done` event with a synthetic `AnswerResult`. Honors `signal` for
4
+ * test-controlled cancellation.
5
+ *
6
+ * @module session/adapters/echo
7
+ */
8
+
9
+ import { STATUS_COMPLETED } from '#core/status.js'
10
+
11
+ import { cancelledDone } from './_cancelled.js'
12
+
13
+ /**
14
+ * @param {import('#core/envelope.js').CallEnvelope} envelope
15
+ * @param {{signal?: AbortSignal}} [deps]
16
+ * @returns {AsyncGenerator<import('#core/events.js').Event>}
17
+ */
18
+ export async function * echo (envelope, { signal } = {}) {
19
+ const start = String(process.hrtime.bigint())
20
+ let first = null
21
+ let output = ''
22
+
23
+ for (const delta of ['Hello', ', world.']) {
24
+ if (signal?.aborted) {
25
+ yield cancelledDone(start, first, envelope, output, 0, 0)
26
+ return
27
+ }
28
+ if (first === null) first = String(process.hrtime.bigint())
29
+ output += delta
30
+ yield { type: 'delta', delta: { type: 'message', delta } }
31
+ }
32
+
33
+ if (signal?.aborted) {
34
+ yield cancelledDone(start, first, envelope, output, 0, 0)
35
+ return
36
+ }
37
+
38
+ const end = String(process.hrtime.bigint())
39
+ yield {
40
+ type: 'done',
41
+ result: {
42
+ status: STATUS_COMPLETED,
43
+ output,
44
+ inputTokens: 0,
45
+ outputTokens: 0,
46
+ thinkingTokens: 0,
47
+ cost: 0,
48
+ timestamps: { start, first: first ?? end, end }
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Fake provider — scenario-driven adapter for stress tests,
3
+ * benchmarks, and bug reproductions. Never calls a real API.
4
+ *
5
+ * The envelope's `prompt` field carries a JSON scenario spec; the
6
+ * `mode` key picks a behavior, the rest are mode-specific params.
7
+ * Invalid / non-JSON prompts fall through to `mode: "echo"` so ad-hoc
8
+ * use from scripts Just Works.
9
+ *
10
+ * ## Modes
11
+ *
12
+ * | mode | params | behavior |
13
+ * |---------------|--------------------------------|------------------------------------------------------|
14
+ * | `echo` | – | one short delta + `done` (default fallback) |
15
+ * | `slow` | `tokens`, `delayMs` | emit N deltas `delayMs` apart |
16
+ * | `volume` | `tokens` | emit N deltas as fast as the event loop allows |
17
+ * | `tool` | `name`, `args` | single `tool_use` terminal with one tool call |
18
+ * | `incomplete` | `warning` | `done` with status=incomplete + warning |
19
+ * | `error` | `type`, `message`, `retryable` | yield typed error event |
20
+ * | `hang` | – | never emits a terminal (caller aborts via signal) |
21
+ * | `cancel_after`| `tokens` | emit N deltas then wait for `signal.aborted` |
22
+ * | `crash` | `code` | `process.exit(code\|1)` — kills whichever process is running the adapter. Used by the isolation benchmark to demonstrate that via-gate the crash stays in the session subprocess, in-process it takes down the caller. |
23
+ *
24
+ * Every mode honors `deps.signal`: when aborted mid-stream, emits a
25
+ * `cancelled` done event. That keeps consumer behavior uniform with
26
+ * the real provider adapters.
27
+ *
28
+ * @module session/adapters/fake
29
+ */
30
+
31
+ import {
32
+ STATUS_COMPLETED,
33
+ STATUS_INCOMPLETE,
34
+ STATUS_TOOL_USE,
35
+ WARNING_INSUFFICIENT_OUTPUT_BUDGET
36
+ } from '#core/status.js'
37
+
38
+ import { cancelledDone } from './_cancelled.js'
39
+
40
+ /**
41
+ * @param {import('#core/envelope.js').CallEnvelope} envelope
42
+ * @param {{signal?: AbortSignal, log?: any}} [deps]
43
+ * @returns {AsyncGenerator<import('#core/events.js').Event>}
44
+ */
45
+ export async function * fake (envelope, deps = {}) {
46
+ const signal = deps.signal
47
+ const spec = parseSpec(envelope.prompt)
48
+ const start = String(process.hrtime.bigint())
49
+ let first = null
50
+
51
+ switch (spec.mode) {
52
+ case 'error':
53
+ yield {
54
+ type: 'error',
55
+ error: {
56
+ message: spec.message ?? 'fake error',
57
+ severity: spec.severity ?? 'error',
58
+ retryable: spec.retryable ?? false,
59
+ type: spec.type ?? 'PROVIDER_ERROR'
60
+ }
61
+ }
62
+ return
63
+
64
+ case 'hang':
65
+ // Wait for abort. If the signal is already aborted, yield
66
+ // cancelled immediately. Otherwise block until it fires.
67
+ await waitForAbort(signal)
68
+ yield cancelledDone(start, first, envelope, '', 0, 0)
69
+ return
70
+
71
+ case 'crash': {
72
+ // Kills whatever process is executing the adapter. In-process
73
+ // this takes down the caller; via the gate it only takes down
74
+ // one session subprocess, which the pool respawns.
75
+ const code = Number.isInteger(spec.code) ? spec.code : 1
76
+ process.exit(code)
77
+ // process.exit does not return; this line is unreachable but
78
+ // the linter can't see that and would flag no-fallthrough.
79
+ return
80
+ }
81
+
82
+ case 'incomplete': {
83
+ const warning = spec.warning ?? WARNING_INSUFFICIENT_OUTPUT_BUDGET
84
+ const text = spec.output ?? 'partial output'
85
+ first = String(process.hrtime.bigint())
86
+ yield { type: 'delta', delta: { type: 'message', delta: text } }
87
+ yield doneEvent({
88
+ status: STATUS_INCOMPLETE,
89
+ output: text,
90
+ warning,
91
+ start,
92
+ first,
93
+ tokens: approxTokens(text)
94
+ })
95
+ return
96
+ }
97
+
98
+ case 'tool': {
99
+ const name = spec.name ?? 'fake_tool'
100
+ const args = spec.args ?? {}
101
+ const id = spec.id ?? `fake_call_${Date.now()}`
102
+ first = String(process.hrtime.bigint())
103
+ yield {
104
+ type: 'delta',
105
+ delta: { type: 'function_call', delta: JSON.stringify(args) }
106
+ }
107
+ yield doneEvent({
108
+ status: STATUS_TOOL_USE,
109
+ output: null,
110
+ start,
111
+ first,
112
+ tokens: 0,
113
+ toolCalls: [{ id, name, arguments: args }]
114
+ })
115
+ return
116
+ }
117
+
118
+ case 'slow':
119
+ case 'volume': {
120
+ const total = clampPositive(spec.tokens, 5)
121
+ const delayMs = spec.mode === 'slow' ? clampPositive(spec.delayMs, 50) : 0
122
+ let output = ''
123
+ for (let i = 0; i < total; i++) {
124
+ if (signal?.aborted) {
125
+ yield cancelledDone(start, first, envelope, output, approxTokens(output), approxTokens(output))
126
+ return
127
+ }
128
+ if (first === null) first = String(process.hrtime.bigint())
129
+ const chunk = spec.chunk ?? `tok${i} `
130
+ output += chunk
131
+ yield { type: 'delta', delta: { type: 'message', delta: chunk } }
132
+ if (delayMs > 0) {
133
+ await sleep(delayMs, signal)
134
+ if (signal?.aborted) {
135
+ yield cancelledDone(start, first, envelope, output, approxTokens(output), approxTokens(output))
136
+ return
137
+ }
138
+ }
139
+ }
140
+ yield doneEvent({
141
+ status: STATUS_COMPLETED,
142
+ output,
143
+ start,
144
+ first,
145
+ tokens: approxTokens(output)
146
+ })
147
+ return
148
+ }
149
+
150
+ case 'cancel_after': {
151
+ const total = clampPositive(spec.tokens, 3)
152
+ let output = ''
153
+ for (let i = 0; i < total; i++) {
154
+ if (first === null) first = String(process.hrtime.bigint())
155
+ const chunk = `tok${i} `
156
+ output += chunk
157
+ yield { type: 'delta', delta: { type: 'message', delta: chunk } }
158
+ }
159
+ await waitForAbort(signal)
160
+ yield cancelledDone(start, first, envelope, output, approxTokens(output), approxTokens(output))
161
+ return
162
+ }
163
+
164
+ case 'echo':
165
+ default: {
166
+ const text = spec.output ?? 'ok'
167
+ first = String(process.hrtime.bigint())
168
+ yield { type: 'delta', delta: { type: 'message', delta: text } }
169
+ yield doneEvent({
170
+ status: STATUS_COMPLETED,
171
+ output: text,
172
+ start,
173
+ first,
174
+ tokens: approxTokens(text)
175
+ })
176
+ }
177
+ }
178
+ }
179
+
180
+ /**
181
+ * @param {string | any[]} prompt
182
+ * @returns {{mode: string, [k: string]: any}}
183
+ */
184
+ function parseSpec (prompt) {
185
+ if (typeof prompt !== 'string') return { mode: 'echo' }
186
+ const trimmed = prompt.trim()
187
+ if (!trimmed.startsWith('{')) return { mode: 'echo' }
188
+ try {
189
+ const obj = JSON.parse(trimmed)
190
+ if (!obj || typeof obj !== 'object' || !obj.mode) return { mode: 'echo' }
191
+ return obj
192
+ } catch {
193
+ return { mode: 'echo' }
194
+ }
195
+ }
196
+
197
+ /**
198
+ * @param {number | undefined} v
199
+ * @param {number} fallback
200
+ */
201
+ function clampPositive (v, fallback) {
202
+ if (typeof v !== 'number' || !Number.isFinite(v) || v < 0) return fallback
203
+ return Math.floor(v)
204
+ }
205
+
206
+ /** Cheap token estimate — 4 chars/token, deterministic across runs. */
207
+ function approxTokens (text) {
208
+ return Math.max(1, Math.ceil((text?.length ?? 0) / 4))
209
+ }
210
+
211
+ /**
212
+ * @param {number} ms
213
+ * @param {AbortSignal} [signal]
214
+ */
215
+ function sleep (ms, signal) {
216
+ return new Promise((resolve) => {
217
+ const timer = setTimeout(resolve, ms)
218
+ if (signal) {
219
+ signal.addEventListener('abort', () => {
220
+ clearTimeout(timer)
221
+ resolve()
222
+ }, { once: true })
223
+ }
224
+ })
225
+ }
226
+
227
+ /** @param {AbortSignal} [signal] */
228
+ function waitForAbort (signal) {
229
+ if (!signal) return new Promise(() => {}) // never resolves — caller had better set a timeout
230
+ if (signal.aborted) return Promise.resolve()
231
+ return new Promise((resolve) => {
232
+ signal.addEventListener('abort', () => resolve(), { once: true })
233
+ })
234
+ }
235
+
236
+ /**
237
+ * @param {{
238
+ * status: string,
239
+ * output: string | null,
240
+ * start: string,
241
+ * first: string | null,
242
+ * tokens: number,
243
+ * warning?: string,
244
+ * toolCalls?: any[]
245
+ * }} opts
246
+ * @returns {import('#core/events.js').DoneEvent}
247
+ */
248
+ function doneEvent (opts) {
249
+ const end = String(process.hrtime.bigint())
250
+ const result = {
251
+ status: opts.status,
252
+ output: opts.output,
253
+ inputTokens: opts.tokens,
254
+ outputTokens: opts.tokens,
255
+ thinkingTokens: 0,
256
+ cost: 0,
257
+ timestamps: { start: opts.start, first: opts.first ?? end, end }
258
+ }
259
+ if (opts.warning) result.warning = opts.warning
260
+ if (opts.toolCalls) result.toolCalls = opts.toolCalls
261
+ return { type: 'done', result }
262
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Fireworks adapter — OpenAI-compatible chat completions with
3
+ * streaming, over api.fireworks.ai/inference/v1.
4
+ *
5
+ * Fireworks model IDs carry an `accounts/fireworks/models/` prefix;
6
+ * envelopes can supply either form. The `mutateArgs` hook normalizes
7
+ * `args.model` before the request leaves the adapter.
8
+ *
9
+ * Implementation uses the OpenAI SDK with a custom baseURL — the
10
+ * wire shape is identical and the SDK's streaming iterator matches
11
+ * what `_chat_completions.js` expects.
12
+ *
13
+ * @module session/adapters/fireworks
14
+ */
15
+
16
+ import OpenAI from 'openai'
17
+
18
+ import { runChatCompletions } from './_chat_completions.js'
19
+
20
+ const BASE_URL = 'https://api.fireworks.ai/inference/v1'
21
+ const MODEL_PREFIX = 'accounts/fireworks/models/'
22
+
23
+ /**
24
+ * @param {import('#core/envelope.js').CallEnvelope} envelope
25
+ * @param {{client?: any, signal?: AbortSignal, log?: any, span?: any}} [deps]
26
+ * @returns {AsyncGenerator<import('#core/events.js').Event>}
27
+ */
28
+ export async function * fireworks (envelope, deps = {}) {
29
+ const client = deps.client ?? new OpenAI({
30
+ apiKey: envelope.auth.key,
31
+ baseURL: envelope.auth.baseURL || BASE_URL
32
+ })
33
+ yield * runChatCompletions(envelope, client, {
34
+ provider: 'fireworks',
35
+ stream: true,
36
+ mutateArgs: (env, args) => {
37
+ if (!args.model.includes('/')) {
38
+ args.model = `${MODEL_PREFIX}${args.model}`
39
+ }
40
+ }
41
+ }, {
42
+ signal: deps.signal,
43
+ log: deps.log,
44
+ span: deps.span
45
+ })
46
+ }