mohdel 1.5.0 → 1.6.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
@@ -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? })` returns an async iterable of events. 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, 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.
359
359
 
360
360
  ### Other languages
361
361
 
package/js/client/call.js CHANGED
@@ -44,9 +44,11 @@ function cancelledDone (start, first, output) {
44
44
  * @param {string} options.socketPath
45
45
  * @param {AbortSignal} [options.signal]
46
46
  * @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
47
49
  * @returns {AsyncGenerator<import('#core/events.js').Event>}
48
50
  */
49
- export async function * call (envelope, { socketPath, signal, path = '/v1/call' }) {
51
+ export async function * call (envelope, { socketPath, signal, path = '/v1/call', headers }) {
50
52
  const start = String(process.hrtime.bigint())
51
53
  if (signal?.aborted) {
52
54
  yield cancelledDone(start, null, '')
@@ -60,7 +62,8 @@ export async function * call (envelope, { socketPath, signal, path = '/v1/call'
60
62
  path,
61
63
  method: 'POST',
62
64
  body: envelope,
63
- signal
65
+ signal,
66
+ headers
64
67
  })
65
68
  } catch (e) {
66
69
  if (signal?.aborted) {
@@ -19,15 +19,18 @@ import { MohdelError } from '#core'
19
19
  * @param {string} options.socketPath
20
20
  * @param {AbortSignal} [options.signal]
21
21
  * @param {string} [options.path] HTTP path; defaults to '/v1/embed'
22
+ * @param {Record<string, string>} [options.headers] sent with the request, for a router in front of the gate;
23
+ * `content-type`, `content-length`, `transfer-encoding`, `connection` and `host` are the transport's
22
24
  * @returns {Promise<import('#core/embedding.js').EmbedResult>}
23
25
  */
24
- export async function callEmbedding (envelope, { socketPath, signal, path = '/v1/embed' }) {
26
+ export async function callEmbedding (envelope, { socketPath, signal, path = '/v1/embed', headers }) {
25
27
  const res = await requestUnix({
26
28
  socketPath,
27
29
  path,
28
30
  method: 'POST',
29
31
  body: envelope,
30
- signal
32
+ signal,
33
+ headers
31
34
  })
32
35
 
33
36
  const body = await readAll(res)
@@ -17,15 +17,18 @@ import { MohdelError } from '#core'
17
17
  * @param {string} options.socketPath
18
18
  * @param {AbortSignal} [options.signal]
19
19
  * @param {string} [options.path] HTTP path; defaults to '/v1/image'
20
+ * @param {Record<string, string>} [options.headers] sent with the request, for a router in front of the gate;
21
+ * `content-type`, `content-length`, `transfer-encoding`, `connection` and `host` are the transport's
20
22
  * @returns {Promise<import('#core/image.js').ImageResult>}
21
23
  */
22
- export async function callImage (envelope, { socketPath, signal, path = '/v1/image' }) {
24
+ export async function callImage (envelope, { socketPath, signal, path = '/v1/image', headers }) {
23
25
  const res = await requestUnix({
24
26
  socketPath,
25
27
  path,
26
28
  method: 'POST',
27
29
  body: envelope,
28
- signal
30
+ signal,
31
+ headers
29
32
  })
30
33
 
31
34
  const body = await readAll(res)
@@ -20,15 +20,18 @@ import { MohdelError } from '#core'
20
20
  * @param {string} options.socketPath
21
21
  * @param {AbortSignal} [options.signal]
22
22
  * @param {string} [options.path] HTTP path; defaults to '/v1/transcription'
23
+ * @param {Record<string, string>} [options.headers] sent with the request, for a router in front of the gate;
24
+ * `content-type`, `content-length`, `transfer-encoding`, `connection` and `host` are the transport's
23
25
  * @returns {Promise<import('#core/transcription.js').TranscriptionResult>}
24
26
  */
25
- export async function callTranscription (envelope, { socketPath, signal, path = '/v1/transcription' }) {
27
+ export async function callTranscription (envelope, { socketPath, signal, path = '/v1/transcription', headers }) {
26
28
  const res = await requestUnix({
27
29
  socketPath,
28
30
  path,
29
31
  method: 'POST',
30
32
  body: envelope,
31
- signal
33
+ signal,
34
+ headers
32
35
  })
33
36
 
34
37
  const body = await readAll(res)
package/js/session/run.js CHANGED
@@ -85,6 +85,9 @@ export async function * run (envelope, {
85
85
  if (norm.error) { yield norm.error; return }
86
86
  envelope = norm.envelope
87
87
 
88
+ const cacheErr = cacheMarkerError(envelope.prompt)
89
+ if (cacheErr) { yield cacheErr; return }
90
+
88
91
  const provider = providerOf(envelope.model)
89
92
  const span = openSpan(envelope)
90
93
  const log = scopedLogger(logger, envelope, span)
@@ -357,6 +360,37 @@ export function normalizeModelId (envelope, resolveSpec) {
357
360
  return { envelope: next, key: base, spec: baseSpec }
358
361
  }
359
362
 
363
+ /**
364
+ * A `cache` marker is honoured on `text` parts only, with a TTL the
365
+ * adapters know. The adapters would ignore any other marker, so it is
366
+ * refused rather than dropped.
367
+ *
368
+ * @param {import('#core/envelope.js').CallEnvelope['prompt']} prompt
369
+ * @returns {import('#core/events.js').ErrorEvent | undefined}
370
+ */
371
+ function cacheMarkerError (prompt) {
372
+ if (!Array.isArray(prompt)) return undefined
373
+ for (const m of prompt) {
374
+ if (!Array.isArray(m?.content)) continue
375
+ for (const p of m.content) {
376
+ if (p?.cache == null) continue
377
+ if (p.type !== 'text') {
378
+ return errorEvent(
379
+ `\`cache\` is accepted on text parts only, not on a ${p.type} part (${m.role} message)`,
380
+ 'SESSION_INVALID_PROMPT'
381
+ )
382
+ }
383
+ if (p.cache !== '5m' && p.cache !== '1h') {
384
+ return errorEvent(
385
+ `\`cache\` on a text part must be '5m' or '1h' (got ${JSON.stringify(p.cache)})`,
386
+ 'SESSION_INVALID_PROMPT'
387
+ )
388
+ }
389
+ }
390
+ }
391
+ return undefined
392
+ }
393
+
360
394
  /**
361
395
  * The two lane guards, in caller-then-internal order. Returns an
362
396
  * error event, or `undefined` when the lane is usable.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mohdel",
3
- "version": "1.5.0",
3
+ "version": "1.6.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.5.0"
139
+ "mohdel-thin-gate-linux-x64-gnu": "1.6.0"
140
140
  },
141
141
  "dependencies": {
142
142
  "@anthropic-ai/sdk": "^0.128.0",