mohdel 0.98.0 → 0.98.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.
@@ -107,6 +107,7 @@ export async function * runChatCompletions (envelope, client, config, deps = {})
107
107
 
108
108
  let content = message.content || ''
109
109
  let toolCalls = message.tool_calls
110
+ const reasoning = message.reasoning_content || null
110
111
 
111
112
  if (config.parseDsml && content && (!toolCalls || !toolCalls.length)) {
112
113
  const dsml = parseDsmlToolCalls(content)
@@ -121,7 +122,7 @@ export async function * runChatCompletions (envelope, client, config, deps = {})
121
122
  }
122
123
 
123
124
  yield finalize({
124
- envelope, content, toolCalls, usage, finishReason, start, first
125
+ envelope, content, toolCalls, usage, finishReason, start, first, reasoning
125
126
  })
126
127
  }
127
128
 
@@ -140,6 +141,7 @@ async function * runStreaming (envelope, client, args, config, start, deps) {
140
141
  // F53: accumulate via array + join to avoid per-delta V8 cons-string
141
142
  // churn on long streams.
142
143
  const contentParts = []
144
+ const reasoningParts = []
143
145
  let first = null
144
146
  let finishReason = null
145
147
  let usage = {}
@@ -164,12 +166,13 @@ async function * runStreaming (envelope, client, args, config, start, deps) {
164
166
  for await (const chunk of stream) {
165
167
  const choice = chunk.choices?.[0]
166
168
  // DeepSeek V4 / deepseek-reasoner / Cerebras reasoning models emit
167
- // `delta.reasoning_content` chunks before visible content. Don't
168
- // accumulate (token count comes from `usage.completion_tokens_details.
169
- // reasoning_tokens`), but do mark TTFT so the first-token timestamp
170
- // reflects actual model start, not just first visible token.
171
- if (choice?.delta?.reasoning_content && first === null) {
172
- first = String(process.hrtime.bigint())
169
+ // `delta.reasoning_content` chunks before visible content. Capture
170
+ // them so multi-turn callers can roundtrip reasoning back to the
171
+ // API (DeepSeek V4 hard-rejects assistant messages without it).
172
+ // Token count comes from `usage.completion_tokens_details.reasoning_tokens`.
173
+ if (choice?.delta?.reasoning_content) {
174
+ if (first === null) first = String(process.hrtime.bigint())
175
+ reasoningParts.push(choice.delta.reasoning_content)
173
176
  }
174
177
  if (choice?.delta?.content) {
175
178
  if (first === null) first = String(process.hrtime.bigint())
@@ -235,7 +238,8 @@ async function * runStreaming (envelope, client, args, config, start, deps) {
235
238
  usage,
236
239
  finishReason,
237
240
  start,
238
- first
241
+ first,
242
+ reasoning: reasoningParts.length ? reasoningParts.join('') : null
239
243
  })
240
244
  }
241
245
 
@@ -247,11 +251,12 @@ async function * runStreaming (envelope, client, args, config, start, deps) {
247
251
  * usage: any,
248
252
  * finishReason: string | null,
249
253
  * start: string,
250
- * first: string | null
254
+ * first: string | null,
255
+ * reasoning?: string | null
251
256
  * }} p
252
257
  * @returns {import('#core/events.js').DoneEvent}
253
258
  */
254
- function finalize ({ envelope, content, toolCalls, usage, finishReason, start, first }) {
259
+ function finalize ({ envelope, content, toolCalls, usage, finishReason, start, first, reasoning = null }) {
255
260
  const end = String(process.hrtime.bigint())
256
261
  const inputTokens = usage.prompt_tokens || 0
257
262
  const totalOutputTokens = usage.completion_tokens || 0
@@ -282,6 +287,7 @@ function finalize ({ envelope, content, toolCalls, usage, finishReason, start, f
282
287
  if (toolCalls && toolCalls.length > 0) {
283
288
  done.result.toolCalls = fromCerebrasToolCalls(toolCalls)
284
289
  }
290
+ if (reasoning) done.result.reasoning = reasoning
285
291
  return done
286
292
  }
287
293
 
@@ -357,11 +363,12 @@ function toChatMessages (prompt) {
357
363
  content: flattenText(m.content)
358
364
  }
359
365
  }
366
+ const reasoning = m.role === 'assistant' ? extractReasoning(m.content) : null
360
367
  if (m.role === 'assistant' && m.toolCalls?.length) {
361
368
  // Chat Completions assistant turn: optional `content` + the
362
369
  // `tool_calls` array. `arguments` must be a JSON string on
363
370
  // the wire.
364
- return {
371
+ const msg = {
365
372
  role: 'assistant',
366
373
  content: flattenText(m.content) || '',
367
374
  tool_calls: m.toolCalls.map(tc => ({
@@ -373,8 +380,12 @@ function toChatMessages (prompt) {
373
380
  }
374
381
  }))
375
382
  }
383
+ if (reasoning) msg.reasoning_content = reasoning
384
+ return msg
376
385
  }
377
- return { role: m.role, content: flattenText(m.content) }
386
+ const msg = { role: m.role, content: flattenText(m.content) }
387
+ if (reasoning) msg.reasoning_content = reasoning
388
+ return msg
378
389
  })
379
390
  }
380
391
 
@@ -391,6 +402,13 @@ function flattenText (content) {
391
402
  return content.filter(p => p.type === 'text' && p.text).map(p => p.text).join('\n')
392
403
  }
393
404
 
405
+ /** @param {string | import('#core/envelope.js').MessagePart[]} content */
406
+ function extractReasoning (content) {
407
+ if (typeof content === 'string' || !Array.isArray(content)) return null
408
+ const parts = content.filter(p => p.type === 'reasoning' && p.text).map(p => p.text)
409
+ return parts.length ? parts.join('\n') : null
410
+ }
411
+
394
412
  /**
395
413
  * @param {any} args
396
414
  * @param {import('#core/envelope.js').MediaRef[]} images
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mohdel",
3
- "version": "0.98.0",
3
+ "version": "0.98.2",
4
4
  "license": "MIT",
5
5
  "author": {
6
6
  "name": "Christophe Le Bars",
@@ -83,16 +83,16 @@
83
83
  }
84
84
  },
85
85
  "optionalDependencies": {
86
- "@clack/prompts": "^1.2.0",
87
- "@opentelemetry/exporter-trace-otlp-grpc": "^0.215.0",
88
- "@opentelemetry/sdk-node": "^0.215.0",
86
+ "@clack/prompts": "^1.3.0",
87
+ "@opentelemetry/exporter-trace-otlp-grpc": "^0.216.0",
88
+ "@opentelemetry/sdk-node": "^0.216.0",
89
89
  "chalk": "^5.4.0",
90
- "mohdel-thin-gate-linux-x64-gnu": "0.98.0"
90
+ "mohdel-thin-gate-linux-x64-gnu": "0.98.2"
91
91
  },
92
92
  "dependencies": {
93
93
  "@anthropic-ai/sdk": "^0.91.1",
94
94
  "@cerebras/cerebras_cloud_sdk": "^1.61.1",
95
- "@google/genai": "^1.50.1",
95
+ "@google/genai": "^1.51.0",
96
96
  "@opentelemetry/api": "^1.9.1",
97
97
  "env-paths": "^4.0.0",
98
98
  "groq-sdk": "^1.1.2",