llm_meta_widget 0.6.1 → 0.7.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.
@@ -0,0 +1,4429 @@
1
+ // GENERATED by `npm run build` from element.js + config.js + orchestrator.js + marked.esm.js + conversation.css + panel.css.
2
+ // Do not edit this file; edit the sources and rebuild. `npm test` fails if it drifts.
3
+
4
+
5
+ // app/assets/javascripts/llm_meta_widget/orchestrator.js
6
+ async function singleLlmCall({
7
+ baseUrl,
8
+ apiKeyUuid,
9
+ modelName,
10
+ messages,
11
+ toolIds = [],
12
+ localTools = [],
13
+ generationSettings = {},
14
+ bearerToken,
15
+ onTextDelta,
16
+ onThinkingDelta,
17
+ onToolCall,
18
+ onPhase,
19
+ signal
20
+ }) {
21
+ if (!baseUrl || !apiKeyUuid || !modelName) {
22
+ throw new Error("singleLlmCall: baseUrl, apiKeyUuid, modelName are required");
23
+ }
24
+ if (!Array.isArray(messages) || messages.length === 0) {
25
+ throw new Error("singleLlmCall: messages must be a non-empty array");
26
+ }
27
+ const url = `${baseUrl.replace(/\/$/, "")}/api/llm_api_keys/${encodeURIComponent(
28
+ apiKeyUuid
29
+ )}/models/${encodeURIComponent(modelName)}/single_llm_calls`;
30
+ const headers = { "Content-Type": "application/json", Accept: "text/event-stream" };
31
+ if (bearerToken) headers["Authorization"] = `Bearer ${bearerToken}`;
32
+ const body = JSON.stringify({
33
+ messages,
34
+ tool_ids: toolIds,
35
+ // Convert camelCase → snake_case at the wire boundary; the server's
36
+ // strong-params permits `input_schema:` and rejects `inputSchema:`.
37
+ local_tools: (localTools || []).map((t) => ({
38
+ name: t.name,
39
+ description: t.description,
40
+ input_schema: t.input_schema || t.inputSchema
41
+ })),
42
+ generation_settings: generationSettings
43
+ });
44
+ const response = await fetch(url, { method: "POST", headers, body, signal });
45
+ if (!response.ok) {
46
+ const text = await response.text().catch(() => "");
47
+ throw new Error(`singleLlmCall: HTTP ${response.status} ${response.statusText}${text ? " \u2014 " + text.slice(0, 200) : ""}`);
48
+ }
49
+ if (!response.body) {
50
+ throw new Error("singleLlmCall: response has no body (streaming unsupported?)");
51
+ }
52
+ const toolCalls = [];
53
+ let content = "";
54
+ let finishReason = null;
55
+ for await (const event of parseSseStream(response.body, signal)) {
56
+ switch (event.name) {
57
+ case "text_delta": {
58
+ const delta = event.data?.delta;
59
+ if (typeof delta === "string" && delta.length > 0) {
60
+ content += delta;
61
+ onTextDelta?.(delta);
62
+ }
63
+ break;
64
+ }
65
+ case "thinking_delta": {
66
+ const delta = event.data?.delta;
67
+ if (typeof delta === "string" && delta.length > 0) {
68
+ onThinkingDelta?.(delta);
69
+ }
70
+ break;
71
+ }
72
+ case "tool_call": {
73
+ const tc = event.data?.tool_call;
74
+ if (tc && tc.name) {
75
+ toolCalls.push(tc);
76
+ onToolCall?.(tc);
77
+ }
78
+ break;
79
+ }
80
+ case "phase": {
81
+ const name = event.data?.name;
82
+ if (typeof name === "string") onPhase?.(name);
83
+ break;
84
+ }
85
+ case "done": {
86
+ if (typeof event.data?.content === "string") content = event.data.content;
87
+ if (event.data?.finish_reason != null) finishReason = event.data.finish_reason;
88
+ return { content, finishReason, toolCalls };
89
+ }
90
+ case "error": {
91
+ const code = event.data?.code || "server_error";
92
+ const message = event.data?.message || "meta-server emitted an error event";
93
+ const err = new Error(`singleLlmCall: ${code}: ${message}`);
94
+ err.code = code;
95
+ throw err;
96
+ }
97
+ }
98
+ }
99
+ throw new Error("singleLlmCall: stream closed without done event");
100
+ }
101
+ async function* parseSseStream(readableStream, signal) {
102
+ const reader = readableStream.getReader();
103
+ const decoder = new TextDecoder("utf-8");
104
+ let buffer = "";
105
+ const onAbort = () => {
106
+ try {
107
+ reader.cancel();
108
+ } catch {
109
+ }
110
+ };
111
+ signal?.addEventListener("abort", onAbort);
112
+ try {
113
+ while (true) {
114
+ const { value, done } = await reader.read();
115
+ if (done) break;
116
+ buffer += decoder.decode(value, { stream: true });
117
+ let sepIdx;
118
+ while ((sepIdx = indexOfEither(buffer, "\n\n", "\r\n\r\n")) !== -1) {
119
+ const rawFrame = buffer.slice(0, sepIdx);
120
+ buffer = buffer.slice(
121
+ sepIdx + (buffer.slice(sepIdx, sepIdx + 4) === "\r\n\r\n" ? 4 : 2)
122
+ );
123
+ const parsed = parseSseFrame(rawFrame);
124
+ if (parsed) yield parsed;
125
+ }
126
+ }
127
+ if (buffer.trim().length > 0) {
128
+ const parsed = parseSseFrame(buffer);
129
+ if (parsed) yield parsed;
130
+ }
131
+ } finally {
132
+ signal?.removeEventListener("abort", onAbort);
133
+ try {
134
+ reader.releaseLock();
135
+ } catch {
136
+ }
137
+ }
138
+ }
139
+ function indexOfEither(str, a, b) {
140
+ const ai = str.indexOf(a);
141
+ const bi = str.indexOf(b);
142
+ if (ai === -1) return bi;
143
+ if (bi === -1) return ai;
144
+ return Math.min(ai, bi);
145
+ }
146
+ async function dispatchLocalToolCalls(toolCalls, aiActions) {
147
+ const dispatched = [];
148
+ const skipped = [];
149
+ for (const tc of toolCalls || []) {
150
+ const handler = aiActions?.[tc.name];
151
+ if (typeof handler !== "function") {
152
+ skipped.push(tc);
153
+ continue;
154
+ }
155
+ const args = coerceArguments(tc.arguments);
156
+ try {
157
+ const value = await handler(args);
158
+ dispatched.push({ toolCall: tc, value });
159
+ } catch (error) {
160
+ dispatched.push({ toolCall: tc, error });
161
+ }
162
+ }
163
+ return { dispatched, skipped };
164
+ }
165
+ async function runChatLoop(opts) {
166
+ const {
167
+ aiActions = {},
168
+ remoteTools = [],
169
+ hostWideTools = [],
170
+ maxRounds = 10,
171
+ // "hub" (default) talks to llm_meta_server; "ollama" talks straight to an
172
+ // Ollama, with no server component of ours in between. The loop is the
173
+ // same either way — only the call is swapped.
174
+ provider = "hub",
175
+ // Where Class 1 (hub-registered) tools are proxied. Usually the same
176
+ // llm_meta_server that answers the chat, but not when the chat goes
177
+ // straight to an Ollama — the tools still belong to the hub.
178
+ toolHubUrl,
179
+ signal,
180
+ onRoundStart,
181
+ onTextDelta,
182
+ onThinkingDelta,
183
+ onToolCall,
184
+ // Fired as each tool finishes, so a caller can show what ran WHILE the
185
+ // turn is still going. The returned `dispatched` array says the same
186
+ // thing, but only once the whole loop ends — which on a slow model is
187
+ // minutes after the work happened, with the user watching unnamed tools.
188
+ onToolDispatched,
189
+ onPhase,
190
+ ...singleOpts
191
+ } = opts;
192
+ const messages = [...singleOpts.messages || []];
193
+ const remoteByName = Object.fromEntries((remoteTools || []).map((t) => [t.name, t]));
194
+ const hostWideByName = Object.fromEntries((hostWideTools || []).map((t) => [t.name, t]));
195
+ const toolIds = [...singleOpts.toolIds || [], ...(remoteTools || []).map((t) => t.id)];
196
+ const inlineByName = {};
197
+ for (const t of singleOpts.localTools || []) inlineByName[t.name] = t;
198
+ for (const t of hostWideTools || []) {
199
+ if (!inlineByName[t.name]) inlineByName[t.name] = { name: t.name, description: t.description, input_schema: t.input_schema };
200
+ }
201
+ const mergedLocalTools = Object.values(inlineByName);
202
+ const rounds = [];
203
+ const allDispatched = [];
204
+ const allSkipped = [];
205
+ let lastResult = null;
206
+ const seenToolCalls = /* @__PURE__ */ new Set();
207
+ const toolKey = (tc) => {
208
+ let args;
209
+ try {
210
+ args = coerceArguments(tc.arguments);
211
+ } catch {
212
+ args = tc.arguments;
213
+ }
214
+ let serialized;
215
+ try {
216
+ if (args && typeof args === "object" && !Array.isArray(args)) {
217
+ const sorted = Object.keys(args).sort().reduce((o, k) => {
218
+ o[k] = args[k];
219
+ return o;
220
+ }, {});
221
+ serialized = JSON.stringify(sorted);
222
+ } else {
223
+ serialized = JSON.stringify(args);
224
+ }
225
+ } catch {
226
+ serialized = String(args);
227
+ }
228
+ return tc.name + "" + serialized;
229
+ };
230
+ for (let round = 0; round < maxRounds; round++) {
231
+ if (signal?.aborted) throw new DOMException("aborted", "AbortError");
232
+ onRoundStart?.(round);
233
+ const call = provider === "ollama" ? ollamaChatCall : singleLlmCall;
234
+ const turnResult = await call({
235
+ ...singleOpts,
236
+ messages,
237
+ toolIds,
238
+ localTools: mergedLocalTools,
239
+ signal,
240
+ onTextDelta: onTextDelta ? (d) => onTextDelta(d, round) : void 0,
241
+ onThinkingDelta: onThinkingDelta ? (d) => onThinkingDelta(d, round) : void 0,
242
+ onToolCall: onToolCall ? (t) => onToolCall(t, round) : void 0,
243
+ onPhase: onPhase ? (n) => onPhase(n, round) : void 0
244
+ });
245
+ lastResult = turnResult;
246
+ const localCalls = [];
247
+ const hostWideCalls = [];
248
+ const remoteCalls = [];
249
+ const unknownCalls = [];
250
+ const duplicateCalls = [];
251
+ const emittedCount = (turnResult.toolCalls || []).length;
252
+ for (const tc of turnResult.toolCalls || []) {
253
+ const key = toolKey(tc);
254
+ if (seenToolCalls.has(key)) {
255
+ duplicateCalls.push(tc);
256
+ continue;
257
+ }
258
+ seenToolCalls.add(key);
259
+ if (typeof aiActions[tc.name] === "function") localCalls.push(tc);
260
+ else if (hostWideByName[tc.name]) hostWideCalls.push(tc);
261
+ else if (remoteByName[tc.name]) remoteCalls.push(tc);
262
+ else unknownCalls.push(tc);
263
+ }
264
+ const allDuplicates = emittedCount > 0 && duplicateCalls.length === emittedCount;
265
+ if (allDuplicates) {
266
+ rounds.push({
267
+ round,
268
+ content: turnResult.content,
269
+ finishReason: turnResult.finishReason,
270
+ localCalls: [],
271
+ hostWideCalls: [],
272
+ remoteCalls: [],
273
+ unknownCalls: [],
274
+ duplicateCalls,
275
+ toolCalls: turnResult.toolCalls
276
+ });
277
+ return {
278
+ content: turnResult.content,
279
+ finishReason: turnResult.finishReason,
280
+ rounds,
281
+ dispatched: allDispatched,
282
+ skipped: allSkipped,
283
+ stopped_reason: "duplicate_tool_calls"
284
+ };
285
+ }
286
+ const localOut = await dispatchLocalToolCalls(localCalls, aiActions);
287
+ allDispatched.push(...localOut.dispatched);
288
+ const roundTripResults = [];
289
+ for (const { toolCall, value, error } of localOut.dispatched) {
290
+ onToolDispatched?.({ toolCall, value, error });
291
+ roundTripResults.push({
292
+ tc: toolCall,
293
+ result: error ? { error: String(error.message || error) } : { ok: true, applied: toolCall.name }
294
+ });
295
+ }
296
+ for (const tc of hostWideCalls) {
297
+ const tool = hostWideByName[tc.name];
298
+ const args = coerceArguments(tc.arguments);
299
+ try {
300
+ const value = await callMcpTool({ endpoint: tool.endpoint, name: tc.name, args, signal });
301
+ allDispatched.push({ toolCall: tc, value });
302
+ onToolDispatched?.({ toolCall: tc, value });
303
+ roundTripResults.push({ tc, result: value });
304
+ } catch (error) {
305
+ allDispatched.push({ toolCall: tc, error });
306
+ onToolDispatched?.({ toolCall: tc, error });
307
+ roundTripResults.push({ tc, result: { error: String(error.message || error) } });
308
+ }
309
+ }
310
+ for (const tc of remoteCalls) {
311
+ const tool = remoteByName[tc.name];
312
+ const args = coerceArguments(tc.arguments);
313
+ try {
314
+ const value = await dispatchRemoteToolCall({
315
+ baseUrl: toolHubUrl || singleOpts.baseUrl,
316
+ bearerToken: singleOpts.bearerToken,
317
+ toolId: tool.id,
318
+ args,
319
+ signal
320
+ });
321
+ allDispatched.push({ toolCall: tc, value });
322
+ onToolDispatched?.({ toolCall: tc, value });
323
+ roundTripResults.push({ tc, result: value });
324
+ } catch (error) {
325
+ allDispatched.push({ toolCall: tc, error });
326
+ onToolDispatched?.({ toolCall: tc, error });
327
+ roundTripResults.push({ tc, result: { error: String(error.message || error) } });
328
+ }
329
+ }
330
+ allSkipped.push(...unknownCalls);
331
+ rounds.push({
332
+ round,
333
+ content: turnResult.content,
334
+ finishReason: turnResult.finishReason,
335
+ localCalls,
336
+ hostWideCalls,
337
+ remoteCalls,
338
+ unknownCalls,
339
+ toolCalls: turnResult.toolCalls
340
+ });
341
+ if (roundTripResults.length === 0) {
342
+ return {
343
+ content: turnResult.content,
344
+ finishReason: turnResult.finishReason,
345
+ rounds,
346
+ dispatched: allDispatched,
347
+ skipped: allSkipped
348
+ };
349
+ }
350
+ messages.push({
351
+ role: "assistant",
352
+ content: turnResult.content || "",
353
+ tool_calls: turnResult.toolCalls
354
+ });
355
+ for (const { tc, result } of roundTripResults) {
356
+ messages.push({
357
+ role: "tool",
358
+ tool_call_id: tc.id || "",
359
+ name: tc.name,
360
+ content: typeof result === "string" ? result : JSON.stringify(result)
361
+ });
362
+ }
363
+ }
364
+ return {
365
+ content: lastResult?.content || "",
366
+ finishReason: lastResult?.finishReason || null,
367
+ rounds,
368
+ dispatched: allDispatched,
369
+ skipped: allSkipped,
370
+ stopped_reason: `max_rounds (${maxRounds}) exceeded`
371
+ };
372
+ }
373
+ async function fetchMcpManifest(manifestUrl) {
374
+ let manifest;
375
+ try {
376
+ const response = await fetch(manifestUrl);
377
+ if (!response.ok) return [];
378
+ manifest = await response.json();
379
+ } catch {
380
+ return [];
381
+ }
382
+ const base = new URL(manifestUrl);
383
+ const out = [];
384
+ for (const server of manifest?.servers || []) {
385
+ let endpoint;
386
+ try {
387
+ endpoint = new URL(server.url, base).toString();
388
+ } catch {
389
+ continue;
390
+ }
391
+ for (const tool of server.tools || []) {
392
+ if (!tool.name) continue;
393
+ out.push({
394
+ name: tool.name,
395
+ description: tool.description || "",
396
+ input_schema: tool.input_schema || tool.inputSchema || { type: "object", properties: {} },
397
+ endpoint,
398
+ serverName: server.name || null
399
+ });
400
+ }
401
+ }
402
+ return out;
403
+ }
404
+ var _mcpReqId = 0;
405
+ async function mcpRpc({ endpoint, method, params, signal, label }) {
406
+ const what = label || method;
407
+ const response = await fetch(endpoint, {
408
+ method: "POST",
409
+ headers: {
410
+ "Content-Type": "application/json",
411
+ "Accept": "application/json, text/event-stream"
412
+ },
413
+ body: JSON.stringify({ jsonrpc: "2.0", id: ++_mcpReqId, method, params: params || {} }),
414
+ signal,
415
+ // Send session cookies for same-origin MCP endpoints. Cross-origin CORS
416
+ // with credentials requires the server to echo Access-Control-Allow-
417
+ // Credentials: true — which most MCP servers won't. This is fine: for
418
+ // cross-origin the widget doesn't send cookies; the server enforces
419
+ // its own auth (API key in header, etc.) if it wants any.
420
+ credentials: "same-origin"
421
+ });
422
+ if (!response.ok) {
423
+ const text = await response.text().catch(() => "");
424
+ throw new Error(`${what}: HTTP ${response.status}${text ? " \u2014 " + text.slice(0, 200) : ""}`);
425
+ }
426
+ const contentType = response.headers.get("content-type") || "";
427
+ if (contentType.includes("application/json")) {
428
+ const body = await response.json();
429
+ if (body?.error) throw new Error(`${what}: ${body.error.message || "JSON-RPC error"}`);
430
+ return body?.result;
431
+ }
432
+ if (contentType.includes("text/event-stream")) {
433
+ for await (const evt of parseSseStream(response.body, signal)) {
434
+ const payload = evt.data;
435
+ if (!payload || typeof payload !== "object") continue;
436
+ if (payload.error) throw new Error(`${what}: ${payload.error.message || "JSON-RPC error"}`);
437
+ if ("result" in payload) return payload.result;
438
+ }
439
+ throw new Error(`${what}: SSE stream ended without result`);
440
+ }
441
+ throw new Error(`${what}: unexpected content-type ${contentType}`);
442
+ }
443
+ async function callMcpTool({ endpoint, name, args, signal }) {
444
+ return mcpRpc({
445
+ endpoint,
446
+ signal,
447
+ method: "tools/call",
448
+ params: { name, arguments: args || {} },
449
+ label: `callMcpTool(${name})`
450
+ });
451
+ }
452
+ async function listMcpPrompts({ endpoint, signal }) {
453
+ try {
454
+ const result = await mcpRpc({ endpoint, method: "prompts/list", signal });
455
+ return (result?.prompts || []).map((p) => ({ ...p, endpoint }));
456
+ } catch {
457
+ return [];
458
+ }
459
+ }
460
+ async function getMcpPrompt({ endpoint, name, args, signal }) {
461
+ return mcpRpc({
462
+ endpoint,
463
+ signal,
464
+ method: "prompts/get",
465
+ params: { name, arguments: args || {} },
466
+ label: `getMcpPrompt(${name})`
467
+ });
468
+ }
469
+ async function listMcpResources({ endpoint, signal }) {
470
+ try {
471
+ const result = await mcpRpc({ endpoint, method: "resources/list", signal });
472
+ return (result?.resources || []).map((r) => ({ ...r, endpoint }));
473
+ } catch {
474
+ return [];
475
+ }
476
+ }
477
+ async function readMcpResource({ endpoint, uri, signal }) {
478
+ return mcpRpc({
479
+ endpoint,
480
+ signal,
481
+ method: "resources/read",
482
+ params: { uri },
483
+ label: `readMcpResource(${uri})`
484
+ });
485
+ }
486
+ function promptMessagesToText(result) {
487
+ return (result?.messages || []).map((m) => {
488
+ const c = m.content;
489
+ if (typeof c === "string") return c;
490
+ if (Array.isArray(c)) return c.filter((b) => b?.type === "text").map((b) => b.text).join("\n");
491
+ if (c && c.type === "text") return c.text;
492
+ return "";
493
+ }).filter(Boolean).join("\n\n");
494
+ }
495
+ async function dispatchRemoteToolCall({ baseUrl, bearerToken, toolId, args, signal }) {
496
+ if (!baseUrl || toolId == null) {
497
+ throw new Error("dispatchRemoteToolCall: baseUrl and toolId are required");
498
+ }
499
+ const url = `${baseUrl.replace(/\/$/, "")}/api/mcp_tools/${encodeURIComponent(toolId)}/call`;
500
+ const headers = { "Content-Type": "application/json" };
501
+ if (bearerToken) headers["Authorization"] = `Bearer ${bearerToken}`;
502
+ const response = await fetch(url, {
503
+ method: "POST",
504
+ headers,
505
+ body: JSON.stringify({ arguments: args || {} }),
506
+ signal
507
+ });
508
+ if (!response.ok) {
509
+ const text = await response.text().catch(() => "");
510
+ throw new Error(`dispatchRemoteToolCall: HTTP ${response.status} ${response.statusText}${text ? " \u2014 " + text.slice(0, 200) : ""}`);
511
+ }
512
+ const body = await response.json();
513
+ return body.result;
514
+ }
515
+ function coerceArguments(raw) {
516
+ if (raw == null) return {};
517
+ if (typeof raw === "object") return raw;
518
+ if (typeof raw === "string") {
519
+ try {
520
+ return JSON.parse(raw);
521
+ } catch {
522
+ return { _raw: raw };
523
+ }
524
+ }
525
+ return { _raw: raw };
526
+ }
527
+ function parseSseFrame(raw) {
528
+ let name = "message";
529
+ const dataLines = [];
530
+ for (const line of raw.split("\n")) {
531
+ if (line.length === 0 || line.startsWith(":")) continue;
532
+ const colon = line.indexOf(":");
533
+ const field = colon === -1 ? line : line.slice(0, colon);
534
+ let value = colon === -1 ? "" : line.slice(colon + 1);
535
+ if (value.startsWith(" ")) value = value.slice(1);
536
+ if (field === "event") name = value;
537
+ else if (field === "data") dataLines.push(value);
538
+ }
539
+ if (dataLines.length === 0 && name === "message") return null;
540
+ const dataStr = dataLines.join("\n");
541
+ let data = null;
542
+ if (dataStr.length > 0) {
543
+ try {
544
+ data = JSON.parse(dataStr);
545
+ } catch (e) {
546
+ data = { _raw: dataStr, _parseError: e.message };
547
+ }
548
+ }
549
+ return { name, data };
550
+ }
551
+ var STATIC_PRIMITIVES_META = "io.modelcontextprotocol/static-primitives";
552
+ var VOLATILITIES = ["stable", "volatile"];
553
+ function resourceHints(entry) {
554
+ const meta = entry && entry._meta && entry._meta[STATIC_PRIMITIVES_META] || {};
555
+ return {
556
+ // A non-numeric or absent size means "unknown", never 0 — 0 would read
557
+ // as a free resource and sail through every budget check.
558
+ sizeBytes: typeof meta.sizeBytes === "number" && isFinite(meta.sizeBytes) ? meta.sizeBytes : null,
559
+ volatility: VOLATILITIES.indexOf(meta.volatility) === -1 ? "stable" : meta.volatility,
560
+ autoAttach: typeof meta.autoAttach === "boolean" ? meta.autoAttach : true
561
+ };
562
+ }
563
+ function planResourceAttachment(entry, budgetBytes) {
564
+ const { sizeBytes, volatility, autoAttach } = resourceHints(entry);
565
+ const base = { sizeBytes, volatility, uri: entry && entry.uri };
566
+ if (!autoAttach) {
567
+ return { ...base, fetch: false, autoAttach: false, reason: "not-auto-attach" };
568
+ }
569
+ if (sizeBytes !== null && sizeBytes > budgetBytes) {
570
+ return { ...base, fetch: false, autoAttach: false, reason: "over-budget" };
571
+ }
572
+ return {
573
+ ...base,
574
+ fetch: true,
575
+ autoAttach: true,
576
+ // Unknown size still gets fetched, then trimmed — the trim is the
577
+ // safety net for servers that do not declare a size.
578
+ reason: sizeBytes === null ? "size-unknown" : "within-budget"
579
+ };
580
+ }
581
+ function trimResourceText(text, maxBytes) {
582
+ if (text.length <= maxBytes) return text;
583
+ try {
584
+ const parsed = JSON.parse(text);
585
+ const rows = parsed && parsed.dictionaries;
586
+ if (Array.isArray(rows)) {
587
+ const names = rows.map((d) => d && d.name).filter(Boolean);
588
+ return JSON.stringify({ dictionaries: names, note: "names only \u2014 full catalog too large to inline" });
589
+ }
590
+ } catch (e) {
591
+ }
592
+ return text.slice(0, maxBytes) + "\n\u2026truncated";
593
+ }
594
+ async function readResourceContext({ endpoint, uri, name, read, budgetBytes }) {
595
+ try {
596
+ const result = await read({ endpoint, uri });
597
+ const entry = (result && result.contents || [])[0];
598
+ if (!entry || !entry.text) return null;
599
+ return { uri, name: name || uri, text: trimResourceText(entry.text, budgetBytes) };
600
+ } catch (e) {
601
+ return null;
602
+ }
603
+ }
604
+ async function loadHostResource({ endpoint, budgetBytes, list: list2, read, onSkip }) {
605
+ const resources = await list2({ endpoint });
606
+ const candidate = (resources || []).filter((r) => (r.mimeType || "") === "application/json")[0];
607
+ if (!candidate) return null;
608
+ const plan = planResourceAttachment(candidate, budgetBytes);
609
+ plan.name = candidate.name || candidate.uri;
610
+ plan.endpoint = endpoint;
611
+ if (!plan.fetch) {
612
+ if (onSkip) onSkip(plan);
613
+ return { plan, context: null };
614
+ }
615
+ if (plan.volatility === "volatile") return { plan, context: null };
616
+ const context = await readResourceContext({
617
+ endpoint,
618
+ uri: candidate.uri,
619
+ name: plan.name,
620
+ read,
621
+ budgetBytes
622
+ });
623
+ return { plan, context };
624
+ }
625
+ function resourceContextLines(context) {
626
+ if (!context) return [];
627
+ return ["", "Reference data \u2014 " + context.name + " (" + context.uri + "):", context.text];
628
+ }
629
+ async function resourceLinesForTurn({ plan, cached, endpoint, read, budgetBytes }) {
630
+ if (!plan || !plan.autoAttach) return { lines: [], cached };
631
+ if (plan.volatility === "volatile") {
632
+ const fresh = await readResourceContext({
633
+ endpoint,
634
+ uri: plan.uri,
635
+ name: plan.name,
636
+ read,
637
+ budgetBytes
638
+ });
639
+ return fresh ? { lines: resourceContextLines(fresh), cached: fresh } : { lines: [], cached };
640
+ }
641
+ return { lines: resourceContextLines(cached), cached };
642
+ }
643
+ var PROMPT_ARG_ALIASES = { dictionaries: "selected_dictionaries" };
644
+ function promptArgFromState(argName, state) {
645
+ const reader = state && state[argName] || state && state[PROMPT_ARG_ALIASES[argName]];
646
+ if (typeof reader !== "function") return "";
647
+ let value;
648
+ try {
649
+ value = reader();
650
+ } catch (e) {
651
+ return "";
652
+ }
653
+ if (value === null || value === void 0) return "";
654
+ return Array.isArray(value) ? value.join(",") : String(value);
655
+ }
656
+ function resolvePromptArguments(prompt, state) {
657
+ const args = {};
658
+ const missing = [];
659
+ for (const arg of prompt && prompt.arguments || []) {
660
+ const value = promptArgFromState(arg.name, state);
661
+ if (value) args[arg.name] = value;
662
+ else if (arg.required) missing.push(arg.name);
663
+ }
664
+ return { args, missing };
665
+ }
666
+ function promptArgumentSummary(prompt, state) {
667
+ return (prompt && prompt.arguments || []).map((arg) => {
668
+ const value = promptArgFromState(arg.name, state);
669
+ return { name: arg.name, value, filled: value.length > 0, required: !!arg.required };
670
+ });
671
+ }
672
+ function promptButtonProps(prompt) {
673
+ return {
674
+ label: prompt.title || prompt.name,
675
+ title: prompt.description || "Run the " + prompt.name + " prompt"
676
+ };
677
+ }
678
+ async function* parseNdjsonStream(readableStream, signal) {
679
+ const reader = readableStream.getReader();
680
+ const decoder = new TextDecoder("utf-8");
681
+ let buffer = "";
682
+ const onAbort = () => {
683
+ try {
684
+ reader.cancel();
685
+ } catch {
686
+ }
687
+ };
688
+ signal?.addEventListener("abort", onAbort);
689
+ try {
690
+ while (true) {
691
+ const { value, done } = await reader.read();
692
+ if (done) break;
693
+ buffer += decoder.decode(value, { stream: true });
694
+ let nl;
695
+ while ((nl = buffer.indexOf("\n")) !== -1) {
696
+ const line = buffer.slice(0, nl).trim();
697
+ buffer = buffer.slice(nl + 1);
698
+ if (!line) continue;
699
+ try {
700
+ yield JSON.parse(line);
701
+ } catch (e) {
702
+ }
703
+ }
704
+ }
705
+ const tail = buffer.trim();
706
+ if (tail) {
707
+ try {
708
+ yield JSON.parse(tail);
709
+ } catch (e) {
710
+ }
711
+ }
712
+ } finally {
713
+ signal?.removeEventListener("abort", onAbort);
714
+ try {
715
+ reader.releaseLock();
716
+ } catch {
717
+ }
718
+ }
719
+ }
720
+ function toolsForOllama(localTools) {
721
+ return (localTools || []).map((t) => ({
722
+ type: "function",
723
+ function: {
724
+ name: t.name,
725
+ description: t.description,
726
+ parameters: t.input_schema || t.inputSchema || { type: "object", properties: {} }
727
+ }
728
+ }));
729
+ }
730
+ function messagesForOllama(messages) {
731
+ return (messages || []).map((m) => {
732
+ if (m.role === "tool") {
733
+ return { role: "tool", tool_name: m.name, content: m.content };
734
+ }
735
+ if (m.tool_calls) {
736
+ return {
737
+ role: m.role,
738
+ content: m.content || "",
739
+ tool_calls: m.tool_calls.map((tc) => ({
740
+ function: { name: tc.name, arguments: coerceArguments(tc.arguments) }
741
+ }))
742
+ };
743
+ }
744
+ return { role: m.role, content: m.content };
745
+ });
746
+ }
747
+ async function ollamaChatCall({
748
+ baseUrl,
749
+ modelName,
750
+ messages,
751
+ localTools = [],
752
+ generationSettings = {},
753
+ onTextDelta,
754
+ onThinkingDelta,
755
+ onToolCall,
756
+ onPhase,
757
+ signal
758
+ }) {
759
+ if (!baseUrl || !modelName) throw new Error("ollamaChatCall: baseUrl and modelName are required");
760
+ if (!Array.isArray(messages) || messages.length === 0) {
761
+ throw new Error("ollamaChatCall: messages must be a non-empty array");
762
+ }
763
+ const { think, ...options2 } = generationSettings || {};
764
+ const body = { model: modelName, messages: messagesForOllama(messages), stream: true };
765
+ if (localTools && localTools.length) body.tools = toolsForOllama(localTools);
766
+ if (think !== void 0) body.think = think;
767
+ if (Object.keys(options2).length) body.options = options2;
768
+ const response = await fetch(`${baseUrl.replace(/\/$/, "")}/api/chat`, {
769
+ method: "POST",
770
+ headers: { "Content-Type": "application/json" },
771
+ body: JSON.stringify(body),
772
+ signal
773
+ });
774
+ if (!response.ok) {
775
+ const text = await response.text().catch(() => "");
776
+ throw new Error(`ollamaChatCall: HTTP ${response.status} ${response.statusText}${text ? " \u2014 " + text.slice(0, 200) : ""}`);
777
+ }
778
+ if (!response.body) throw new Error("ollamaChatCall: response has no body (streaming unsupported?)");
779
+ const toolCalls = [];
780
+ let content = "";
781
+ let finishReason = null;
782
+ let announcedPhase = null;
783
+ const phase = (name) => {
784
+ if (announcedPhase === name) return;
785
+ announcedPhase = name;
786
+ onPhase?.(name);
787
+ };
788
+ for await (const frame of parseNdjsonStream(response.body, signal)) {
789
+ const message = frame.message || {};
790
+ if (message.thinking) {
791
+ phase("thinking");
792
+ onThinkingDelta?.(message.thinking);
793
+ }
794
+ if (message.content) {
795
+ phase("responding");
796
+ content += message.content;
797
+ onTextDelta?.(message.content);
798
+ }
799
+ for (const tc of message.tool_calls || []) {
800
+ const call = {
801
+ id: tc.id || `ollama-${toolCalls.length}`,
802
+ name: tc.function?.name,
803
+ arguments: tc.function?.arguments ?? {}
804
+ };
805
+ toolCalls.push(call);
806
+ onToolCall?.(call);
807
+ }
808
+ if (frame.done) finishReason = frame.done_reason || "stop";
809
+ }
810
+ return { content, toolCalls, finishReason };
811
+ }
812
+ async function fetchOllamaModels({ baseUrl, signal }) {
813
+ try {
814
+ const response = await fetch(`${baseUrl.replace(/\/$/, "")}/api/tags`, { signal });
815
+ if (!response.ok) return [];
816
+ const data = await response.json();
817
+ return (data.models || []).map((m) => m.name).filter(Boolean);
818
+ } catch (e) {
819
+ return [];
820
+ }
821
+ }
822
+ var CONVERSATION_FORMAT = 1;
823
+ function packConversation({
824
+ turns = [],
825
+ open = false,
826
+ model = null,
827
+ maxTurns = 40,
828
+ maxBytes = 2e5
829
+ } = {}) {
830
+ let kept = turns.slice(-maxTurns);
831
+ let payload = { v: CONVERSATION_FORMAT, savedAt: Date.now(), open, model, turns: kept };
832
+ let json = JSON.stringify(payload);
833
+ while (json.length > maxBytes && kept.length > 1) {
834
+ kept = kept.slice(1);
835
+ payload = { ...payload, turns: kept };
836
+ json = JSON.stringify(payload);
837
+ }
838
+ return json;
839
+ }
840
+ function unpackConversation(raw) {
841
+ if (!raw) return null;
842
+ let payload;
843
+ try {
844
+ payload = JSON.parse(raw);
845
+ } catch (e) {
846
+ return null;
847
+ }
848
+ if (!payload || payload.v !== CONVERSATION_FORMAT || !Array.isArray(payload.turns)) return null;
849
+ const turns = payload.turns.filter((t) => t && typeof t.content === "string" && (t.role === "user" || t.role === "assistant"));
850
+ return { turns, open: !!payload.open, model: payload.model || null, savedAt: payload.savedAt || 0 };
851
+ }
852
+ function createConversationStore({ storage, key }) {
853
+ const safely = (fn, fallback = null) => {
854
+ try {
855
+ return fn();
856
+ } catch (e) {
857
+ return fallback;
858
+ }
859
+ };
860
+ return {
861
+ key,
862
+ save(state) {
863
+ return safely(() => {
864
+ storage.setItem(key, packConversation(state));
865
+ return true;
866
+ }, false);
867
+ },
868
+ load() {
869
+ return safely(() => unpackConversation(storage.getItem(key)));
870
+ },
871
+ clear() {
872
+ return safely(() => {
873
+ storage.removeItem(key);
874
+ return true;
875
+ }, false);
876
+ }
877
+ };
878
+ }
879
+ function conversationKeyFor(location, prefix = "llm_meta_widget") {
880
+ return `${prefix}:${location.pathname}`;
881
+ }
882
+
883
+ // app/assets/javascripts/llm_meta_widget/marked.esm.js
884
+ function _getDefaults() {
885
+ return {
886
+ async: false,
887
+ breaks: false,
888
+ extensions: null,
889
+ gfm: true,
890
+ hooks: null,
891
+ pedantic: false,
892
+ renderer: null,
893
+ silent: false,
894
+ tokenizer: null,
895
+ walkTokens: null
896
+ };
897
+ }
898
+ var _defaults = _getDefaults();
899
+ function changeDefaults(newDefaults) {
900
+ _defaults = newDefaults;
901
+ }
902
+ var escapeTest = /[&<>"']/;
903
+ var escapeReplace = new RegExp(escapeTest.source, "g");
904
+ var escapeTestNoEncode = /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/;
905
+ var escapeReplaceNoEncode = new RegExp(escapeTestNoEncode.source, "g");
906
+ var escapeReplacements = {
907
+ "&": "&amp;",
908
+ "<": "&lt;",
909
+ ">": "&gt;",
910
+ '"': "&quot;",
911
+ "'": "&#39;"
912
+ };
913
+ var getEscapeReplacement = (ch) => escapeReplacements[ch];
914
+ function escape$1(html2, encode) {
915
+ if (encode) {
916
+ if (escapeTest.test(html2)) {
917
+ return html2.replace(escapeReplace, getEscapeReplacement);
918
+ }
919
+ } else {
920
+ if (escapeTestNoEncode.test(html2)) {
921
+ return html2.replace(escapeReplaceNoEncode, getEscapeReplacement);
922
+ }
923
+ }
924
+ return html2;
925
+ }
926
+ var unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;
927
+ function unescape(html2) {
928
+ return html2.replace(unescapeTest, (_, n) => {
929
+ n = n.toLowerCase();
930
+ if (n === "colon")
931
+ return ":";
932
+ if (n.charAt(0) === "#") {
933
+ return n.charAt(1) === "x" ? String.fromCharCode(parseInt(n.substring(2), 16)) : String.fromCharCode(+n.substring(1));
934
+ }
935
+ return "";
936
+ });
937
+ }
938
+ var caret = /(^|[^\[])\^/g;
939
+ function edit(regex, opt) {
940
+ let source = typeof regex === "string" ? regex : regex.source;
941
+ opt = opt || "";
942
+ const obj = {
943
+ replace: (name, val) => {
944
+ let valSource = typeof val === "string" ? val : val.source;
945
+ valSource = valSource.replace(caret, "$1");
946
+ source = source.replace(name, valSource);
947
+ return obj;
948
+ },
949
+ getRegex: () => {
950
+ return new RegExp(source, opt);
951
+ }
952
+ };
953
+ return obj;
954
+ }
955
+ function cleanUrl(href) {
956
+ try {
957
+ href = encodeURI(href).replace(/%25/g, "%");
958
+ } catch (e) {
959
+ return null;
960
+ }
961
+ return href;
962
+ }
963
+ var noopTest = { exec: () => null };
964
+ function splitCells(tableRow, count) {
965
+ const row = tableRow.replace(/\|/g, (match, offset, str) => {
966
+ let escaped = false;
967
+ let curr = offset;
968
+ while (--curr >= 0 && str[curr] === "\\")
969
+ escaped = !escaped;
970
+ if (escaped) {
971
+ return "|";
972
+ } else {
973
+ return " |";
974
+ }
975
+ }), cells = row.split(/ \|/);
976
+ let i = 0;
977
+ if (!cells[0].trim()) {
978
+ cells.shift();
979
+ }
980
+ if (cells.length > 0 && !cells[cells.length - 1].trim()) {
981
+ cells.pop();
982
+ }
983
+ if (count) {
984
+ if (cells.length > count) {
985
+ cells.splice(count);
986
+ } else {
987
+ while (cells.length < count)
988
+ cells.push("");
989
+ }
990
+ }
991
+ for (; i < cells.length; i++) {
992
+ cells[i] = cells[i].trim().replace(/\\\|/g, "|");
993
+ }
994
+ return cells;
995
+ }
996
+ function rtrim(str, c, invert) {
997
+ const l = str.length;
998
+ if (l === 0) {
999
+ return "";
1000
+ }
1001
+ let suffLen = 0;
1002
+ while (suffLen < l) {
1003
+ const currChar = str.charAt(l - suffLen - 1);
1004
+ if (currChar === c && !invert) {
1005
+ suffLen++;
1006
+ } else if (currChar !== c && invert) {
1007
+ suffLen++;
1008
+ } else {
1009
+ break;
1010
+ }
1011
+ }
1012
+ return str.slice(0, l - suffLen);
1013
+ }
1014
+ function findClosingBracket(str, b) {
1015
+ if (str.indexOf(b[1]) === -1) {
1016
+ return -1;
1017
+ }
1018
+ let level = 0;
1019
+ for (let i = 0; i < str.length; i++) {
1020
+ if (str[i] === "\\") {
1021
+ i++;
1022
+ } else if (str[i] === b[0]) {
1023
+ level++;
1024
+ } else if (str[i] === b[1]) {
1025
+ level--;
1026
+ if (level < 0) {
1027
+ return i;
1028
+ }
1029
+ }
1030
+ }
1031
+ return -1;
1032
+ }
1033
+ function outputLink(cap, link2, raw, lexer2) {
1034
+ const href = link2.href;
1035
+ const title = link2.title ? escape$1(link2.title) : null;
1036
+ const text = cap[1].replace(/\\([\[\]])/g, "$1");
1037
+ if (cap[0].charAt(0) !== "!") {
1038
+ lexer2.state.inLink = true;
1039
+ const token = {
1040
+ type: "link",
1041
+ raw,
1042
+ href,
1043
+ title,
1044
+ text,
1045
+ tokens: lexer2.inlineTokens(text)
1046
+ };
1047
+ lexer2.state.inLink = false;
1048
+ return token;
1049
+ }
1050
+ return {
1051
+ type: "image",
1052
+ raw,
1053
+ href,
1054
+ title,
1055
+ text: escape$1(text)
1056
+ };
1057
+ }
1058
+ function indentCodeCompensation(raw, text) {
1059
+ const matchIndentToCode = raw.match(/^(\s+)(?:```)/);
1060
+ if (matchIndentToCode === null) {
1061
+ return text;
1062
+ }
1063
+ const indentToCode = matchIndentToCode[1];
1064
+ return text.split("\n").map((node) => {
1065
+ const matchIndentInNode = node.match(/^\s+/);
1066
+ if (matchIndentInNode === null) {
1067
+ return node;
1068
+ }
1069
+ const [indentInNode] = matchIndentInNode;
1070
+ if (indentInNode.length >= indentToCode.length) {
1071
+ return node.slice(indentToCode.length);
1072
+ }
1073
+ return node;
1074
+ }).join("\n");
1075
+ }
1076
+ var _Tokenizer = class {
1077
+ options;
1078
+ rules;
1079
+ // set by the lexer
1080
+ lexer;
1081
+ // set by the lexer
1082
+ constructor(options2) {
1083
+ this.options = options2 || _defaults;
1084
+ }
1085
+ space(src) {
1086
+ const cap = this.rules.block.newline.exec(src);
1087
+ if (cap && cap[0].length > 0) {
1088
+ return {
1089
+ type: "space",
1090
+ raw: cap[0]
1091
+ };
1092
+ }
1093
+ }
1094
+ code(src) {
1095
+ const cap = this.rules.block.code.exec(src);
1096
+ if (cap) {
1097
+ const text = cap[0].replace(/^ {1,4}/gm, "");
1098
+ return {
1099
+ type: "code",
1100
+ raw: cap[0],
1101
+ codeBlockStyle: "indented",
1102
+ text: !this.options.pedantic ? rtrim(text, "\n") : text
1103
+ };
1104
+ }
1105
+ }
1106
+ fences(src) {
1107
+ const cap = this.rules.block.fences.exec(src);
1108
+ if (cap) {
1109
+ const raw = cap[0];
1110
+ const text = indentCodeCompensation(raw, cap[3] || "");
1111
+ return {
1112
+ type: "code",
1113
+ raw,
1114
+ lang: cap[2] ? cap[2].trim().replace(this.rules.inline.anyPunctuation, "$1") : cap[2],
1115
+ text
1116
+ };
1117
+ }
1118
+ }
1119
+ heading(src) {
1120
+ const cap = this.rules.block.heading.exec(src);
1121
+ if (cap) {
1122
+ let text = cap[2].trim();
1123
+ if (/#$/.test(text)) {
1124
+ const trimmed = rtrim(text, "#");
1125
+ if (this.options.pedantic) {
1126
+ text = trimmed.trim();
1127
+ } else if (!trimmed || / $/.test(trimmed)) {
1128
+ text = trimmed.trim();
1129
+ }
1130
+ }
1131
+ return {
1132
+ type: "heading",
1133
+ raw: cap[0],
1134
+ depth: cap[1].length,
1135
+ text,
1136
+ tokens: this.lexer.inline(text)
1137
+ };
1138
+ }
1139
+ }
1140
+ hr(src) {
1141
+ const cap = this.rules.block.hr.exec(src);
1142
+ if (cap) {
1143
+ return {
1144
+ type: "hr",
1145
+ raw: cap[0]
1146
+ };
1147
+ }
1148
+ }
1149
+ blockquote(src) {
1150
+ const cap = this.rules.block.blockquote.exec(src);
1151
+ if (cap) {
1152
+ let text = cap[0].replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g, "\n $1");
1153
+ text = rtrim(text.replace(/^ *>[ \t]?/gm, ""), "\n");
1154
+ const top = this.lexer.state.top;
1155
+ this.lexer.state.top = true;
1156
+ const tokens = this.lexer.blockTokens(text);
1157
+ this.lexer.state.top = top;
1158
+ return {
1159
+ type: "blockquote",
1160
+ raw: cap[0],
1161
+ tokens,
1162
+ text
1163
+ };
1164
+ }
1165
+ }
1166
+ list(src) {
1167
+ let cap = this.rules.block.list.exec(src);
1168
+ if (cap) {
1169
+ let bull = cap[1].trim();
1170
+ const isordered = bull.length > 1;
1171
+ const list2 = {
1172
+ type: "list",
1173
+ raw: "",
1174
+ ordered: isordered,
1175
+ start: isordered ? +bull.slice(0, -1) : "",
1176
+ loose: false,
1177
+ items: []
1178
+ };
1179
+ bull = isordered ? `\\d{1,9}\\${bull.slice(-1)}` : `\\${bull}`;
1180
+ if (this.options.pedantic) {
1181
+ bull = isordered ? bull : "[*+-]";
1182
+ }
1183
+ const itemRegex = new RegExp(`^( {0,3}${bull})((?:[ ][^\\n]*)?(?:\\n|$))`);
1184
+ let raw = "";
1185
+ let itemContents = "";
1186
+ let endsWithBlankLine = false;
1187
+ while (src) {
1188
+ let endEarly = false;
1189
+ if (!(cap = itemRegex.exec(src))) {
1190
+ break;
1191
+ }
1192
+ if (this.rules.block.hr.test(src)) {
1193
+ break;
1194
+ }
1195
+ raw = cap[0];
1196
+ src = src.substring(raw.length);
1197
+ let line = cap[2].split("\n", 1)[0].replace(/^\t+/, (t) => " ".repeat(3 * t.length));
1198
+ let nextLine = src.split("\n", 1)[0];
1199
+ let indent = 0;
1200
+ if (this.options.pedantic) {
1201
+ indent = 2;
1202
+ itemContents = line.trimStart();
1203
+ } else {
1204
+ indent = cap[2].search(/[^ ]/);
1205
+ indent = indent > 4 ? 1 : indent;
1206
+ itemContents = line.slice(indent);
1207
+ indent += cap[1].length;
1208
+ }
1209
+ let blankLine = false;
1210
+ if (!line && /^ *$/.test(nextLine)) {
1211
+ raw += nextLine + "\n";
1212
+ src = src.substring(nextLine.length + 1);
1213
+ endEarly = true;
1214
+ }
1215
+ if (!endEarly) {
1216
+ const nextBulletRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`);
1217
+ const hrRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`);
1218
+ const fencesBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`);
1219
+ const headingBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`);
1220
+ while (src) {
1221
+ const rawLine = src.split("\n", 1)[0];
1222
+ nextLine = rawLine;
1223
+ if (this.options.pedantic) {
1224
+ nextLine = nextLine.replace(/^ {1,4}(?=( {4})*[^ ])/g, " ");
1225
+ }
1226
+ if (fencesBeginRegex.test(nextLine)) {
1227
+ break;
1228
+ }
1229
+ if (headingBeginRegex.test(nextLine)) {
1230
+ break;
1231
+ }
1232
+ if (nextBulletRegex.test(nextLine)) {
1233
+ break;
1234
+ }
1235
+ if (hrRegex.test(src)) {
1236
+ break;
1237
+ }
1238
+ if (nextLine.search(/[^ ]/) >= indent || !nextLine.trim()) {
1239
+ itemContents += "\n" + nextLine.slice(indent);
1240
+ } else {
1241
+ if (blankLine) {
1242
+ break;
1243
+ }
1244
+ if (line.search(/[^ ]/) >= 4) {
1245
+ break;
1246
+ }
1247
+ if (fencesBeginRegex.test(line)) {
1248
+ break;
1249
+ }
1250
+ if (headingBeginRegex.test(line)) {
1251
+ break;
1252
+ }
1253
+ if (hrRegex.test(line)) {
1254
+ break;
1255
+ }
1256
+ itemContents += "\n" + nextLine;
1257
+ }
1258
+ if (!blankLine && !nextLine.trim()) {
1259
+ blankLine = true;
1260
+ }
1261
+ raw += rawLine + "\n";
1262
+ src = src.substring(rawLine.length + 1);
1263
+ line = nextLine.slice(indent);
1264
+ }
1265
+ }
1266
+ if (!list2.loose) {
1267
+ if (endsWithBlankLine) {
1268
+ list2.loose = true;
1269
+ } else if (/\n *\n *$/.test(raw)) {
1270
+ endsWithBlankLine = true;
1271
+ }
1272
+ }
1273
+ let istask = null;
1274
+ let ischecked;
1275
+ if (this.options.gfm) {
1276
+ istask = /^\[[ xX]\] /.exec(itemContents);
1277
+ if (istask) {
1278
+ ischecked = istask[0] !== "[ ] ";
1279
+ itemContents = itemContents.replace(/^\[[ xX]\] +/, "");
1280
+ }
1281
+ }
1282
+ list2.items.push({
1283
+ type: "list_item",
1284
+ raw,
1285
+ task: !!istask,
1286
+ checked: ischecked,
1287
+ loose: false,
1288
+ text: itemContents,
1289
+ tokens: []
1290
+ });
1291
+ list2.raw += raw;
1292
+ }
1293
+ list2.items[list2.items.length - 1].raw = raw.trimEnd();
1294
+ list2.items[list2.items.length - 1].text = itemContents.trimEnd();
1295
+ list2.raw = list2.raw.trimEnd();
1296
+ for (let i = 0; i < list2.items.length; i++) {
1297
+ this.lexer.state.top = false;
1298
+ list2.items[i].tokens = this.lexer.blockTokens(list2.items[i].text, []);
1299
+ if (!list2.loose) {
1300
+ const spacers = list2.items[i].tokens.filter((t) => t.type === "space");
1301
+ const hasMultipleLineBreaks = spacers.length > 0 && spacers.some((t) => /\n.*\n/.test(t.raw));
1302
+ list2.loose = hasMultipleLineBreaks;
1303
+ }
1304
+ }
1305
+ if (list2.loose) {
1306
+ for (let i = 0; i < list2.items.length; i++) {
1307
+ list2.items[i].loose = true;
1308
+ }
1309
+ }
1310
+ return list2;
1311
+ }
1312
+ }
1313
+ html(src) {
1314
+ const cap = this.rules.block.html.exec(src);
1315
+ if (cap) {
1316
+ const token = {
1317
+ type: "html",
1318
+ block: true,
1319
+ raw: cap[0],
1320
+ pre: cap[1] === "pre" || cap[1] === "script" || cap[1] === "style",
1321
+ text: cap[0]
1322
+ };
1323
+ return token;
1324
+ }
1325
+ }
1326
+ def(src) {
1327
+ const cap = this.rules.block.def.exec(src);
1328
+ if (cap) {
1329
+ const tag2 = cap[1].toLowerCase().replace(/\s+/g, " ");
1330
+ const href = cap[2] ? cap[2].replace(/^<(.*)>$/, "$1").replace(this.rules.inline.anyPunctuation, "$1") : "";
1331
+ const title = cap[3] ? cap[3].substring(1, cap[3].length - 1).replace(this.rules.inline.anyPunctuation, "$1") : cap[3];
1332
+ return {
1333
+ type: "def",
1334
+ tag: tag2,
1335
+ raw: cap[0],
1336
+ href,
1337
+ title
1338
+ };
1339
+ }
1340
+ }
1341
+ table(src) {
1342
+ const cap = this.rules.block.table.exec(src);
1343
+ if (!cap) {
1344
+ return;
1345
+ }
1346
+ if (!/[:|]/.test(cap[2])) {
1347
+ return;
1348
+ }
1349
+ const headers = splitCells(cap[1]);
1350
+ const aligns = cap[2].replace(/^\||\| *$/g, "").split("|");
1351
+ const rows = cap[3] && cap[3].trim() ? cap[3].replace(/\n[ \t]*$/, "").split("\n") : [];
1352
+ const item = {
1353
+ type: "table",
1354
+ raw: cap[0],
1355
+ header: [],
1356
+ align: [],
1357
+ rows: []
1358
+ };
1359
+ if (headers.length !== aligns.length) {
1360
+ return;
1361
+ }
1362
+ for (const align of aligns) {
1363
+ if (/^ *-+: *$/.test(align)) {
1364
+ item.align.push("right");
1365
+ } else if (/^ *:-+: *$/.test(align)) {
1366
+ item.align.push("center");
1367
+ } else if (/^ *:-+ *$/.test(align)) {
1368
+ item.align.push("left");
1369
+ } else {
1370
+ item.align.push(null);
1371
+ }
1372
+ }
1373
+ for (const header of headers) {
1374
+ item.header.push({
1375
+ text: header,
1376
+ tokens: this.lexer.inline(header)
1377
+ });
1378
+ }
1379
+ for (const row of rows) {
1380
+ item.rows.push(splitCells(row, item.header.length).map((cell) => {
1381
+ return {
1382
+ text: cell,
1383
+ tokens: this.lexer.inline(cell)
1384
+ };
1385
+ }));
1386
+ }
1387
+ return item;
1388
+ }
1389
+ lheading(src) {
1390
+ const cap = this.rules.block.lheading.exec(src);
1391
+ if (cap) {
1392
+ return {
1393
+ type: "heading",
1394
+ raw: cap[0],
1395
+ depth: cap[2].charAt(0) === "=" ? 1 : 2,
1396
+ text: cap[1],
1397
+ tokens: this.lexer.inline(cap[1])
1398
+ };
1399
+ }
1400
+ }
1401
+ paragraph(src) {
1402
+ const cap = this.rules.block.paragraph.exec(src);
1403
+ if (cap) {
1404
+ const text = cap[1].charAt(cap[1].length - 1) === "\n" ? cap[1].slice(0, -1) : cap[1];
1405
+ return {
1406
+ type: "paragraph",
1407
+ raw: cap[0],
1408
+ text,
1409
+ tokens: this.lexer.inline(text)
1410
+ };
1411
+ }
1412
+ }
1413
+ text(src) {
1414
+ const cap = this.rules.block.text.exec(src);
1415
+ if (cap) {
1416
+ return {
1417
+ type: "text",
1418
+ raw: cap[0],
1419
+ text: cap[0],
1420
+ tokens: this.lexer.inline(cap[0])
1421
+ };
1422
+ }
1423
+ }
1424
+ escape(src) {
1425
+ const cap = this.rules.inline.escape.exec(src);
1426
+ if (cap) {
1427
+ return {
1428
+ type: "escape",
1429
+ raw: cap[0],
1430
+ text: escape$1(cap[1])
1431
+ };
1432
+ }
1433
+ }
1434
+ tag(src) {
1435
+ const cap = this.rules.inline.tag.exec(src);
1436
+ if (cap) {
1437
+ if (!this.lexer.state.inLink && /^<a /i.test(cap[0])) {
1438
+ this.lexer.state.inLink = true;
1439
+ } else if (this.lexer.state.inLink && /^<\/a>/i.test(cap[0])) {
1440
+ this.lexer.state.inLink = false;
1441
+ }
1442
+ if (!this.lexer.state.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
1443
+ this.lexer.state.inRawBlock = true;
1444
+ } else if (this.lexer.state.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
1445
+ this.lexer.state.inRawBlock = false;
1446
+ }
1447
+ return {
1448
+ type: "html",
1449
+ raw: cap[0],
1450
+ inLink: this.lexer.state.inLink,
1451
+ inRawBlock: this.lexer.state.inRawBlock,
1452
+ block: false,
1453
+ text: cap[0]
1454
+ };
1455
+ }
1456
+ }
1457
+ link(src) {
1458
+ const cap = this.rules.inline.link.exec(src);
1459
+ if (cap) {
1460
+ const trimmedUrl = cap[2].trim();
1461
+ if (!this.options.pedantic && /^</.test(trimmedUrl)) {
1462
+ if (!/>$/.test(trimmedUrl)) {
1463
+ return;
1464
+ }
1465
+ const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), "\\");
1466
+ if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) {
1467
+ return;
1468
+ }
1469
+ } else {
1470
+ const lastParenIndex = findClosingBracket(cap[2], "()");
1471
+ if (lastParenIndex > -1) {
1472
+ const start = cap[0].indexOf("!") === 0 ? 5 : 4;
1473
+ const linkLen = start + cap[1].length + lastParenIndex;
1474
+ cap[2] = cap[2].substring(0, lastParenIndex);
1475
+ cap[0] = cap[0].substring(0, linkLen).trim();
1476
+ cap[3] = "";
1477
+ }
1478
+ }
1479
+ let href = cap[2];
1480
+ let title = "";
1481
+ if (this.options.pedantic) {
1482
+ const link2 = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
1483
+ if (link2) {
1484
+ href = link2[1];
1485
+ title = link2[3];
1486
+ }
1487
+ } else {
1488
+ title = cap[3] ? cap[3].slice(1, -1) : "";
1489
+ }
1490
+ href = href.trim();
1491
+ if (/^</.test(href)) {
1492
+ if (this.options.pedantic && !/>$/.test(trimmedUrl)) {
1493
+ href = href.slice(1);
1494
+ } else {
1495
+ href = href.slice(1, -1);
1496
+ }
1497
+ }
1498
+ return outputLink(cap, {
1499
+ href: href ? href.replace(this.rules.inline.anyPunctuation, "$1") : href,
1500
+ title: title ? title.replace(this.rules.inline.anyPunctuation, "$1") : title
1501
+ }, cap[0], this.lexer);
1502
+ }
1503
+ }
1504
+ reflink(src, links) {
1505
+ let cap;
1506
+ if ((cap = this.rules.inline.reflink.exec(src)) || (cap = this.rules.inline.nolink.exec(src))) {
1507
+ const linkString = (cap[2] || cap[1]).replace(/\s+/g, " ");
1508
+ const link2 = links[linkString.toLowerCase()];
1509
+ if (!link2) {
1510
+ const text = cap[0].charAt(0);
1511
+ return {
1512
+ type: "text",
1513
+ raw: text,
1514
+ text
1515
+ };
1516
+ }
1517
+ return outputLink(cap, link2, cap[0], this.lexer);
1518
+ }
1519
+ }
1520
+ emStrong(src, maskedSrc, prevChar = "") {
1521
+ let match = this.rules.inline.emStrongLDelim.exec(src);
1522
+ if (!match)
1523
+ return;
1524
+ if (match[3] && prevChar.match(/[\p{L}\p{N}]/u))
1525
+ return;
1526
+ const nextChar = match[1] || match[2] || "";
1527
+ if (!nextChar || !prevChar || this.rules.inline.punctuation.exec(prevChar)) {
1528
+ const lLength = [...match[0]].length - 1;
1529
+ let rDelim, rLength, delimTotal = lLength, midDelimTotal = 0;
1530
+ const endReg = match[0][0] === "*" ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd;
1531
+ endReg.lastIndex = 0;
1532
+ maskedSrc = maskedSrc.slice(-1 * src.length + lLength);
1533
+ while ((match = endReg.exec(maskedSrc)) != null) {
1534
+ rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6];
1535
+ if (!rDelim)
1536
+ continue;
1537
+ rLength = [...rDelim].length;
1538
+ if (match[3] || match[4]) {
1539
+ delimTotal += rLength;
1540
+ continue;
1541
+ } else if (match[5] || match[6]) {
1542
+ if (lLength % 3 && !((lLength + rLength) % 3)) {
1543
+ midDelimTotal += rLength;
1544
+ continue;
1545
+ }
1546
+ }
1547
+ delimTotal -= rLength;
1548
+ if (delimTotal > 0)
1549
+ continue;
1550
+ rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal);
1551
+ const lastCharLength = [...match[0]][0].length;
1552
+ const raw = src.slice(0, lLength + match.index + lastCharLength + rLength);
1553
+ if (Math.min(lLength, rLength) % 2) {
1554
+ const text2 = raw.slice(1, -1);
1555
+ return {
1556
+ type: "em",
1557
+ raw,
1558
+ text: text2,
1559
+ tokens: this.lexer.inlineTokens(text2)
1560
+ };
1561
+ }
1562
+ const text = raw.slice(2, -2);
1563
+ return {
1564
+ type: "strong",
1565
+ raw,
1566
+ text,
1567
+ tokens: this.lexer.inlineTokens(text)
1568
+ };
1569
+ }
1570
+ }
1571
+ }
1572
+ codespan(src) {
1573
+ const cap = this.rules.inline.code.exec(src);
1574
+ if (cap) {
1575
+ let text = cap[2].replace(/\n/g, " ");
1576
+ const hasNonSpaceChars = /[^ ]/.test(text);
1577
+ const hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text);
1578
+ if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {
1579
+ text = text.substring(1, text.length - 1);
1580
+ }
1581
+ text = escape$1(text, true);
1582
+ return {
1583
+ type: "codespan",
1584
+ raw: cap[0],
1585
+ text
1586
+ };
1587
+ }
1588
+ }
1589
+ br(src) {
1590
+ const cap = this.rules.inline.br.exec(src);
1591
+ if (cap) {
1592
+ return {
1593
+ type: "br",
1594
+ raw: cap[0]
1595
+ };
1596
+ }
1597
+ }
1598
+ del(src) {
1599
+ const cap = this.rules.inline.del.exec(src);
1600
+ if (cap) {
1601
+ return {
1602
+ type: "del",
1603
+ raw: cap[0],
1604
+ text: cap[2],
1605
+ tokens: this.lexer.inlineTokens(cap[2])
1606
+ };
1607
+ }
1608
+ }
1609
+ autolink(src) {
1610
+ const cap = this.rules.inline.autolink.exec(src);
1611
+ if (cap) {
1612
+ let text, href;
1613
+ if (cap[2] === "@") {
1614
+ text = escape$1(cap[1]);
1615
+ href = "mailto:" + text;
1616
+ } else {
1617
+ text = escape$1(cap[1]);
1618
+ href = text;
1619
+ }
1620
+ return {
1621
+ type: "link",
1622
+ raw: cap[0],
1623
+ text,
1624
+ href,
1625
+ tokens: [
1626
+ {
1627
+ type: "text",
1628
+ raw: text,
1629
+ text
1630
+ }
1631
+ ]
1632
+ };
1633
+ }
1634
+ }
1635
+ url(src) {
1636
+ let cap;
1637
+ if (cap = this.rules.inline.url.exec(src)) {
1638
+ let text, href;
1639
+ if (cap[2] === "@") {
1640
+ text = escape$1(cap[0]);
1641
+ href = "mailto:" + text;
1642
+ } else {
1643
+ let prevCapZero;
1644
+ do {
1645
+ prevCapZero = cap[0];
1646
+ cap[0] = this.rules.inline._backpedal.exec(cap[0])?.[0] ?? "";
1647
+ } while (prevCapZero !== cap[0]);
1648
+ text = escape$1(cap[0]);
1649
+ if (cap[1] === "www.") {
1650
+ href = "http://" + cap[0];
1651
+ } else {
1652
+ href = cap[0];
1653
+ }
1654
+ }
1655
+ return {
1656
+ type: "link",
1657
+ raw: cap[0],
1658
+ text,
1659
+ href,
1660
+ tokens: [
1661
+ {
1662
+ type: "text",
1663
+ raw: text,
1664
+ text
1665
+ }
1666
+ ]
1667
+ };
1668
+ }
1669
+ }
1670
+ inlineText(src) {
1671
+ const cap = this.rules.inline.text.exec(src);
1672
+ if (cap) {
1673
+ let text;
1674
+ if (this.lexer.state.inRawBlock) {
1675
+ text = cap[0];
1676
+ } else {
1677
+ text = escape$1(cap[0]);
1678
+ }
1679
+ return {
1680
+ type: "text",
1681
+ raw: cap[0],
1682
+ text
1683
+ };
1684
+ }
1685
+ }
1686
+ };
1687
+ var newline = /^(?: *(?:\n|$))+/;
1688
+ var blockCode = /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/;
1689
+ var fences = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/;
1690
+ var hr = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/;
1691
+ var heading = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/;
1692
+ var bullet = /(?:[*+-]|\d{1,9}[.)])/;
1693
+ var lheading = edit(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/).replace(/bull/g, bullet).replace(/blockCode/g, / {4}/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).getRegex();
1694
+ var _paragraph = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/;
1695
+ var blockText = /^[^\n]+/;
1696
+ var _blockLabel = /(?!\s*\])(?:\\.|[^\[\]\\])+/;
1697
+ var def = edit(/^ {0,3}\[(label)\]: *(?:\n *)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/).replace("label", _blockLabel).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex();
1698
+ var list = edit(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g, bullet).getRegex();
1699
+ var _tag = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul";
1700
+ var _comment = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
1701
+ var html = edit("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n *)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$))", "i").replace("comment", _comment).replace("tag", _tag).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex();
1702
+ var paragraph = edit(_paragraph).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex();
1703
+ var blockquote = edit(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", paragraph).getRegex();
1704
+ var blockNormal = {
1705
+ blockquote,
1706
+ code: blockCode,
1707
+ def,
1708
+ fences,
1709
+ heading,
1710
+ hr,
1711
+ html,
1712
+ lheading,
1713
+ list,
1714
+ newline,
1715
+ paragraph,
1716
+ table: noopTest,
1717
+ text: blockText
1718
+ };
1719
+ var gfmTable = edit("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", " {4}[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex();
1720
+ var blockGfm = {
1721
+ ...blockNormal,
1722
+ table: gfmTable,
1723
+ paragraph: edit(_paragraph).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", gfmTable).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex()
1724
+ };
1725
+ var blockPedantic = {
1726
+ ...blockNormal,
1727
+ html: edit(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", _comment).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),
1728
+ def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,
1729
+ heading: /^(#{1,6})(.*)(?:\n+|$)/,
1730
+ fences: noopTest,
1731
+ // fences not supported
1732
+ lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,
1733
+ paragraph: edit(_paragraph).replace("hr", hr).replace("heading", " *#{1,6} *[^\n]").replace("lheading", lheading).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex()
1734
+ };
1735
+ var escape = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/;
1736
+ var inlineCode = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/;
1737
+ var br = /^( {2,}|\\)\n(?!\s*$)/;
1738
+ var inlineText = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/;
1739
+ var _punctuation = "\\p{P}\\p{S}";
1740
+ var punctuation = edit(/^((?![*_])[\spunctuation])/, "u").replace(/punctuation/g, _punctuation).getRegex();
1741
+ var blockSkip = /\[[^[\]]*?\]\([^\(\)]*?\)|`[^`]*?`|<[^<>]*?>/g;
1742
+ var emStrongLDelim = edit(/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/, "u").replace(/punct/g, _punctuation).getRegex();
1743
+ var emStrongRDelimAst = edit("^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)[punct](\\*+)(?=[\\s]|$)|[^punct\\s](\\*+)(?!\\*)(?=[punct\\s]|$)|(?!\\*)[punct\\s](\\*+)(?=[^punct\\s])|[\\s](\\*+)(?!\\*)(?=[punct])|(?!\\*)[punct](\\*+)(?!\\*)(?=[punct])|[^punct\\s](\\*+)(?=[^punct\\s])", "gu").replace(/punct/g, _punctuation).getRegex();
1744
+ var emStrongRDelimUnd = edit("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)[punct](_+)(?=[\\s]|$)|[^punct\\s](_+)(?!_)(?=[punct\\s]|$)|(?!_)[punct\\s](_+)(?=[^punct\\s])|[\\s](_+)(?!_)(?=[punct])|(?!_)[punct](_+)(?!_)(?=[punct])", "gu").replace(/punct/g, _punctuation).getRegex();
1745
+ var anyPunctuation = edit(/\\([punct])/, "gu").replace(/punct/g, _punctuation).getRegex();
1746
+ var autolink = edit(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme", /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email", /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex();
1747
+ var _inlineComment = edit(_comment).replace("(?:-->|$)", "-->").getRegex();
1748
+ var tag = edit("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment", _inlineComment).replace("attribute", /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex();
1749
+ var _inlineLabel = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/;
1750
+ var link = edit(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/).replace("label", _inlineLabel).replace("href", /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/).replace("title", /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex();
1751
+ var reflink = edit(/^!?\[(label)\]\[(ref)\]/).replace("label", _inlineLabel).replace("ref", _blockLabel).getRegex();
1752
+ var nolink = edit(/^!?\[(ref)\](?:\[\])?/).replace("ref", _blockLabel).getRegex();
1753
+ var reflinkSearch = edit("reflink|nolink(?!\\()", "g").replace("reflink", reflink).replace("nolink", nolink).getRegex();
1754
+ var inlineNormal = {
1755
+ _backpedal: noopTest,
1756
+ // only used for GFM url
1757
+ anyPunctuation,
1758
+ autolink,
1759
+ blockSkip,
1760
+ br,
1761
+ code: inlineCode,
1762
+ del: noopTest,
1763
+ emStrongLDelim,
1764
+ emStrongRDelimAst,
1765
+ emStrongRDelimUnd,
1766
+ escape,
1767
+ link,
1768
+ nolink,
1769
+ punctuation,
1770
+ reflink,
1771
+ reflinkSearch,
1772
+ tag,
1773
+ text: inlineText,
1774
+ url: noopTest
1775
+ };
1776
+ var inlinePedantic = {
1777
+ ...inlineNormal,
1778
+ link: edit(/^!?\[(label)\]\((.*?)\)/).replace("label", _inlineLabel).getRegex(),
1779
+ reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", _inlineLabel).getRegex()
1780
+ };
1781
+ var inlineGfm = {
1782
+ ...inlineNormal,
1783
+ escape: edit(escape).replace("])", "~|])").getRegex(),
1784
+ url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, "i").replace("email", /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),
1785
+ _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,
1786
+ del: /^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,
1787
+ text: /^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/
1788
+ };
1789
+ var inlineBreaks = {
1790
+ ...inlineGfm,
1791
+ br: edit(br).replace("{2,}", "*").getRegex(),
1792
+ text: edit(inlineGfm.text).replace("\\b_", "\\b_| {2,}\\n").replace(/\{2,\}/g, "*").getRegex()
1793
+ };
1794
+ var block = {
1795
+ normal: blockNormal,
1796
+ gfm: blockGfm,
1797
+ pedantic: blockPedantic
1798
+ };
1799
+ var inline = {
1800
+ normal: inlineNormal,
1801
+ gfm: inlineGfm,
1802
+ breaks: inlineBreaks,
1803
+ pedantic: inlinePedantic
1804
+ };
1805
+ var _Lexer = class __Lexer {
1806
+ tokens;
1807
+ options;
1808
+ state;
1809
+ tokenizer;
1810
+ inlineQueue;
1811
+ constructor(options2) {
1812
+ this.tokens = [];
1813
+ this.tokens.links = /* @__PURE__ */ Object.create(null);
1814
+ this.options = options2 || _defaults;
1815
+ this.options.tokenizer = this.options.tokenizer || new _Tokenizer();
1816
+ this.tokenizer = this.options.tokenizer;
1817
+ this.tokenizer.options = this.options;
1818
+ this.tokenizer.lexer = this;
1819
+ this.inlineQueue = [];
1820
+ this.state = {
1821
+ inLink: false,
1822
+ inRawBlock: false,
1823
+ top: true
1824
+ };
1825
+ const rules = {
1826
+ block: block.normal,
1827
+ inline: inline.normal
1828
+ };
1829
+ if (this.options.pedantic) {
1830
+ rules.block = block.pedantic;
1831
+ rules.inline = inline.pedantic;
1832
+ } else if (this.options.gfm) {
1833
+ rules.block = block.gfm;
1834
+ if (this.options.breaks) {
1835
+ rules.inline = inline.breaks;
1836
+ } else {
1837
+ rules.inline = inline.gfm;
1838
+ }
1839
+ }
1840
+ this.tokenizer.rules = rules;
1841
+ }
1842
+ /**
1843
+ * Expose Rules
1844
+ */
1845
+ static get rules() {
1846
+ return {
1847
+ block,
1848
+ inline
1849
+ };
1850
+ }
1851
+ /**
1852
+ * Static Lex Method
1853
+ */
1854
+ static lex(src, options2) {
1855
+ const lexer2 = new __Lexer(options2);
1856
+ return lexer2.lex(src);
1857
+ }
1858
+ /**
1859
+ * Static Lex Inline Method
1860
+ */
1861
+ static lexInline(src, options2) {
1862
+ const lexer2 = new __Lexer(options2);
1863
+ return lexer2.inlineTokens(src);
1864
+ }
1865
+ /**
1866
+ * Preprocessing
1867
+ */
1868
+ lex(src) {
1869
+ src = src.replace(/\r\n|\r/g, "\n");
1870
+ this.blockTokens(src, this.tokens);
1871
+ for (let i = 0; i < this.inlineQueue.length; i++) {
1872
+ const next = this.inlineQueue[i];
1873
+ this.inlineTokens(next.src, next.tokens);
1874
+ }
1875
+ this.inlineQueue = [];
1876
+ return this.tokens;
1877
+ }
1878
+ blockTokens(src, tokens = []) {
1879
+ if (this.options.pedantic) {
1880
+ src = src.replace(/\t/g, " ").replace(/^ +$/gm, "");
1881
+ } else {
1882
+ src = src.replace(/^( *)(\t+)/gm, (_, leading, tabs) => {
1883
+ return leading + " ".repeat(tabs.length);
1884
+ });
1885
+ }
1886
+ let token;
1887
+ let lastToken;
1888
+ let cutSrc;
1889
+ let lastParagraphClipped;
1890
+ while (src) {
1891
+ if (this.options.extensions && this.options.extensions.block && this.options.extensions.block.some((extTokenizer) => {
1892
+ if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
1893
+ src = src.substring(token.raw.length);
1894
+ tokens.push(token);
1895
+ return true;
1896
+ }
1897
+ return false;
1898
+ })) {
1899
+ continue;
1900
+ }
1901
+ if (token = this.tokenizer.space(src)) {
1902
+ src = src.substring(token.raw.length);
1903
+ if (token.raw.length === 1 && tokens.length > 0) {
1904
+ tokens[tokens.length - 1].raw += "\n";
1905
+ } else {
1906
+ tokens.push(token);
1907
+ }
1908
+ continue;
1909
+ }
1910
+ if (token = this.tokenizer.code(src)) {
1911
+ src = src.substring(token.raw.length);
1912
+ lastToken = tokens[tokens.length - 1];
1913
+ if (lastToken && (lastToken.type === "paragraph" || lastToken.type === "text")) {
1914
+ lastToken.raw += "\n" + token.raw;
1915
+ lastToken.text += "\n" + token.text;
1916
+ this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
1917
+ } else {
1918
+ tokens.push(token);
1919
+ }
1920
+ continue;
1921
+ }
1922
+ if (token = this.tokenizer.fences(src)) {
1923
+ src = src.substring(token.raw.length);
1924
+ tokens.push(token);
1925
+ continue;
1926
+ }
1927
+ if (token = this.tokenizer.heading(src)) {
1928
+ src = src.substring(token.raw.length);
1929
+ tokens.push(token);
1930
+ continue;
1931
+ }
1932
+ if (token = this.tokenizer.hr(src)) {
1933
+ src = src.substring(token.raw.length);
1934
+ tokens.push(token);
1935
+ continue;
1936
+ }
1937
+ if (token = this.tokenizer.blockquote(src)) {
1938
+ src = src.substring(token.raw.length);
1939
+ tokens.push(token);
1940
+ continue;
1941
+ }
1942
+ if (token = this.tokenizer.list(src)) {
1943
+ src = src.substring(token.raw.length);
1944
+ tokens.push(token);
1945
+ continue;
1946
+ }
1947
+ if (token = this.tokenizer.html(src)) {
1948
+ src = src.substring(token.raw.length);
1949
+ tokens.push(token);
1950
+ continue;
1951
+ }
1952
+ if (token = this.tokenizer.def(src)) {
1953
+ src = src.substring(token.raw.length);
1954
+ lastToken = tokens[tokens.length - 1];
1955
+ if (lastToken && (lastToken.type === "paragraph" || lastToken.type === "text")) {
1956
+ lastToken.raw += "\n" + token.raw;
1957
+ lastToken.text += "\n" + token.raw;
1958
+ this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
1959
+ } else if (!this.tokens.links[token.tag]) {
1960
+ this.tokens.links[token.tag] = {
1961
+ href: token.href,
1962
+ title: token.title
1963
+ };
1964
+ }
1965
+ continue;
1966
+ }
1967
+ if (token = this.tokenizer.table(src)) {
1968
+ src = src.substring(token.raw.length);
1969
+ tokens.push(token);
1970
+ continue;
1971
+ }
1972
+ if (token = this.tokenizer.lheading(src)) {
1973
+ src = src.substring(token.raw.length);
1974
+ tokens.push(token);
1975
+ continue;
1976
+ }
1977
+ cutSrc = src;
1978
+ if (this.options.extensions && this.options.extensions.startBlock) {
1979
+ let startIndex = Infinity;
1980
+ const tempSrc = src.slice(1);
1981
+ let tempStart;
1982
+ this.options.extensions.startBlock.forEach((getStartIndex) => {
1983
+ tempStart = getStartIndex.call({ lexer: this }, tempSrc);
1984
+ if (typeof tempStart === "number" && tempStart >= 0) {
1985
+ startIndex = Math.min(startIndex, tempStart);
1986
+ }
1987
+ });
1988
+ if (startIndex < Infinity && startIndex >= 0) {
1989
+ cutSrc = src.substring(0, startIndex + 1);
1990
+ }
1991
+ }
1992
+ if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) {
1993
+ lastToken = tokens[tokens.length - 1];
1994
+ if (lastParagraphClipped && lastToken.type === "paragraph") {
1995
+ lastToken.raw += "\n" + token.raw;
1996
+ lastToken.text += "\n" + token.text;
1997
+ this.inlineQueue.pop();
1998
+ this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
1999
+ } else {
2000
+ tokens.push(token);
2001
+ }
2002
+ lastParagraphClipped = cutSrc.length !== src.length;
2003
+ src = src.substring(token.raw.length);
2004
+ continue;
2005
+ }
2006
+ if (token = this.tokenizer.text(src)) {
2007
+ src = src.substring(token.raw.length);
2008
+ lastToken = tokens[tokens.length - 1];
2009
+ if (lastToken && lastToken.type === "text") {
2010
+ lastToken.raw += "\n" + token.raw;
2011
+ lastToken.text += "\n" + token.text;
2012
+ this.inlineQueue.pop();
2013
+ this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
2014
+ } else {
2015
+ tokens.push(token);
2016
+ }
2017
+ continue;
2018
+ }
2019
+ if (src) {
2020
+ const errMsg = "Infinite loop on byte: " + src.charCodeAt(0);
2021
+ if (this.options.silent) {
2022
+ console.error(errMsg);
2023
+ break;
2024
+ } else {
2025
+ throw new Error(errMsg);
2026
+ }
2027
+ }
2028
+ }
2029
+ this.state.top = true;
2030
+ return tokens;
2031
+ }
2032
+ inline(src, tokens = []) {
2033
+ this.inlineQueue.push({ src, tokens });
2034
+ return tokens;
2035
+ }
2036
+ /**
2037
+ * Lexing/Compiling
2038
+ */
2039
+ inlineTokens(src, tokens = []) {
2040
+ let token, lastToken, cutSrc;
2041
+ let maskedSrc = src;
2042
+ let match;
2043
+ let keepPrevChar, prevChar;
2044
+ if (this.tokens.links) {
2045
+ const links = Object.keys(this.tokens.links);
2046
+ if (links.length > 0) {
2047
+ while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) {
2048
+ if (links.includes(match[0].slice(match[0].lastIndexOf("[") + 1, -1))) {
2049
+ maskedSrc = maskedSrc.slice(0, match.index) + "[" + "a".repeat(match[0].length - 2) + "]" + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex);
2050
+ }
2051
+ }
2052
+ }
2053
+ }
2054
+ while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) {
2055
+ maskedSrc = maskedSrc.slice(0, match.index) + "[" + "a".repeat(match[0].length - 2) + "]" + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);
2056
+ }
2057
+ while ((match = this.tokenizer.rules.inline.anyPunctuation.exec(maskedSrc)) != null) {
2058
+ maskedSrc = maskedSrc.slice(0, match.index) + "++" + maskedSrc.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);
2059
+ }
2060
+ while (src) {
2061
+ if (!keepPrevChar) {
2062
+ prevChar = "";
2063
+ }
2064
+ keepPrevChar = false;
2065
+ if (this.options.extensions && this.options.extensions.inline && this.options.extensions.inline.some((extTokenizer) => {
2066
+ if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
2067
+ src = src.substring(token.raw.length);
2068
+ tokens.push(token);
2069
+ return true;
2070
+ }
2071
+ return false;
2072
+ })) {
2073
+ continue;
2074
+ }
2075
+ if (token = this.tokenizer.escape(src)) {
2076
+ src = src.substring(token.raw.length);
2077
+ tokens.push(token);
2078
+ continue;
2079
+ }
2080
+ if (token = this.tokenizer.tag(src)) {
2081
+ src = src.substring(token.raw.length);
2082
+ lastToken = tokens[tokens.length - 1];
2083
+ if (lastToken && token.type === "text" && lastToken.type === "text") {
2084
+ lastToken.raw += token.raw;
2085
+ lastToken.text += token.text;
2086
+ } else {
2087
+ tokens.push(token);
2088
+ }
2089
+ continue;
2090
+ }
2091
+ if (token = this.tokenizer.link(src)) {
2092
+ src = src.substring(token.raw.length);
2093
+ tokens.push(token);
2094
+ continue;
2095
+ }
2096
+ if (token = this.tokenizer.reflink(src, this.tokens.links)) {
2097
+ src = src.substring(token.raw.length);
2098
+ lastToken = tokens[tokens.length - 1];
2099
+ if (lastToken && token.type === "text" && lastToken.type === "text") {
2100
+ lastToken.raw += token.raw;
2101
+ lastToken.text += token.text;
2102
+ } else {
2103
+ tokens.push(token);
2104
+ }
2105
+ continue;
2106
+ }
2107
+ if (token = this.tokenizer.emStrong(src, maskedSrc, prevChar)) {
2108
+ src = src.substring(token.raw.length);
2109
+ tokens.push(token);
2110
+ continue;
2111
+ }
2112
+ if (token = this.tokenizer.codespan(src)) {
2113
+ src = src.substring(token.raw.length);
2114
+ tokens.push(token);
2115
+ continue;
2116
+ }
2117
+ if (token = this.tokenizer.br(src)) {
2118
+ src = src.substring(token.raw.length);
2119
+ tokens.push(token);
2120
+ continue;
2121
+ }
2122
+ if (token = this.tokenizer.del(src)) {
2123
+ src = src.substring(token.raw.length);
2124
+ tokens.push(token);
2125
+ continue;
2126
+ }
2127
+ if (token = this.tokenizer.autolink(src)) {
2128
+ src = src.substring(token.raw.length);
2129
+ tokens.push(token);
2130
+ continue;
2131
+ }
2132
+ if (!this.state.inLink && (token = this.tokenizer.url(src))) {
2133
+ src = src.substring(token.raw.length);
2134
+ tokens.push(token);
2135
+ continue;
2136
+ }
2137
+ cutSrc = src;
2138
+ if (this.options.extensions && this.options.extensions.startInline) {
2139
+ let startIndex = Infinity;
2140
+ const tempSrc = src.slice(1);
2141
+ let tempStart;
2142
+ this.options.extensions.startInline.forEach((getStartIndex) => {
2143
+ tempStart = getStartIndex.call({ lexer: this }, tempSrc);
2144
+ if (typeof tempStart === "number" && tempStart >= 0) {
2145
+ startIndex = Math.min(startIndex, tempStart);
2146
+ }
2147
+ });
2148
+ if (startIndex < Infinity && startIndex >= 0) {
2149
+ cutSrc = src.substring(0, startIndex + 1);
2150
+ }
2151
+ }
2152
+ if (token = this.tokenizer.inlineText(cutSrc)) {
2153
+ src = src.substring(token.raw.length);
2154
+ if (token.raw.slice(-1) !== "_") {
2155
+ prevChar = token.raw.slice(-1);
2156
+ }
2157
+ keepPrevChar = true;
2158
+ lastToken = tokens[tokens.length - 1];
2159
+ if (lastToken && lastToken.type === "text") {
2160
+ lastToken.raw += token.raw;
2161
+ lastToken.text += token.text;
2162
+ } else {
2163
+ tokens.push(token);
2164
+ }
2165
+ continue;
2166
+ }
2167
+ if (src) {
2168
+ const errMsg = "Infinite loop on byte: " + src.charCodeAt(0);
2169
+ if (this.options.silent) {
2170
+ console.error(errMsg);
2171
+ break;
2172
+ } else {
2173
+ throw new Error(errMsg);
2174
+ }
2175
+ }
2176
+ }
2177
+ return tokens;
2178
+ }
2179
+ };
2180
+ var _Renderer = class {
2181
+ options;
2182
+ constructor(options2) {
2183
+ this.options = options2 || _defaults;
2184
+ }
2185
+ code(code, infostring, escaped) {
2186
+ const lang = (infostring || "").match(/^\S*/)?.[0];
2187
+ code = code.replace(/\n$/, "") + "\n";
2188
+ if (!lang) {
2189
+ return "<pre><code>" + (escaped ? code : escape$1(code, true)) + "</code></pre>\n";
2190
+ }
2191
+ return '<pre><code class="language-' + escape$1(lang) + '">' + (escaped ? code : escape$1(code, true)) + "</code></pre>\n";
2192
+ }
2193
+ blockquote(quote) {
2194
+ return `<blockquote>
2195
+ ${quote}</blockquote>
2196
+ `;
2197
+ }
2198
+ html(html2, block2) {
2199
+ return html2;
2200
+ }
2201
+ heading(text, level, raw) {
2202
+ return `<h${level}>${text}</h${level}>
2203
+ `;
2204
+ }
2205
+ hr() {
2206
+ return "<hr>\n";
2207
+ }
2208
+ list(body, ordered, start) {
2209
+ const type = ordered ? "ol" : "ul";
2210
+ const startatt = ordered && start !== 1 ? ' start="' + start + '"' : "";
2211
+ return "<" + type + startatt + ">\n" + body + "</" + type + ">\n";
2212
+ }
2213
+ listitem(text, task, checked) {
2214
+ return `<li>${text}</li>
2215
+ `;
2216
+ }
2217
+ checkbox(checked) {
2218
+ return "<input " + (checked ? 'checked="" ' : "") + 'disabled="" type="checkbox">';
2219
+ }
2220
+ paragraph(text) {
2221
+ return `<p>${text}</p>
2222
+ `;
2223
+ }
2224
+ table(header, body) {
2225
+ if (body)
2226
+ body = `<tbody>${body}</tbody>`;
2227
+ return "<table>\n<thead>\n" + header + "</thead>\n" + body + "</table>\n";
2228
+ }
2229
+ tablerow(content) {
2230
+ return `<tr>
2231
+ ${content}</tr>
2232
+ `;
2233
+ }
2234
+ tablecell(content, flags) {
2235
+ const type = flags.header ? "th" : "td";
2236
+ const tag2 = flags.align ? `<${type} align="${flags.align}">` : `<${type}>`;
2237
+ return tag2 + content + `</${type}>
2238
+ `;
2239
+ }
2240
+ /**
2241
+ * span level renderer
2242
+ */
2243
+ strong(text) {
2244
+ return `<strong>${text}</strong>`;
2245
+ }
2246
+ em(text) {
2247
+ return `<em>${text}</em>`;
2248
+ }
2249
+ codespan(text) {
2250
+ return `<code>${text}</code>`;
2251
+ }
2252
+ br() {
2253
+ return "<br>";
2254
+ }
2255
+ del(text) {
2256
+ return `<del>${text}</del>`;
2257
+ }
2258
+ link(href, title, text) {
2259
+ const cleanHref = cleanUrl(href);
2260
+ if (cleanHref === null) {
2261
+ return text;
2262
+ }
2263
+ href = cleanHref;
2264
+ let out = '<a href="' + href + '"';
2265
+ if (title) {
2266
+ out += ' title="' + title + '"';
2267
+ }
2268
+ out += ">" + text + "</a>";
2269
+ return out;
2270
+ }
2271
+ image(href, title, text) {
2272
+ const cleanHref = cleanUrl(href);
2273
+ if (cleanHref === null) {
2274
+ return text;
2275
+ }
2276
+ href = cleanHref;
2277
+ let out = `<img src="${href}" alt="${text}"`;
2278
+ if (title) {
2279
+ out += ` title="${title}"`;
2280
+ }
2281
+ out += ">";
2282
+ return out;
2283
+ }
2284
+ text(text) {
2285
+ return text;
2286
+ }
2287
+ };
2288
+ var _TextRenderer = class {
2289
+ // no need for block level renderers
2290
+ strong(text) {
2291
+ return text;
2292
+ }
2293
+ em(text) {
2294
+ return text;
2295
+ }
2296
+ codespan(text) {
2297
+ return text;
2298
+ }
2299
+ del(text) {
2300
+ return text;
2301
+ }
2302
+ html(text) {
2303
+ return text;
2304
+ }
2305
+ text(text) {
2306
+ return text;
2307
+ }
2308
+ link(href, title, text) {
2309
+ return "" + text;
2310
+ }
2311
+ image(href, title, text) {
2312
+ return "" + text;
2313
+ }
2314
+ br() {
2315
+ return "";
2316
+ }
2317
+ };
2318
+ var _Parser = class __Parser {
2319
+ options;
2320
+ renderer;
2321
+ textRenderer;
2322
+ constructor(options2) {
2323
+ this.options = options2 || _defaults;
2324
+ this.options.renderer = this.options.renderer || new _Renderer();
2325
+ this.renderer = this.options.renderer;
2326
+ this.renderer.options = this.options;
2327
+ this.textRenderer = new _TextRenderer();
2328
+ }
2329
+ /**
2330
+ * Static Parse Method
2331
+ */
2332
+ static parse(tokens, options2) {
2333
+ const parser2 = new __Parser(options2);
2334
+ return parser2.parse(tokens);
2335
+ }
2336
+ /**
2337
+ * Static Parse Inline Method
2338
+ */
2339
+ static parseInline(tokens, options2) {
2340
+ const parser2 = new __Parser(options2);
2341
+ return parser2.parseInline(tokens);
2342
+ }
2343
+ /**
2344
+ * Parse Loop
2345
+ */
2346
+ parse(tokens, top = true) {
2347
+ let out = "";
2348
+ for (let i = 0; i < tokens.length; i++) {
2349
+ const token = tokens[i];
2350
+ if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
2351
+ const genericToken = token;
2352
+ const ret = this.options.extensions.renderers[genericToken.type].call({ parser: this }, genericToken);
2353
+ if (ret !== false || !["space", "hr", "heading", "code", "table", "blockquote", "list", "html", "paragraph", "text"].includes(genericToken.type)) {
2354
+ out += ret || "";
2355
+ continue;
2356
+ }
2357
+ }
2358
+ switch (token.type) {
2359
+ case "space": {
2360
+ continue;
2361
+ }
2362
+ case "hr": {
2363
+ out += this.renderer.hr();
2364
+ continue;
2365
+ }
2366
+ case "heading": {
2367
+ const headingToken = token;
2368
+ out += this.renderer.heading(this.parseInline(headingToken.tokens), headingToken.depth, unescape(this.parseInline(headingToken.tokens, this.textRenderer)));
2369
+ continue;
2370
+ }
2371
+ case "code": {
2372
+ const codeToken = token;
2373
+ out += this.renderer.code(codeToken.text, codeToken.lang, !!codeToken.escaped);
2374
+ continue;
2375
+ }
2376
+ case "table": {
2377
+ const tableToken = token;
2378
+ let header = "";
2379
+ let cell = "";
2380
+ for (let j = 0; j < tableToken.header.length; j++) {
2381
+ cell += this.renderer.tablecell(this.parseInline(tableToken.header[j].tokens), { header: true, align: tableToken.align[j] });
2382
+ }
2383
+ header += this.renderer.tablerow(cell);
2384
+ let body = "";
2385
+ for (let j = 0; j < tableToken.rows.length; j++) {
2386
+ const row = tableToken.rows[j];
2387
+ cell = "";
2388
+ for (let k = 0; k < row.length; k++) {
2389
+ cell += this.renderer.tablecell(this.parseInline(row[k].tokens), { header: false, align: tableToken.align[k] });
2390
+ }
2391
+ body += this.renderer.tablerow(cell);
2392
+ }
2393
+ out += this.renderer.table(header, body);
2394
+ continue;
2395
+ }
2396
+ case "blockquote": {
2397
+ const blockquoteToken = token;
2398
+ const body = this.parse(blockquoteToken.tokens);
2399
+ out += this.renderer.blockquote(body);
2400
+ continue;
2401
+ }
2402
+ case "list": {
2403
+ const listToken = token;
2404
+ const ordered = listToken.ordered;
2405
+ const start = listToken.start;
2406
+ const loose = listToken.loose;
2407
+ let body = "";
2408
+ for (let j = 0; j < listToken.items.length; j++) {
2409
+ const item = listToken.items[j];
2410
+ const checked = item.checked;
2411
+ const task = item.task;
2412
+ let itemBody = "";
2413
+ if (item.task) {
2414
+ const checkbox = this.renderer.checkbox(!!checked);
2415
+ if (loose) {
2416
+ if (item.tokens.length > 0 && item.tokens[0].type === "paragraph") {
2417
+ item.tokens[0].text = checkbox + " " + item.tokens[0].text;
2418
+ if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === "text") {
2419
+ item.tokens[0].tokens[0].text = checkbox + " " + item.tokens[0].tokens[0].text;
2420
+ }
2421
+ } else {
2422
+ item.tokens.unshift({
2423
+ type: "text",
2424
+ text: checkbox + " "
2425
+ });
2426
+ }
2427
+ } else {
2428
+ itemBody += checkbox + " ";
2429
+ }
2430
+ }
2431
+ itemBody += this.parse(item.tokens, loose);
2432
+ body += this.renderer.listitem(itemBody, task, !!checked);
2433
+ }
2434
+ out += this.renderer.list(body, ordered, start);
2435
+ continue;
2436
+ }
2437
+ case "html": {
2438
+ const htmlToken = token;
2439
+ out += this.renderer.html(htmlToken.text, htmlToken.block);
2440
+ continue;
2441
+ }
2442
+ case "paragraph": {
2443
+ const paragraphToken = token;
2444
+ out += this.renderer.paragraph(this.parseInline(paragraphToken.tokens));
2445
+ continue;
2446
+ }
2447
+ case "text": {
2448
+ let textToken = token;
2449
+ let body = textToken.tokens ? this.parseInline(textToken.tokens) : textToken.text;
2450
+ while (i + 1 < tokens.length && tokens[i + 1].type === "text") {
2451
+ textToken = tokens[++i];
2452
+ body += "\n" + (textToken.tokens ? this.parseInline(textToken.tokens) : textToken.text);
2453
+ }
2454
+ out += top ? this.renderer.paragraph(body) : body;
2455
+ continue;
2456
+ }
2457
+ default: {
2458
+ const errMsg = 'Token with "' + token.type + '" type was not found.';
2459
+ if (this.options.silent) {
2460
+ console.error(errMsg);
2461
+ return "";
2462
+ } else {
2463
+ throw new Error(errMsg);
2464
+ }
2465
+ }
2466
+ }
2467
+ }
2468
+ return out;
2469
+ }
2470
+ /**
2471
+ * Parse Inline Tokens
2472
+ */
2473
+ parseInline(tokens, renderer) {
2474
+ renderer = renderer || this.renderer;
2475
+ let out = "";
2476
+ for (let i = 0; i < tokens.length; i++) {
2477
+ const token = tokens[i];
2478
+ if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
2479
+ const ret = this.options.extensions.renderers[token.type].call({ parser: this }, token);
2480
+ if (ret !== false || !["escape", "html", "link", "image", "strong", "em", "codespan", "br", "del", "text"].includes(token.type)) {
2481
+ out += ret || "";
2482
+ continue;
2483
+ }
2484
+ }
2485
+ switch (token.type) {
2486
+ case "escape": {
2487
+ const escapeToken = token;
2488
+ out += renderer.text(escapeToken.text);
2489
+ break;
2490
+ }
2491
+ case "html": {
2492
+ const tagToken = token;
2493
+ out += renderer.html(tagToken.text);
2494
+ break;
2495
+ }
2496
+ case "link": {
2497
+ const linkToken = token;
2498
+ out += renderer.link(linkToken.href, linkToken.title, this.parseInline(linkToken.tokens, renderer));
2499
+ break;
2500
+ }
2501
+ case "image": {
2502
+ const imageToken = token;
2503
+ out += renderer.image(imageToken.href, imageToken.title, imageToken.text);
2504
+ break;
2505
+ }
2506
+ case "strong": {
2507
+ const strongToken = token;
2508
+ out += renderer.strong(this.parseInline(strongToken.tokens, renderer));
2509
+ break;
2510
+ }
2511
+ case "em": {
2512
+ const emToken = token;
2513
+ out += renderer.em(this.parseInline(emToken.tokens, renderer));
2514
+ break;
2515
+ }
2516
+ case "codespan": {
2517
+ const codespanToken = token;
2518
+ out += renderer.codespan(codespanToken.text);
2519
+ break;
2520
+ }
2521
+ case "br": {
2522
+ out += renderer.br();
2523
+ break;
2524
+ }
2525
+ case "del": {
2526
+ const delToken = token;
2527
+ out += renderer.del(this.parseInline(delToken.tokens, renderer));
2528
+ break;
2529
+ }
2530
+ case "text": {
2531
+ const textToken = token;
2532
+ out += renderer.text(textToken.text);
2533
+ break;
2534
+ }
2535
+ default: {
2536
+ const errMsg = 'Token with "' + token.type + '" type was not found.';
2537
+ if (this.options.silent) {
2538
+ console.error(errMsg);
2539
+ return "";
2540
+ } else {
2541
+ throw new Error(errMsg);
2542
+ }
2543
+ }
2544
+ }
2545
+ }
2546
+ return out;
2547
+ }
2548
+ };
2549
+ var _Hooks = class {
2550
+ options;
2551
+ constructor(options2) {
2552
+ this.options = options2 || _defaults;
2553
+ }
2554
+ static passThroughHooks = /* @__PURE__ */ new Set([
2555
+ "preprocess",
2556
+ "postprocess",
2557
+ "processAllTokens"
2558
+ ]);
2559
+ /**
2560
+ * Process markdown before marked
2561
+ */
2562
+ preprocess(markdown) {
2563
+ return markdown;
2564
+ }
2565
+ /**
2566
+ * Process HTML after marked is finished
2567
+ */
2568
+ postprocess(html2) {
2569
+ return html2;
2570
+ }
2571
+ /**
2572
+ * Process all tokens before walk tokens
2573
+ */
2574
+ processAllTokens(tokens) {
2575
+ return tokens;
2576
+ }
2577
+ };
2578
+ var Marked = class {
2579
+ defaults = _getDefaults();
2580
+ options = this.setOptions;
2581
+ parse = this.#parseMarkdown(_Lexer.lex, _Parser.parse);
2582
+ parseInline = this.#parseMarkdown(_Lexer.lexInline, _Parser.parseInline);
2583
+ Parser = _Parser;
2584
+ Renderer = _Renderer;
2585
+ TextRenderer = _TextRenderer;
2586
+ Lexer = _Lexer;
2587
+ Tokenizer = _Tokenizer;
2588
+ Hooks = _Hooks;
2589
+ constructor(...args) {
2590
+ this.use(...args);
2591
+ }
2592
+ /**
2593
+ * Run callback for every token
2594
+ */
2595
+ walkTokens(tokens, callback) {
2596
+ let values = [];
2597
+ for (const token of tokens) {
2598
+ values = values.concat(callback.call(this, token));
2599
+ switch (token.type) {
2600
+ case "table": {
2601
+ const tableToken = token;
2602
+ for (const cell of tableToken.header) {
2603
+ values = values.concat(this.walkTokens(cell.tokens, callback));
2604
+ }
2605
+ for (const row of tableToken.rows) {
2606
+ for (const cell of row) {
2607
+ values = values.concat(this.walkTokens(cell.tokens, callback));
2608
+ }
2609
+ }
2610
+ break;
2611
+ }
2612
+ case "list": {
2613
+ const listToken = token;
2614
+ values = values.concat(this.walkTokens(listToken.items, callback));
2615
+ break;
2616
+ }
2617
+ default: {
2618
+ const genericToken = token;
2619
+ if (this.defaults.extensions?.childTokens?.[genericToken.type]) {
2620
+ this.defaults.extensions.childTokens[genericToken.type].forEach((childTokens) => {
2621
+ const tokens2 = genericToken[childTokens].flat(Infinity);
2622
+ values = values.concat(this.walkTokens(tokens2, callback));
2623
+ });
2624
+ } else if (genericToken.tokens) {
2625
+ values = values.concat(this.walkTokens(genericToken.tokens, callback));
2626
+ }
2627
+ }
2628
+ }
2629
+ }
2630
+ return values;
2631
+ }
2632
+ use(...args) {
2633
+ const extensions = this.defaults.extensions || { renderers: {}, childTokens: {} };
2634
+ args.forEach((pack) => {
2635
+ const opts = { ...pack };
2636
+ opts.async = this.defaults.async || opts.async || false;
2637
+ if (pack.extensions) {
2638
+ pack.extensions.forEach((ext) => {
2639
+ if (!ext.name) {
2640
+ throw new Error("extension name required");
2641
+ }
2642
+ if ("renderer" in ext) {
2643
+ const prevRenderer = extensions.renderers[ext.name];
2644
+ if (prevRenderer) {
2645
+ extensions.renderers[ext.name] = function(...args2) {
2646
+ let ret = ext.renderer.apply(this, args2);
2647
+ if (ret === false) {
2648
+ ret = prevRenderer.apply(this, args2);
2649
+ }
2650
+ return ret;
2651
+ };
2652
+ } else {
2653
+ extensions.renderers[ext.name] = ext.renderer;
2654
+ }
2655
+ }
2656
+ if ("tokenizer" in ext) {
2657
+ if (!ext.level || ext.level !== "block" && ext.level !== "inline") {
2658
+ throw new Error("extension level must be 'block' or 'inline'");
2659
+ }
2660
+ const extLevel = extensions[ext.level];
2661
+ if (extLevel) {
2662
+ extLevel.unshift(ext.tokenizer);
2663
+ } else {
2664
+ extensions[ext.level] = [ext.tokenizer];
2665
+ }
2666
+ if (ext.start) {
2667
+ if (ext.level === "block") {
2668
+ if (extensions.startBlock) {
2669
+ extensions.startBlock.push(ext.start);
2670
+ } else {
2671
+ extensions.startBlock = [ext.start];
2672
+ }
2673
+ } else if (ext.level === "inline") {
2674
+ if (extensions.startInline) {
2675
+ extensions.startInline.push(ext.start);
2676
+ } else {
2677
+ extensions.startInline = [ext.start];
2678
+ }
2679
+ }
2680
+ }
2681
+ }
2682
+ if ("childTokens" in ext && ext.childTokens) {
2683
+ extensions.childTokens[ext.name] = ext.childTokens;
2684
+ }
2685
+ });
2686
+ opts.extensions = extensions;
2687
+ }
2688
+ if (pack.renderer) {
2689
+ const renderer = this.defaults.renderer || new _Renderer(this.defaults);
2690
+ for (const prop in pack.renderer) {
2691
+ if (!(prop in renderer)) {
2692
+ throw new Error(`renderer '${prop}' does not exist`);
2693
+ }
2694
+ if (prop === "options") {
2695
+ continue;
2696
+ }
2697
+ const rendererProp = prop;
2698
+ const rendererFunc = pack.renderer[rendererProp];
2699
+ const prevRenderer = renderer[rendererProp];
2700
+ renderer[rendererProp] = (...args2) => {
2701
+ let ret = rendererFunc.apply(renderer, args2);
2702
+ if (ret === false) {
2703
+ ret = prevRenderer.apply(renderer, args2);
2704
+ }
2705
+ return ret || "";
2706
+ };
2707
+ }
2708
+ opts.renderer = renderer;
2709
+ }
2710
+ if (pack.tokenizer) {
2711
+ const tokenizer = this.defaults.tokenizer || new _Tokenizer(this.defaults);
2712
+ for (const prop in pack.tokenizer) {
2713
+ if (!(prop in tokenizer)) {
2714
+ throw new Error(`tokenizer '${prop}' does not exist`);
2715
+ }
2716
+ if (["options", "rules", "lexer"].includes(prop)) {
2717
+ continue;
2718
+ }
2719
+ const tokenizerProp = prop;
2720
+ const tokenizerFunc = pack.tokenizer[tokenizerProp];
2721
+ const prevTokenizer = tokenizer[tokenizerProp];
2722
+ tokenizer[tokenizerProp] = (...args2) => {
2723
+ let ret = tokenizerFunc.apply(tokenizer, args2);
2724
+ if (ret === false) {
2725
+ ret = prevTokenizer.apply(tokenizer, args2);
2726
+ }
2727
+ return ret;
2728
+ };
2729
+ }
2730
+ opts.tokenizer = tokenizer;
2731
+ }
2732
+ if (pack.hooks) {
2733
+ const hooks = this.defaults.hooks || new _Hooks();
2734
+ for (const prop in pack.hooks) {
2735
+ if (!(prop in hooks)) {
2736
+ throw new Error(`hook '${prop}' does not exist`);
2737
+ }
2738
+ if (prop === "options") {
2739
+ continue;
2740
+ }
2741
+ const hooksProp = prop;
2742
+ const hooksFunc = pack.hooks[hooksProp];
2743
+ const prevHook = hooks[hooksProp];
2744
+ if (_Hooks.passThroughHooks.has(prop)) {
2745
+ hooks[hooksProp] = (arg) => {
2746
+ if (this.defaults.async) {
2747
+ return Promise.resolve(hooksFunc.call(hooks, arg)).then((ret2) => {
2748
+ return prevHook.call(hooks, ret2);
2749
+ });
2750
+ }
2751
+ const ret = hooksFunc.call(hooks, arg);
2752
+ return prevHook.call(hooks, ret);
2753
+ };
2754
+ } else {
2755
+ hooks[hooksProp] = (...args2) => {
2756
+ let ret = hooksFunc.apply(hooks, args2);
2757
+ if (ret === false) {
2758
+ ret = prevHook.apply(hooks, args2);
2759
+ }
2760
+ return ret;
2761
+ };
2762
+ }
2763
+ }
2764
+ opts.hooks = hooks;
2765
+ }
2766
+ if (pack.walkTokens) {
2767
+ const walkTokens2 = this.defaults.walkTokens;
2768
+ const packWalktokens = pack.walkTokens;
2769
+ opts.walkTokens = function(token) {
2770
+ let values = [];
2771
+ values.push(packWalktokens.call(this, token));
2772
+ if (walkTokens2) {
2773
+ values = values.concat(walkTokens2.call(this, token));
2774
+ }
2775
+ return values;
2776
+ };
2777
+ }
2778
+ this.defaults = { ...this.defaults, ...opts };
2779
+ });
2780
+ return this;
2781
+ }
2782
+ setOptions(opt) {
2783
+ this.defaults = { ...this.defaults, ...opt };
2784
+ return this;
2785
+ }
2786
+ lexer(src, options2) {
2787
+ return _Lexer.lex(src, options2 ?? this.defaults);
2788
+ }
2789
+ parser(tokens, options2) {
2790
+ return _Parser.parse(tokens, options2 ?? this.defaults);
2791
+ }
2792
+ #parseMarkdown(lexer2, parser2) {
2793
+ return (src, options2) => {
2794
+ const origOpt = { ...options2 };
2795
+ const opt = { ...this.defaults, ...origOpt };
2796
+ if (this.defaults.async === true && origOpt.async === false) {
2797
+ if (!opt.silent) {
2798
+ console.warn("marked(): The async option was set to true by an extension. The async: false option sent to parse will be ignored.");
2799
+ }
2800
+ opt.async = true;
2801
+ }
2802
+ const throwError = this.#onError(!!opt.silent, !!opt.async);
2803
+ if (typeof src === "undefined" || src === null) {
2804
+ return throwError(new Error("marked(): input parameter is undefined or null"));
2805
+ }
2806
+ if (typeof src !== "string") {
2807
+ return throwError(new Error("marked(): input parameter is of type " + Object.prototype.toString.call(src) + ", string expected"));
2808
+ }
2809
+ if (opt.hooks) {
2810
+ opt.hooks.options = opt;
2811
+ }
2812
+ if (opt.async) {
2813
+ return Promise.resolve(opt.hooks ? opt.hooks.preprocess(src) : src).then((src2) => lexer2(src2, opt)).then((tokens) => opt.hooks ? opt.hooks.processAllTokens(tokens) : tokens).then((tokens) => opt.walkTokens ? Promise.all(this.walkTokens(tokens, opt.walkTokens)).then(() => tokens) : tokens).then((tokens) => parser2(tokens, opt)).then((html2) => opt.hooks ? opt.hooks.postprocess(html2) : html2).catch(throwError);
2814
+ }
2815
+ try {
2816
+ if (opt.hooks) {
2817
+ src = opt.hooks.preprocess(src);
2818
+ }
2819
+ let tokens = lexer2(src, opt);
2820
+ if (opt.hooks) {
2821
+ tokens = opt.hooks.processAllTokens(tokens);
2822
+ }
2823
+ if (opt.walkTokens) {
2824
+ this.walkTokens(tokens, opt.walkTokens);
2825
+ }
2826
+ let html2 = parser2(tokens, opt);
2827
+ if (opt.hooks) {
2828
+ html2 = opt.hooks.postprocess(html2);
2829
+ }
2830
+ return html2;
2831
+ } catch (e) {
2832
+ return throwError(e);
2833
+ }
2834
+ };
2835
+ }
2836
+ #onError(silent, async) {
2837
+ return (e) => {
2838
+ e.message += "\nPlease report this to https://github.com/markedjs/marked.";
2839
+ if (silent) {
2840
+ const msg = "<p>An error occurred:</p><pre>" + escape$1(e.message + "", true) + "</pre>";
2841
+ if (async) {
2842
+ return Promise.resolve(msg);
2843
+ }
2844
+ return msg;
2845
+ }
2846
+ if (async) {
2847
+ return Promise.reject(e);
2848
+ }
2849
+ throw e;
2850
+ };
2851
+ }
2852
+ };
2853
+ var markedInstance = new Marked();
2854
+ function marked(src, opt) {
2855
+ return markedInstance.parse(src, opt);
2856
+ }
2857
+ marked.options = marked.setOptions = function(options2) {
2858
+ markedInstance.setOptions(options2);
2859
+ marked.defaults = markedInstance.defaults;
2860
+ changeDefaults(marked.defaults);
2861
+ return marked;
2862
+ };
2863
+ marked.getDefaults = _getDefaults;
2864
+ marked.defaults = _defaults;
2865
+ marked.use = function(...args) {
2866
+ markedInstance.use(...args);
2867
+ marked.defaults = markedInstance.defaults;
2868
+ changeDefaults(marked.defaults);
2869
+ return marked;
2870
+ };
2871
+ marked.walkTokens = function(tokens, callback) {
2872
+ return markedInstance.walkTokens(tokens, callback);
2873
+ };
2874
+ marked.parseInline = markedInstance.parseInline;
2875
+ marked.Parser = _Parser;
2876
+ marked.parser = _Parser.parse;
2877
+ marked.Renderer = _Renderer;
2878
+ marked.TextRenderer = _TextRenderer;
2879
+ marked.Lexer = _Lexer;
2880
+ marked.lexer = _Lexer.lex;
2881
+ marked.Tokenizer = _Tokenizer;
2882
+ marked.Hooks = _Hooks;
2883
+ marked.parse = marked;
2884
+ var options = marked.options;
2885
+ var setOptions = marked.setOptions;
2886
+ var use = marked.use;
2887
+ var walkTokens = marked.walkTokens;
2888
+ var parseInline = marked.parseInline;
2889
+ var parser = _Parser.parse;
2890
+ var lexer = _Lexer.lex;
2891
+
2892
+ // app/assets/javascripts/llm_meta_widget/config.js
2893
+ var DEFAULTS = Object.freeze({
2894
+ API_KEY_UUID: "ollama-local",
2895
+ ACTIONS_SCHEMA_ID: "ai-actions",
2896
+ STATE_GLOBAL: "aiState",
2897
+ ACTIONS_GLOBAL: "aiActions",
2898
+ REMOTE_TOOLS_SCHEMA_ID: "remote-mcp-tools",
2899
+ LLM_PROVIDER: "llm_meta_server",
2900
+ MAX_ROUNDS: 3
2901
+ });
2902
+ var splitList = (v) => v.split(",").map((s) => s.trim()).filter(Boolean);
2903
+ function readConfig(el) {
2904
+ const raw = (n) => el && el.hasAttribute(n) ? el.getAttribute(n) : null;
2905
+ const str = (n, d) => {
2906
+ const v = raw(n);
2907
+ return v === null ? d : v;
2908
+ };
2909
+ const bool = (n, d) => {
2910
+ const v = raw(n);
2911
+ return v === null ? d : v !== "false";
2912
+ };
2913
+ const int = (n, d) => {
2914
+ const v = raw(n);
2915
+ if (v === null) return d;
2916
+ const i = parseInt(v, 10);
2917
+ return Number.isNaN(i) ? d : i;
2918
+ };
2919
+ const list2 = (n) => {
2920
+ const v = raw(n);
2921
+ return v === null || v.trim() === "" ? null : splitList(v);
2922
+ };
2923
+ const toolHub = str("tool-hub-url", null) || null;
2924
+ const wellKnown = raw("well-known-urls");
2925
+ return {
2926
+ LLM_BASE: str("llm-url", null),
2927
+ TOOL_HUB_BASE: toolHub,
2928
+ API_KEY_UUID: str("api-key-uuid", DEFAULTS.API_KEY_UUID),
2929
+ MODEL: str("model", null),
2930
+ ACTIONS_SCHEMA_ID: str("actions-schema-id", DEFAULTS.ACTIONS_SCHEMA_ID),
2931
+ GREETING: str("greeting", null),
2932
+ STATE_GLOBAL: str("state-global", DEFAULTS.STATE_GLOBAL),
2933
+ ACTIONS_GLOBAL: str("actions-global", DEFAULTS.ACTIONS_GLOBAL),
2934
+ REMOTE_TOOLS_SCHEMA_ID: str("remote-tools-schema-id", DEFAULTS.REMOTE_TOOLS_SCHEMA_ID),
2935
+ MAX_ROUNDS: int("max-rounds", DEFAULTS.MAX_ROUNDS),
2936
+ WELL_KNOWN_URLS: wellKnown === null ? null : wellKnown.trim() === "" ? [] : splitList(wellKnown),
2937
+ LLM_PROVIDER: str("llm-provider", DEFAULTS.LLM_PROVIDER),
2938
+ ENABLE_MODEL_PICKER: bool("enable-model-picker", true),
2939
+ // Class 1 tools are registered on a hub, so the picker needs one — which is
2940
+ // independent of who answers the chat. Page actions and the host's own
2941
+ // .well-known MCP work regardless.
2942
+ ENABLE_TOOL_PICKER: bool("enable-tool-picker", true) && toolHub !== null,
2943
+ MODEL_ALLOWLIST: list2("models"),
2944
+ HUB_TOOLS_ALLOWLIST: list2("hub-tools")
2945
+ };
2946
+ }
2947
+
2948
+ // app/assets/stylesheets/llm_meta_widget/conversation.css
2949
+ var conversation_default = `/*
2950
+ * Canonical "chat conversation" styles for the llm_meta ecosystem.
2951
+ *
2952
+ * Owned by the llm_meta_widget gem. Consumed by:
2953
+ * - the widget partial itself (llm_meta_widget/_chat_panel.html.erb)
2954
+ * - any llm_meta_client-scaffolded chat host that adopts the same
2955
+ * conversation surface
2956
+ *
2957
+ * Scoped by the \`.llm-meta-conversation\` opt-in class on a wrapping
2958
+ * element \u2014 so these rules only affect elements the consumer has
2959
+ * declared as chat-conversation surfaces. Prevents accidental style
2960
+ * bleed onto random host-page elements named .message or .message-role.
2961
+ *
2962
+ * Any host that wants the styles applied wraps its conversation
2963
+ * markup in <div class="llm-meta-conversation"> ... </div> and includes
2964
+ * this file (served via /llm_meta_widget_assets/conversation.css).
2965
+ */
2966
+
2967
+ .llm-meta-conversation .message {
2968
+ position: relative;
2969
+ margin-bottom: 6px;
2970
+ padding: 6px 10px;
2971
+ border-radius: 8px;
2972
+ max-width: 85%;
2973
+ line-height: 1.4;
2974
+ word-wrap: break-word;
2975
+ }
2976
+ .llm-meta-conversation .message.user {
2977
+ background-color: #dbeafe;
2978
+ border: 1px solid #bfdbfe;
2979
+ margin-left: auto;
2980
+ }
2981
+ .llm-meta-conversation .message.assistant {
2982
+ background-color: #ffffff;
2983
+ border: 1px solid #e5e7eb;
2984
+ margin-right: auto;
2985
+ }
2986
+ .llm-meta-conversation .message.system {
2987
+ background-color: #fef3c7;
2988
+ border: 1px solid #fde68a;
2989
+ color: #78350f;
2990
+ font-size: 0.9em;
2991
+ font-style: italic;
2992
+ }
2993
+ .llm-meta-conversation .message.error {
2994
+ background-color: #fee2e2;
2995
+ border: 1px solid #fecaca;
2996
+ color: #991b1b;
2997
+ }
2998
+
2999
+ .llm-meta-conversation .message-role {
3000
+ display: block;
3001
+ font-weight: 600;
3002
+ font-size: 13px;
3003
+ color: #374151;
3004
+ margin-bottom: 4px;
3005
+ }
3006
+ .llm-meta-conversation .message-content {
3007
+ line-height: 1.4;
3008
+ word-wrap: break-word;
3009
+ overflow-wrap: anywhere;
3010
+ min-width: 0;
3011
+ color: #1f2937;
3012
+ }
3013
+
3014
+ /* Markdown-rendered content styling \u2014 matches llm_meta_chat's
3015
+ * .message-content descendants in chats.css so LLM output looks the
3016
+ * same wherever it renders (widget's streaming path uses marked.esm.js;
3017
+ * chat host's saved-message path uses Redcarpet server-side; both
3018
+ * produce standard HTML that these rules style). */
3019
+ .llm-meta-conversation .message-content p { margin: 0 0 0.5em 0; }
3020
+ .llm-meta-conversation .message-content p:last-child { margin-bottom: 0; }
3021
+ .llm-meta-conversation .message-content h1,
3022
+ .llm-meta-conversation .message-content h2,
3023
+ .llm-meta-conversation .message-content h3,
3024
+ .llm-meta-conversation .message-content h4,
3025
+ .llm-meta-conversation .message-content h5,
3026
+ .llm-meta-conversation .message-content h6 {
3027
+ margin: 0.5em 0 0.25em 0;
3028
+ line-height: 1.25;
3029
+ }
3030
+ .llm-meta-conversation .message-content ul,
3031
+ .llm-meta-conversation .message-content ol {
3032
+ margin: 0.25em 0 0.5em 1.25em;
3033
+ padding: 0;
3034
+ }
3035
+ .llm-meta-conversation .message-content li { margin: 0.1em 0; }
3036
+ .llm-meta-conversation .message-content blockquote {
3037
+ border-left: 3px solid #d1d5db;
3038
+ margin: 0.5em 0;
3039
+ padding: 0.1em 0.75em;
3040
+ color: #4b5563;
3041
+ }
3042
+ .llm-meta-conversation .message-content code {
3043
+ background-color: #f3f4f6;
3044
+ padding: 1px 4px;
3045
+ border-radius: 4px;
3046
+ font-size: 0.9em;
3047
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
3048
+ }
3049
+ .llm-meta-conversation .message-content pre {
3050
+ background-color: #f3f4f6;
3051
+ border: 1px solid #e5e7eb;
3052
+ border-radius: 6px;
3053
+ padding: 8px 10px;
3054
+ margin: 0.5em 0;
3055
+ overflow-x: auto;
3056
+ max-width: 100%;
3057
+ white-space: pre;
3058
+ }
3059
+ .llm-meta-conversation .message-content pre code { background: none; padding: 0; }
3060
+ .llm-meta-conversation .message-content table {
3061
+ border-collapse: collapse;
3062
+ margin: 0.5em 0;
3063
+ display: block;
3064
+ overflow-x: auto;
3065
+ max-width: 100%;
3066
+ }
3067
+ .llm-meta-conversation .message-content th,
3068
+ .llm-meta-conversation .message-content td {
3069
+ border: 1px solid #d1d5db;
3070
+ padding: 4px 8px;
3071
+ }
3072
+ .llm-meta-conversation .message-content th { background-color: #f3f4f6; }
3073
+ .llm-meta-conversation .message-content a {
3074
+ color: #2563eb;
3075
+ text-decoration: underline;
3076
+ }
3077
+ .llm-meta-conversation .message-content img { max-width: 100%; height: auto; }
3078
+
3079
+ /* Tool-use footer inside an assistant bubble \u2014 compact chips naming the
3080
+ * tools the LLM invoked this turn. Gives users visual confirmation of
3081
+ * what actually happened, particularly when the LLM's own text is thin. */
3082
+ .llm-meta-conversation .lmw-tool-chips {
3083
+ margin-top: 0.5em;
3084
+ padding-top: 0.5em;
3085
+ border-top: 1px dashed #e5e7eb;
3086
+ display: flex; flex-wrap: wrap; gap: 4px;
3087
+ }
3088
+ .llm-meta-conversation .lmw-tool-chip {
3089
+ display: inline-block;
3090
+ padding: 2px 8px;
3091
+ background: #eef2ff;
3092
+ color: #3730a3;
3093
+ border: 1px solid #c7d2fe;
3094
+ border-radius: 10px;
3095
+ font-size: 0.8em;
3096
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
3097
+ line-height: 1.4;
3098
+ }
3099
+ .llm-meta-conversation .lmw-tool-chip.error {
3100
+ background: #fee2e2;
3101
+ color: #991b1b;
3102
+ border-color: #fecaca;
3103
+ }
3104
+
3105
+ /* Thinking-token collapsible block \u2014 same shape as llm_meta_chat's
3106
+ * .message-thinking. Streams during reasoning, collapses to a
3107
+ * "(finished)" summary once text starts flowing. */
3108
+ .llm-meta-conversation .message-thinking {
3109
+ margin: 0.5em 0;
3110
+ padding: 0.5em 0.75em;
3111
+ background: #f3f4f6;
3112
+ border-left: 3px solid #9ca3af;
3113
+ border-radius: 4px;
3114
+ font-size: 0.85em;
3115
+ }
3116
+ .llm-meta-conversation .message-thinking summary {
3117
+ cursor: pointer;
3118
+ color: #6b7280;
3119
+ font-weight: 500;
3120
+ user-select: none;
3121
+ }
3122
+ .llm-meta-conversation .message-thinking-content {
3123
+ margin-top: 0.5em;
3124
+ color: #4b5563;
3125
+ white-space: pre-wrap;
3126
+ font-family: ui-monospace, "SF Mono", monospace;
3127
+ font-size: 0.9em;
3128
+ line-height: 1.5;
3129
+ max-height: 10em;
3130
+ overflow-y: auto;
3131
+ }
3132
+
3133
+ /* ---- "working" signals, ported from llm_meta_chat's chats.css ------------
3134
+ *
3135
+ * The wait is often long on local models \u2014 a tool round re-processes the
3136
+ * whole prompt plus the tool results before the first token \u2014 so a still
3137
+ * label reads as a hang. Same class names as the chat app, so the two
3138
+ * surfaces behave identically. */
3139
+ .message-role .role-spinner {
3140
+ display: inline-block;
3141
+ animation: role-working-spin 1.8s linear infinite;
3142
+ transform-origin: 50% 50%;
3143
+ }
3144
+
3145
+ @keyframes role-working-spin {
3146
+ from { transform: rotate(0deg); }
3147
+ to { transform: rotate(360deg); }
3148
+ }
3149
+
3150
+ /* Staggered three-dot indicator on the reasoning summary while thinking is
3151
+ * still streaming. Hidden unless .thinking-active is present. */
3152
+ .thinking-dots {
3153
+ display: none;
3154
+ margin-left: 0.35em;
3155
+ letter-spacing: 0.15em;
3156
+ font-weight: bold;
3157
+ }
3158
+
3159
+ .message-thinking.thinking-active .thinking-dots { display: inline-block; }
3160
+
3161
+ .message-thinking.thinking-active .thinking-dots span {
3162
+ display: inline-block;
3163
+ opacity: 0.25;
3164
+ animation: thinking-dot 1.4s ease-in-out infinite;
3165
+ }
3166
+
3167
+ .message-thinking.thinking-active .thinking-dots span:nth-child(2) { animation-delay: 0.2s; }
3168
+ .message-thinking.thinking-active .thinking-dots span:nth-child(3) { animation-delay: 0.4s; }
3169
+
3170
+ @keyframes thinking-dot {
3171
+ 0%, 60%, 100% { opacity: 0.25; }
3172
+ 30% { opacity: 1; }
3173
+ }
3174
+
3175
+ /* Respect a reader's motion preference: keep the label, drop the movement. */
3176
+ @media (prefers-reduced-motion: reduce) {
3177
+ .message-role .role-spinner,
3178
+ .message-thinking.thinking-active .thinking-dots span { animation: none; }
3179
+ }
3180
+ `;
3181
+
3182
+ // app/assets/stylesheets/llm_meta_widget/panel.css
3183
+ var panel_default = `/* The chat panel's own styles. Extracted from _chat_panel.html.erb when the
3184
+ panel became a custom element; bundled into the element so a non-Rails host
3185
+ serves no stylesheet of its own. Conversation-surface styles (bubbles, role
3186
+ labels, thinking blocks) stay in conversation.css. */
3187
+ /* Widget-specific chrome \u2014 floating container, header, scroll region,
3188
+ * input area. Everything BUBBLE / THINKING / ROLE-related lives in the
3189
+ * shared conversation.css above. */
3190
+ #llm-meta-widget-chat {
3191
+ /* Positioned via JS on open (see setOpen()) so top/left are set in
3192
+ * pixels \u2014 required for CSS \`resize: both\` to grow correctly
3193
+ * (bottom/right anchoring blocks the resize handle from being
3194
+ * dragged outward from viewport corner). */
3195
+ position: fixed;
3196
+ width: 40em; height: 55vh;
3197
+ min-width: 24em; min-height: 16em;
3198
+ max-width: calc(100vw - 2em); max-height: calc(100vh - 2em);
3199
+ background: #ffffff;
3200
+ border: 1px solid #e5e7eb;
3201
+ border-radius: 8px;
3202
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
3203
+ padding: 10px;
3204
+ z-index: 9999;
3205
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
3206
+ font-size: 14px;
3207
+ color: #1f2937;
3208
+ display: flex; flex-direction: column;
3209
+ /* Native drag-to-resize handle appears at bottom-right when
3210
+ * overflow != visible. overflow:hidden clips at widget edge; the
3211
+ * inner .lmw-messages has its own scroll so no content is lost. */
3212
+ resize: both;
3213
+ overflow: hidden;
3214
+ }
3215
+ #llm-meta-widget-chat.lmw-collapsed { display: none; }
3216
+
3217
+ /* Collapsed-state toggle \u2014 a small floating chat-bubble button in the
3218
+ * bottom-right corner. Always in the DOM; visible only when the chat
3219
+ * panel is collapsed. Click to open the chat panel; the panel's \xD7 button
3220
+ * collapses back to this. State persists across page loads via
3221
+ * localStorage (see the JS below). */
3222
+ #llm-meta-widget-toggle {
3223
+ position: fixed; bottom: 1em; right: 1em;
3224
+ width: 52px; height: 52px;
3225
+ border-radius: 26px;
3226
+ background-color: #3b82f6;
3227
+ color: white;
3228
+ border: none;
3229
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
3230
+ cursor: pointer;
3231
+ z-index: 9999;
3232
+ display: flex; align-items: center; justify-content: center;
3233
+ transition: background-color 0.15s, transform 0.1s;
3234
+ }
3235
+ #llm-meta-widget-toggle:hover { background-color: #2563eb; }
3236
+ #llm-meta-widget-toggle:active { transform: scale(0.96); }
3237
+ #llm-meta-widget-toggle.lmw-hidden { display: none; }
3238
+ #llm-meta-widget-toggle svg { display: block; }
3239
+
3240
+ /* Header hide/close: a subtle "\u2212" button that collapses to the toggle
3241
+ * bubble. Sits between the "clear" button and title. */
3242
+ #llm-meta-widget-chat .lmw-hide {
3243
+ background: none; border: none;
3244
+ color: #6b7280;
3245
+ font: inherit; font-size: 18px; line-height: 1;
3246
+ cursor: pointer;
3247
+ padding: 0 8px;
3248
+ transition: color 0.15s;
3249
+ }
3250
+ #llm-meta-widget-chat .lmw-hide:hover { color: #1f2937; }
3251
+ #llm-meta-widget-chat .lmw-header {
3252
+ display: flex; justify-content: space-between; align-items: center;
3253
+ gap: 8px;
3254
+ padding-bottom: 8px; margin-bottom: 8px;
3255
+ border-bottom: 2px solid #e5e7eb;
3256
+ }
3257
+ #llm-meta-widget-chat .lmw-header-right { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
3258
+
3259
+ /* Level-1 pickers row \u2014 matches chat.aibranch.org's input-container
3260
+ * convention (attach / model / tools / settings in a horizontal row
3261
+ * below the textarea). We only render model + tools for now. Uses
3262
+ * relative positioning so the tools <details> panel can drop down
3263
+ * as a floating overlay instead of pushing the input area around. */
3264
+ #llm-meta-widget-chat .lmw-input-controls {
3265
+ display: flex;
3266
+ align-items: center;
3267
+ gap: 6px;
3268
+ margin-top: 6px;
3269
+ position: relative;
3270
+ min-height: 26px;
3271
+ }
3272
+ #llm-meta-widget-chat .lmw-title { font-weight: 600; color: #1f2937; }
3273
+
3274
+ /* Level-1 model picker \u2014 compact <select> next to the title.
3275
+ * Populated by JS on widget open from GET /api/llms (anon path
3276
+ * returns Ollama-only). Hidden entirely if enable_model_picker
3277
+ * is false at the helper call site. */
3278
+ #llm-meta-widget-chat .lmw-model-picker {
3279
+ font: inherit; font-size: 12px;
3280
+ padding: 3px 6px;
3281
+ border: 1px solid #d1d5db;
3282
+ border-radius: 4px;
3283
+ background: #f9fafb;
3284
+ color: #374151;
3285
+ cursor: pointer;
3286
+ max-width: 12em;
3287
+ text-overflow: ellipsis;
3288
+ }
3289
+ #llm-meta-widget-chat .lmw-model-picker:focus {
3290
+ outline: none;
3291
+ border-color: #3b82f6;
3292
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15);
3293
+ }
3294
+
3295
+ /* Level-1 tools picker \u2014 inline chip in the input-controls row (same
3296
+ * convention as chat.aibranch.org). The tools list drops down as a
3297
+ * floating overlay above the input, so opening the panel doesn't
3298
+ * push the textarea around. Populated by JS on widget open from
3299
+ * GET /api/mcp_servers (anon path returns public_to_anonymous MCP
3300
+ * servers with their tools inline). One checkbox per SERVER \u2014
3301
+ * toggling enables/disables ALL of its tools. */
3302
+ #llm-meta-widget-chat .lmw-tools-picker {
3303
+ position: relative; /* anchor for the absolutely-positioned list */
3304
+ }
3305
+ #llm-meta-widget-chat .lmw-tools-picker > summary {
3306
+ list-style: none;
3307
+ cursor: pointer;
3308
+ padding: 3px 8px;
3309
+ font-size: 12px;
3310
+ color: #374151;
3311
+ background: #f9fafb;
3312
+ border: 1px solid #d1d5db;
3313
+ border-radius: 4px;
3314
+ display: inline-flex; align-items: center; gap: 6px;
3315
+ }
3316
+ #llm-meta-widget-chat .lmw-tools-picker > summary:hover { background: #f3f4f6; }
3317
+ #llm-meta-widget-chat .lmw-tools-picker > summary::-webkit-details-marker { display: none; }
3318
+ #llm-meta-widget-chat .lmw-tools-picker[open] > summary {
3319
+ background: #eff6ff;
3320
+ border-color: #93c5fd;
3321
+ }
3322
+ #llm-meta-widget-chat .lmw-tools-count {
3323
+ display: inline-block;
3324
+ background: #0ea5e9;
3325
+ color: #fff;
3326
+ font-size: 10px;
3327
+ padding: 0 5px;
3328
+ border-radius: 8px;
3329
+ min-width: 14px;
3330
+ text-align: center;
3331
+ }
3332
+ #llm-meta-widget-chat .lmw-tools-count.lmw-tools-count-zero {
3333
+ background: #d1d5db;
3334
+ color: #6b7280;
3335
+ }
3336
+ /* Floating dropdown panel \u2014 sits above the input area when open so
3337
+ * expanding never shifts the textarea. Bottom-anchored so it opens
3338
+ * UPWARD (natural direction since we're at the panel's bottom). */
3339
+ #llm-meta-widget-chat .lmw-tools-list {
3340
+ position: absolute;
3341
+ bottom: calc(100% + 4px);
3342
+ left: 0;
3343
+ min-width: 220px;
3344
+ max-height: 12em;
3345
+ overflow-y: auto;
3346
+ background: #ffffff;
3347
+ border: 1px solid #d1d5db;
3348
+ border-radius: 6px;
3349
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.10);
3350
+ padding: 4px 0;
3351
+ z-index: 10;
3352
+ }
3353
+ #llm-meta-widget-chat .lmw-tools-item {
3354
+ display: flex; align-items: center; gap: 6px;
3355
+ padding: 4px 10px;
3356
+ font-size: 12px;
3357
+ color: #374151;
3358
+ cursor: pointer;
3359
+ }
3360
+ #llm-meta-widget-chat .lmw-tools-item:hover { background: #eff6ff; }
3361
+ #llm-meta-widget-chat .lmw-tools-item input[type="checkbox"] { margin: 0; }
3362
+ #llm-meta-widget-chat .lmw-tools-empty {
3363
+ padding: 6px 10px;
3364
+ font-size: 12px;
3365
+ color: #94a3b8;
3366
+ font-style: italic;
3367
+ }
3368
+ /* Two-level tool selection \u2014 mirrors chat.aibranch.org's tool_selector.
3369
+ * Server row (bulk checkbox + name + count + caret) is clickable to
3370
+ * expand its children. Individual tool rows are indented one level. */
3371
+ #llm-meta-widget-chat .lmw-tools-server + .lmw-tools-server {
3372
+ border-top: 1px solid #f1f5f9;
3373
+ }
3374
+ #llm-meta-widget-chat .lmw-tools-server-row {
3375
+ display: flex; align-items: center; gap: 6px;
3376
+ padding: 4px 10px;
3377
+ font-size: 12px;
3378
+ font-weight: 500;
3379
+ color: #1f2937;
3380
+ cursor: pointer;
3381
+ }
3382
+ #llm-meta-widget-chat .lmw-tools-server-row:hover { background: #eff6ff; }
3383
+ #llm-meta-widget-chat .lmw-tools-server-name { flex: 1; }
3384
+ #llm-meta-widget-chat .lmw-tools-server-caret {
3385
+ color: #94a3b8;
3386
+ font-size: 10px;
3387
+ line-height: 1;
3388
+ flex-shrink: 0;
3389
+ }
3390
+ #llm-meta-widget-chat .lmw-tools-item-child {
3391
+ padding-left: 26px; /* align tool checkboxes past the server bulk checkbox */
3392
+ font-size: 12px;
3393
+ color: #4b5563;
3394
+ }
3395
+ #llm-meta-widget-chat .lmw-clear {
3396
+ background: #ef4444; color: white; border: none;
3397
+ padding: 4px 10px; border-radius: 6px; cursor: pointer;
3398
+ font: inherit; font-size: 12px;
3399
+ transition: background-color 0.15s;
3400
+ }
3401
+ #llm-meta-widget-chat .lmw-clear:hover { background: #dc2626; }
3402
+
3403
+ #llm-meta-widget-chat .lmw-messages {
3404
+ flex: 1; overflow-y: auto;
3405
+ padding: 10px;
3406
+ background-color: #f9fafb;
3407
+ border-radius: 8px;
3408
+ margin-bottom: 8px;
3409
+ min-height: 6em;
3410
+ /* No max-height \u2014 messages grows with the widget (which the user
3411
+ * can drag to any size). A hard cap here would leave an ugly gap
3412
+ * between the messages area and the input on tall widget sizes. */
3413
+ }
3414
+ #llm-meta-widget-chat .lmw-messages::-webkit-scrollbar { width: 8px; }
3415
+ #llm-meta-widget-chat .lmw-messages::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; }
3416
+ #llm-meta-widget-chat .lmw-messages::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
3417
+ #llm-meta-widget-chat .lmw-messages::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
3418
+
3419
+ /* Input area \u2014 mirrors llm_meta_chat's .chat-input-container +
3420
+ * .chat-input + .send-button so it reads as the same UI. Multi-line
3421
+ * textarea with an absolutely-positioned paper-plane send button
3422
+ * (SVG inlined so no external icon-font dep). */
3423
+ #llm-meta-widget-chat .lmw-input-container {
3424
+ background-color: white;
3425
+ border: 2px solid #e5e7eb;
3426
+ border-radius: 8px;
3427
+ padding: 8px;
3428
+ }
3429
+ #llm-meta-widget-chat .message-content details > summary {
3430
+ cursor: pointer;
3431
+ font-weight: 600;
3432
+ }
3433
+ #llm-meta-widget-chat .lmw-sent-text {
3434
+ margin-top: 6px;
3435
+ font-size: 12px;
3436
+ opacity: 0.8;
3437
+ white-space: pre-wrap;
3438
+ }
3439
+
3440
+ /* Opening state: greeting + the offers themselves. Shown until the first
3441
+ * real turn, and again after Clear. */
3442
+ #llm-meta-widget-chat .lmw-welcome { padding: 4px 2px 2px; }
3443
+ #llm-meta-widget-chat .lmw-welcome-hello {
3444
+ margin: 0 0 12px;
3445
+ font-size: 14px;
3446
+ line-height: 1.5;
3447
+ color: #374151;
3448
+ }
3449
+ #llm-meta-widget-chat .lmw-welcome-card {
3450
+ background-color: #f9fafb;
3451
+ border: 1px solid #e5e7eb;
3452
+ border-radius: 8px;
3453
+ padding: 10px 12px;
3454
+ margin-bottom: 8px;
3455
+ }
3456
+ #llm-meta-widget-chat .lmw-welcome-start {
3457
+ background-color: #eff6ff;
3458
+ color: #1d4ed8;
3459
+ border: 1px solid #bfdbfe;
3460
+ border-radius: 999px;
3461
+ padding: 4px 12px;
3462
+ font-size: 13px;
3463
+ font-family: inherit;
3464
+ cursor: pointer;
3465
+ }
3466
+ #llm-meta-widget-chat .lmw-welcome-start:hover { background-color: #dbeafe; }
3467
+ #llm-meta-widget-chat .lmw-welcome-why {
3468
+ margin: 8px 0 0;
3469
+ font-size: 13px;
3470
+ line-height: 1.45;
3471
+ color: #4b5563;
3472
+ }
3473
+ #llm-meta-widget-chat .lmw-welcome-slots {
3474
+ margin: 8px 0 0;
3475
+ padding-left: 16px;
3476
+ font-size: 12px;
3477
+ color: #6b7280;
3478
+ }
3479
+ #llm-meta-widget-chat .lmw-welcome-slots li { margin-bottom: 2px; }
3480
+ #llm-meta-widget-chat .lmw-welcome-slots li.filled { color: #047857; }
3481
+
3482
+ /* Server-offered prompt templates. The row is display:none in markup and
3483
+ * gets its display back only when a prompt actually exists, so the inline
3484
+ * style and this rule have to agree on "flex". */
3485
+ #llm-meta-widget-chat .lmw-prompts {
3486
+ display: flex;
3487
+ flex-wrap: wrap;
3488
+ gap: 6px;
3489
+ margin-bottom: 8px;
3490
+ }
3491
+ #llm-meta-widget-chat .lmw-prompt {
3492
+ background-color: #eff6ff;
3493
+ color: #1d4ed8;
3494
+ border: 1px solid #bfdbfe;
3495
+ border-radius: 999px;
3496
+ padding: 4px 12px;
3497
+ font-size: 12px;
3498
+ font-family: inherit;
3499
+ cursor: pointer;
3500
+ transition: background-color 0.2s, border-color 0.2s;
3501
+ }
3502
+ #llm-meta-widget-chat .lmw-prompt:hover:not(:disabled) {
3503
+ background-color: #dbeafe;
3504
+ border-color: #93c5fd;
3505
+ }
3506
+ #llm-meta-widget-chat .lmw-prompt:disabled { opacity: 0.5; cursor: default; }
3507
+ #llm-meta-widget-chat .lmw-form { display: flex; flex-direction: column; gap: 8px; }
3508
+ #llm-meta-widget-chat .lmw-input-wrapper { position: relative; flex: 1; }
3509
+ #llm-meta-widget-chat .lmw-input {
3510
+ width: 100%;
3511
+ padding: 10px 48px 10px 10px;
3512
+ border: 1px solid #d1d5db;
3513
+ border-radius: 6px;
3514
+ font-size: 14px;
3515
+ font-family: inherit;
3516
+ resize: vertical;
3517
+ min-height: 44px;
3518
+ box-sizing: border-box;
3519
+ transition: border-color 0.2s, box-shadow 0.2s;
3520
+ }
3521
+ #llm-meta-widget-chat .lmw-input:focus {
3522
+ outline: none;
3523
+ border-color: #3b82f6;
3524
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
3525
+ }
3526
+ #llm-meta-widget-chat .lmw-send {
3527
+ position: absolute;
3528
+ bottom: 6px;
3529
+ right: 6px;
3530
+ background-color: #3b82f6;
3531
+ color: white;
3532
+ border: none;
3533
+ width: 32px;
3534
+ height: 32px;
3535
+ padding: 0;
3536
+ border-radius: 6px;
3537
+ cursor: pointer;
3538
+ display: flex;
3539
+ align-items: center;
3540
+ justify-content: center;
3541
+ transition: background-color 0.15s, opacity 0.15s;
3542
+ }
3543
+ #llm-meta-widget-chat .lmw-send:hover { background-color: #2563eb; }
3544
+ #llm-meta-widget-chat .lmw-send:disabled { opacity: 0.5; cursor: not-allowed; }
3545
+ #llm-meta-widget-chat .lmw-send svg { display: block; }
3546
+ `;
3547
+
3548
+ // app/assets/javascripts/llm_meta_widget/element.js
3549
+ marked.setOptions({ gfm: true, breaks: true });
3550
+ var MARKUP = `
3551
+ <button type="button" id="llm-meta-widget-toggle" title="Open AI assistant" aria-label="Open AI assistant">
3552
+ <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
3553
+ <path d="M2 4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2h-3l-3.5 3.5V12H4a2 2 0 0 1-2-2V4Zm3 2a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm3 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm3 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z"/>
3554
+ </svg>
3555
+ </button>
3556
+
3557
+ <div id="llm-meta-widget-chat" class="llm-meta-conversation">
3558
+ <div class="lmw-header">
3559
+ <span class="lmw-title">AI assistant</span>
3560
+ <div class="lmw-header-right">
3561
+ <button type="button" class="lmw-clear" title="Clear conversation">clear</button>
3562
+ <button type="button" class="lmw-hide" title="Hide">\u2212</button>
3563
+ </div>
3564
+ </div>
3565
+ <div class="lmw-messages"></div>
3566
+ <div class="lmw-input-container">
3567
+ host MCP server actually offers one, so a tools-only server leaves
3568
+ the widget exactly as it was. %>
3569
+ <div class="lmw-prompts" style="display:none"></div>
3570
+ <form class="lmw-form">
3571
+ <div class="lmw-input-wrapper">
3572
+ <textarea class="lmw-input" placeholder="Enter your message..." rows="2" autocomplete="off"></textarea>
3573
+ <button type="submit" class="lmw-send" title="Send message">
3574
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
3575
+ <path d="M15.964.686a.5.5 0 0 0-.65-.65L.293 6.011a.513.513 0 0 0 .002.947l4.708 1.878 8.94-6.94-6.94 8.94 1.879 4.708c.163.407.756.416.951.016l6.13-13.884Z"/>
3576
+ </svg>
3577
+ </button>
3578
+ </div>
3579
+ <% if enable_model_picker || enable_tool_picker %>
3580
+ <div class="lmw-input-controls">
3581
+ <% if enable_model_picker %>
3582
+ <select class="lmw-model-picker" title="Select model" aria-label="Select model" style="display:none">
3583
+ </select>
3584
+ <% end %>
3585
+ <% if enable_tool_picker %>
3586
+ <details class="lmw-tools-picker">
3587
+ <summary>
3588
+ \u{1F527} Tools
3589
+ <span class="lmw-tools-count lmw-tools-count-zero">0</span>
3590
+ </summary>
3591
+ <div class="lmw-tools-list">
3592
+ <div class="lmw-tools-empty">Loading\u2026</div>
3593
+ </div>
3594
+ </details>
3595
+ <% end %>
3596
+ </div>
3597
+ <% end %>
3598
+ </form>
3599
+ </div>
3600
+ </div>
3601
+ `;
3602
+ var stylesInjected = false;
3603
+ function injectStylesOnce(doc) {
3604
+ if (stylesInjected) return;
3605
+ stylesInjected = true;
3606
+ const style = doc.createElement("style");
3607
+ style.setAttribute("data-llm-meta-widget", "styles");
3608
+ style.textContent = conversation_default + "\n" + panel_default;
3609
+ doc.head.appendChild(style);
3610
+ }
3611
+ var LlmMetaWidgetElement = class extends HTMLElement {
3612
+ connectedCallback() {
3613
+ if (this.__lmwWired) return;
3614
+ this.__lmwWired = true;
3615
+ if (!this.getAttribute("llm-url") || !this.getAttribute("model")) {
3616
+ console.error("[llm-meta-widget] llm-url and model are both required attributes; not starting.");
3617
+ return;
3618
+ }
3619
+ if (document.getElementById("llm-meta-widget-chat")) {
3620
+ console.warn("[llm-meta-widget] a widget is already on this page; ignoring this one.");
3621
+ return;
3622
+ }
3623
+ injectStylesOnce(document);
3624
+ this.insertAdjacentHTML("beforeend", MARKUP);
3625
+ boot(readConfig(this));
3626
+ }
3627
+ };
3628
+ if (typeof customElements !== "undefined" && !customElements.get("llm-meta-widget")) {
3629
+ customElements.define("llm-meta-widget", LlmMetaWidgetElement);
3630
+ }
3631
+ function boot(cfg) {
3632
+ var LLM_BASE = cfg.LLM_BASE;
3633
+ var TOOL_HUB_BASE = cfg.TOOL_HUB_BASE;
3634
+ var API_KEY_UUID = cfg.API_KEY_UUID;
3635
+ var MODEL = cfg.MODEL;
3636
+ var ACTIONS_SCHEMA_ID = cfg.ACTIONS_SCHEMA_ID;
3637
+ var GREETING = cfg.GREETING;
3638
+ var STATE_GLOBAL = cfg.STATE_GLOBAL;
3639
+ var ACTIONS_GLOBAL = cfg.ACTIONS_GLOBAL;
3640
+ var REMOTE_TOOLS_SCHEMA_ID = cfg.REMOTE_TOOLS_SCHEMA_ID;
3641
+ var MAX_ROUNDS = cfg.MAX_ROUNDS;
3642
+ var WELL_KNOWN_URLS = cfg.WELL_KNOWN_URLS;
3643
+ var LLM_PROVIDER = cfg.LLM_PROVIDER;
3644
+ var ENABLE_MODEL_PICKER = cfg.ENABLE_MODEL_PICKER;
3645
+ var ENABLE_TOOL_PICKER = cfg.ENABLE_TOOL_PICKER;
3646
+ var MODEL_ALLOWLIST = cfg.MODEL_ALLOWLIST;
3647
+ var HUB_TOOLS_ALLOWLIST = cfg.HUB_TOOLS_ALLOWLIST;
3648
+ var root = document.getElementById("llm-meta-widget-chat");
3649
+ var toggleBtn = document.getElementById("llm-meta-widget-toggle");
3650
+ var historyEl = root.querySelector(".lmw-messages");
3651
+ var formEl = root.querySelector(".lmw-form");
3652
+ var inputEl = root.querySelector(".lmw-input");
3653
+ var clearBtn = root.querySelector(".lmw-clear");
3654
+ var hideBtn = root.querySelector(".lmw-hide");
3655
+ var modelPicker = root.querySelector(".lmw-model-picker");
3656
+ var toolsPicker = root.querySelector(".lmw-tools-picker");
3657
+ var toolsListEl = root.querySelector(".lmw-tools-list");
3658
+ var toolsCountEl = root.querySelector(".lmw-tools-count");
3659
+ var STORAGE_KEY_OPEN = "llm_meta_widget:open";
3660
+ var STORAGE_KEY_SIZE = "llm_meta_widget:size";
3661
+ var MIN_SAVED_WIDTH = 320;
3662
+ var MIN_SAVED_HEIGHT = 240;
3663
+ function plausibleSize(w, h) {
3664
+ return w >= MIN_SAVED_WIDTH && h >= MIN_SAVED_HEIGHT && w <= window.innerWidth * 2 && h <= window.innerHeight * 2;
3665
+ }
3666
+ function applyStoredSize() {
3667
+ try {
3668
+ var raw = localStorage.getItem(STORAGE_KEY_SIZE);
3669
+ if (!raw) return;
3670
+ var parts = raw.split("x");
3671
+ var w = parseInt(parts[0], 10), h = parseInt(parts[1], 10);
3672
+ if (!plausibleSize(w, h)) {
3673
+ localStorage.removeItem(STORAGE_KEY_SIZE);
3674
+ return;
3675
+ }
3676
+ root.style.width = w + "px";
3677
+ root.style.height = h + "px";
3678
+ } catch (e) {
3679
+ }
3680
+ }
3681
+ function saveCurrentSize() {
3682
+ try {
3683
+ var rect = root.getBoundingClientRect();
3684
+ var w = Math.round(rect.width), h = Math.round(rect.height);
3685
+ if (!plausibleSize(w, h)) return;
3686
+ localStorage.setItem(STORAGE_KEY_SIZE, w + "x" + h);
3687
+ } catch (e) {
3688
+ }
3689
+ }
3690
+ function positionAtBottomRight(attempt) {
3691
+ var margin = 16;
3692
+ var rect = root.getBoundingClientRect();
3693
+ var top = Math.max(margin, window.innerHeight - rect.height - margin);
3694
+ var left = Math.max(margin, window.innerWidth - rect.width - margin);
3695
+ root.style.top = top + "px";
3696
+ root.style.left = left + "px";
3697
+ var tries = attempt || 0;
3698
+ if (rect.height < MIN_SAVED_HEIGHT && tries < 3 && typeof requestAnimationFrame === "function") {
3699
+ requestAnimationFrame(function() {
3700
+ positionAtBottomRight(tries + 1);
3701
+ });
3702
+ }
3703
+ }
3704
+ function setOpen(open) {
3705
+ if (open) {
3706
+ applyStoredSize();
3707
+ root.classList.remove("lmw-collapsed");
3708
+ toggleBtn.classList.add("lmw-hidden");
3709
+ positionAtBottomRight();
3710
+ try {
3711
+ inputEl.focus();
3712
+ } catch (e) {
3713
+ }
3714
+ ensurePickerLoaded().catch(function(e) {
3715
+ console.warn("[widget] picker load failed:", e);
3716
+ });
3717
+ } else {
3718
+ saveCurrentSize();
3719
+ root.classList.add("lmw-collapsed");
3720
+ toggleBtn.classList.remove("lmw-hidden");
3721
+ }
3722
+ try {
3723
+ localStorage.setItem(STORAGE_KEY_OPEN, open ? "1" : "0");
3724
+ } catch (e) {
3725
+ }
3726
+ }
3727
+ var initialOpen = false;
3728
+ try {
3729
+ initialOpen = localStorage.getItem(STORAGE_KEY_OPEN) === "1";
3730
+ } catch (e) {
3731
+ }
3732
+ setOpen(initialOpen);
3733
+ window.addEventListener("resize", function() {
3734
+ if (!root.classList.contains("lmw-collapsed")) positionAtBottomRight();
3735
+ });
3736
+ toggleBtn.addEventListener("click", function() {
3737
+ setOpen(true);
3738
+ });
3739
+ hideBtn.addEventListener("click", function() {
3740
+ setOpen(false);
3741
+ });
3742
+ if (typeof ResizeObserver === "function") {
3743
+ var sawFirstResizeTick = false;
3744
+ var ro = new ResizeObserver(function() {
3745
+ if (root.classList.contains("lmw-collapsed")) return;
3746
+ positionAtBottomRight();
3747
+ if (!sawFirstResizeTick) {
3748
+ sawFirstResizeTick = true;
3749
+ return;
3750
+ }
3751
+ saveCurrentSize();
3752
+ });
3753
+ ro.observe(root);
3754
+ }
3755
+ var conversation = [];
3756
+ var conversationStore = createConversationStore({
3757
+ storage: window.sessionStorage,
3758
+ key: conversationKeyFor(window.location)
3759
+ });
3760
+ function persistConversation() {
3761
+ conversationStore.save({
3762
+ turns: conversation,
3763
+ open: !root.classList.contains("lmw-collapsed"),
3764
+ model: MODEL
3765
+ });
3766
+ }
3767
+ var actionsSchemaEl = document.getElementById(ACTIONS_SCHEMA_ID);
3768
+ var localTools = actionsSchemaEl ? JSON.parse(actionsSchemaEl.textContent) : [];
3769
+ var remoteToolsEl = document.getElementById(REMOTE_TOOLS_SCHEMA_ID);
3770
+ var remoteTools = remoteToolsEl ? JSON.parse(remoteToolsEl.textContent) : [];
3771
+ var hubMcpServers = [];
3772
+ var selectedToolIds = /* @__PURE__ */ new Set();
3773
+ function anyAllowedByAllowlist(name, allowlist) {
3774
+ return allowlist === null || allowlist.indexOf(name) >= 0;
3775
+ }
3776
+ function refreshRemoteToolsFromPicker() {
3777
+ var flat = [];
3778
+ hubMcpServers.forEach(function(s) {
3779
+ (s.tools || []).forEach(function(t) {
3780
+ if (!selectedToolIds.has(t.id)) return;
3781
+ flat.push({
3782
+ id: t.id,
3783
+ name: t.name,
3784
+ description: t.description,
3785
+ input_schema: t.input_schema
3786
+ });
3787
+ });
3788
+ });
3789
+ remoteTools = flat;
3790
+ if (toolsCountEl) {
3791
+ toolsCountEl.textContent = flat.length;
3792
+ toolsCountEl.classList.toggle("lmw-tools-count-zero", flat.length === 0);
3793
+ }
3794
+ }
3795
+ function refreshServerBulkCheckbox(server, bulkCheckbox) {
3796
+ var tools = server.tools || [];
3797
+ var n = tools.length;
3798
+ if (n === 0) {
3799
+ bulkCheckbox.checked = false;
3800
+ bulkCheckbox.indeterminate = false;
3801
+ return;
3802
+ }
3803
+ var onCount = 0;
3804
+ tools.forEach(function(t) {
3805
+ if (selectedToolIds.has(t.id)) onCount++;
3806
+ });
3807
+ bulkCheckbox.checked = onCount === n;
3808
+ bulkCheckbox.indeterminate = onCount > 0 && onCount < n;
3809
+ }
3810
+ async function loadHubResourcesForPickers() {
3811
+ var tasks = [];
3812
+ if (ENABLE_MODEL_PICKER && modelPicker && LLM_PROVIDER === "ollama") {
3813
+ tasks.push(fetchOllamaModels({ baseUrl: LLM_BASE }).then(function(names) {
3814
+ var flat = names.filter(function(n) {
3815
+ return anyAllowedByAllowlist(n, MODEL_ALLOWLIST);
3816
+ }).map(function(n) {
3817
+ return { value: n, label: n };
3818
+ });
3819
+ if (!flat.some(function(m) {
3820
+ return m.value === MODEL;
3821
+ })) {
3822
+ flat.unshift({ value: MODEL, label: MODEL });
3823
+ }
3824
+ modelPicker.innerHTML = "";
3825
+ flat.forEach(function(m) {
3826
+ var opt = document.createElement("option");
3827
+ opt.value = m.value;
3828
+ opt.textContent = m.label;
3829
+ if (m.value === MODEL) opt.selected = true;
3830
+ modelPicker.appendChild(opt);
3831
+ });
3832
+ if (flat.length > 1) modelPicker.style.display = "";
3833
+ }));
3834
+ } else if (ENABLE_MODEL_PICKER && modelPicker) {
3835
+ tasks.push(fetch(LLM_BASE + "/api/llms", { headers: { "Accept": "application/json" } }).then(function(r) {
3836
+ return r.ok ? r.json() : { llms: [] };
3837
+ }).then(function(payload) {
3838
+ var flat = [];
3839
+ (payload.llms || []).forEach(function(llm) {
3840
+ if (llm.family !== "ollama") return;
3841
+ (llm.available_models || []).forEach(function(m) {
3842
+ if (anyAllowedByAllowlist(m.value, MODEL_ALLOWLIST)) {
3843
+ flat.push({ value: m.value, label: m.label });
3844
+ }
3845
+ });
3846
+ });
3847
+ if (!flat.some(function(m) {
3848
+ return m.value === MODEL;
3849
+ })) {
3850
+ flat.unshift({ value: MODEL, label: MODEL });
3851
+ }
3852
+ modelPicker.innerHTML = "";
3853
+ flat.forEach(function(m) {
3854
+ var opt = document.createElement("option");
3855
+ opt.value = m.value;
3856
+ opt.textContent = m.label;
3857
+ if (m.value === MODEL) opt.selected = true;
3858
+ modelPicker.appendChild(opt);
3859
+ });
3860
+ if (flat.length > 1) modelPicker.style.display = "";
3861
+ }).catch(function(e) {
3862
+ console.warn("[widget] model list fetch failed:", e);
3863
+ }));
3864
+ }
3865
+ if (ENABLE_TOOL_PICKER && toolsListEl) {
3866
+ tasks.push(fetch(TOOL_HUB_BASE + "/api/mcp_servers", { headers: { "Accept": "application/json" } }).then(function(r) {
3867
+ return r.ok ? r.json() : { mcp_servers: [] };
3868
+ }).then(function(payload) {
3869
+ hubMcpServers = (payload.mcp_servers || []).filter(function(s) {
3870
+ return anyAllowedByAllowlist(s.name, HUB_TOOLS_ALLOWLIST);
3871
+ });
3872
+ toolsListEl.innerHTML = "";
3873
+ if (hubMcpServers.length === 0) {
3874
+ var empty = document.createElement("div");
3875
+ empty.className = "lmw-tools-empty";
3876
+ empty.textContent = "No hub-registered tools available.";
3877
+ toolsListEl.appendChild(empty);
3878
+ return;
3879
+ }
3880
+ hubMcpServers.forEach(function(s) {
3881
+ var serverBlock = document.createElement("div");
3882
+ serverBlock.className = "lmw-tools-server";
3883
+ var headerRow = document.createElement("div");
3884
+ headerRow.className = "lmw-tools-server-row";
3885
+ var bulkCb = document.createElement("input");
3886
+ bulkCb.type = "checkbox";
3887
+ bulkCb.className = "lmw-tools-server-bulk";
3888
+ bulkCb.title = "Enable all tools on this server";
3889
+ bulkCb.addEventListener("click", function(e) {
3890
+ e.stopPropagation();
3891
+ });
3892
+ bulkCb.addEventListener("change", function() {
3893
+ var shouldOn = bulkCb.checked;
3894
+ (s.tools || []).forEach(function(t) {
3895
+ if (shouldOn) selectedToolIds.add(t.id);
3896
+ else selectedToolIds.delete(t.id);
3897
+ });
3898
+ bulkCb.indeterminate = false;
3899
+ childList.querySelectorAll('input[type="checkbox"]').forEach(function(cb) {
3900
+ cb.checked = shouldOn;
3901
+ });
3902
+ refreshRemoteToolsFromPicker();
3903
+ });
3904
+ var nameSpan = document.createElement("span");
3905
+ nameSpan.className = "lmw-tools-server-name";
3906
+ var toolCount = (s.tools || []).length;
3907
+ nameSpan.textContent = s.name + " (" + toolCount + " tool" + (toolCount === 1 ? "" : "s") + ")";
3908
+ var caret2 = document.createElement("span");
3909
+ caret2.className = "lmw-tools-server-caret";
3910
+ caret2.textContent = "\u25B8";
3911
+ headerRow.appendChild(bulkCb);
3912
+ headerRow.appendChild(nameSpan);
3913
+ headerRow.appendChild(caret2);
3914
+ var childList = document.createElement("div");
3915
+ childList.className = "lmw-tools-server-children";
3916
+ childList.style.display = "none";
3917
+ (s.tools || []).forEach(function(t) {
3918
+ var toolLabel = document.createElement("label");
3919
+ toolLabel.className = "lmw-tools-item lmw-tools-item-child";
3920
+ toolLabel.title = t.description || t.name;
3921
+ var cb = document.createElement("input");
3922
+ cb.type = "checkbox";
3923
+ cb.value = t.id;
3924
+ cb.checked = selectedToolIds.has(t.id);
3925
+ cb.addEventListener("change", function() {
3926
+ if (cb.checked) selectedToolIds.add(t.id);
3927
+ else selectedToolIds.delete(t.id);
3928
+ refreshServerBulkCheckbox(s, bulkCb);
3929
+ refreshRemoteToolsFromPicker();
3930
+ });
3931
+ var span = document.createElement("span");
3932
+ span.textContent = t.name;
3933
+ toolLabel.appendChild(cb);
3934
+ toolLabel.appendChild(span);
3935
+ childList.appendChild(toolLabel);
3936
+ });
3937
+ headerRow.addEventListener("click", function() {
3938
+ var isOpen = childList.style.display !== "none";
3939
+ childList.style.display = isOpen ? "none" : "block";
3940
+ caret2.textContent = isOpen ? "\u25B8" : "\u25BE";
3941
+ });
3942
+ serverBlock.appendChild(headerRow);
3943
+ serverBlock.appendChild(childList);
3944
+ toolsListEl.appendChild(serverBlock);
3945
+ refreshServerBulkCheckbox(s, bulkCb);
3946
+ });
3947
+ refreshRemoteToolsFromPicker();
3948
+ }).catch(function(e) {
3949
+ console.warn("[widget] MCP server list fetch failed:", e);
3950
+ }));
3951
+ }
3952
+ await Promise.all(tasks);
3953
+ }
3954
+ if (modelPicker) {
3955
+ modelPicker.addEventListener("change", function() {
3956
+ MODEL = modelPicker.value;
3957
+ });
3958
+ }
3959
+ if (toolsPicker) {
3960
+ document.addEventListener("click", function(e) {
3961
+ if (!toolsPicker.open) return;
3962
+ if (toolsPicker.contains(e.target)) return;
3963
+ toolsPicker.open = false;
3964
+ });
3965
+ }
3966
+ var pickerLoaded = false;
3967
+ async function ensurePickerLoaded() {
3968
+ if (pickerLoaded) return;
3969
+ pickerLoaded = true;
3970
+ try {
3971
+ await loadHubResourcesForPickers();
3972
+ } catch (e) {
3973
+ pickerLoaded = false;
3974
+ throw e;
3975
+ }
3976
+ }
3977
+ var hostWideTools = [];
3978
+ var hostWidePrompts = [];
3979
+ var resourceContext = null;
3980
+ var resourcePlan = null;
3981
+ var RESOURCE_BUDGET_BYTES = 8e3;
3982
+ restoreConversation();
3983
+ var wellKnownReady = (async function() {
3984
+ var urls = WELL_KNOWN_URLS === null ? [window.location.origin + "/.well-known/mcp.json"] : WELL_KNOWN_URLS;
3985
+ for (var i = 0; i < urls.length; i++) {
3986
+ var tools = await fetchMcpManifest(urls[i]);
3987
+ hostWideTools = hostWideTools.concat(tools);
3988
+ }
3989
+ var endpoints = [];
3990
+ hostWideTools.forEach(function(t) {
3991
+ if (t.endpoint && endpoints.indexOf(t.endpoint) === -1) endpoints.push(t.endpoint);
3992
+ });
3993
+ for (var j = 0; j < endpoints.length; j++) {
3994
+ var endpoint = endpoints[j];
3995
+ var prompts = await listMcpPrompts({ endpoint });
3996
+ hostWidePrompts = hostWidePrompts.concat(prompts);
3997
+ if (resourcePlan === null) {
3998
+ var loaded = await loadHostResource({
3999
+ endpoint,
4000
+ budgetBytes: RESOURCE_BUDGET_BYTES,
4001
+ list: listMcpResources,
4002
+ read: readMcpResource,
4003
+ onSkip: function(plan) {
4004
+ console.info("[llm_meta_widget] not attaching " + plan.uri + " (" + plan.reason + ", sizeBytes=" + plan.sizeBytes + ")");
4005
+ }
4006
+ });
4007
+ if (loaded && loaded.plan && loaded.plan.fetch) {
4008
+ resourceContext = loaded.context;
4009
+ resourcePlan = loaded.plan;
4010
+ }
4011
+ }
4012
+ }
4013
+ renderPromptButtons();
4014
+ renderWelcome();
4015
+ })();
4016
+ var promptsEl = root.querySelector(".lmw-prompts");
4017
+ function renderPromptButtons() {
4018
+ if (!promptsEl) return;
4019
+ promptsEl.textContent = "";
4020
+ if (!hostWidePrompts.length) {
4021
+ promptsEl.style.display = "none";
4022
+ return;
4023
+ }
4024
+ hostWidePrompts.forEach(function(prompt) {
4025
+ var props = promptButtonProps(prompt);
4026
+ var button = document.createElement("button");
4027
+ button.type = "button";
4028
+ button.className = "lmw-prompt";
4029
+ button.textContent = props.label;
4030
+ button.title = props.title;
4031
+ button.addEventListener("click", function() {
4032
+ runPromptTemplate(prompt, button);
4033
+ });
4034
+ promptsEl.appendChild(button);
4035
+ });
4036
+ promptsEl.style.display = "";
4037
+ }
4038
+ function restoreConversation() {
4039
+ var saved = conversationStore.load();
4040
+ if (!saved || !saved.turns.length) return false;
4041
+ conversation = saved.turns;
4042
+ saved.turns.forEach(function(turn) {
4043
+ var body = appendTurn(turn.role, turn.role === "assistant" ? "" : turn.content);
4044
+ if (turn.role === "assistant") renderMarkdownInto(body, turn.content || "");
4045
+ if (!turn.tools || !turn.tools.length) return;
4046
+ var row = document.createElement("div");
4047
+ row.className = "lmw-tool-chips";
4048
+ turn.tools.forEach(function(tool) {
4049
+ var chip = document.createElement("span");
4050
+ chip.className = "lmw-tool-chip" + (tool.error ? " error" : "");
4051
+ chip.textContent = (tool.error ? "\u274C " : "\u{1F527} ") + tool.name;
4052
+ row.appendChild(chip);
4053
+ });
4054
+ if (body.parentNode) body.parentNode.appendChild(row);
4055
+ });
4056
+ historyEl.scrollTop = historyEl.scrollHeight;
4057
+ return true;
4058
+ }
4059
+ function renderWelcome() {
4060
+ if (!historyEl || historyEl.querySelector(".message")) return;
4061
+ historyEl.textContent = "";
4062
+ var box = document.createElement("div");
4063
+ box.className = "lmw-welcome";
4064
+ var hello = document.createElement("p");
4065
+ hello.className = "lmw-welcome-hello";
4066
+ hello.textContent = GREETING || "Hi \u2014 I can work this page for you. Tell me what you need in your own words" + (hostWidePrompts.length ? ", or start with one of these:" : ".");
4067
+ box.appendChild(hello);
4068
+ var state = window[STATE_GLOBAL] || {};
4069
+ hostWidePrompts.forEach(function(prompt) {
4070
+ var props = promptButtonProps(prompt);
4071
+ var card = document.createElement("div");
4072
+ card.className = "lmw-welcome-card";
4073
+ var start = document.createElement("button");
4074
+ start.type = "button";
4075
+ start.className = "lmw-welcome-start";
4076
+ start.textContent = props.label;
4077
+ start.addEventListener("click", function() {
4078
+ runPromptTemplate(prompt, start);
4079
+ });
4080
+ card.appendChild(start);
4081
+ if (prompt.description) {
4082
+ var why = document.createElement("p");
4083
+ why.className = "lmw-welcome-why";
4084
+ why.textContent = prompt.description;
4085
+ card.appendChild(why);
4086
+ }
4087
+ var summary = promptArgumentSummary(prompt, state);
4088
+ if (summary.length) {
4089
+ var slots = document.createElement("ul");
4090
+ slots.className = "lmw-welcome-slots";
4091
+ summary.forEach(function(slot) {
4092
+ var li = document.createElement("li");
4093
+ li.className = slot.filled ? "filled" : "empty";
4094
+ li.textContent = slot.filled ? slot.name + ": " + (slot.value.length > 60 ? slot.value.slice(0, 60) + "\u2026" : slot.value) : slot.name + ": not set yet \u2014 I'll ask, or work it out";
4095
+ slots.appendChild(li);
4096
+ });
4097
+ card.appendChild(slots);
4098
+ }
4099
+ box.appendChild(card);
4100
+ });
4101
+ historyEl.appendChild(box);
4102
+ }
4103
+ async function runPromptTemplate(prompt, button) {
4104
+ var resolved = resolvePromptArguments(prompt, window[STATE_GLOBAL] || {});
4105
+ var args = resolved.args;
4106
+ var missing = resolved.missing;
4107
+ if (missing.length) {
4108
+ appendTurn("error", 'Cannot run "' + (prompt.title || prompt.name) + '" yet \u2014 nothing to use for: ' + missing.join(", ") + ". Fill that in on the page first.");
4109
+ return;
4110
+ }
4111
+ button.disabled = true;
4112
+ try {
4113
+ var result = await getMcpPrompt({ endpoint: prompt.endpoint, name: prompt.name, args });
4114
+ var text = promptMessagesToText(result);
4115
+ if (!text) {
4116
+ appendTurn("error", "The server returned an empty prompt.");
4117
+ return;
4118
+ }
4119
+ inputEl.value = text;
4120
+ pendingTurnLabel = promptButtonProps(prompt).label;
4121
+ if (typeof formEl.requestSubmit === "function") formEl.requestSubmit();
4122
+ else formEl.dispatchEvent(new Event("submit", { cancelable: true }));
4123
+ } catch (e) {
4124
+ appendTurn("error", "Prompt failed: " + (e && e.message ? e.message : e));
4125
+ } finally {
4126
+ button.disabled = false;
4127
+ }
4128
+ }
4129
+ var pendingTurnLabel = null;
4130
+ function appendTurn(role, text, turnLabel) {
4131
+ var welcome = historyEl.querySelector(".lmw-welcome");
4132
+ if (welcome) welcome.remove();
4133
+ var div = document.createElement("div");
4134
+ div.className = "message " + role;
4135
+ var label = document.createElement("div");
4136
+ label.className = "message-role";
4137
+ label.textContent = roleLabel(role);
4138
+ var body = document.createElement("div");
4139
+ body.className = "message-content";
4140
+ if (turnLabel) {
4141
+ var details = document.createElement("details");
4142
+ var summary = document.createElement("summary");
4143
+ summary.textContent = turnLabel;
4144
+ var full = document.createElement("div");
4145
+ full.className = "lmw-sent-text";
4146
+ full.textContent = text;
4147
+ details.appendChild(summary);
4148
+ details.appendChild(full);
4149
+ body.appendChild(details);
4150
+ } else {
4151
+ body.textContent = text;
4152
+ }
4153
+ div.appendChild(label);
4154
+ div.appendChild(document.createTextNode(" "));
4155
+ div.appendChild(body);
4156
+ historyEl.appendChild(div);
4157
+ historyEl.scrollTop = historyEl.scrollHeight;
4158
+ body.roleLabel = label;
4159
+ return body;
4160
+ }
4161
+ function renderMarkdownInto(el, mdText) {
4162
+ try {
4163
+ el.innerHTML = marked.parse(mdText);
4164
+ } catch (e) {
4165
+ el.textContent = mdText;
4166
+ }
4167
+ }
4168
+ function roleLabel(role) {
4169
+ if (role === "user") return "\u{1F464} You";
4170
+ if (role === "assistant") return "\u{1F916} " + MODEL;
4171
+ if (role === "system") return "\u2022 system";
4172
+ if (role === "error") return "\u26A0 error";
4173
+ return role;
4174
+ }
4175
+ function markWorking(label) {
4176
+ if (!label || label.classList.contains("is-working")) return;
4177
+ label.innerHTML = '<span class="role-spinner" aria-hidden="true">\u2699\uFE0F</span> Working\u2026';
4178
+ label.classList.add("is-working");
4179
+ }
4180
+ function markDone(label) {
4181
+ if (!label || !label.classList.contains("is-working")) return;
4182
+ label.classList.remove("is-working");
4183
+ label.textContent = roleLabel("assistant");
4184
+ }
4185
+ var liveChips = null;
4186
+ var pendingChips = [];
4187
+ function chipRow() {
4188
+ if (liveChips && liveChips.parentNode) return liveChips;
4189
+ if (!activeAssistantBody || !activeAssistantBody.parentNode) return null;
4190
+ liveChips = document.createElement("div");
4191
+ liveChips.className = "lmw-tool-chips";
4192
+ activeAssistantBody.parentNode.appendChild(liveChips);
4193
+ return liveChips;
4194
+ }
4195
+ function announceToolCall(toolCall) {
4196
+ var row = chipRow();
4197
+ if (!row) return;
4198
+ var chip = document.createElement("span");
4199
+ chip.className = "lmw-tool-chip running";
4200
+ chip.textContent = "\u23F3 " + toolCall.name;
4201
+ chip.dataset.tool = toolCall.name;
4202
+ row.appendChild(chip);
4203
+ pendingChips.push(chip);
4204
+ historyEl.scrollTop = historyEl.scrollHeight;
4205
+ }
4206
+ function resolveToolCall(outcome) {
4207
+ var idx = pendingChips.findIndex(function(c) {
4208
+ return c.dataset.tool === outcome.toolCall.name;
4209
+ });
4210
+ var chip = idx === -1 ? null : pendingChips.splice(idx, 1)[0];
4211
+ if (!chip) return;
4212
+ chip.className = "lmw-tool-chip" + (outcome.error ? " error" : "");
4213
+ chip.textContent = (outcome.error ? "\u274C " : "\u{1F527} ") + outcome.toolCall.name;
4214
+ if (outcome.error) chip.title = outcome.error.message || String(outcome.error);
4215
+ }
4216
+ var currentThinkingBlock = null;
4217
+ var currentThinkingBody = null;
4218
+ var activeAssistantBody = null;
4219
+ function ensureThinkingBlock() {
4220
+ if (currentThinkingBody) return currentThinkingBody;
4221
+ var details = document.createElement("details");
4222
+ details.className = "message-thinking";
4223
+ details.open = true;
4224
+ details.classList.add("thinking-active");
4225
+ var summary = document.createElement("summary");
4226
+ summary.textContent = "\u{1F914} thinking\u2026";
4227
+ var dots = document.createElement("span");
4228
+ dots.className = "thinking-dots";
4229
+ for (var i = 0; i < 3; i++) {
4230
+ var dot = document.createElement("span");
4231
+ dot.textContent = ".";
4232
+ dots.appendChild(dot);
4233
+ }
4234
+ summary.appendChild(dots);
4235
+ var body = document.createElement("div");
4236
+ body.className = "message-thinking-content";
4237
+ details.appendChild(summary);
4238
+ details.appendChild(body);
4239
+ if (activeAssistantBody && activeAssistantBody.parentNode) {
4240
+ activeAssistantBody.parentNode.insertBefore(details, activeAssistantBody);
4241
+ } else {
4242
+ historyEl.appendChild(details);
4243
+ }
4244
+ historyEl.scrollTop = historyEl.scrollHeight;
4245
+ currentThinkingBlock = details;
4246
+ currentThinkingBody = body;
4247
+ return body;
4248
+ }
4249
+ function collapseThinkingBlock() {
4250
+ if (currentThinkingBlock) {
4251
+ currentThinkingBlock.classList.remove("thinking-active");
4252
+ currentThinkingBlock.open = false;
4253
+ var summary = currentThinkingBlock.querySelector("summary");
4254
+ if (summary) summary.textContent = "\u{1F914} thinking (finished)";
4255
+ }
4256
+ currentThinkingBlock = null;
4257
+ currentThinkingBody = null;
4258
+ }
4259
+ function currentPageState() {
4260
+ var reader = window[STATE_GLOBAL] || {};
4261
+ var out = {};
4262
+ Object.keys(reader).forEach(function(k) {
4263
+ try {
4264
+ out[k] = reader[k]();
4265
+ } catch (e) {
4266
+ out[k] = "<error: " + e.message + ">";
4267
+ }
4268
+ });
4269
+ return out;
4270
+ }
4271
+ function currentSystemPrompt(resourceLines) {
4272
+ return [
4273
+ "You are integrated into a web page as an AI assistant. You have tools available to change page state or fetch information.",
4274
+ "",
4275
+ "RULES for tool use:",
4276
+ "1. If the user's question can be answered from the Current page state below, answer directly with a plain-text response \u2014 do NOT invoke a tool.",
4277
+ '2. If the user requests a state change, or needs information not in the page state, invoke the matching tool via a function call. Do not describe your intent in text without actually invoking (a textual promise like "I will add X" is a failure).',
4278
+ "3. After a tool returns a result, use it: either take the next step the task needs, or \u2014 if the task is done \u2014 answer in plain text. Do not stop silently after a tool call.",
4279
+ "4. NEVER repeat a call you have already made with the same or similar arguments \u2014 its result is already in the conversation history.",
4280
+ "",
4281
+ "Current page state:",
4282
+ JSON.stringify(currentPageState(), null, 2)
4283
+ ].concat(resourceLines || []).join("\n");
4284
+ }
4285
+ async function resourceLinesForThisTurn() {
4286
+ var turn = await resourceLinesForTurn({
4287
+ plan: resourcePlan,
4288
+ cached: resourceContext,
4289
+ endpoint: resourcePlan && resourcePlan.endpoint,
4290
+ read: readMcpResource,
4291
+ budgetBytes: RESOURCE_BUDGET_BYTES
4292
+ });
4293
+ resourceContext = turn.cached;
4294
+ return turn.lines;
4295
+ }
4296
+ var currentAbort = null;
4297
+ clearBtn.addEventListener("click", function() {
4298
+ if (currentAbort) {
4299
+ try {
4300
+ currentAbort.abort();
4301
+ } catch (e) {
4302
+ }
4303
+ }
4304
+ conversation = [];
4305
+ conversationStore.clear();
4306
+ historyEl.innerHTML = "";
4307
+ renderWelcome();
4308
+ });
4309
+ inputEl.addEventListener("keydown", function(e) {
4310
+ if (e.key === "Enter" && !e.shiftKey && !e.isComposing) {
4311
+ e.preventDefault();
4312
+ if (typeof formEl.requestSubmit === "function") formEl.requestSubmit();
4313
+ else formEl.dispatchEvent(new Event("submit", { cancelable: true }));
4314
+ }
4315
+ });
4316
+ formEl.addEventListener("submit", async function(event) {
4317
+ event.preventDefault();
4318
+ var userText = inputEl.value.trim();
4319
+ if (!userText) return;
4320
+ inputEl.value = "";
4321
+ appendTurn("user", userText, pendingTurnLabel);
4322
+ pendingTurnLabel = null;
4323
+ if (currentAbort) {
4324
+ try {
4325
+ currentAbort.abort();
4326
+ } catch (e) {
4327
+ }
4328
+ }
4329
+ currentAbort = new AbortController();
4330
+ var assistantBody = appendTurn("assistant", "");
4331
+ activeAssistantBody = assistantBody;
4332
+ liveChips = null;
4333
+ pendingChips = [];
4334
+ markWorking(assistantBody.roleLabel);
4335
+ var assistantMarkdown = "";
4336
+ try {
4337
+ await wellKnownReady;
4338
+ var resourceLines = await resourceLinesForThisTurn();
4339
+ var messages = [{ role: "system", content: currentSystemPrompt(resourceLines) }].concat(conversation.map(function(t) {
4340
+ return { role: t.role, content: t.content };
4341
+ })).concat([{ role: "user", content: userText }]);
4342
+ var result = await runChatLoop({
4343
+ baseUrl: LLM_BASE,
4344
+ toolHubUrl: TOOL_HUB_BASE,
4345
+ apiKeyUuid: API_KEY_UUID,
4346
+ modelName: MODEL,
4347
+ messages,
4348
+ localTools,
4349
+ remoteTools,
4350
+ hostWideTools,
4351
+ aiActions: window[ACTIONS_GLOBAL] || {},
4352
+ maxRounds: MAX_ROUNDS,
4353
+ provider: LLM_PROVIDER === "ollama" ? "ollama" : "hub",
4354
+ signal: currentAbort.signal,
4355
+ onToolCall: function(toolCall) {
4356
+ announceToolCall(toolCall);
4357
+ },
4358
+ onToolDispatched: function(outcome) {
4359
+ resolveToolCall(outcome);
4360
+ },
4361
+ onPhase: function(name) {
4362
+ if (name === "responding") markDone(assistantBody.roleLabel);
4363
+ else markWorking(assistantBody.roleLabel);
4364
+ },
4365
+ onRoundStart: function(roundIdx) {
4366
+ markWorking(assistantBody.roleLabel);
4367
+ collapseThinkingBlock();
4368
+ },
4369
+ onThinkingDelta: function(delta) {
4370
+ var body = ensureThinkingBlock();
4371
+ body.appendChild(document.createTextNode(delta));
4372
+ body.scrollTop = body.scrollHeight;
4373
+ historyEl.scrollTop = historyEl.scrollHeight;
4374
+ },
4375
+ onTextDelta: function(delta) {
4376
+ markDone(assistantBody.roleLabel);
4377
+ collapseThinkingBlock();
4378
+ assistantMarkdown += delta;
4379
+ renderMarkdownInto(assistantBody, assistantMarkdown);
4380
+ historyEl.scrollTop = historyEl.scrollHeight;
4381
+ }
4382
+ });
4383
+ conversation.push({ role: "user", content: userText });
4384
+ conversation.push({
4385
+ role: "assistant",
4386
+ content: result.content,
4387
+ // Kept so a restored transcript still shows what ran — losing
4388
+ // that record is what made a navigating action unbearable.
4389
+ tools: result.dispatched.map(function(d) {
4390
+ return d.error ? { name: d.toolCall.name, error: true } : { name: d.toolCall.name };
4391
+ })
4392
+ });
4393
+ persistConversation();
4394
+ result.dispatched.forEach(function(d) {
4395
+ resolveToolCall(d);
4396
+ });
4397
+ pendingChips.forEach(function(chip) {
4398
+ chip.className = "lmw-tool-chip";
4399
+ chip.textContent = "\u{1F527} " + chip.dataset.tool;
4400
+ });
4401
+ pendingChips = [];
4402
+ if (result.skipped.length > 0) {
4403
+ appendTurn("system", "Not available on this page: " + result.skipped.map(function(t) {
4404
+ return t.name;
4405
+ }).join(", "));
4406
+ }
4407
+ if (result.stopped_reason && result.stopped_reason !== "duplicate_tool_calls") {
4408
+ appendTurn(
4409
+ "system",
4410
+ "The assistant reached its tool-use limit before finishing. The reply above may be incomplete \u2014 try asking again or rephrasing."
4411
+ );
4412
+ }
4413
+ } catch (err) {
4414
+ if (err.name === "AbortError" || /aborted/i.test(err.message || "")) {
4415
+ appendTurn("system", "\u23F9 stopped");
4416
+ } else {
4417
+ appendTurn("error", err.message);
4418
+ }
4419
+ } finally {
4420
+ markDone(assistantBody.roleLabel);
4421
+ collapseThinkingBlock();
4422
+ activeAssistantBody = null;
4423
+ currentAbort = null;
4424
+ }
4425
+ });
4426
+ }
4427
+ export {
4428
+ LlmMetaWidgetElement
4429
+ };