mohdel 0.114.0 → 0.115.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/js/core/events.js CHANGED
@@ -87,6 +87,10 @@
87
87
  * Input tokens written to a fresh prompt cache breakpoint, billed at
88
88
  * `cacheWritePrice`. Absent when the provider has no separate
89
89
  * cache-write counter.
90
+ * @property {number} [cacheWrite1hInputTokens]
91
+ * The 1h-TTL subset of `cacheWriteInputTokens` (Anthropic), billed at
92
+ * `cacheWrite1hPrice`; the 5m portion is the remainder. A request may mix
93
+ * TTLs. Absent when the turn has no 1h-TTL cache writes.
90
94
  * @property {number} [cacheReadInputTokens]
91
95
  * Input tokens served from prompt cache, billed at `cacheReadPrice`.
92
96
  * Absent when the provider has no prompt caching.
@@ -12,8 +12,6 @@
12
12
  * @module session/driver
13
13
  */
14
14
 
15
- import readline from 'node:readline'
16
-
17
15
  import { run } from './run.js'
18
16
  import { runImage } from './run_image.js'
19
17
  import { runTranscription } from './run_transcription.js'
@@ -29,8 +27,6 @@ const PRECANCEL_CAP = 128
29
27
  * @returns {Promise<void>}
30
28
  */
31
29
  export async function drive (stdin, stdout) {
32
- const rl = readline.createInterface({ input: stdin, crlfDelay: Infinity })
33
-
34
30
  /** @type {{callId: string, controller: AbortController} | null} */
35
31
  let currentCall = null
36
32
  /** @type {import('#core/envelope.js').CallEnvelope[]} */
@@ -38,6 +34,7 @@ export async function drive (stdin, stdout) {
38
34
  /** @type {(() => void) | null} */
39
35
  let queueNotify = null
40
36
  let stdinClosed = false
37
+ let framingError = null
41
38
  /** Cancel messages received before their envelope was dequeued.
42
39
  * JS Sets are insertion-ordered, so `values().next()` is the
43
40
  * oldest entry — cheap FIFO eviction at cap. */
@@ -51,7 +48,8 @@ export async function drive (stdin, stdout) {
51
48
  precancelled.add(callId)
52
49
  }
53
50
 
54
- rl.on('line', (line) => {
51
+ const onLine = (line) => {
52
+ if (framingError) return
55
53
  const trimmed = line.trim()
56
54
  if (!trimmed) return
57
55
 
@@ -59,7 +57,17 @@ export async function drive (stdin, stdout) {
59
57
  try {
60
58
  obj = JSON.parse(trimmed)
61
59
  } catch (e) {
62
- process.stderr.write(`session: failed to parse stdin line: ${e.message}\n`)
60
+ process.stderr.write(`session: malformed stdin line, exiting: ${e.message}\n`)
61
+ const error = {
62
+ message: 'SESSION_STDIN_MALFORMED',
63
+ detail: `stdin line is not valid JSON: ${e.message}`,
64
+ severity: 'error',
65
+ retryable: false,
66
+ type: 'SESSION_STDIN_MALFORMED'
67
+ }
68
+ stdout.write(JSON.stringify({ type: 'error', error }) + '\n')
69
+ framingError = Object.assign(new Error(error.message), { detail: error.detail })
70
+ if (queueNotify) { queueNotify(); queueNotify = null }
63
71
  return
64
72
  }
65
73
 
@@ -113,22 +121,35 @@ export async function drive (stdin, stdout) {
113
121
 
114
122
  envelopeQueue.push(obj)
115
123
  if (queueNotify) { queueNotify(); queueNotify = null }
116
- })
124
+ }
117
125
 
118
- rl.on('close', () => {
126
+ let stdinBuf = ''
127
+ stdin.setEncoding('utf8')
128
+ stdin.on('data', (chunk) => {
129
+ stdinBuf += chunk
130
+ let nl
131
+ while ((nl = stdinBuf.indexOf('\n')) !== -1) {
132
+ const line = stdinBuf.slice(0, nl)
133
+ stdinBuf = stdinBuf.slice(nl + 1)
134
+ onLine(line)
135
+ }
136
+ })
137
+ stdin.on('end', () => {
138
+ if (stdinBuf) onLine(stdinBuf)
119
139
  stdinClosed = true
120
140
  if (queueNotify) { queueNotify(); queueNotify = null }
121
141
  })
122
142
 
123
143
  while (true) {
124
- // `stdinClosed` flips asynchronously inside the `rl.on('close')`
144
+ // `stdinClosed` flips asynchronously inside the `stdin.on('end')`
125
145
  // callback above, which also signals `queueNotify`. The linter's
126
146
  // no-unmodified-loop-condition rule can't see callback mutation,
127
147
  // so it false-positives here.
128
148
  // eslint-disable-next-line no-unmodified-loop-condition
129
- while (envelopeQueue.length === 0 && !stdinClosed) {
149
+ while (envelopeQueue.length === 0 && !stdinClosed && !framingError) {
130
150
  await new Promise(resolve => { queueNotify = resolve })
131
151
  }
152
+ if (framingError) throw framingError
132
153
  if (envelopeQueue.length === 0) return
133
154
 
134
155
  const envelope = envelopeQueue.shift()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mohdel",
3
- "version": "0.114.0",
3
+ "version": "0.115.0",
4
4
  "license": "MIT",
5
5
  "author": {
6
6
  "name": "Christophe Le Bars",
@@ -83,20 +83,20 @@
83
83
  }
84
84
  },
85
85
  "optionalDependencies": {
86
- "@clack/prompts": "^1.5.1",
87
- "@opentelemetry/exporter-trace-otlp-grpc": "^0.219.0",
88
- "@opentelemetry/sdk-node": "^0.219.0",
86
+ "@clack/prompts": "^1.7.0",
87
+ "@opentelemetry/exporter-trace-otlp-grpc": "^0.220.0",
88
+ "@opentelemetry/sdk-node": "^0.220.0",
89
89
  "chalk": "^5.4.0",
90
- "mohdel-thin-gate-linux-x64-gnu": "0.114.0"
90
+ "mohdel-thin-gate-linux-x64-gnu": "0.115.0"
91
91
  },
92
92
  "dependencies": {
93
- "@anthropic-ai/sdk": "^0.104.1",
93
+ "@anthropic-ai/sdk": "^0.110.0",
94
94
  "@cerebras/cerebras_cloud_sdk": "^1.61.1",
95
- "@google/genai": "^2.8.0",
95
+ "@google/genai": "^2.10.0",
96
96
  "@opentelemetry/api": "^1.9.1",
97
97
  "env-paths": "^4.0.0",
98
- "groq-sdk": "^1.2.1",
99
- "openai": "^6.42.0",
98
+ "groq-sdk": "^1.3.0",
99
+ "openai": "^6.45.0",
100
100
  "undici": "^7.24.5"
101
101
  },
102
102
  "lint-staged": {
@@ -104,9 +104,9 @@
104
104
  },
105
105
  "devDependencies": {
106
106
  "gpt-tokenizer": "^3.4.0",
107
- "lint-staged": "^17.0.7",
108
- "release-it": "^20.2.0",
107
+ "lint-staged": "^17.0.8",
108
+ "release-it": "^20.2.1",
109
109
  "standard": "^17.1.2",
110
- "vitest": "^4.1.8"
110
+ "vitest": "^4.1.10"
111
111
  }
112
112
  }