@mlx-node/server 0.0.0 → 0.0.8
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/dist/chat-session-warm-reuse.d.ts +51 -0
- package/dist/chat-session-warm-reuse.d.ts.map +1 -0
- package/dist/chat-session-warm-reuse.js +68 -0
- package/dist/endpoints/messages-count-tokens.d.ts +8 -0
- package/dist/endpoints/messages-count-tokens.d.ts.map +1 -0
- package/dist/endpoints/messages-count-tokens.js +121 -0
- package/dist/endpoints/messages.d.ts +57 -5
- package/dist/endpoints/messages.d.ts.map +1 -1
- package/dist/endpoints/messages.js +1043 -147
- package/dist/endpoints/models.d.ts +2 -1
- package/dist/endpoints/models.d.ts.map +1 -1
- package/dist/endpoints/models.js +2 -2
- package/dist/endpoints/responses.d.ts +20 -7
- package/dist/endpoints/responses.d.ts.map +1 -1
- package/dist/endpoints/responses.js +572 -82
- package/dist/errors.d.ts +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -0
- package/dist/handler.d.ts +42 -0
- package/dist/handler.d.ts.map +1 -1
- package/dist/handler.js +6 -1
- package/dist/idle-sweeper.d.ts +245 -0
- package/dist/idle-sweeper.d.ts.map +1 -0
- package/dist/idle-sweeper.js +408 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/mappers/anthropic-request.d.ts +24 -2
- package/dist/mappers/anthropic-request.d.ts.map +1 -1
- package/dist/mappers/anthropic-request.js +222 -24
- package/dist/mappers/anthropic-response.d.ts +29 -4
- package/dist/mappers/anthropic-response.d.ts.map +1 -1
- package/dist/mappers/anthropic-response.js +143 -21
- package/dist/mappers/request.d.ts +48 -0
- package/dist/mappers/request.d.ts.map +1 -1
- package/dist/mappers/request.js +211 -35
- package/dist/mappers/response.d.ts.map +1 -1
- package/dist/mappers/response.js +13 -1
- package/dist/model-work-coordinator.d.ts +70 -0
- package/dist/model-work-coordinator.d.ts.map +1 -0
- package/dist/model-work-coordinator.js +120 -0
- package/dist/pending-writes.d.ts.map +1 -1
- package/dist/presets.d.ts +82 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +98 -0
- package/dist/registry.d.ts +31 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +33 -5
- package/dist/router.d.ts +4 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +34 -4
- package/dist/server.d.ts +76 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +48 -1
- package/dist/session-registry.d.ts +272 -18
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/session-registry.js +509 -37
- package/dist/stop-sequence-buffer.d.ts +58 -0
- package/dist/stop-sequence-buffer.d.ts.map +1 -0
- package/dist/stop-sequence-buffer.js +148 -0
- package/dist/text-recovery.d.ts +35 -0
- package/dist/text-recovery.d.ts.map +1 -0
- package/dist/text-recovery.js +41 -0
- package/dist/timing.d.ts +80 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +121 -0
- package/dist/tool-call-buffer.d.ts +5 -5
- package/dist/tool-call-buffer.d.ts.map +1 -1
- package/dist/tool-call-buffer.js +28 -8
- package/dist/types-anthropic.d.ts +161 -1
- package/dist/types-anthropic.d.ts.map +1 -1
- package/dist/types.d.ts +172 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -5,11 +5,59 @@ export interface MappedRequest {
|
|
|
5
5
|
messages: ChatMessage[];
|
|
6
6
|
config: ChatConfig;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Shared MTP-extension parser for the `extra_body` carrier on both
|
|
10
|
+
* `/v1/responses` (OpenAI) and `/v1/messages` (Anthropic) request
|
|
11
|
+
* shapes. Mutates the passed `config` in place:
|
|
12
|
+
*
|
|
13
|
+
* * `generation_mode: "mtp"` → `enableMtp = true`
|
|
14
|
+
* * `generation_mode: "ar"` → `enableMtp = false`
|
|
15
|
+
* * any other / absent value → `enableMtp` untouched, so the
|
|
16
|
+
* downstream `ChatSession.mergeConfig` auto-default (true when
|
|
17
|
+
* the model ships an MTP head) applies.
|
|
18
|
+
*
|
|
19
|
+
* * `mtp_depth: <positive int ≤ 64>` → `mtpDepth = value`
|
|
20
|
+
* * non-integer, out-of-range, or absent → `mtpDepth` untouched.
|
|
21
|
+
* The real clamps are per-family and owned by native
|
|
22
|
+
* `resolve_params`: qwen3.5 native MTP clamps to [1, 5], gemma4
|
|
23
|
+
* DSpark caps at the draft block size (7 on v1), and the gemma4
|
|
24
|
+
* assistant draft clamps to [1, 8]. The server therefore only
|
|
25
|
+
* rejects garbage — non-integers, non-positives, and values
|
|
26
|
+
* > 64 (a generous sanity ceiling far above any family's real
|
|
27
|
+
* clamp) — which saves a round-trip into the model thread.
|
|
28
|
+
*
|
|
29
|
+
* Kept as a pure helper rather than inlined into each mapper so the
|
|
30
|
+
* two endpoints can't drift in semantics.
|
|
31
|
+
*/
|
|
32
|
+
export declare function applyExtraBodyMtpOverrides(config: ChatConfig, extraBody: {
|
|
33
|
+
generation_mode?: string | null;
|
|
34
|
+
mtp_depth?: number | null;
|
|
35
|
+
} | undefined): void;
|
|
8
36
|
export declare function mapRequest(req: ResponsesAPIRequest, priorMessages?: ChatMessage[]): MappedRequest;
|
|
37
|
+
/**
|
|
38
|
+
* Serialise a `ChatMessage[]` snapshot for `StoredResponseRecord.inputJson`,
|
|
39
|
+
* preserving any `Uint8Array` image payloads as base64-encoded sentinels
|
|
40
|
+
* so a later `reconstructMessagesFromChain` can revive them into real
|
|
41
|
+
* `Uint8Array`s for the NAPI chat-session boundary.
|
|
42
|
+
*
|
|
43
|
+
* The replacer runs AFTER `toJSON`, so a `Buffer` (which defines
|
|
44
|
+
* `Buffer.prototype.toJSON` returning `{type:"Buffer",data:[...]}`)
|
|
45
|
+
* would otherwise slip past the `instanceof Uint8Array` check. We
|
|
46
|
+
* match both shapes defensively — the production `resolveMessageContent`
|
|
47
|
+
* path now wraps with `new Uint8Array(...)` at decode time, but any
|
|
48
|
+
* future caller that sneaks a `Buffer` through still round-trips
|
|
49
|
+
* instead of silently corrupting image state.
|
|
50
|
+
*/
|
|
51
|
+
export declare function stringifyStoredInputMessages(messages: ChatMessage[]): string;
|
|
9
52
|
/**
|
|
10
53
|
* Reconstruct `ChatMessage[]` from a stored response chain. Each record
|
|
11
54
|
* stores `inputJson` (messages sent) and `outputJson` (output items); we
|
|
12
55
|
* interleave them.
|
|
56
|
+
*
|
|
57
|
+
* Image payloads encoded as `{__u8__: "<base64>"}` by
|
|
58
|
+
* `stringifyStoredInputMessages` are rehydrated back into `Buffer`
|
|
59
|
+
* (a `Uint8Array` subclass) so replayed user turns carry the same
|
|
60
|
+
* runtime shape the native chat-session APIs expect.
|
|
13
61
|
*/
|
|
14
62
|
export declare function reconstructMessagesFromChain(chain: {
|
|
15
63
|
inputJson: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/mappers/request.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAE9E,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAkB,MAAM,gBAAgB,CAAC;AAE9E,OAAO,KAAK,EAAe,mBAAmB,EAA2B,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/mappers/request.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAE9E,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAkB,MAAM,gBAAgB,CAAC;AAE9E,OAAO,KAAK,EAAe,mBAAmB,EAA2B,MAAM,aAAa,CAAC;AA2G7F,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,MAAM,EAAE,UAAU,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE;IAAE,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,SAAS,GACpF,IAAI,CAcN;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,mBAAmB,EAAE,aAAa,CAAC,EAAE,WAAW,EAAE,GAAG,aAAa,CAmIjG;AAwBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,MAAM,CAgB5E;AAED;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,WAAW,EAAE,CAyE9G"}
|
package/dist/mappers/request.js
CHANGED
|
@@ -1,17 +1,78 @@
|
|
|
1
1
|
/** OpenAI Responses API request → internal `ChatMessage[]` + `ChatConfig`. */
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a message's `content` array into text + optional image bytes.
|
|
4
|
+
*
|
|
5
|
+
* Accepts both input-side (`input_text`, `input_image`) and replay-side
|
|
6
|
+
* (`output_text`, `refusal`, `summary_text`) content parts. Clients that echo
|
|
7
|
+
* prior assistant turns in `input[]` instead of using `previous_response_id`
|
|
8
|
+
* (pi-ai, Codex) send `output_text` on assistant messages — rejecting those
|
|
9
|
+
* would break cold-start replay. `input_image` with a base64 `data:` URL is
|
|
10
|
+
* decoded to bytes; `http(s)://` URLs are not fetched (the mapper stays sync).
|
|
11
|
+
*/
|
|
12
|
+
function resolveMessageContent(content, role) {
|
|
3
13
|
if (typeof content === 'string')
|
|
4
|
-
return content;
|
|
14
|
+
return { text: content };
|
|
5
15
|
const parts = [];
|
|
16
|
+
const images = [];
|
|
17
|
+
// The internal `ChatMessage` shape is `{ content: string, images: Uint8Array[] }`
|
|
18
|
+
// and the downstream Jinja serializer always emits `[{type:"text",...},
|
|
19
|
+
// {type:"image"}*N]` — it cannot represent a text part that appears AFTER
|
|
20
|
+
// an image part in the caller's content array. Detect and reject that
|
|
21
|
+
// shape rather than silently reordering it and changing user intent.
|
|
22
|
+
// Mirrors the existing rejection in `anthropic-request.ts` for the
|
|
23
|
+
// tool_result + trailing-mixed case.
|
|
24
|
+
let seenImage = false;
|
|
6
25
|
for (const p of content) {
|
|
7
|
-
if (p.type === 'input_text') {
|
|
26
|
+
if (p.type === 'input_text' || p.type === 'output_text' || p.type === 'summary_text') {
|
|
27
|
+
if (seenImage) {
|
|
28
|
+
throw new Error('Unsupported: text content part after an image part in the same message is not representable ' +
|
|
29
|
+
'in the internal message model. The flat ChatMessage shape and the Jinja serializer both place ' +
|
|
30
|
+
'all text before all images in a user turn, so any mapping would silently reorder your content. ' +
|
|
31
|
+
'Place all text parts before any image parts, or split across separate user turns.');
|
|
32
|
+
}
|
|
8
33
|
parts.push(p.text);
|
|
9
34
|
}
|
|
35
|
+
else if (p.type === 'refusal') {
|
|
36
|
+
if (seenImage) {
|
|
37
|
+
throw new Error('Unsupported: refusal content part after an image part in the same message is not representable ' +
|
|
38
|
+
'in the internal message model; the flat ChatMessage shape would silently reorder it.');
|
|
39
|
+
}
|
|
40
|
+
parts.push(p.refusal);
|
|
41
|
+
}
|
|
42
|
+
else if (p.type === 'input_image') {
|
|
43
|
+
if (role !== 'user') {
|
|
44
|
+
throw new Error(`input_image content parts are only allowed on user messages (got role="${role}")`);
|
|
45
|
+
}
|
|
46
|
+
if (p.file_id) {
|
|
47
|
+
throw new Error('input_image.file_id is not supported — inline the image as a data URL');
|
|
48
|
+
}
|
|
49
|
+
if (!p.image_url) {
|
|
50
|
+
throw new Error('input_image is missing image_url');
|
|
51
|
+
}
|
|
52
|
+
const match = /^data:[^;,]+;base64,(.+)$/s.exec(p.image_url);
|
|
53
|
+
if (!match) {
|
|
54
|
+
throw new Error('input_image.image_url must be a base64 data URL (data:<mime>;base64,<payload>); ' +
|
|
55
|
+
'remote http(s) URLs are not fetched by this server');
|
|
56
|
+
}
|
|
57
|
+
// Wrap as a plain `Uint8Array` rather than storing the raw
|
|
58
|
+
// `Buffer`. `Buffer` is a `Uint8Array` subclass, but it defines
|
|
59
|
+
// its own `toJSON()` that `JSON.stringify` calls BEFORE any
|
|
60
|
+
// replacer runs — so a Buffer-backed value would serialise as
|
|
61
|
+
// `{type:"Buffer",data:[...]}` and skip the `__u8__` sentinel in
|
|
62
|
+
// `stringifyStoredInputMessages`, corrupting image round-trip
|
|
63
|
+
// through `previous_response_id` chains. A plain `Uint8Array`
|
|
64
|
+
// has no `toJSON`, so the replacer fires as intended.
|
|
65
|
+
images.push(new Uint8Array(Buffer.from(match[1], 'base64')));
|
|
66
|
+
seenImage = true;
|
|
67
|
+
}
|
|
10
68
|
else {
|
|
11
69
|
throw new Error(`Unsupported content part type: "${p.type}"`);
|
|
12
70
|
}
|
|
13
71
|
}
|
|
14
|
-
|
|
72
|
+
const out = { text: parts.join('') };
|
|
73
|
+
if (images.length > 0)
|
|
74
|
+
out.images = images;
|
|
75
|
+
return out;
|
|
15
76
|
}
|
|
16
77
|
/** NAPI `ToolDefinition` requires `parameters.properties` to be a JSON string. */
|
|
17
78
|
function mapTool(tool) {
|
|
@@ -34,6 +95,46 @@ function mapTool(tool) {
|
|
|
34
95
|
},
|
|
35
96
|
};
|
|
36
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Shared MTP-extension parser for the `extra_body` carrier on both
|
|
100
|
+
* `/v1/responses` (OpenAI) and `/v1/messages` (Anthropic) request
|
|
101
|
+
* shapes. Mutates the passed `config` in place:
|
|
102
|
+
*
|
|
103
|
+
* * `generation_mode: "mtp"` → `enableMtp = true`
|
|
104
|
+
* * `generation_mode: "ar"` → `enableMtp = false`
|
|
105
|
+
* * any other / absent value → `enableMtp` untouched, so the
|
|
106
|
+
* downstream `ChatSession.mergeConfig` auto-default (true when
|
|
107
|
+
* the model ships an MTP head) applies.
|
|
108
|
+
*
|
|
109
|
+
* * `mtp_depth: <positive int ≤ 64>` → `mtpDepth = value`
|
|
110
|
+
* * non-integer, out-of-range, or absent → `mtpDepth` untouched.
|
|
111
|
+
* The real clamps are per-family and owned by native
|
|
112
|
+
* `resolve_params`: qwen3.5 native MTP clamps to [1, 5], gemma4
|
|
113
|
+
* DSpark caps at the draft block size (7 on v1), and the gemma4
|
|
114
|
+
* assistant draft clamps to [1, 8]. The server therefore only
|
|
115
|
+
* rejects garbage — non-integers, non-positives, and values
|
|
116
|
+
* > 64 (a generous sanity ceiling far above any family's real
|
|
117
|
+
* clamp) — which saves a round-trip into the model thread.
|
|
118
|
+
*
|
|
119
|
+
* Kept as a pure helper rather than inlined into each mapper so the
|
|
120
|
+
* two endpoints can't drift in semantics.
|
|
121
|
+
*/
|
|
122
|
+
export function applyExtraBodyMtpOverrides(config, extraBody) {
|
|
123
|
+
if (!extraBody)
|
|
124
|
+
return;
|
|
125
|
+
const mode = extraBody.generation_mode;
|
|
126
|
+
if (mode === 'mtp') {
|
|
127
|
+
config.enableMtp = true;
|
|
128
|
+
}
|
|
129
|
+
else if (mode === 'ar') {
|
|
130
|
+
config.enableMtp = false;
|
|
131
|
+
}
|
|
132
|
+
// Any other value (null, undefined, unknown string) → leave alone.
|
|
133
|
+
const depth = extraBody.mtp_depth;
|
|
134
|
+
if (depth != null && Number.isInteger(depth) && depth > 0 && depth <= 64) {
|
|
135
|
+
config.mtpDepth = depth;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
37
138
|
export function mapRequest(req, priorMessages) {
|
|
38
139
|
const messages = [];
|
|
39
140
|
if (req.instructions) {
|
|
@@ -42,18 +143,33 @@ export function mapRequest(req, priorMessages) {
|
|
|
42
143
|
if (priorMessages) {
|
|
43
144
|
messages.push(...priorMessages);
|
|
44
145
|
}
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
146
|
+
// An assistant turn may serialise into any interleaving of `reasoning`,
|
|
147
|
+
// `message` (assistant), and `function_call` items. We coalesce that run
|
|
148
|
+
// into ONE assistant `ChatMessage` carrying `content` + `reasoningContent`
|
|
149
|
+
// + `toolCalls`, matching the hot-path `ChatSession` shape exactly. Any
|
|
150
|
+
// non-assistant item (user / system / function_call_output) flushes the
|
|
151
|
+
// current turn. An assistant `message` item that appears AFTER a
|
|
152
|
+
// `function_call` opens a fresh turn — preserving the pre-existing
|
|
153
|
+
// convention documented in the fan-out tests.
|
|
52
154
|
if (typeof req.input === 'string') {
|
|
53
155
|
messages.push({ role: 'user', content: req.input });
|
|
54
156
|
}
|
|
55
157
|
else {
|
|
56
|
-
let
|
|
158
|
+
let currentAssistant = null;
|
|
159
|
+
let assistantHasToolCalls = false;
|
|
160
|
+
const flushAssistant = () => {
|
|
161
|
+
if (currentAssistant) {
|
|
162
|
+
messages.push(currentAssistant);
|
|
163
|
+
currentAssistant = null;
|
|
164
|
+
assistantHasToolCalls = false;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
const ensureAssistant = () => {
|
|
168
|
+
if (!currentAssistant) {
|
|
169
|
+
currentAssistant = { role: 'assistant', content: '' };
|
|
170
|
+
}
|
|
171
|
+
return currentAssistant;
|
|
172
|
+
};
|
|
57
173
|
for (const item of req.input) {
|
|
58
174
|
if (item == null || typeof item !== 'object') {
|
|
59
175
|
throw new Error('Each input item must be a non-null object');
|
|
@@ -66,34 +182,40 @@ export function mapRequest(req, priorMessages) {
|
|
|
66
182
|
if (role !== 'user' && role !== 'assistant' && role !== 'system') {
|
|
67
183
|
throw new Error(`Unsupported message role: "${msg.role}"`);
|
|
68
184
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
else if (itemType === 'function_call') {
|
|
75
|
-
// Coalesce onto the preceding assistant turn — see the loop header.
|
|
76
|
-
const fc = item;
|
|
77
|
-
const last = messages[messages.length - 1];
|
|
78
|
-
const canCoalesce = (prevItemType === 'function_call' || prevItemType === 'message') &&
|
|
79
|
-
last !== undefined &&
|
|
80
|
-
last.role === 'assistant';
|
|
81
|
-
if (canCoalesce) {
|
|
82
|
-
if (last.toolCalls === undefined) {
|
|
83
|
-
last.toolCalls = [];
|
|
185
|
+
if (role === 'assistant') {
|
|
186
|
+
// `message` after a `function_call` opens a new turn.
|
|
187
|
+
if (assistantHasToolCalls) {
|
|
188
|
+
flushAssistant();
|
|
84
189
|
}
|
|
85
|
-
|
|
190
|
+
const a = ensureAssistant();
|
|
191
|
+
const { text } = resolveMessageContent(msg.content, 'assistant');
|
|
192
|
+
a.content = (a.content ?? '') + text;
|
|
86
193
|
}
|
|
87
194
|
else {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
195
|
+
flushAssistant();
|
|
196
|
+
const { text, images } = resolveMessageContent(msg.content, role);
|
|
197
|
+
const m = { role, content: text };
|
|
198
|
+
if (images)
|
|
199
|
+
m.images = images;
|
|
200
|
+
messages.push(m);
|
|
93
201
|
}
|
|
94
202
|
}
|
|
203
|
+
else if (itemType === 'reasoning') {
|
|
204
|
+
const r = item;
|
|
205
|
+
const summary = (r.summary ?? []).map((s) => s.text ?? '').join('');
|
|
206
|
+
const a = ensureAssistant();
|
|
207
|
+
a.reasoningContent = (a.reasoningContent ?? '') + summary;
|
|
208
|
+
}
|
|
209
|
+
else if (itemType === 'function_call') {
|
|
210
|
+
const fc = item;
|
|
211
|
+
const a = ensureAssistant();
|
|
212
|
+
a.toolCalls ??= [];
|
|
213
|
+
a.toolCalls.push({ name: fc.name, arguments: fc.arguments, id: fc.call_id });
|
|
214
|
+
assistantHasToolCalls = true;
|
|
215
|
+
}
|
|
95
216
|
else if (itemType === 'function_call_output') {
|
|
96
217
|
const fco = item;
|
|
218
|
+
flushAssistant();
|
|
97
219
|
messages.push({
|
|
98
220
|
role: 'tool',
|
|
99
221
|
content: fco.output,
|
|
@@ -103,8 +225,8 @@ export function mapRequest(req, priorMessages) {
|
|
|
103
225
|
else {
|
|
104
226
|
throw new Error(`Unsupported input item type: "${itemType}"`);
|
|
105
227
|
}
|
|
106
|
-
prevItemType = itemType;
|
|
107
228
|
}
|
|
229
|
+
flushAssistant();
|
|
108
230
|
}
|
|
109
231
|
const config = {
|
|
110
232
|
reportPerformance: true,
|
|
@@ -139,17 +261,71 @@ export function mapRequest(req, priorMessages) {
|
|
|
139
261
|
if (priorMessages && priorMessages.length > 0) {
|
|
140
262
|
config.reuseCache = true;
|
|
141
263
|
}
|
|
264
|
+
applyExtraBodyMtpOverrides(config, req.extra_body);
|
|
142
265
|
return { messages, config };
|
|
143
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Sentinel key used to tag base64-encoded `Uint8Array` payloads in
|
|
269
|
+
* persisted `inputJson`. Plain `JSON.stringify` turns a `Uint8Array`
|
|
270
|
+
* into a numeric-keyed object (e.g. `{"0":1,"1":2,...}`), which
|
|
271
|
+
* (a) bloats the row ~8× vs base64 and (b) does not round-trip — the
|
|
272
|
+
* parsed object fails the NAPI `Uint8Array` type check on cold replay,
|
|
273
|
+
* breaking `previous_response_id` continuations that carry images.
|
|
274
|
+
*/
|
|
275
|
+
const UINT8_SENTINEL = '__u8__';
|
|
276
|
+
function isEncodedUint8Array(value) {
|
|
277
|
+
return (value !== null &&
|
|
278
|
+
typeof value === 'object' &&
|
|
279
|
+
typeof value[UINT8_SENTINEL] === 'string');
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Serialise a `ChatMessage[]` snapshot for `StoredResponseRecord.inputJson`,
|
|
283
|
+
* preserving any `Uint8Array` image payloads as base64-encoded sentinels
|
|
284
|
+
* so a later `reconstructMessagesFromChain` can revive them into real
|
|
285
|
+
* `Uint8Array`s for the NAPI chat-session boundary.
|
|
286
|
+
*
|
|
287
|
+
* The replacer runs AFTER `toJSON`, so a `Buffer` (which defines
|
|
288
|
+
* `Buffer.prototype.toJSON` returning `{type:"Buffer",data:[...]}`)
|
|
289
|
+
* would otherwise slip past the `instanceof Uint8Array` check. We
|
|
290
|
+
* match both shapes defensively — the production `resolveMessageContent`
|
|
291
|
+
* path now wraps with `new Uint8Array(...)` at decode time, but any
|
|
292
|
+
* future caller that sneaks a `Buffer` through still round-trips
|
|
293
|
+
* instead of silently corrupting image state.
|
|
294
|
+
*/
|
|
295
|
+
export function stringifyStoredInputMessages(messages) {
|
|
296
|
+
return JSON.stringify(messages, (_key, value) => {
|
|
297
|
+
if (value instanceof Uint8Array) {
|
|
298
|
+
return { [UINT8_SENTINEL]: Buffer.from(value).toString('base64') };
|
|
299
|
+
}
|
|
300
|
+
if (value !== null &&
|
|
301
|
+
typeof value === 'object' &&
|
|
302
|
+
value.type === 'Buffer' &&
|
|
303
|
+
Array.isArray(value.data)) {
|
|
304
|
+
const data = value.data;
|
|
305
|
+
return { [UINT8_SENTINEL]: Buffer.from(data).toString('base64') };
|
|
306
|
+
}
|
|
307
|
+
return value;
|
|
308
|
+
});
|
|
309
|
+
}
|
|
144
310
|
/**
|
|
145
311
|
* Reconstruct `ChatMessage[]` from a stored response chain. Each record
|
|
146
312
|
* stores `inputJson` (messages sent) and `outputJson` (output items); we
|
|
147
313
|
* interleave them.
|
|
314
|
+
*
|
|
315
|
+
* Image payloads encoded as `{__u8__: "<base64>"}` by
|
|
316
|
+
* `stringifyStoredInputMessages` are rehydrated back into `Buffer`
|
|
317
|
+
* (a `Uint8Array` subclass) so replayed user turns carry the same
|
|
318
|
+
* runtime shape the native chat-session APIs expect.
|
|
148
319
|
*/
|
|
149
320
|
export function reconstructMessagesFromChain(chain) {
|
|
150
321
|
const messages = [];
|
|
151
322
|
for (const record of chain) {
|
|
152
|
-
const inputMessages = JSON.parse(record.inputJson)
|
|
323
|
+
const inputMessages = JSON.parse(record.inputJson, (_key, value) => {
|
|
324
|
+
if (isEncodedUint8Array(value)) {
|
|
325
|
+
return Buffer.from(value[UINT8_SENTINEL], 'base64');
|
|
326
|
+
}
|
|
327
|
+
return value;
|
|
328
|
+
});
|
|
153
329
|
messages.push(...inputMessages);
|
|
154
330
|
const outputItems = JSON.parse(record.outputJson);
|
|
155
331
|
let assistantText = '';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/mappers/response.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAIlE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/mappers/response.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAIlE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGjD,OAAO,KAAK,EAGV,UAAU,EAEV,cAAc,EACd,mBAAmB,EACnB,aAAa,EACd,MAAM,aAAa,CAAC;AAErB,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,WAAW,GAAG,YAAY,CAOxF;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,EAAE,CAwCjE;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,aAAa,CAkB5D;AAED,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,CAU7D;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,mBAAmB,EACxB,UAAU,EAAE,MAAM,EAClB,kBAAkB,CAAC,EAAE,MAAM,GAC1B,cAAc,CAwBhB;AAED,qHAAqH;AACrH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,mBAAmB,EACxB,UAAU,EAAE,MAAM,EAClB,kBAAkB,CAAC,EAAE,MAAM,GAC1B,cAAc,CAqBhB"}
|
package/dist/mappers/response.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** ChatResult / ChatStreamEvent → OpenAI Responses API output. */
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { mergeTimingUsageExtensions } from '../timing.js';
|
|
3
4
|
export function genId(prefix) {
|
|
4
5
|
return `${prefix}${randomUUID().replaceAll('-', '')}`;
|
|
5
6
|
}
|
|
@@ -48,12 +49,23 @@ export function buildOutputItems(result) {
|
|
|
48
49
|
return items;
|
|
49
50
|
}
|
|
50
51
|
export function buildUsage(result) {
|
|
51
|
-
|
|
52
|
+
const usage = {
|
|
52
53
|
input_tokens: result.promptTokens,
|
|
53
54
|
output_tokens: result.numTokens,
|
|
54
55
|
output_tokens_details: { reasoning_tokens: result.reasoningTokens },
|
|
55
56
|
total_tokens: result.promptTokens + result.numTokens,
|
|
56
57
|
};
|
|
58
|
+
// Surface reused KV-cache prefix tokens via the upstream
|
|
59
|
+
// `input_tokens_details.cached_tokens` field when the native
|
|
60
|
+
// dispatch reports a non-zero reuse count. Omitted when zero so
|
|
61
|
+
// consumers can cheaply `usage.input_tokens_details?.cached_tokens`
|
|
62
|
+
// without also distinguishing a meaningful zero from "feature not
|
|
63
|
+
// active on this turn".
|
|
64
|
+
if (result.cachedTokens > 0) {
|
|
65
|
+
usage.input_tokens_details = { cached_tokens: result.cachedTokens };
|
|
66
|
+
}
|
|
67
|
+
mergeTimingUsageExtensions(usage, result.performance, result.promptTokens, result.numTokens, result.cachedTokens);
|
|
68
|
+
return usage;
|
|
57
69
|
}
|
|
58
70
|
/** Concatenate all `output_text` parts from message items. */
|
|
59
71
|
export function computeOutputText(items) {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of a `withModelLoad` call that surfaces who actually drove the load
|
|
3
|
+
* vs. who merely parked behind one that was already in flight. Callers use
|
|
4
|
+
* this to split observability between the request that triggered a cold
|
|
5
|
+
* weight-materialize from one that arrived a millisecond later and merely
|
|
6
|
+
* inherited the wait — without the split a 60-second cold-load shows up
|
|
7
|
+
* on every concurrent request as if each one paid for a separate load.
|
|
8
|
+
*
|
|
9
|
+
* `owner` reflects the SYNCHRONOUS state observed at lock acquisition:
|
|
10
|
+
* `true` if the writer lock was free when this caller arrived and the
|
|
11
|
+
* caller itself executed the supplied `fn`; `false` if there was already
|
|
12
|
+
* a writer active (or queued ahead of this caller) when it arrived.
|
|
13
|
+
*
|
|
14
|
+
* `waitMs` and `ownMs` partition the wall-clock interval between when
|
|
15
|
+
* the caller arrived at the coordinator and when its `fn` resolved:
|
|
16
|
+
* - `waitMs` is time spent blocked inside `acquireWrite()` (zero for a
|
|
17
|
+
* no-contention owner; ≈ peer's load duration for a follower).
|
|
18
|
+
* - `ownMs` is time spent inside `fn` once the writer lock was held
|
|
19
|
+
* (≈ load duration for an owner driving a cold load; near-zero for a
|
|
20
|
+
* follower whose `fn` is a no-op cache lookup).
|
|
21
|
+
* Both are measured from `Date.now()` and clamped at zero to absorb
|
|
22
|
+
* monotonic-skew. Their sum equals the total elapsed time in the call,
|
|
23
|
+
* so handlers can plumb them into separate observability fields
|
|
24
|
+
* (`server_load_wait_ms` vs. `server_model_resolve_ms`) without
|
|
25
|
+
* double-counting.
|
|
26
|
+
*/
|
|
27
|
+
export interface ModelLoadOutcome<T> {
|
|
28
|
+
result: T;
|
|
29
|
+
owner: boolean;
|
|
30
|
+
waitMs: number;
|
|
31
|
+
ownMs: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Process-local gate for native MLX work.
|
|
35
|
+
*
|
|
36
|
+
* Individual model instances already have a per-model execution mutex, but a
|
|
37
|
+
* lazy `loadModel()` can still run load-time materialization / warmup Metal
|
|
38
|
+
* work while another model is decoding. MLX's allocator and command queues are
|
|
39
|
+
* process-wide, so model load/swap takes an exclusive writer slot; inference
|
|
40
|
+
* takes shared reader slots.
|
|
41
|
+
*/
|
|
42
|
+
export declare class ModelWorkCoordinator {
|
|
43
|
+
private activeReaders;
|
|
44
|
+
private writerActive;
|
|
45
|
+
private waitingWriters;
|
|
46
|
+
private readonly readerWaiters;
|
|
47
|
+
private readonly writerWaiters;
|
|
48
|
+
withModelLoad<T>(fn: () => Promise<T> | T): Promise<T>;
|
|
49
|
+
/**
|
|
50
|
+
* Like {@link withModelLoad} but reports whether THIS caller owned the
|
|
51
|
+
* load (acquired the writer lock with no contention) or merely waited
|
|
52
|
+
* behind a load that was already in flight when it arrived.
|
|
53
|
+
*
|
|
54
|
+
* Decided at sync-time before any await: if neither a writer is active
|
|
55
|
+
* nor any writer is queued ahead, this caller is the owner; otherwise
|
|
56
|
+
* it is parked behind someone else's load and `owner` is `false`. The
|
|
57
|
+
* distinction is used by `/v1/messages` to split `resolve_ms` (own
|
|
58
|
+
* load + lookup) from `load_wait_ms` (waiting on a peer's load) so a
|
|
59
|
+
* 60-second cold-load does not look like 60 seconds of own work for
|
|
60
|
+
* every concurrent request.
|
|
61
|
+
*/
|
|
62
|
+
withModelLoadInstrumented<T>(fn: () => Promise<T> | T): Promise<ModelLoadOutcome<T>>;
|
|
63
|
+
withInference<T>(fn: () => Promise<T> | T): Promise<T>;
|
|
64
|
+
private acquireRead;
|
|
65
|
+
private acquireWrite;
|
|
66
|
+
private releaseRead;
|
|
67
|
+
private releaseWrite;
|
|
68
|
+
private drain;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=model-work-coordinator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-work-coordinator.d.ts","sourceRoot":"","sources":["../src/model-work-coordinator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,MAAM,EAAE,CAAC,CAAC;IACV,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyB;IACvD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyB;IAEjD,aAAa,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAO3D;IAED;;;;;;;;;;;;OAYG;IACG,yBAAyB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAoBzF;IAEK,aAAa,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAO3D;IAED,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,KAAK;CAWd"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-local gate for native MLX work.
|
|
3
|
+
*
|
|
4
|
+
* Individual model instances already have a per-model execution mutex, but a
|
|
5
|
+
* lazy `loadModel()` can still run load-time materialization / warmup Metal
|
|
6
|
+
* work while another model is decoding. MLX's allocator and command queues are
|
|
7
|
+
* process-wide, so model load/swap takes an exclusive writer slot; inference
|
|
8
|
+
* takes shared reader slots.
|
|
9
|
+
*/
|
|
10
|
+
export class ModelWorkCoordinator {
|
|
11
|
+
activeReaders = 0;
|
|
12
|
+
writerActive = false;
|
|
13
|
+
waitingWriters = 0;
|
|
14
|
+
readerWaiters = [];
|
|
15
|
+
writerWaiters = [];
|
|
16
|
+
async withModelLoad(fn) {
|
|
17
|
+
await this.acquireWrite();
|
|
18
|
+
try {
|
|
19
|
+
return await fn();
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
this.releaseWrite();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Like {@link withModelLoad} but reports whether THIS caller owned the
|
|
27
|
+
* load (acquired the writer lock with no contention) or merely waited
|
|
28
|
+
* behind a load that was already in flight when it arrived.
|
|
29
|
+
*
|
|
30
|
+
* Decided at sync-time before any await: if neither a writer is active
|
|
31
|
+
* nor any writer is queued ahead, this caller is the owner; otherwise
|
|
32
|
+
* it is parked behind someone else's load and `owner` is `false`. The
|
|
33
|
+
* distinction is used by `/v1/messages` to split `resolve_ms` (own
|
|
34
|
+
* load + lookup) from `load_wait_ms` (waiting on a peer's load) so a
|
|
35
|
+
* 60-second cold-load does not look like 60 seconds of own work for
|
|
36
|
+
* every concurrent request.
|
|
37
|
+
*/
|
|
38
|
+
async withModelLoadInstrumented(fn) {
|
|
39
|
+
// `owner` MUST be decided synchronously, before any await, so the
|
|
40
|
+
// signal reflects coordinator state at arrival rather than after
|
|
41
|
+
// any peer transition. The wait/own split is measured around the
|
|
42
|
+
// actual phase boundaries (lock acquisition, fn completion) so the
|
|
43
|
+
// two intervals partition cleanly instead of both reporting total
|
|
44
|
+
// elapsed time — see `ModelLoadOutcome` for the contract.
|
|
45
|
+
const owner = !this.writerActive && this.waitingWriters === 0;
|
|
46
|
+
const arrivedAt = Date.now();
|
|
47
|
+
await this.acquireWrite();
|
|
48
|
+
const lockAcquiredAt = Date.now();
|
|
49
|
+
try {
|
|
50
|
+
const result = await fn();
|
|
51
|
+
const fnDoneAt = Date.now();
|
|
52
|
+
const waitMs = Math.max(0, lockAcquiredAt - arrivedAt);
|
|
53
|
+
const ownMs = Math.max(0, fnDoneAt - lockAcquiredAt);
|
|
54
|
+
return { result, owner, waitMs, ownMs };
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
this.releaseWrite();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async withInference(fn) {
|
|
61
|
+
await this.acquireRead();
|
|
62
|
+
try {
|
|
63
|
+
return await fn();
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
this.releaseRead();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
acquireRead() {
|
|
70
|
+
if (!this.writerActive && this.waitingWriters === 0) {
|
|
71
|
+
this.activeReaders += 1;
|
|
72
|
+
return Promise.resolve();
|
|
73
|
+
}
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
this.readerWaiters.push(() => {
|
|
76
|
+
this.activeReaders += 1;
|
|
77
|
+
resolve();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
acquireWrite() {
|
|
82
|
+
this.waitingWriters += 1;
|
|
83
|
+
if (!this.writerActive && this.activeReaders === 0) {
|
|
84
|
+
this.waitingWriters -= 1;
|
|
85
|
+
this.writerActive = true;
|
|
86
|
+
return Promise.resolve();
|
|
87
|
+
}
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
this.writerWaiters.push(() => {
|
|
90
|
+
this.waitingWriters -= 1;
|
|
91
|
+
this.writerActive = true;
|
|
92
|
+
resolve();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
releaseRead() {
|
|
97
|
+
this.activeReaders -= 1;
|
|
98
|
+
if (this.activeReaders < 0)
|
|
99
|
+
this.activeReaders = 0;
|
|
100
|
+
if (this.activeReaders === 0)
|
|
101
|
+
this.drain();
|
|
102
|
+
}
|
|
103
|
+
releaseWrite() {
|
|
104
|
+
this.writerActive = false;
|
|
105
|
+
this.drain();
|
|
106
|
+
}
|
|
107
|
+
drain() {
|
|
108
|
+
if (this.writerActive)
|
|
109
|
+
return;
|
|
110
|
+
if (this.activeReaders === 0 && this.writerWaiters.length > 0) {
|
|
111
|
+
this.writerWaiters.shift()?.();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (this.waitingWriters === 0 && this.readerWaiters.length > 0) {
|
|
115
|
+
const readers = this.readerWaiters.splice(0);
|
|
116
|
+
for (const resolve of readers)
|
|
117
|
+
resolve();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pending-writes.d.ts","sourceRoot":"","sources":["../src/pending-writes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwGG;AAEH;;;;;;;GAOG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyC;IAEjE;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAkC;IAE3E;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CACjB;IAEZ;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAM;IAElD;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"pending-writes.d.ts","sourceRoot":"","sources":["../src/pending-writes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwGG;AAEH;;;;;;;GAOG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyC;IAEjE;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAkC;IAE3E;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CACjB;IAEZ;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAM;IAElD;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAwCjF;IAED;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAElD;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASrD;IAED;;;;;;;;OAQG;IACH,OAAO,CAAC,MAAM,CAAC,YAAY;IAI3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAuB9E;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,YAAY;IAYpB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAmBlC;IAED,iEAAiE;IACjE,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;;;;;;;;OAYG;IACH,IAAI,gBAAgB,IAAI,MAAM,CAG7B;IAED;;;;;;OAMG;IACH,IAAI,4BAA4B,IAAI,MAAM,CAEzC;CACF;AAYD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,qBAAqB,CAOxE"}
|