mohdel 0.90.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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +377 -0
  3. package/config/benchmarks.json +39 -0
  4. package/js/client/call.js +75 -0
  5. package/js/client/call_image.js +82 -0
  6. package/js/client/gate-binary.js +72 -0
  7. package/js/client/index.js +16 -0
  8. package/js/client/ndjson.js +29 -0
  9. package/js/client/transport.js +48 -0
  10. package/js/core/envelope.js +141 -0
  11. package/js/core/errors.js +75 -0
  12. package/js/core/events.js +96 -0
  13. package/js/core/image.js +58 -0
  14. package/js/core/index.js +10 -0
  15. package/js/core/status.js +48 -0
  16. package/js/factory/bridge.js +372 -0
  17. package/js/session/_cooldown.js +114 -0
  18. package/js/session/_logger.js +138 -0
  19. package/js/session/_rate_limiter.js +77 -0
  20. package/js/session/_tracing.js +58 -0
  21. package/js/session/adapters/_cancelled.js +44 -0
  22. package/js/session/adapters/_catalog.js +58 -0
  23. package/js/session/adapters/_chat_completions.js +439 -0
  24. package/js/session/adapters/_errors.js +85 -0
  25. package/js/session/adapters/_images.js +60 -0
  26. package/js/session/adapters/_lazy_json_cache.js +76 -0
  27. package/js/session/adapters/_pricing.js +67 -0
  28. package/js/session/adapters/_providers.js +60 -0
  29. package/js/session/adapters/_tools.js +185 -0
  30. package/js/session/adapters/_videos.js +283 -0
  31. package/js/session/adapters/anthropic.js +397 -0
  32. package/js/session/adapters/cerebras.js +28 -0
  33. package/js/session/adapters/deepseek.js +32 -0
  34. package/js/session/adapters/echo.js +51 -0
  35. package/js/session/adapters/fake.js +262 -0
  36. package/js/session/adapters/fireworks.js +46 -0
  37. package/js/session/adapters/gemini.js +381 -0
  38. package/js/session/adapters/groq.js +23 -0
  39. package/js/session/adapters/image/fake.js +55 -0
  40. package/js/session/adapters/image/index.js +40 -0
  41. package/js/session/adapters/image/novita.js +135 -0
  42. package/js/session/adapters/image/openai.js +50 -0
  43. package/js/session/adapters/index.js +53 -0
  44. package/js/session/adapters/mistral.js +31 -0
  45. package/js/session/adapters/novita.js +29 -0
  46. package/js/session/adapters/openai.js +381 -0
  47. package/js/session/adapters/openrouter.js +66 -0
  48. package/js/session/adapters/xai.js +27 -0
  49. package/js/session/bin.js +54 -0
  50. package/js/session/driver.js +160 -0
  51. package/js/session/index.js +18 -0
  52. package/js/session/run.js +393 -0
  53. package/js/session/run_image.js +61 -0
  54. package/package.json +107 -0
  55. package/src/cli/ask.js +160 -0
  56. package/src/cli/backup.js +107 -0
  57. package/src/cli/bench.js +262 -0
  58. package/src/cli/check.js +123 -0
  59. package/src/cli/colored-logger.js +67 -0
  60. package/src/cli/colors.js +13 -0
  61. package/src/cli/default.js +39 -0
  62. package/src/cli/index.js +150 -0
  63. package/src/cli/json-output.js +60 -0
  64. package/src/cli/model.js +571 -0
  65. package/src/cli/onboard.js +232 -0
  66. package/src/cli/rank.js +176 -0
  67. package/src/cli/ratelimit.js +160 -0
  68. package/src/cli/tag.js +105 -0
  69. package/src/lib/assets/alibaba.svg +1 -0
  70. package/src/lib/assets/anthropic.svg +5 -0
  71. package/src/lib/assets/deepseek.svg +1 -0
  72. package/src/lib/assets/gemini.svg +1 -0
  73. package/src/lib/assets/google.svg +2 -0
  74. package/src/lib/assets/kwaipilot.svg +1 -0
  75. package/src/lib/assets/meta.svg +1 -0
  76. package/src/lib/assets/minimax.svg +9 -0
  77. package/src/lib/assets/moonshotai.svg +4 -0
  78. package/src/lib/assets/openai.svg +5 -0
  79. package/src/lib/assets/xai.svg +1 -0
  80. package/src/lib/assets/xiaomi.svg +2 -0
  81. package/src/lib/assets/zai.svg +219 -0
  82. package/src/lib/benchmark-score.js +215 -0
  83. package/src/lib/benchmark-truth.js +68 -0
  84. package/src/lib/cache.js +76 -0
  85. package/src/lib/common.js +208 -0
  86. package/src/lib/cooldown.js +63 -0
  87. package/src/lib/creators.js +71 -0
  88. package/src/lib/curated-cache.js +146 -0
  89. package/src/lib/errors.js +126 -0
  90. package/src/lib/index.js +726 -0
  91. package/src/lib/logger.js +29 -0
  92. package/src/lib/providers.js +87 -0
  93. package/src/lib/rank.js +390 -0
  94. package/src/lib/rate-limiter.js +50 -0
  95. package/src/lib/schema.js +150 -0
  96. package/src/lib/select.js +474 -0
  97. package/src/lib/tracing.js +62 -0
  98. package/src/lib/utils.js +85 -0
@@ -0,0 +1,16 @@
1
+ /**
2
+ * mohdel client — thin caller for thin-gate's data-plane unix socket.
3
+ *
4
+ * Public surface (0.90):
5
+ * - call(envelope, { socketPath, signal }): AsyncGenerator<Event>
6
+ * - callImage(envelope, { socketPath, signal }): Promise<ImageResult>
7
+ *
8
+ * No provider SDKs are imported transitively. This module can be
9
+ * consumed by callers that must not pull openai-node, anthropic-sdk,
10
+ * etc. into their bundle.
11
+ *
12
+ * @module client
13
+ */
14
+
15
+ export { call } from './call.js'
16
+ export { callImage } from './call_image.js'
@@ -0,0 +1,29 @@
1
+ /**
2
+ * NDJSON line parser. Yields parsed objects from a byte/string stream.
3
+ *
4
+ * @module client/ndjson
5
+ */
6
+
7
+ const MAX_LINE_BYTES = 16 * 1024 * 1024
8
+
9
+ /**
10
+ * @param {AsyncIterable<Buffer|string>} stream
11
+ * @returns {AsyncGenerator<unknown>}
12
+ */
13
+ export async function * parseNDJSON (stream) {
14
+ let buf = ''
15
+ for await (const chunk of stream) {
16
+ buf += typeof chunk === 'string' ? chunk : chunk.toString('utf8')
17
+ if (buf.length > MAX_LINE_BYTES) {
18
+ throw new Error(`NDJSON line exceeds ${MAX_LINE_BYTES} bytes without newline`)
19
+ }
20
+ let nl
21
+ while ((nl = buf.indexOf('\n')) !== -1) {
22
+ const line = buf.slice(0, nl).trim()
23
+ buf = buf.slice(nl + 1)
24
+ if (line) yield JSON.parse(line)
25
+ }
26
+ }
27
+ const tail = buf.trim()
28
+ if (tail) yield JSON.parse(tail)
29
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Low-level unix-socket HTTP transport for mohdel client.
3
+ *
4
+ * Stateless — each request opens and closes its own socket. Pooling/
5
+ * keep-alive can be added later if measurement shows a bottleneck;
6
+ * unix-socket overhead is typically negligible.
7
+ *
8
+ * @module client/transport
9
+ */
10
+
11
+ import http from 'node:http'
12
+
13
+ /**
14
+ * @typedef {object} RequestOptions
15
+ * @property {string} socketPath
16
+ * @property {string} path
17
+ * @property {string} method
18
+ * @property {object} [body]
19
+ * @property {AbortSignal} [signal]
20
+ * @property {Record<string,string>} [headers]
21
+ */
22
+
23
+ /**
24
+ * @param {RequestOptions} options
25
+ * @returns {Promise<http.IncomingMessage>}
26
+ */
27
+ export function requestUnix ({ socketPath, path, method, body, signal, headers }) {
28
+ return new Promise((resolve, reject) => {
29
+ /** @type {Record<string,string>} */
30
+ const h = { ...(headers || {}) }
31
+ if (body !== undefined) h['content-type'] = 'application/json'
32
+
33
+ const req = http.request({ socketPath, path, method, headers: h }, resolve)
34
+ req.on('error', reject)
35
+
36
+ if (signal) {
37
+ if (signal.aborted) {
38
+ req.destroy(new Error('aborted'))
39
+ reject(new Error('aborted'))
40
+ return
41
+ }
42
+ signal.addEventListener('abort', () => req.destroy(new Error('aborted')), { once: true })
43
+ }
44
+
45
+ if (body !== undefined) req.end(JSON.stringify(body))
46
+ else req.end()
47
+ })
48
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Canonical call envelope — the frozen wire shape for a single
3
+ * inference call. Flat `answer(prompt, options)` surface plus the
4
+ * minimum transport metadata needed to cross a process boundary
5
+ * (callId, authId, auth.key, traceparent).
6
+ *
7
+ * camelCase on the wire. JSON on NDJSON frames. Rust mirror:
8
+ * `rust/thin-gate/src/protocol.rs`.
9
+ *
10
+ * @module core/envelope
11
+ */
12
+
13
+ /**
14
+ * @typedef {object} CallEnvelope
15
+ *
16
+ * @property {string} callId
17
+ * Unique per call.
18
+ * @property {string} authId
19
+ * Quota-scoping identity (rate-limit/cooldown bucket). Distinct
20
+ * from `identifier`, which is the provider-side safety/tracking
21
+ * ID that gets forwarded to the provider API.
22
+ * @property {Auth} auth
23
+ * @property {string} [traceparent] W3C tracecontext header.
24
+ * @property {string} [baggage] W3C baggage header.
25
+ *
26
+ * @property {string} provider Adapter discriminator.
27
+ * @property {string} model Provider-native model id.
28
+ *
29
+ * @property {(string|Message[])} prompt
30
+ * Either a plain string or a structured array of messages.
31
+ *
32
+ * --- Answer options (flat) ---
33
+ *
34
+ * @property {number} [outputBudget]
35
+ * Max output tokens (clamped to model's outputTokenLimit).
36
+ * @property {('text'|'json')} [outputType]
37
+ * Default 'text'.
38
+ * @property {('chat'|'coding'|'analysis'|'translation'|'creative')} [outputStyle]
39
+ * @property {string} [outputEffort]
40
+ * Thinking effort level. Valid keys are per-model — validated at
41
+ * runtime against the curated entry's `thinkingEffortLevels`.
42
+ * Default is the model's `defaultThinkingEffort`.
43
+ *
44
+ * @property {MediaRef[]} [images]
45
+ * @property {MediaRef[]} [videos]
46
+ * @property {boolean} [cache]
47
+ * Cache uploaded files (Gemini videos).
48
+ *
49
+ * @property {ToolSpec[]} [tools]
50
+ * @property {('auto'|'required'|'none'|string)} [toolChoice]
51
+ * @property {boolean} [parallelToolCalls]
52
+ *
53
+ * @property {string} [identifier]
54
+ * Opaque per-user ID passed to the provider for tracking / abuse
55
+ * monitoring.
56
+ *
57
+ * @property {Object<string, object>} [providerOptions]
58
+ * Namespaced bag of provider-specific knobs that don't fit the
59
+ * shared envelope. Keys are provider names; values are arbitrary
60
+ * JSON objects the matching adapter consumes. Today only
61
+ * `providerOptions.openrouter = {order?, allow?, deny?}` is
62
+ * recognized (OpenRouter routing preferences). Unknown keys are
63
+ * accepted on the wire and silently ignored by adapters that
64
+ * don't read them — upgrade to a typed sub-struct per provider if
65
+ * strict validation becomes necessary.
66
+ */
67
+
68
+ /**
69
+ * @typedef {object} Auth
70
+ * @property {string} key Provider API key. Redact in logs; never persist.
71
+ * @property {string} [baseURL]
72
+ * Optional override of the adapter's default provider endpoint.
73
+ * Lets callers point at a self-hosted deployment, regional endpoint,
74
+ * proxy, or test server. Adapters treat it as `baseURL ?? ADAPTER_DEFAULT`.
75
+ */
76
+
77
+ /**
78
+ * @typedef {object} Message
79
+ * @property {('system'|'user'|'assistant'|'tool')} role
80
+ * @property {(string|MessagePart[])} content
81
+ * @property {string} [toolCallId]
82
+ * Set on `tool` messages — identifies which assistant tool call
83
+ * this is a response to.
84
+ * @property {string} [name]
85
+ * Optional function name for `tool` messages (providers that
86
+ * require naming the tool alongside the response).
87
+ * @property {ToolCall[]} [toolCalls]
88
+ * Set on `assistant` messages when the model called tools in this
89
+ * turn. Preserves multi-turn histories where the assistant both
90
+ * spoke text AND invoked tools; adapters emit the provider-native
91
+ * tool_use / function_call / tool_calls representation.
92
+ */
93
+
94
+ /**
95
+ * @typedef {object} ToolCall
96
+ * @property {string} id
97
+ * @property {string} name
98
+ * @property {object} arguments Parsed object, not a JSON string.
99
+ */
100
+
101
+ /**
102
+ * @typedef {object} MessagePart
103
+ * @property {('text'|'reasoning')} type
104
+ * @property {string} text
105
+ */
106
+
107
+ /**
108
+ * @typedef {object} MediaRef
109
+ * @property {string} fileUri
110
+ * @property {string} mimeType
111
+ */
112
+
113
+ /**
114
+ * @typedef {object} ToolSpec
115
+ * @property {string} name
116
+ * @property {string} [description]
117
+ * @property {object} parameters JSON Schema
118
+ */
119
+
120
+ export const ENVELOPE_FIELDS = Object.freeze([
121
+ 'callId',
122
+ 'authId',
123
+ 'auth',
124
+ 'traceparent',
125
+ 'baggage',
126
+ 'provider',
127
+ 'model',
128
+ 'prompt',
129
+ 'outputBudget',
130
+ 'outputType',
131
+ 'outputStyle',
132
+ 'outputEffort',
133
+ 'images',
134
+ 'videos',
135
+ 'cache',
136
+ 'tools',
137
+ 'toolChoice',
138
+ 'parallelToolCalls',
139
+ 'identifier',
140
+ 'providerOptions'
141
+ ])
@@ -0,0 +1,75 @@
1
+ /**
2
+ * `TypedError` — wire-format error. Carries `message` (machine key),
3
+ * optional `detail` (user-facing context), `severity` (lowercase
4
+ * string), `retryable`, and optional `type` (canonical tag).
5
+ *
6
+ * Rust mirror: `rust/thin-gate/src/protocol.rs::TypedError`.
7
+ *
8
+ * @module core/errors
9
+ */
10
+
11
+ /**
12
+ * @typedef {('trace'|'debug'|'info'|'warn'|'error'|'fatal')} SeverityTag
13
+ */
14
+
15
+ /**
16
+ * @typedef {object} TypedError
17
+ * @property {string} message
18
+ * Top-level message. Never echo provider response bodies.
19
+ * @property {string} [detail]
20
+ * User-facing error detail (mirrors `MohdelError.detail`).
21
+ * @property {SeverityTag} severity
22
+ * @property {boolean} retryable
23
+ * @property {string} [type]
24
+ * Optional canonical tag (e.g. `'PROVIDER_COOLDOWN'`, `'AUTH_INVALID'`).
25
+ */
26
+
27
+ export const SEVERITY_TAGS = Object.freeze([
28
+ 'trace', 'debug', 'info', 'warn', 'error', 'fatal'
29
+ ])
30
+
31
+ export class MohdelTypedError extends Error {
32
+ /**
33
+ * @param {string} message
34
+ * @param {{
35
+ * severity?: SeverityTag,
36
+ * retryable?: boolean,
37
+ * detail?: string,
38
+ * type?: string
39
+ * }} [options]
40
+ */
41
+ constructor (message, { severity = 'error', retryable = false, detail, type } = {}) {
42
+ super(message)
43
+ this.name = 'MohdelTypedError'
44
+ this.severity = severity
45
+ this.retryable = retryable
46
+ if (detail) this.detail = detail
47
+ if (type) this.type = type
48
+ }
49
+
50
+ /** @returns {TypedError} */
51
+ toJSON () {
52
+ /** @type {TypedError} */
53
+ const out = {
54
+ message: this.message,
55
+ severity: this.severity,
56
+ retryable: this.retryable
57
+ }
58
+ if (this.detail) out.detail = this.detail
59
+ if (this.type) out.type = this.type
60
+ return out
61
+ }
62
+
63
+ /**
64
+ * @param {TypedError} data
65
+ * @returns {MohdelTypedError}
66
+ */
67
+ static fromJSON (data) {
68
+ return new MohdelTypedError(data.message, {
69
+ severity: data.severity,
70
+ retryable: data.retryable,
71
+ detail: data.detail,
72
+ type: data.type
73
+ })
74
+ }
75
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Event union for the session → thin-gate → client stream.
3
+ *
4
+ * Three events:
5
+ * - `delta` — streaming chunk (message text or function-call args).
6
+ * - `done` — terminal with the full `AnswerResult`.
7
+ * - `error` — wire-format error (serializable `TypedError`).
8
+ *
9
+ * Rust mirror: `rust/thin-gate/src/protocol.rs::Event`.
10
+ *
11
+ * @module core/events
12
+ */
13
+
14
+ /**
15
+ * @typedef {(DeltaEvent | DoneEvent | ErrorEvent)} Event
16
+ */
17
+
18
+ /**
19
+ * Streaming chunk: `{ type: 'message' | 'function_call', delta: string }`.
20
+ *
21
+ * @typedef {object} DeltaEvent
22
+ * @property {'delta'} type
23
+ * @property {DeltaChunk} delta
24
+ */
25
+
26
+ /**
27
+ * @typedef {object} DeltaChunk
28
+ * @property {('message'|'function_call')} type
29
+ * @property {string} delta
30
+ */
31
+
32
+ /**
33
+ * Terminal event on success.
34
+ *
35
+ * @typedef {object} DoneEvent
36
+ * @property {'done'} type
37
+ * @property {AnswerResult} result
38
+ */
39
+
40
+ /**
41
+ * Terminal event on failure.
42
+ *
43
+ * @typedef {object} ErrorEvent
44
+ * @property {'error'} type
45
+ * @property {import('./errors.js').TypedError} error
46
+ */
47
+
48
+ /**
49
+ * Inference result — the shape returned by the factory `answer()` and
50
+ * carried in `DoneEvent.result`.
51
+ *
52
+ * @typedef {object} AnswerResult
53
+ * @property {import('./status.js').Status} status
54
+ * `'completed' | 'tool_use' | 'incomplete'`.
55
+ * @property {(string|null)} output
56
+ * Final text (null when `status === 'tool_use'` with no text).
57
+ * @property {number} inputTokens
58
+ * @property {number} outputTokens
59
+ * @property {number} thinkingTokens
60
+ * @property {number} cost
61
+ * USD, computed from curated pricing. Single number (not a breakdown).
62
+ * @property {Timestamps} timestamps
63
+ * @property {string} [warning]
64
+ * `'insufficientOutputBudget' | 'cancelled' | ...` additive union.
65
+ * @property {ToolCall[]} [toolCalls]
66
+ * Present when `status === 'tool_use'`.
67
+ */
68
+
69
+ /**
70
+ * `process.hrtime.bigint()` values as strings (nanoseconds).
71
+ *
72
+ * @typedef {object} Timestamps
73
+ * @property {string} start Adapter invocation start.
74
+ * @property {string} first Time of first delta.
75
+ * @property {string} end Completion time.
76
+ */
77
+
78
+ /**
79
+ * Tool call as emitted on `AnswerResult.toolCalls`. `arguments` is a
80
+ * **parsed object**, not a JSON string.
81
+ *
82
+ * @typedef {object} ToolCall
83
+ * @property {string} id
84
+ * @property {string} name
85
+ * @property {object} arguments
86
+ */
87
+
88
+ export const EVENT_TYPES = Object.freeze(['delta', 'done', 'error'])
89
+
90
+ /**
91
+ * @param {unknown} x
92
+ * @returns {x is Event}
93
+ */
94
+ export function isEvent (x) {
95
+ return x !== null && typeof x === 'object' && EVENT_TYPES.includes(/** @type {any} */(x).type)
96
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Image-generation envelope and result.
3
+ *
4
+ * Separate call path from `CallEnvelope` / `AnswerResult`: image
5
+ * generation is a single synchronous request/response (no streaming).
6
+ * Result shape: `{ status, images, seed, timestamps }`.
7
+ *
8
+ * @module core/image
9
+ */
10
+
11
+ /**
12
+ * @typedef {object} ImageEnvelope
13
+ *
14
+ * @property {string} callId
15
+ * @property {string} authId
16
+ * @property {import('./envelope.js').Auth} auth
17
+ * @property {string} [traceparent]
18
+ * @property {string} [baggage]
19
+ *
20
+ * @property {string} provider
21
+ * @property {string} model
22
+ * @property {string} prompt
23
+ *
24
+ * @property {string} [size] e.g. "1024x1024". Provider-specific.
25
+ * @property {number} [seed] Deterministic generation seed (provider support varies).
26
+ */
27
+
28
+ /**
29
+ * @typedef {object} ImageData
30
+ * @property {string} mimeType
31
+ * @property {string} [url] Remote URL (transient — providers may expire).
32
+ * @property {string} [base64] Inline base64-encoded image bytes.
33
+ */
34
+
35
+ /**
36
+ * @typedef {object} ImageResult
37
+ *
38
+ * @property {'completed'} status
39
+ * Images are one-shot — no `incomplete` state.
40
+ * @property {ImageData[]} images
41
+ * @property {number | null} seed
42
+ * Echo of provider seed when available; null otherwise.
43
+ * @property {{start: string, first: string, end: string}} timestamps
44
+ * hrtime-bigint-as-string. `first` = `end` for image (no streaming).
45
+ */
46
+
47
+ export const IMAGE_ENVELOPE_FIELDS = Object.freeze([
48
+ 'callId',
49
+ 'authId',
50
+ 'auth',
51
+ 'traceparent',
52
+ 'baggage',
53
+ 'provider',
54
+ 'model',
55
+ 'prompt',
56
+ 'size',
57
+ 'seed'
58
+ ])
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Internal barrel for mohdel 0.90 core types.
3
+ *
4
+ * @module core
5
+ */
6
+
7
+ export * from './envelope.js'
8
+ export * from './events.js'
9
+ export * from './status.js'
10
+ export * from './errors.js'
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Answer-result status contract.
3
+ *
4
+ * Three states:
5
+ * - `completed` — the call finished normally.
6
+ * - `tool_use` — the model emitted tool calls; the caller is
7
+ * expected to round-trip results back.
8
+ * - `incomplete` — the call was cut short (budget, cancel, policy).
9
+ *
10
+ * Rust mirror: `rust/thin-gate/src/protocol.rs::Status`.
11
+ *
12
+ * @module core/status
13
+ */
14
+
15
+ /** @typedef {('completed'|'tool_use'|'incomplete')} Status */
16
+
17
+ /**
18
+ * Warning values emitted on `AnswerResult.warning` when status is
19
+ * `incomplete`:
20
+ * - `insufficientOutputBudget` — the model hit `max_tokens`.
21
+ * - `cancelled` — the call was aborted via a cancel control
22
+ * message or `AbortSignal`.
23
+ *
24
+ * Additive: future releases may add new warning strings; consumers
25
+ * should treat unknown warnings as pass-through metadata.
26
+ *
27
+ * @typedef {('insufficientOutputBudget'|'cancelled'|string)} Warning
28
+ */
29
+
30
+ export const STATUS_COMPLETED = 'completed'
31
+ export const STATUS_TOOL_USE = 'tool_use'
32
+ export const STATUS_INCOMPLETE = 'incomplete'
33
+ export const STATUSES = Object.freeze([
34
+ STATUS_COMPLETED,
35
+ STATUS_TOOL_USE,
36
+ STATUS_INCOMPLETE
37
+ ])
38
+
39
+ export const WARNING_INSUFFICIENT_OUTPUT_BUDGET = 'insufficientOutputBudget'
40
+ export const WARNING_CANCELLED = 'cancelled'
41
+
42
+ /**
43
+ * @param {unknown} x
44
+ * @returns {x is Status}
45
+ */
46
+ export function isStatus (x) {
47
+ return typeof x === 'string' && STATUSES.includes(/** @type {Status} */(x))
48
+ }