mohdel 1.6.0 → 3.0.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 CHANGED
@@ -276,7 +276,7 @@ const result = await mo.use('anthropic/claude-sonnet-4-6').answer('Hello')
276
276
  console.log(result.output, result.cost)
277
277
  ```
278
278
 
279
- 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.
279
+ 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 abort in flight.
280
280
 
281
281
  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.
282
282
 
@@ -333,7 +333,7 @@ Mohdel splits into three planes that can be deployed independently:
333
333
  ```
334
334
 
335
335
  - **`mohdel/client`** (JS) — thin stub that callers import. Opens a unix socket to thin-gate, sends a `CallEnvelope`, receives an async-iterable of `Event`s. Zero transitive provider-SDK imports — caller-side code stays light.
336
- - **`mohdel-thin-gate`** (Rust binary, prebuilt and shipped via the `mohdel-thin-gate-<platform>` npm sub-packages) — scheduler / state owner / supervisor. Binds the data-plane socket, validates the envelope, dispatches to a pooled session subprocess, relays events back, handles graceful cancellation on client disconnect. Binds the admin plane for `GET /v1/health`. Pushes OTLP metrics (sessions alive/respawned, calls by provider/status, call-duration histogram, cooldown / quota / policy rejections) when `OTEL_EXPORTER_OTLP_ENDPOINT` is set. Internal trait hooks (`RoutePolicy`, `QuotaPolicy`, `ConfigSource`, `CachePolicy`) make the crate testable and fork-friendly for deployments that need bespoke policy — not a published-library surface.
336
+ - **`mohdel-thin-gate`** (Rust binary, prebuilt and shipped via the `mohdel-thin-gate-<platform>` npm sub-packages) — scheduler / state owner / supervisor. Binds the data-plane socket, validates the envelope, dispatches to a pooled session subprocess, relays events back, aborts gracefully on client disconnect. Binds the admin plane for `GET /v1/health`. Pushes OTLP metrics (sessions alive/respawned, calls by provider/status, call-duration histogram, cooldown / quota / policy rejections) when `OTEL_EXPORTER_OTLP_ENDPOINT` is set. Internal trait hooks (`RoutePolicy`, `QuotaPolicy`, `ConfigSource`, `CachePolicy`) make the crate testable and fork-friendly for deployments that need bespoke policy — not a published-library surface.
337
337
  - **`mohdel/session`** (JS subprocess) — provider executor. Spawned by thin-gate, reads envelopes from stdin, dispatches to the matching adapter, writes events to stdout. A napi-rs addon was scoped for hot-loop optimization but current benchmarks show per-call JS CPU is not the bottleneck; the stub stays under `rust/napi-addon/` for future reactivation.
338
338
 
339
339
  ### Running thin-gate
@@ -355,7 +355,7 @@ With no session-bin configured, thin-gate runs in demo mode: `POST /v1/call` ret
355
355
 
356
356
  ### Calling from JS
357
357
 
358
- The client snippet under [Library Usage](#library-usage) above is the full surface: `call(envelope, { socketPath, signal?, headers? })` returns an async iterable of events. `headers` go with the request, for a router in front of the gate that authenticates its callers. Pass an `AbortSignal` to cancel in flight; thin-gate forwards a cancel control message to the session and reuses it on the pool, and the client ends the stream with the same cancelled `done` the in-process path returns (status `incomplete`, warning `cancelled`, the partial output it relayed, zero tokens). The envelope is the flat `answer(prompt, options)` surface plus transport metadata (`callId`, `authId`, `auth.key`, optional `traceparent`); see [`js/core/envelope.js`](js/core/envelope.js) for the full field list.
358
+ The client snippet under [Library Usage](#library-usage) above is the full surface: `call(envelope, { socketPath, signal?, headers? })` returns an async iterable of events. `headers` go with the request and with its abort, for a router in front of the gate that authenticates its callers. Pass an `AbortSignal` to abort in flight; the client posts `/v1/abort`, thin-gate forwards an abort control message to the session and reuses it on the pool, and the stream ends with the session's aborted `done`, the one the in-process path returns (status `incomplete`, warning `aborted`, the partial output and the usage reported before the cut). The envelope is the flat `answer(prompt, options)` surface plus transport metadata (`callId`, `authId`, `auth.key`, optional `traceparent`); see [`js/core/envelope.js`](js/core/envelope.js) for the full field list.
359
359
 
360
360
  ### Other languages
361
361
 
@@ -376,7 +376,7 @@ end
376
376
 
377
377
  ### Canonical types (frozen wire contract)
378
378
 
379
- Wire format is JSON over NDJSON frames, camelCase. Types are defined in `js/core/` (JSDoc) and mirrored in `rust/protocol/src/protocol.rs` (serde, the `mohdel-protocol` crate). Cross-language conformance tests enforce round-trip fidelity. The session-side protocol (envelopes in, events out, cancel control messages) is specified in [PROTOCOL.md](PROTOCOL.md) — read that to implement a session in another language.
379
+ Wire format is JSON over NDJSON frames, camelCase. Types are defined in `js/core/` (JSDoc) and mirrored in `rust/protocol/src/protocol.rs` (serde, the `mohdel-protocol` crate). Cross-language conformance tests enforce round-trip fidelity. The session-side protocol (envelopes in, events out, abort control messages) is specified in [PROTOCOL.md](PROTOCOL.md) — read that to implement a session in another language.
380
380
 
381
381
  - **`CallEnvelope`** — flat `answer()` options plus transport metadata: `callId`, `authId`, `auth.key`, `traceparent?`, `baggage?`, `provider`, `model`, `prompt`, `outputBudget?`, `outputType?`, `outputStyle?`, `outputEffort?`, `images?`, `videos?`, `cache?`, `tools?`, `toolChoice?`, `parallelToolCalls?`, `identifier?`.
382
382
  - **`Event`** — three-variant union discriminated on `type`:
@@ -385,10 +385,10 @@ Wire format is JSON over NDJSON frames, camelCase. Types are defined in `js/core
385
385
  - `{ type: 'error', error: TypedError }`
386
386
  - **`AnswerResult`** — `status`, `output`, `inputTokens`, `outputTokens`, `thinkingTokens`, `cost` (single number), `timestamps`, `warning?`, `toolCalls?`.
387
387
  - **`Status`** — `'completed' | 'tool_use' | 'incomplete'`.
388
- - **`Warning`** — additive string union: `'insufficientOutputBudget'`, `'cancelled'`, ...
388
+ - **`Warning`** — additive string union: `'insufficientOutputBudget'`, `'aborted'`, ...
389
389
  - **`TypedError`** — `{ message, detail?, severity, retryable, type }`. `type` is the canonical tag callers branch on (e.g. `'AUTH_INVALID'`, `'PROVIDER_COOLDOWN'`), optional on the wire; `message` is a short human-readable label; `detail` is the provider's own rejection text; `severity` is `'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'`.
390
390
 
391
- A `cancel` control message `{ op: "cancel", callId }` on session stdin aborts the matching in-flight call.
391
+ An `abort` control message `{ op: "abort", callId }` on session stdin aborts the matching in-flight call.
392
392
 
393
393
  Extending the frozen wire types is breaking — additive changes only on trait method sets and non-frozen internals. See [ARCHITECTURE.md §What isn't frozen](ARCHITECTURE.md#what-isnt-frozen) for the refinable-vs-frozen split.
394
394
 
@@ -398,8 +398,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md#adding-a-session-adapter). Short version:
398
398
 
399
399
  1. Create `js/session/adapters/<provider>.js` exporting `async function* <provider>(envelope, { client?, signal? })`.
400
400
  2. Map provider-native events to the canonical Event union.
401
- 3. Pass `{ signal }` to the SDK's streaming method so cancellation aborts in-flight HTTP.
402
- 4. On SDK throw: if `signal?.aborted`, return silently (run() emits call.cancelled); else yield `call.error` via `classifyProviderError(e)` from `./_errors.js`.
401
+ 3. Pass `{ signal }` to the SDK's streaming method so an abort tears down in-flight HTTP.
402
+ 4. On SDK throw: if `signal?.aborted`, return silently (run() yields the aborted `done`); else yield `{ type: 'error', error: classifyProviderError(e) }` from `./_errors.js`.
403
403
  5. Register in `js/session/adapters/index.js`.
404
404
  6. Write unit tests with a dependency-injected mock client.
405
405
  7. Optionally add a gated live test in `test/live/<provider>.live.test.js`.
@@ -492,11 +492,12 @@ Test files under `rust/thin-gate/tests/`:
492
492
  | `conformance.rs` | JS↔Rust protocol round-trip |
493
493
  | `protocol.rs` | serde (de)serialization of envelope/events/results |
494
494
  | `server.rs` | HTTP layer, synthetic dispatch, 404/400 paths |
495
- | `session_dispatch.rs` | real `node js/session/bin.js` spawn + dispatch + graceful cancel |
495
+ | `session_dispatch.rs` | real `node js/session/bin.js` spawn + dispatch + graceful abort + `/v1/abort` |
496
+ | `oneshot_abandon.rs` | one-shot handler dropped mid-exchange: the session finishes and returns to the pool |
496
497
  | `policy.rs` | `RoutePolicy` + `QuotaPolicy` + `Enforcer` end-to-end |
497
498
  | `config.rs` | TOML `ConfigSource` parsing, defaults, malformed, env override |
498
499
  | `supervision.rs` | readiness ping/pong + readiness timeout + garbage-response handling |
499
- | `stress.rs` | 100 concurrent calls, cancel storm, session-death-under-load |
500
+ | `stress.rs` | 100 concurrent calls, abort storm, session-death-under-load |
500
501
 
501
502
  Spawning tests require `node` in PATH.
502
503
 
@@ -528,11 +529,11 @@ For deterministic stress, benchmark, and bug-repro work, register `provider: "fa
528
529
  { mode: 'volume', tokens: 1000 } // throughput stress
529
530
  { mode: 'slow', tokens: 50, delayMs: 100 } // streaming cadence
530
531
  { mode: 'error', type: 'AUTH_INVALID' } // error classification
531
- { mode: 'hang' } // cancel / timeout plumbing
532
+ { mode: 'hang' } // abort / timeout plumbing
532
533
  { mode: 'tool', name: 'f', args: { x: 1 } } // tool round-trip
533
534
  { mode: 'incomplete' } // status contract
534
535
  { mode: 'crash' } // process isolation (exits the adapter process)
535
- { mode: 'cancel_after', tokens: 5 } // cancel mid-stream
536
+ { mode: 'abort_after', tokens: 5 } // abort mid-stream
536
537
  ```
537
538
 
538
539
  All modes honor `AbortSignal`. The benchmarks in `bench/` use this to pin adapter work to a fixed shape and isolate what's being measured — see `bench/bench.js` (throughput) and `bench/isolation.js` (crash containment).
package/js/client/call.js CHANGED
@@ -1,57 +1,90 @@
1
1
  /**
2
2
  * Send a CallEnvelope to thin-gate; returns an async iterable of Events.
3
3
  *
4
- * Cancellation: pass an AbortSignal. Aborting closes the HTTP request;
5
- * thin-gate infers cancel from connection close and sends the session a
6
- * cancel control message. The session's own cancelled terminal is drained
7
- * gate-side, so the client synthesizes the same cancelled `done` here —
8
- * partial output from the deltas it relayed, zero tokens, zero cost —
9
- * giving a caller the same terminal on this path as on the in-process one.
4
+ * Abort: pass an AbortSignal. Once the gate has answered, an abort
5
+ * posts `/v1/abort` for this call and the stream keeps flowing until the
6
+ * session's own aborted `done` — partial output and the usage reported
7
+ * before the cut, as on the in-process path. That wait has no limit of
8
+ * its own: to stop waiting, stop iterating, which abandons the call. An
9
+ * abort before the gate answered drops the request instead; nothing has
10
+ * been reported yet, so the client ends with an aborted `done` of its
11
+ * own, zero usage.
10
12
  * @module client/call
11
13
  */
12
14
 
13
15
  import { requestUnix } from './transport.js'
14
16
  import { readAll, parseErrorBody } from './response.js'
15
17
  import { parseNDJSON } from './ndjson.js'
16
- import { isEvent, MohdelError, STATUS_INCOMPLETE, WARNING_CANCELLED } from '#core'
18
+ import { isEvent, MohdelError, STATUS_INCOMPLETE, WARNING_ABORTED } from '#core'
19
+
20
+ const ABORT_PATH = '/v1/abort'
21
+ const GATE_HEADER = 'mohdel-gate'
17
22
 
18
23
  /**
19
24
  * @param {string} start
20
- * @param {string | null} first
21
- * @param {string} output
22
25
  * @returns {import('#core/events.js').DoneEvent}
23
26
  */
24
- function cancelledDone (start, first, output) {
27
+ function abortedDone (start) {
25
28
  const end = String(process.hrtime.bigint())
26
29
  return {
27
30
  type: 'done',
28
31
  result: {
29
32
  status: STATUS_INCOMPLETE,
30
- output: output || null,
33
+ output: null,
31
34
  inputTokens: 0,
32
35
  outputTokens: 0,
33
36
  thinkingTokens: 0,
34
37
  cost: 0,
35
- timestamps: { start, first: first ?? end, end },
36
- warning: WARNING_CANCELLED
38
+ timestamps: { start, first: end, end },
39
+ warning: WARNING_ABORTED
37
40
  }
38
41
  }
39
42
  }
40
43
 
44
+ /**
45
+ * `CALL_NOT_FOUND` means the call ended before the abort reached it; its
46
+ * terminal is already on the stream.
47
+ *
48
+ * @param {import('#core/envelope.js').CallEnvelope} envelope
49
+ * @param {string | string[] | undefined} gate the call response's `mohdel-gate`
50
+ * @param {string} socketPath
51
+ * @param {Record<string, string> | undefined} headers
52
+ */
53
+ async function requestAbort (envelope, gate, socketPath, headers) {
54
+ if (typeof gate !== 'string') {
55
+ throw new MohdelError(
56
+ `the /v1/call response carries no ${GATE_HEADER} header`,
57
+ { type: 'PROTOCOL_GATE_UNIDENTIFIED', retryable: false }
58
+ )
59
+ }
60
+ const res = await requestUnix({
61
+ socketPath,
62
+ path: ABORT_PATH,
63
+ method: 'POST',
64
+ body: { callId: envelope.callId, authId: envelope.authId, gate },
65
+ headers
66
+ })
67
+ const body = await readAll(res)
68
+ if (res.statusCode === 202) return
69
+ const error = parseErrorBody(body, res.statusCode ?? 0)
70
+ if (error.type === 'CALL_NOT_FOUND') return
71
+ throw MohdelError.fromJSON(error)
72
+ }
73
+
41
74
  /**
42
75
  * @param {import('#core/envelope.js').CallEnvelope} envelope
43
76
  * @param {object} options
44
77
  * @param {string} options.socketPath
45
78
  * @param {AbortSignal} [options.signal]
46
79
  * @param {string} [options.path] HTTP path; defaults to '/v1/call'
47
- * @param {Record<string, string>} [options.headers] sent with the request, for a router in front of the gate;
48
- * `content-type`, `content-length`, `transfer-encoding`, `connection` and `host` are the transport's
80
+ * @param {Record<string, string>} [options.headers] sent with the request and with its abort, for a router in
81
+ * front of the gate; `content-type`, `content-length`, `transfer-encoding`, `connection` and `host` are the transport's
49
82
  * @returns {AsyncGenerator<import('#core/events.js').Event>}
50
83
  */
51
84
  export async function * call (envelope, { socketPath, signal, path = '/v1/call', headers }) {
52
85
  const start = String(process.hrtime.bigint())
53
86
  if (signal?.aborted) {
54
- yield cancelledDone(start, null, '')
87
+ yield abortedDone(start)
55
88
  return
56
89
  }
57
90
 
@@ -67,7 +100,7 @@ export async function * call (envelope, { socketPath, signal, path = '/v1/call',
67
100
  })
68
101
  } catch (e) {
69
102
  if (signal?.aborted) {
70
- yield cancelledDone(start, null, '')
103
+ yield abortedDone(start)
71
104
  return
72
105
  }
73
106
  throw e
@@ -78,9 +111,19 @@ export async function * call (envelope, { socketPath, signal, path = '/v1/call',
78
111
  throw MohdelError.fromJSON(parseErrorBody(body, res.statusCode ?? 0))
79
112
  }
80
113
 
81
- const outputParts = []
82
- let first = null
114
+ /** @type {unknown} */
115
+ let abortError = null
83
116
  let sawTerminal = false
117
+ const onAbort = () => {
118
+ if (sawTerminal) return
119
+ requestAbort(envelope, res.headers[GATE_HEADER], socketPath, headers).catch((e) => {
120
+ abortError = e
121
+ res.destroy()
122
+ })
123
+ }
124
+ if (signal?.aborted) onAbort()
125
+ else signal?.addEventListener('abort', onAbort, { once: true })
126
+
84
127
  try {
85
128
  for await (const obj of parseNDJSON(res)) {
86
129
  if (!isEvent(obj)) {
@@ -89,19 +132,13 @@ export async function * call (envelope, { socketPath, signal, path = '/v1/call',
89
132
  { type: 'PROTOCOL_INVALID_EVENT', retryable: false }
90
133
  )
91
134
  }
92
- if (obj.type === 'delta') {
93
- if (first === null) first = String(process.hrtime.bigint())
94
- if (obj.delta?.type === 'message') outputParts.push(obj.delta.delta)
95
- } else if (obj.type === 'done' || obj.type === 'error') {
96
- sawTerminal = true
97
- }
135
+ if (obj.type === 'done' || obj.type === 'error') sawTerminal = true
98
136
  yield /** @type {import('#core/events.js').Event} */(obj)
99
137
  }
100
138
  } catch (e) {
101
- if (!signal?.aborted) throw e
102
- }
103
-
104
- if (signal?.aborted && !sawTerminal) {
105
- yield cancelledDone(start, first, outputParts.join(''))
139
+ throw abortError ?? e
140
+ } finally {
141
+ signal?.removeEventListener('abort', onAbort)
106
142
  }
143
+ if (abortError) throw abortError
107
144
  }
@@ -30,7 +30,11 @@ export function requestUnix ({ socketPath, path, method, body, signal, headers }
30
30
  const h = { ...(headers || {}) }
31
31
  if (body !== undefined) h['content-type'] = 'application/json'
32
32
 
33
- const req = http.request({ socketPath, path, method, headers: h }, resolve)
33
+ const onAbort = () => req.destroy(new Error('aborted'))
34
+ const req = http.request({ socketPath, path, method, headers: h }, (res) => {
35
+ signal?.removeEventListener('abort', onAbort)
36
+ resolve(res)
37
+ })
34
38
  req.on('error', reject)
35
39
 
36
40
  if (signal) {
@@ -39,11 +43,7 @@ export function requestUnix ({ socketPath, path, method, body, signal, headers }
39
43
  reject(new Error('aborted'))
40
44
  return
41
45
  }
42
- const onAbort = () => req.destroy(new Error('aborted'))
43
46
  signal.addEventListener('abort', onAbort, { once: true })
44
- // Released on `close` rather than on resolve: the promise
45
- // resolves at response headers, and cancellation has to stay
46
- // live for the streaming body that follows.
47
47
  req.on('close', () => signal.removeEventListener('abort', onAbort))
48
48
  }
49
49
 
package/js/core/events.js CHANGED
@@ -98,7 +98,7 @@
98
98
  * USD, computed from curated pricing. Single number (not a breakdown).
99
99
  * @property {Timestamps} timestamps
100
100
  * @property {string} [warning]
101
- * `'insufficientOutputBudget' | 'cancelled' | ...` additive union.
101
+ * `'insufficientOutputBudget' | 'aborted' | ...` additive union.
102
102
  * @property {ToolCall[]} [toolCalls]
103
103
  * Present when `status === 'tool_use'`.
104
104
  * @property {number} [maxInterFrameMs]
package/js/core/status.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * - `completed` — the call finished normally.
6
6
  * - `tool_use` — the model emitted tool calls; the caller is
7
7
  * expected to round-trip results back.
8
- * - `incomplete` — the call was cut short (budget, cancel, policy).
8
+ * - `incomplete` — the call was cut short (budget, abort, policy).
9
9
  *
10
10
  * Rust mirror: `rust/thin-gate/src/protocol.rs::Status`.
11
11
  *
@@ -18,13 +18,13 @@
18
18
  * Warning values emitted on `AnswerResult.warning` when status is
19
19
  * `incomplete`:
20
20
  * - `insufficientOutputBudget` — the model hit `max_tokens`.
21
- * - `cancelled` — the call was aborted via a cancel control
21
+ * - `aborted` — the call was aborted via an abort control
22
22
  * message or `AbortSignal`.
23
23
  *
24
24
  * Additive: future releases may add new warning strings; consumers
25
25
  * should treat unknown warnings as pass-through metadata.
26
26
  *
27
- * @typedef {('insufficientOutputBudget'|'cancelled'|string)} Warning
27
+ * @typedef {('insufficientOutputBudget'|'aborted'|string)} Warning
28
28
  */
29
29
 
30
30
  export const STATUS_COMPLETED = 'completed'
@@ -37,4 +37,4 @@ export const STATUSES = Object.freeze([
37
37
  ])
38
38
 
39
39
  export const WARNING_INSUFFICIENT_OUTPUT_BUDGET = 'insufficientOutputBudget'
40
- export const WARNING_CANCELLED = 'cancelled'
40
+ export const WARNING_ABORTED = 'aborted'
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Shared `cancelledDone` helper for adapters that need to synthesize
2
+ * Shared `abortedDone` helper for adapters that need to synthesize
3
3
  * a terminal `done` event on `signal.aborted` mid-stream.
4
4
  *
5
- * @module session/adapters/_cancelled
5
+ * @module session/adapters/_aborted
6
6
  */
7
7
 
8
- import { STATUS_INCOMPLETE, WARNING_CANCELLED } from '#core/status.js'
8
+ import { STATUS_INCOMPLETE, WARNING_ABORTED } from '#core/status.js'
9
9
  import { costFor } from './_pricing.js'
10
10
 
11
11
  /**
@@ -16,12 +16,12 @@ import { costFor } from './_pricing.js'
16
16
  * @param {number} inputTokens
17
17
  * @param {number} outputTokens
18
18
  * @param {{cacheWriteInputTokens?: number, cacheReadInputTokens?: number}} [extra]
19
- * Optional cache token counts captured before cancellation. Threaded through
20
- * so the cancellation-cost calculation prices any cache writes/reads that
19
+ * Optional cache token counts captured before the abort. Threaded through
20
+ * so the abort-cost calculation prices any cache writes/reads that
21
21
  * already happened before the abort.
22
22
  * @returns {import('#core/events.js').DoneEvent}
23
23
  */
24
- export function cancelledDone (start, first, envelope, output, inputTokens, outputTokens, extra = {}) {
24
+ export function abortedDone (start, first, envelope, output, inputTokens, outputTokens, extra = {}) {
25
25
  const end = String(process.hrtime.bigint())
26
26
  const cacheWriteInputTokens = extra.cacheWriteInputTokens || 0
27
27
  const cacheReadInputTokens = extra.cacheReadInputTokens || 0
@@ -40,7 +40,7 @@ export function cancelledDone (start, first, envelope, output, inputTokens, outp
40
40
  { inputTokens, outputTokens, thinkingTokens: 0, cacheWriteInputTokens, cacheReadInputTokens }
41
41
  ),
42
42
  timestamps: { start, first: first ?? end, end },
43
- warning: WARNING_CANCELLED
43
+ warning: WARNING_ABORTED
44
44
  }
45
45
  }
46
46
  }
@@ -22,7 +22,7 @@ import { classifyProviderError } from './_errors.js'
22
22
  import { hasImagePart, loadImage, loadImageParts } from './_images.js'
23
23
  import { isTrustedMedia, mediaError, mediaScheme } from './_media.js'
24
24
  import { costFor } from './_pricing.js'
25
- import { cancelledDone } from './_cancelled.js'
25
+ import { abortedDone } from './_aborted.js'
26
26
  import { catalogKey, bareOf } from '#core/model-id.js'
27
27
  import {
28
28
  STATUS_COMPLETED,
@@ -116,7 +116,7 @@ export async function * runChatCompletions (envelope, client, config, deps = {})
116
116
  response = await client.chat.completions.create(args, { signal: deps.signal })
117
117
  } catch (e) {
118
118
  if (deps.signal?.aborted) {
119
- yield cancelledDone(start, null, envelope, '', 0, 0)
119
+ yield abortedDone(start, null, envelope, '', 0, 0)
120
120
  return
121
121
  }
122
122
  deps.log?.warn({ err: e }, `[mohdel:${config.provider}] request failed`)
@@ -182,7 +182,7 @@ async function * runStreaming (envelope, client, args, config, start, deps) {
182
182
  stream = await client.chat.completions.create(args, { signal: deps.signal })
183
183
  } catch (e) {
184
184
  if (deps.signal?.aborted) {
185
- yield cancelledDone(start, null, envelope, '', 0, 0)
185
+ yield abortedDone(start, null, envelope, '', 0, 0)
186
186
  return
187
187
  }
188
188
  deps.log?.warn({ err: e }, `[mohdel:${config.provider}] request failed`)
@@ -193,7 +193,7 @@ async function * runStreaming (envelope, client, args, config, start, deps) {
193
193
  try {
194
194
  for await (const chunk of stream) {
195
195
  if (deps.signal?.aborted) {
196
- yield cancelledDone(start, first, envelope, contentParts.join(''), 0, 0)
196
+ yield abortedDone(start, first, envelope, contentParts.join(''), 0, 0)
197
197
  return
198
198
  }
199
199
  const choice = chunk.choices?.[0]
@@ -249,7 +249,7 @@ async function * runStreaming (envelope, client, args, config, start, deps) {
249
249
  }
250
250
  } catch (e) {
251
251
  if (deps.signal?.aborted) {
252
- yield cancelledDone(start, first, envelope, contentParts.join(''), 0, 0)
252
+ yield abortedDone(start, first, envelope, contentParts.join(''), 0, 0)
253
253
  return
254
254
  }
255
255
  deps.log?.warn({ err: e }, `[mohdel:${config.provider}] stream failed`)
@@ -258,7 +258,7 @@ async function * runStreaming (envelope, client, args, config, start, deps) {
258
258
  }
259
259
 
260
260
  if (deps.signal?.aborted) {
261
- yield cancelledDone(start, first, envelope, contentParts.join(''), 0, 0)
261
+ yield abortedDone(start, first, envelope, contentParts.join(''), 0, 0)
262
262
  return
263
263
  }
264
264
 
@@ -7,11 +7,12 @@
7
7
  * phase, so any non-trivial task on a thinking-capable provider can
8
8
  * blow that limit and surface as a `NET_ERROR / "terminated"` mid-run.
9
9
  *
10
- * We disable the inter-chunk idle timeout. Cancellation comes from
11
- * three layers above us:
12
- * 1. caller's `AbortSignal` (per-run timeout, user cancel)
13
- * 2. SDK request-level timeout (OpenAI/Anthropic/Groq default 600 s)
14
- * 3. provider-side stream limits
10
+ * We disable the inter-chunk idle timeout, so nothing in this process
11
+ * bounds a stream once its headers have arrived: the SDKs' request
12
+ * timeout is armed around `fetch` only and cleared when the headers
13
+ * land. What can still end a stream:
14
+ * 1. caller's `AbortSignal` (per-run timeout, user abort)
15
+ * 2. provider-side stream limits
15
16
  *
16
17
  * Headers timeout stays bounded — connect + first response must be
17
18
  * fast even when the body afterwards may be slow.
@@ -288,7 +288,7 @@ async function uploadFile (filePath, mimeType, ctx) {
288
288
  /**
289
289
  * Raise an `AbortError` when `signal` is aborted. The gemini
290
290
  * adapter's video-load catch block already converts this shape to
291
- * the standard cancelled terminal via the outer `signal?.aborted`
291
+ * the standard aborted terminal via the outer `signal?.aborted`
292
292
  * check in `run.js`.
293
293
  *
294
294
  * @param {AbortSignal | undefined} signal
@@ -24,7 +24,7 @@ import {
24
24
  WARNING_INSUFFICIENT_OUTPUT_BUDGET
25
25
  } from '#core/status.js'
26
26
 
27
- import { cancelledDone } from './_cancelled.js'
27
+ import { abortedDone } from './_aborted.js'
28
28
  import { getSpec } from './_catalog.js'
29
29
  import { classifyProviderError } from './_errors.js'
30
30
  import { hasImagePart, loadImageParts, loadImages } from './_images.js'
@@ -58,7 +58,7 @@ import { streamingDispatcher } from './_dispatcher.js'
58
58
  *
59
59
  * **Cost impact:** provably zero when `thinkingPrice == outputPrice`
60
60
  * (true for every Anthropic entry in the curated catalog today) —
61
- * the heuristic error cancels in `cost = i*ip + o*op + t*tp` because
61
+ * the heuristic error drops out of `cost = i*ip + o*op + t*tp` because
62
62
  * `o*op + t*op = (o+t)*op = totalOutput*op`. If a catalog maintainer
63
63
  * ever sets asymmetric Anthropic pricing, cost drifts by
64
64
  * `estimate_error × (thinkingPrice − outputPrice)` — that's a
@@ -134,7 +134,7 @@ export async function * anthropic (envelope, deps = {}) {
134
134
 
135
135
  for await (const event of stream) {
136
136
  if (signal?.aborted) {
137
- yield cancelledDone(start, first, envelope, currentOutput(), inputTokens, outputTokens, { cacheWriteInputTokens: cacheWriteTokens, cacheReadInputTokens: cacheReadTokens })
137
+ yield abortedDone(start, first, envelope, currentOutput(), inputTokens, outputTokens, { cacheWriteInputTokens: cacheWriteTokens, cacheReadInputTokens: cacheReadTokens })
138
138
  return
139
139
  }
140
140
  switch (event.type) {
@@ -208,7 +208,7 @@ export async function * anthropic (envelope, deps = {}) {
208
208
  }
209
209
  } catch (e) {
210
210
  if (signal?.aborted) {
211
- yield cancelledDone(start, first, envelope, currentOutput(), inputTokens, outputTokens, { cacheWriteInputTokens: cacheWriteTokens, cacheReadInputTokens: cacheReadTokens })
211
+ yield abortedDone(start, first, envelope, currentOutput(), inputTokens, outputTokens, { cacheWriteInputTokens: cacheWriteTokens, cacheReadInputTokens: cacheReadTokens })
212
212
  return
213
213
  }
214
214
  log?.warn({ err: e }, '[mohdel:anthropic] stream failed')
@@ -217,7 +217,7 @@ export async function * anthropic (envelope, deps = {}) {
217
217
  }
218
218
 
219
219
  if (signal?.aborted) {
220
- yield cancelledDone(start, first, envelope, currentOutput(), inputTokens, outputTokens, { cacheWriteInputTokens: cacheWriteTokens, cacheReadInputTokens: cacheReadTokens })
220
+ yield abortedDone(start, first, envelope, currentOutput(), inputTokens, outputTokens, { cacheWriteInputTokens: cacheWriteTokens, cacheReadInputTokens: cacheReadTokens })
221
221
  return
222
222
  }
223
223
 
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Echo adapter — deterministic stub. Emits two message deltas and a
3
3
  * `done` event with a synthetic `AnswerResult`. Honors `signal` for
4
- * test-controlled cancellation.
4
+ * test-controlled aborts.
5
5
  *
6
6
  * @module session/adapters/echo
7
7
  */
8
8
 
9
9
  import { STATUS_COMPLETED } from '#core/status.js'
10
10
 
11
- import { cancelledDone } from './_cancelled.js'
11
+ import { abortedDone } from './_aborted.js'
12
12
 
13
13
  /**
14
14
  * @param {import('#core/envelope.js').CallEnvelope} envelope
@@ -22,7 +22,7 @@ export async function * echo (envelope, { signal } = {}) {
22
22
 
23
23
  for (const delta of ['Hello', ', world.']) {
24
24
  if (signal?.aborted) {
25
- yield cancelledDone(start, first, envelope, output, 0, 0)
25
+ yield abortedDone(start, first, envelope, output, 0, 0)
26
26
  return
27
27
  }
28
28
  if (first === null) first = String(process.hrtime.bigint())
@@ -31,7 +31,7 @@ export async function * echo (envelope, { signal } = {}) {
31
31
  }
32
32
 
33
33
  if (signal?.aborted) {
34
- yield cancelledDone(start, first, envelope, output, 0, 0)
34
+ yield abortedDone(start, first, envelope, output, 0, 0)
35
35
  return
36
36
  }
37
37
 
@@ -18,11 +18,11 @@
18
18
  * | `incomplete` | `warning` | `done` with status=incomplete + warning |
19
19
  * | `error` | `type`, `message`, `retryable` | yield typed error event |
20
20
  * | `hang` | – | never emits a terminal (caller aborts via signal) |
21
- * | `cancel_after`| `tokens` | emit N deltas then wait for `signal.aborted` |
21
+ * | `abort_after`| `tokens` | emit N deltas then wait for `signal.aborted` |
22
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
23
  *
24
24
  * Every mode honors `deps.signal`: when aborted mid-stream, emits a
25
- * `cancelled` done event. That keeps consumer behavior uniform with
25
+ * `aborted` done event. That keeps consumer behavior uniform with
26
26
  * the real provider adapters.
27
27
  *
28
28
  * @module session/adapters/fake
@@ -35,7 +35,7 @@ import {
35
35
  WARNING_INSUFFICIENT_OUTPUT_BUDGET
36
36
  } from '#core/status.js'
37
37
 
38
- import { cancelledDone } from './_cancelled.js'
38
+ import { abortedDone } from './_aborted.js'
39
39
 
40
40
  /**
41
41
  * @param {import('#core/envelope.js').CallEnvelope} envelope
@@ -63,7 +63,7 @@ export async function * fake (envelope, deps = {}) {
63
63
 
64
64
  case 'hang':
65
65
  await waitForAbort(signal)
66
- yield cancelledDone(start, first, envelope, '', 0, 0)
66
+ yield abortedDone(start, first, envelope, '', 0, 0)
67
67
  return
68
68
 
69
69
  case 'crash': {
@@ -120,7 +120,7 @@ export async function * fake (envelope, deps = {}) {
120
120
  let output = ''
121
121
  for (let i = 0; i < total; i++) {
122
122
  if (signal?.aborted) {
123
- yield cancelledDone(start, first, envelope, output, approxTokens(output), approxTokens(output))
123
+ yield abortedDone(start, first, envelope, output, approxTokens(output), approxTokens(output))
124
124
  return
125
125
  }
126
126
  if (first === null) first = String(process.hrtime.bigint())
@@ -130,7 +130,7 @@ export async function * fake (envelope, deps = {}) {
130
130
  if (delayMs > 0) {
131
131
  await sleep(delayMs, signal)
132
132
  if (signal?.aborted) {
133
- yield cancelledDone(start, first, envelope, output, approxTokens(output), approxTokens(output))
133
+ yield abortedDone(start, first, envelope, output, approxTokens(output), approxTokens(output))
134
134
  return
135
135
  }
136
136
  }
@@ -145,7 +145,7 @@ export async function * fake (envelope, deps = {}) {
145
145
  return
146
146
  }
147
147
 
148
- case 'cancel_after': {
148
+ case 'abort_after': {
149
149
  const total = clampPositive(spec.tokens, 3)
150
150
  let output = ''
151
151
  for (let i = 0; i < total; i++) {
@@ -155,7 +155,7 @@ export async function * fake (envelope, deps = {}) {
155
155
  yield { type: 'delta', delta: { type: 'message', delta: chunk } }
156
156
  }
157
157
  await waitForAbort(signal)
158
- yield cancelledDone(start, first, envelope, output, approxTokens(output), approxTokens(output))
158
+ yield abortedDone(start, first, envelope, output, approxTokens(output), approxTokens(output))
159
159
  return
160
160
  }
161
161
 
@@ -25,7 +25,7 @@ import {
25
25
  WARNING_INSUFFICIENT_OUTPUT_BUDGET
26
26
  } from '#core/status.js'
27
27
 
28
- import { cancelledDone } from './_cancelled.js'
28
+ import { abortedDone } from './_aborted.js'
29
29
  import { getSpec } from './_catalog.js'
30
30
  import { classifyProviderError } from './_errors.js'
31
31
  import { loadImageParts, loadImages } from './_images.js'
@@ -80,7 +80,7 @@ export async function * gemini (envelope, deps = {}) {
80
80
  if (parts.length) injectParts(contents, parts)
81
81
  } catch (e) {
82
82
  if (signal?.aborted) {
83
- yield cancelledDone(start, first, envelope, '', 0, 0)
83
+ yield abortedDone(start, first, envelope, '', 0, 0)
84
84
  return
85
85
  }
86
86
  log?.warn({ err: e }, '[mohdel:gemini] video load failed')
@@ -98,7 +98,7 @@ export async function * gemini (envelope, deps = {}) {
98
98
  // from `params.config.abortSignal` — a second-arg `{signal}` is
99
99
  // dropped (verified against the compiled SDK at
100
100
  // node_modules/@google/genai/dist/index.cjs). Merge into config
101
- // so cancellation actually tears down the HTTPS request, not just
101
+ // so an abort actually tears down the HTTPS request, not just
102
102
  // the local loop.
103
103
  if (signal) {
104
104
  request.config = { ...(request.config ?? {}), abortSignal: signal }
@@ -122,7 +122,7 @@ export async function * gemini (envelope, deps = {}) {
122
122
 
123
123
  for await (const chunk of stream) {
124
124
  if (signal?.aborted) {
125
- yield cancelledDone(start, first, envelope, currentOutput(), inputTokens, outputTokens)
125
+ yield abortedDone(start, first, envelope, currentOutput(), inputTokens, outputTokens)
126
126
  return
127
127
  }
128
128
 
@@ -178,7 +178,7 @@ export async function * gemini (envelope, deps = {}) {
178
178
  }
179
179
  } catch (e) {
180
180
  if (signal?.aborted) {
181
- yield cancelledDone(start, first, envelope, currentOutput(), inputTokens, outputTokens)
181
+ yield abortedDone(start, first, envelope, currentOutput(), inputTokens, outputTokens)
182
182
  return
183
183
  }
184
184
  log?.warn({ err: e }, '[mohdel:gemini] stream failed')
@@ -187,7 +187,7 @@ export async function * gemini (envelope, deps = {}) {
187
187
  }
188
188
 
189
189
  if (signal?.aborted) {
190
- yield cancelledDone(start, first, envelope, currentOutput(), inputTokens, outputTokens)
190
+ yield abortedDone(start, first, envelope, currentOutput(), inputTokens, outputTokens)
191
191
  return
192
192
  }
193
193
 
@@ -25,7 +25,7 @@ import {
25
25
  WARNING_INSUFFICIENT_OUTPUT_BUDGET
26
26
  } from '#core/status.js'
27
27
 
28
- import { cancelledDone } from './_cancelled.js'
28
+ import { abortedDone } from './_aborted.js'
29
29
  import { getSpec } from './_catalog.js'
30
30
  import { classifyProviderError } from './_errors.js'
31
31
  import { hasImagePart, loadImageParts, loadImages } from './_images.js'
@@ -99,7 +99,7 @@ export async function * openai (envelope, deps = {}) {
99
99
 
100
100
  for await (const event of stream) {
101
101
  if (signal?.aborted) {
102
- yield cancelledDone(start, first, envelope, currentOutput(), inputTokens, outputTokens)
102
+ yield abortedDone(start, first, envelope, currentOutput(), inputTokens, outputTokens)
103
103
  return
104
104
  }
105
105
  switch (event.type) {
@@ -170,7 +170,7 @@ export async function * openai (envelope, deps = {}) {
170
170
  }
171
171
  } catch (e) {
172
172
  if (signal?.aborted) {
173
- yield cancelledDone(start, first, envelope, currentOutput(), inputTokens, outputTokens)
173
+ yield abortedDone(start, first, envelope, currentOutput(), inputTokens, outputTokens)
174
174
  return
175
175
  }
176
176
  log?.warn({ err: e }, '[mohdel:openai] stream failed')
@@ -179,7 +179,7 @@ export async function * openai (envelope, deps = {}) {
179
179
  }
180
180
 
181
181
  if (signal?.aborted) {
182
- yield cancelledDone(start, first, envelope, currentOutput(), inputTokens, outputTokens)
182
+ yield abortedDone(start, first, envelope, currentOutput(), inputTokens, outputTokens)
183
183
  return
184
184
  }
185
185
 
@@ -4,7 +4,7 @@
4
4
  * Reads lines from stdin concurrently with running calls:
5
5
  * - Envelope line (no `op` field): queued for sequential dispatch
6
6
  * via `run()`. Events written to stdout as NDJSON.
7
- * - Control message `{op:"cancel", callId}`: if it matches the
7
+ * - Control message `{op:"abort", callId}`: if it matches the
8
8
  * in-flight call, aborts via AbortController.
9
9
  *
10
10
  * Single-call-at-a-time per process.
@@ -22,9 +22,9 @@ import { runEmbedding } from './run_embedding.js'
22
22
  import { runInfo } from './run_info.js'
23
23
  import { setCatalog } from './adapters/_catalog.js'
24
24
 
25
- // Bounded memory for pre-dequeue cancels. Hostile/buggy supervisors
25
+ // Bounded memory for pre-dequeue aborts. Hostile/buggy supervisors
26
26
  // spamming random callIds can't grow the set without bound.
27
- const PRECANCEL_CAP = 128
27
+ const EARLY_ABORT_CAP = 128
28
28
 
29
29
  /**
30
30
  * @param {NodeJS.ReadableStream} stdin
@@ -40,17 +40,17 @@ export async function drive (stdin, stdout) {
40
40
  let queueNotify = null
41
41
  let stdinClosed = false
42
42
  let framingError = null
43
- /** Cancel messages received before their envelope was dequeued.
43
+ /** Abort messages received before their envelope was dequeued.
44
44
  * JS Sets are insertion-ordered, so `values().next()` is the
45
45
  * oldest entry — cheap FIFO eviction at cap. */
46
- const precancelled = new Set()
46
+ const earlyAborted = new Set()
47
47
 
48
- function recordPrecancel (callId) {
49
- if (precancelled.has(callId)) return
50
- if (precancelled.size >= PRECANCEL_CAP) {
51
- precancelled.delete(precancelled.values().next().value)
48
+ function recordEarlyAbort (callId) {
49
+ if (earlyAborted.has(callId)) return
50
+ if (earlyAborted.size >= EARLY_ABORT_CAP) {
51
+ earlyAborted.delete(earlyAborted.values().next().value)
52
52
  }
53
- precancelled.add(callId)
53
+ earlyAborted.add(callId)
54
54
  }
55
55
 
56
56
  /**
@@ -94,13 +94,13 @@ export async function drive (stdin, stdout) {
94
94
  return
95
95
  }
96
96
 
97
- if (obj && typeof obj === 'object' && obj.op === 'cancel') {
97
+ if (obj && typeof obj === 'object' && obj.op === 'abort') {
98
98
  if (currentCall && currentCall.callId === obj.callId) {
99
99
  currentCall.controller.abort()
100
100
  } else {
101
- // Pre-dequeue cancel: remember the callId so the envelope
101
+ // Pre-dequeue abort: remember the callId so the envelope
102
102
  // aborts immediately on dispatch. Honored once then cleared.
103
- recordPrecancel(obj.callId)
103
+ recordEarlyAbort(obj.callId)
104
104
  }
105
105
  return
106
106
  }
@@ -197,7 +197,7 @@ export async function drive (stdin, stdout) {
197
197
 
198
198
  const envelope = envelopeQueue.shift()
199
199
  const controller = new AbortController()
200
- if (precancelled.delete(envelope.callId)) controller.abort()
200
+ if (earlyAborted.delete(envelope.callId)) controller.abort()
201
201
  currentCall = { callId: envelope.callId, controller }
202
202
 
203
203
  try {
package/js/session/run.js CHANGED
@@ -14,7 +14,7 @@
14
14
  * skipped for non-retryable provider errors). Span ends with
15
15
  * `gen_ai.*` + `mohdel.*` attributes.
16
16
  *
17
- * Terminal events are `done` (success / incomplete / cancelled /
17
+ * Terminal events are `done` (success / incomplete / aborted /
18
18
  * tool_use) and `error`. The adapter is expected to emit exactly
19
19
  * one terminal per call. If it returns without one, `run()`
20
20
  * synthesizes an `error`.
@@ -37,7 +37,7 @@ import {
37
37
  endSpanError,
38
38
  remoteParentFromTraceparent
39
39
  } from './_tracing.js'
40
- import { STATUS_INCOMPLETE, WARNING_CANCELLED } from '#core/status.js'
40
+ import { STATUS_INCOMPLETE, WARNING_ABORTED } from '#core/status.js'
41
41
 
42
42
  /**
43
43
  * @param {import('#core/envelope.js').CallEnvelope} envelope
@@ -227,12 +227,12 @@ export async function * run (envelope, {
227
227
  sawDelta = true
228
228
  } else if (ev.type === 'done') {
229
229
  sawTerminal = true
230
- // A cancelled terminal is the caller's action, not evidence
230
+ // An aborted terminal is the caller's action, not evidence
231
231
  // of provider recovery — don't wipe an accumulated failure
232
232
  // streak. Every other `done` state (completed /
233
233
  // incomplete-budget / tool_use) IS a genuine provider-side
234
234
  // success and resets the streak.
235
- if (ev.result?.warning !== WARNING_CANCELLED) {
235
+ if (ev.result?.warning !== WARNING_ABORTED) {
236
236
  cooldown.reset(provider)
237
237
  }
238
238
  if (tpmLimit != null && ev.result) {
@@ -267,7 +267,7 @@ export async function * run (envelope, {
267
267
  }
268
268
  } catch (e) {
269
269
  if (signal?.aborted && !sawTerminal) {
270
- const fallback = cancelledFallback()
270
+ const fallback = abortedFallback()
271
271
  if (fallback.result) fallback.result.maxInterFrameMs = maxInterFrameMs
272
272
  finalizeSpanOk(span, fallback.result, sawDelta, maxInterFrameMs)
273
273
  yield fallback
@@ -281,7 +281,7 @@ export async function * run (envelope, {
281
281
 
282
282
  if (!sawTerminal) {
283
283
  if (signal?.aborted) {
284
- const fallback = cancelledFallback()
284
+ const fallback = abortedFallback()
285
285
  if (fallback.result) fallback.result.maxInterFrameMs = maxInterFrameMs
286
286
  finalizeSpanOk(span, fallback.result, sawDelta, maxInterFrameMs)
287
287
  yield fallback
@@ -561,7 +561,7 @@ function recordFailureFromError (cooldown, provider, err) {
561
561
  }
562
562
 
563
563
  /** @returns {import('#core/events.js').DoneEvent} */
564
- function cancelledFallback () {
564
+ function abortedFallback () {
565
565
  const now = String(process.hrtime.bigint())
566
566
  return {
567
567
  type: 'done',
@@ -573,7 +573,7 @@ function cancelledFallback () {
573
573
  thinkingTokens: 0,
574
574
  cost: 0,
575
575
  timestamps: { start: now, first: now, end: now },
576
- warning: WARNING_CANCELLED
576
+ warning: WARNING_ABORTED
577
577
  }
578
578
  }
579
579
  }
@@ -3,7 +3,7 @@
3
3
  * returns either a result or a typed error, never throwing.
4
4
  *
5
5
  * Mirrors `run_transcription.js`: one synchronous request, no streaming, no
6
- * cancellation path beyond the caller's own signal. Rate limits are enforced
6
+ * abort path beyond the caller's own signal. Rate limits are enforced
7
7
  * as in `run.js`, minus the speed lanes embeddings do not have.
8
8
  *
9
9
  * @module session/run_embedding
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mohdel",
3
- "version": "1.6.0",
3
+ "version": "3.0.0",
4
4
  "license": "MIT",
5
5
  "author": {
6
6
  "name": "Christophe Le Bars",
@@ -136,7 +136,7 @@
136
136
  "@opentelemetry/exporter-trace-otlp-grpc": "^0.222.0",
137
137
  "@opentelemetry/sdk-node": "^0.222.0",
138
138
  "chalk": "^6.0.0",
139
- "mohdel-thin-gate-linux-x64-gnu": "1.6.0"
139
+ "mohdel-thin-gate-linux-x64-gnu": "3.0.0"
140
140
  },
141
141
  "dependencies": {
142
142
  "@anthropic-ai/sdk": "^0.128.0",