mohdel 3.8.1 → 3.9.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.
@@ -122,6 +122,13 @@
122
122
  * @property {('text'|'reasoning'|'image')} type
123
123
  * @property {string} [text]
124
124
  * Required on `text` and `reasoning` parts.
125
+ * @property {string} [encrypted]
126
+ * On a `reasoning` part: the provider's opaque reasoning, replayed only
127
+ * to `model` and dropped for any other.
128
+ * @property {string} [id]
129
+ * On a `reasoning` part carrying `encrypted`: the provider's item id.
130
+ * @property {string} [model]
131
+ * On a `reasoning` part carrying `encrypted`: the model that produced it.
125
132
  * @property {string} [fileUri]
126
133
  * Required on `image` parts. Same schemes and local-read rules as
127
134
  * an envelope `images` ref. Accepted on `user` and `tool` messages.
package/js/core/events.js CHANGED
@@ -101,6 +101,10 @@
101
101
  * `'insufficientOutputBudget' | 'aborted' | ...` additive union.
102
102
  * @property {ToolCall[]} [toolCalls]
103
103
  * Present when `status === 'tool_use'`.
104
+ * @property {import('./envelope.js').MessagePart[]} [reasoningParts]
105
+ * Reasoning parts carrying `encrypted`, from a provider that returns its
106
+ * reasoning to be replayed. A multi-turn caller puts them at the head
107
+ * of the assistant message it records for this result.
104
108
  * @property {number} [maxInterFrameMs]
105
109
  * Longest gap (ms) between adapter events during the call —
106
110
  * from `startedAt` to the first frame, between consecutive
@@ -562,7 +562,7 @@ function flattenText (content) {
562
562
  /** @param {string | import('#core/envelope.js').MessagePart[]} content */
563
563
  function extractReasoning (content) {
564
564
  if (typeof content === 'string' || !Array.isArray(content)) return null
565
- const parts = content.filter(p => p.type === 'reasoning' && p.text).map(p => p.text)
565
+ const parts = content.filter(p => p.type === 'reasoning' && p.text && !p.encrypted).map(p => p.text)
566
566
  return parts.length ? parts.join('\n') : null
567
567
  }
568
568
 
@@ -70,7 +70,8 @@ export async function * openai (envelope, deps = {}) {
70
70
  }
71
71
 
72
72
  const { instructions, input } = splitPrompt(envelope.prompt, imageParts, {
73
- toolResultImages: ['openai', 'chatgpt'].includes(providerOf(envelope.model))
73
+ toolResultImages: ['openai', 'chatgpt'].includes(providerOf(envelope.model)),
74
+ model: envelope.model
74
75
  })
75
76
  if (imageInputs.length) injectImageParts(input, imageInputs)
76
77
 
@@ -94,6 +95,8 @@ export async function * openai (envelope, deps = {}) {
94
95
  // Tool-call accumulation: itemId → {call_id, name, arguments}
95
96
  /** @type {Map<string, {call_id: string, name: string, arguments: string}>} */
96
97
  const toolItems = new Map()
98
+ /** @type {import('#core/envelope.js').MessagePart[]} */
99
+ const reasoningParts = []
97
100
 
98
101
  try {
99
102
  const stream = await client.responses.stream(request, { signal })
@@ -122,6 +125,18 @@ export async function * openai (envelope, deps = {}) {
122
125
  }
123
126
  break
124
127
 
128
+ case 'response.output_item.done':
129
+ if (event.item?.type === 'reasoning' && event.item.encrypted_content) {
130
+ reasoningParts.push({
131
+ type: 'reasoning',
132
+ text: (event.item.summary ?? []).map(s => s.text).join('\n'),
133
+ id: event.item.id,
134
+ encrypted: event.item.encrypted_content,
135
+ model: envelope.model
136
+ })
137
+ }
138
+ break
139
+
125
140
  case 'response.function_call_arguments.delta':
126
141
  if (event.item_id && event.delta) {
127
142
  const t = toolItems.get(event.item_id)
@@ -240,6 +255,7 @@ export async function * openai (envelope, deps = {}) {
240
255
  }
241
256
  }
242
257
  if (warning) done.result.warning = warning
258
+ if (reasoningParts.length) done.result.reasoningParts = reasoningParts
243
259
  if (toolItems.size > 0) {
244
260
  done.result.toolCalls = fromOpenAIToolCalls(Array.from(toolItems.values()))
245
261
  }
@@ -346,6 +362,7 @@ function buildRequest (envelope, input, instructions) {
346
362
  request.max_output_tokens += headroom
347
363
  }
348
364
  request.reasoning = { effort }
365
+ if (effort !== 'none') request.include = ['reasoning.encrypted_content']
349
366
  }
350
367
  }
351
368
 
@@ -392,7 +409,7 @@ function buildRequest (envelope, input, instructions) {
392
409
  *
393
410
  * @param {string | import('#core/envelope.js').Message[]} prompt
394
411
  * @param {Map<object, import('./_images.js').LoadedImage>} imageParts
395
- * @param {{toolResultImages: boolean}} opts
412
+ * @param {{toolResultImages: boolean, model: string}} opts
396
413
  */
397
414
  function splitPrompt (prompt, imageParts, opts) {
398
415
  if (typeof prompt === 'string') {
@@ -410,6 +427,18 @@ function splitPrompt (prompt, imageParts, opts) {
410
427
  }
411
428
  for (const m of prompt) {
412
429
  if (m.role !== 'tool') flushHoisted()
430
+ if (m.role === 'assistant' && Array.isArray(m.content)) {
431
+ for (const p of m.content) {
432
+ if (p.type === 'reasoning' && p.encrypted && p.model === opts.model) {
433
+ input.push({
434
+ type: 'reasoning',
435
+ id: p.id,
436
+ summary: p.text ? [{ type: 'summary_text', text: p.text }] : [],
437
+ encrypted_content: p.encrypted
438
+ })
439
+ }
440
+ }
441
+ }
413
442
  if (m.role === 'system') {
414
443
  systemParts.push(flattenText(m.content))
415
444
  } else if (m.role === 'tool') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mohdel",
3
- "version": "3.8.1",
3
+ "version": "3.9.0",
4
4
  "license": "MIT",
5
5
  "author": {
6
6
  "name": "Christophe Le Bars",
@@ -144,7 +144,7 @@
144
144
  "@opentelemetry/exporter-trace-otlp-grpc": "^0.222.0",
145
145
  "@opentelemetry/sdk-node": "^0.222.0",
146
146
  "chalk": "^6.0.1",
147
- "mohdel-thin-gate-linux-x64-gnu": "3.8.1"
147
+ "mohdel-thin-gate-linux-x64-gnu": "3.9.0"
148
148
  },
149
149
  "dependencies": {
150
150
  "@anthropic-ai/sdk": "^0.129.0",