@ykurilov/agent-chat-diagrams 0.0.0-stage → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,238 @@
1
+ # @ykurilov/agent-chat-diagrams
2
+
3
+ ## 0.55.0
4
+
5
+ No changes in this release.
6
+
7
+ ## 0.54.0
8
+
9
+ No changes in this release.
10
+
11
+ ## 0.53.0
12
+
13
+ No changes in this release.
14
+
15
+ ## 0.52.0
16
+
17
+ No changes in this release.
18
+
19
+ ## 0.51.0
20
+
21
+ No changes in this release.
22
+
23
+ ## 0.50.0
24
+
25
+ No changes in this release.
26
+
27
+ ## 0.49.0
28
+
29
+ No changes in this release.
30
+
31
+ ## 0.48.0
32
+
33
+ No changes in this release.
34
+
35
+ ## 0.47.0
36
+
37
+ No changes in this release.
38
+
39
+ ## 0.46.2
40
+
41
+ No changes in this release.
42
+
43
+ ## 0.46.1
44
+
45
+ No changes in this release.
46
+
47
+ ## 0.46.0
48
+
49
+ No changes in this release.
50
+
51
+ ## 0.45.0
52
+
53
+ No changes in this release.
54
+
55
+ ## 0.44.0
56
+
57
+ No changes in this release.
58
+
59
+ ## 0.43.0
60
+
61
+ No changes in this release.
62
+
63
+ ## 0.42.0
64
+
65
+ No changes in this release.
66
+
67
+ ## 0.41.0
68
+
69
+ No changes in this release.
70
+
71
+ ## 0.40.0
72
+
73
+ No changes in this release.
74
+
75
+ ## 0.39.1
76
+
77
+ No changes in this release.
78
+
79
+ ## 0.39.0
80
+
81
+ No changes in this release.
82
+
83
+ ## 0.38.0
84
+
85
+ No changes in this release.
86
+
87
+ ## 0.37.0
88
+
89
+ No changes in this release.
90
+
91
+ ## 0.36.0
92
+
93
+ No changes in this release.
94
+
95
+ ## 0.35.0
96
+
97
+ No changes in this release.
98
+
99
+ ## 0.34.0
100
+
101
+ No changes in this release.
102
+
103
+ ## 0.33.0
104
+
105
+ No changes in this release.
106
+
107
+ ## 0.32.0
108
+
109
+ No changes in this release.
110
+
111
+ ## 0.31.2
112
+
113
+ No changes in this release.
114
+
115
+ ## 0.31.1
116
+
117
+ No changes in this release.
118
+
119
+ ## 0.31.0
120
+
121
+ No changes in this release.
122
+
123
+ ## 0.30.0
124
+
125
+ No changes in this release.
126
+
127
+ ## 0.29.0
128
+
129
+ No changes in this release.
130
+
131
+ ## 0.28.0
132
+
133
+ No changes in this release.
134
+
135
+ ## 0.27.0
136
+
137
+ No changes in this release.
138
+
139
+ ## 0.26.2
140
+
141
+ No changes in this release.
142
+
143
+ ## 0.26.1
144
+
145
+ No changes in this release.
146
+
147
+ ## 0.26.0
148
+
149
+ No changes in this release.
150
+
151
+ ## 0.25.0
152
+
153
+ No changes in this release.
154
+
155
+ ## 0.24.0
156
+
157
+ No changes in this release.
158
+
159
+ ## 0.23.0
160
+
161
+ No changes in this release.
162
+
163
+ ## 0.22.0
164
+
165
+ No changes in this release.
166
+
167
+ ## 0.21.0
168
+
169
+ No changes in this release.
170
+
171
+ ## 0.20.0
172
+
173
+ No changes in this release.
174
+
175
+ ## 0.19.0
176
+
177
+ No changes in this release.
178
+
179
+ ## 0.18.6
180
+
181
+ No changes in this release.
182
+
183
+ ## 0.18.5
184
+
185
+ No changes in this release.
186
+
187
+ ## 0.18.4
188
+
189
+ No changes in this release.
190
+
191
+ ## 0.18.3
192
+
193
+ No changes in this release.
194
+
195
+ ## 0.18.2
196
+
197
+ No changes in this release.
198
+
199
+ ## 0.18.1
200
+
201
+ No changes in this release.
202
+
203
+ ## 0.18.0
204
+
205
+ No changes in this release.
206
+
207
+ ## 0.17.0
208
+
209
+ No changes in this release.
210
+
211
+ ## 0.16.0
212
+
213
+ ### Minor Changes
214
+
215
+ - cf52f10: **Ломает только исчерпывающие проверки:** у `UsageSource` появилось значение `images`, у видов ошибок сервера — `widget_not_pending` и `invalid_answer`, у `ChatService` — метод `answerWidget`. Своё хранилище должно поддержать условную запись `AwaitingMessagePatch.expectedParts` (при несовпадении частей — `MessageNotAwaitingError`; помощник `sameMessageParts` из core), иначе две копии сервера могут затереть ответы друг друга; общие тесты хранилищ это проверяют. Остальное необязательно, приложение без правок работает как раньше.
216
+
217
+ Что нового: агент может спросить кнопками, звёздами или формой (виджеты), нарисовать схему блоком ` ```mermaid ` и создать картинки через Runware. Всё включается отдельно:
218
+
219
+ - виджеты: `agent: { widgets: { basic: true, custom: [свойВиджет] } }`, свой виджет — `defineWidget` из core и компонент в `widgets={{ … }}` у `AgentChat`; ответ человека идёт на `POST /:chatId/widgets/:toolCallId`, ход ждёт со статусом `awaiting_approval`;
220
+ - схемы: новый пакет `@ykurilov/agent-chat-diagrams`, `agent: { diagrams: true }` на сервере и `diagrams={diagramsClient}` на странице;
221
+ - картинки: новый пакет `@ykurilov/agent-chat-images` (нужен пакет вложений и `RUNWARE_API_KEY`), `images.tools(model)` в `agent.tools`, `images={imagesClient}` на странице; свой срок ожидания (210 с) инструмент сообщает ядру сам (`declareToolTimeout`), расход бронируется до заказа (`assertWithinLimit({ pendingUsd })`);
222
+ - окно просмотра картинок и схем: Тандем готов, своему скину — необязательные части договора версии 13 (`Widget`, `SuggestedReplies`, `GeneratedImages`, `MediaViewer`, `diagramColors`, `renderDiagram`). `splitMermaidBlocks` экспортируется из react;
223
+ - вложения: `Attachments.storage` и `detectFileKind` экспортируются из пакета вложений;
224
+ - голосовой режим произносит вопрос и варианты ждущего виджета и говорит «схема на экране» вместо блока схемы;
225
+ - «Стоп» на экране закрывает висящие вызовы так же, как сервер, разрешённые без результата — «результат неизвестен» (`stoppedToolCallReasons.resultUnknown`);
226
+ - упавший свой виджет страницы заменяется запасным видом скина, лента цела.
227
+
228
+ Подробно: `docs/widgets.md`, `docs/diagrams.md`, `docs/images.md`, `docs/media-viewer.md`.
229
+
230
+ ### Как обновить приложение
231
+
232
+ Обычному приложению на SQLite или PostgreSQL из набора делать ничего не нужно: миграций нет, всё новое включается отдельно.
233
+
234
+ 1. Исчерпывающий `switch` по `UsageSource` — добавьте ветку `"images"`; по видам ошибок сервера — `"widget_not_pending"` и `"invalid_answer"`.
235
+ 2. Своя реализация `ChatService` — добавьте метод `answerWidget({ chatId, ownerId, toolCallId, answer, page }): Promise<{ resumed: boolean }>` (тип `WidgetAnswerRequest` из core, образец — служба из core).
236
+ 3. Своё хранилище: `updateAwaitingMessage` получает необязательное поле `expectedParts`. Если оно есть и части сообщения в базе с ним не совпадают (`sameMessageParts` из core), бросайте `MessageNotAwaitingError`; проверку и запись делайте в одной транзакции. Прогоните общие тесты хранилищ из core.
237
+ 4. Своя обёртка `assertWithinLimit` из limits — пробрасывайте новое необязательное поле `pendingUsd`.
238
+ 5. Проверка: `pnpm typecheck`, затем в чате отправить сообщение и получить ответ.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yury Kurilov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,28 @@
1
- # Temporary Holding Version
1
+ # @ykurilov/agent-chat-diagrams
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Схемы в ответах агента: блок ` ```mermaid ` рисуется на экране и открывается в окне просмотра. Отдельное расширение:
4
+ приложению без схем пакет не нужен, и в его сборке нет ни байта mermaid (он грузится отдельным куском при первой схеме).
5
+ Серверу пакет не нужен.
6
+
7
+ ```bash
8
+ pnpm add @ykurilov/agent-chat-diagrams
9
+ ```
10
+
11
+ ```ts
12
+ // сервер: одна постоянная строка в инструкции агента
13
+ createChatHandler({ …, agent: { instructions: appInstructions, diagrams: true } });
14
+ ```
15
+
16
+ ```tsx
17
+ // страница
18
+ import { diagramsClient } from "@ykurilov/agent-chat-diagrams";
19
+ <AgentChat … diagrams={diagramsClient} />;
20
+ ```
21
+
22
+ - Рисует `mermaid` 12 в строгом режиме; цвета берутся у скина (`diagramColors`), смена темы перерисовывает схему.
23
+ - Готовый SVG чистится (`cleanSvg`): без скриптов, `foreignObject`, ссылок наружу и обработчиков событий.
24
+ - Пределы: до 20 000 знаков и 200 узлов. Не нарисовалось — скин показывает исходный текст блоком кода.
25
+ - Без `diagrams` блок остаётся обычным кодом.
26
+
27
+ Скину нужны необязательные `renderDiagram` и `onOpenDiagram` у `AgentText` (договор версии 13); в Тандеме они есть.
28
+ Подробно — [docs/diagrams.md](../../docs/diagrams.md).
package/dist/index.js ADDED
@@ -0,0 +1,279 @@
1
+ // src/clean-svg.ts
2
+ var SVG_NS = "http://www.w3.org/2000/svg";
3
+ var ALLOWED_ELEMENTS = /* @__PURE__ */ new Set([
4
+ "svg",
5
+ "g",
6
+ "defs",
7
+ "style",
8
+ "title",
9
+ "desc",
10
+ "symbol",
11
+ "use",
12
+ "marker",
13
+ "clippath",
14
+ "mask",
15
+ "pattern",
16
+ "lineargradient",
17
+ "radialgradient",
18
+ "stop",
19
+ "path",
20
+ "rect",
21
+ "circle",
22
+ "ellipse",
23
+ "line",
24
+ "polyline",
25
+ "polygon",
26
+ "text",
27
+ "tspan",
28
+ "textpath",
29
+ "filter",
30
+ "feblend",
31
+ "fecolormatrix",
32
+ "fecomponenttransfer",
33
+ "fecomposite",
34
+ "fedropshadow",
35
+ "feflood",
36
+ "fefunca",
37
+ "fefuncb",
38
+ "fefuncg",
39
+ "fefuncr",
40
+ "fegaussianblur",
41
+ "femerge",
42
+ "femergenode",
43
+ "femorphology",
44
+ "feoffset"
45
+ ]);
46
+ var OUTSIDE_URL = /url\(\s*(?!['"]?\s*#)/;
47
+ var LOADING_FUNCTION = /(?:^|[^a-z0-9_-])(?:-[a-z]+-)?(?:image-set|image|cross-fade|element|src)\s*\(/;
48
+ var BLOCKED_PROPERTIES = /* @__PURE__ */ new Set(["behavior", "-moz-binding"]);
49
+ var escapesFrame = (property, value) => property === "position" && !/^(?:static|relative)$/i.test(value);
50
+ var PROPERTY_NAME = /^(?:--|-)?[a-z][a-z0-9-]*$/;
51
+ function decodeCss(value) {
52
+ return value.replace(/\\([0-9a-f]{1,6})\s?/gi, (_whole, hex) => String.fromCodePoint(Math.min(Number.parseInt(hex, 16), 1114111) || 65533)).replace(/\\([\s\S])/g, "$1").replace(/\/\*[\s\S]*?(?:\*\/|$)/g, "");
53
+ }
54
+ function loadsOutside(value) {
55
+ const plain = decodeCss(value).toLowerCase();
56
+ return OUTSIDE_URL.test(plain) || LOADING_FUNCTION.test(plain) || /expression\(|javascript:|@import/.test(plain);
57
+ }
58
+ function splitTopLevel(text, separator) {
59
+ const parts = [];
60
+ let quote = null;
61
+ let depth = 0;
62
+ let start = 0;
63
+ for (let index = 0; index < text.length; index++) {
64
+ const char = text[index];
65
+ if (quote) {
66
+ if (char === quote || char === "\n") quote = null;
67
+ } else if (char === '"' || char === "'") quote = char;
68
+ else if (char === "(" || char === "[") depth++;
69
+ else if (char === ")" || char === "]") depth = Math.max(0, depth - 1);
70
+ else if (char === separator && depth === 0) {
71
+ parts.push(text.slice(start, index));
72
+ start = index + 1;
73
+ }
74
+ }
75
+ parts.push(text.slice(start));
76
+ return parts;
77
+ }
78
+ function cssBlocks(css) {
79
+ const blocks = [];
80
+ let quote = null;
81
+ let depth = 0;
82
+ let start = 0;
83
+ let bodyStart = 0;
84
+ let prelude = "";
85
+ for (let index = 0; index < css.length; index++) {
86
+ const char = css[index];
87
+ if (quote) {
88
+ if (char === quote || char === "\n") quote = null;
89
+ } else if (char === '"' || char === "'") quote = char;
90
+ else if (char === "{") {
91
+ if (depth++ === 0) {
92
+ prelude = css.slice(start, index).trim();
93
+ bodyStart = index + 1;
94
+ }
95
+ } else if (char === "}") {
96
+ if (depth === 0) start = index + 1;
97
+ else if (--depth === 0) {
98
+ blocks.push({ prelude, body: css.slice(bodyStart, index) });
99
+ start = index + 1;
100
+ }
101
+ } else if (char === ";" && depth === 0) {
102
+ blocks.push({ prelude: css.slice(start, index).trim(), body: null });
103
+ start = index + 1;
104
+ }
105
+ }
106
+ return blocks;
107
+ }
108
+ function quotesClosed(text) {
109
+ let quote = null;
110
+ for (const char of text) {
111
+ if (quote) {
112
+ if (char === "\n") return false;
113
+ if (char === quote) quote = null;
114
+ } else if (char === '"' || char === "'") quote = char;
115
+ }
116
+ return quote === null;
117
+ }
118
+ var AMBIGUOUS_CSS = /[\\{};<@]|\/\*|\*\//;
119
+ function scopedSelectors(prelude, rootId) {
120
+ const scope = `#${rootId}`;
121
+ const kept = splitTopLevel(prelude, ",").map((selector) => selector.trim()).filter(
122
+ (selector) => selector.startsWith(scope) && /^(?:$|[\s.:[>])/.test(selector.slice(scope.length)) && !/[~+]/.test(selector) && quotesClosed(selector) && !AMBIGUOUS_CSS.test(selector)
123
+ );
124
+ return kept.length > 0 ? kept.join(",") : null;
125
+ }
126
+ function cleanDeclarations(body) {
127
+ return splitTopLevel(body, ";").flatMap((declaration) => {
128
+ const colon = declaration.indexOf(":");
129
+ if (colon < 0) return [];
130
+ const property = declaration.slice(0, colon).trim().toLowerCase();
131
+ const value = declaration.slice(colon + 1).trim();
132
+ const safe = PROPERTY_NAME.test(property) && !BLOCKED_PROPERTIES.has(property) && !escapesFrame(property, value) && value.length > 0 && !AMBIGUOUS_CSS.test(value) && quotesClosed(value) && !loadsOutside(value);
133
+ return safe ? [`${declaration.slice(0, colon).trim()}:${value}`] : [];
134
+ }).join(";");
135
+ }
136
+ function cleanCss(css, rootId) {
137
+ if (!rootId || !/^[\w-]+$/.test(rootId)) return "";
138
+ const kept = [];
139
+ for (const { prelude, body } of cssBlocks(css)) {
140
+ if (body === null || prelude.includes("@")) continue;
141
+ const selectors = scopedSelectors(prelude, rootId);
142
+ const declarations = selectors && cleanDeclarations(body);
143
+ if (selectors && declarations) kept.push(`${selectors}{${declarations}}`);
144
+ }
145
+ return kept.join("");
146
+ }
147
+ function isUnsafeAttribute(name, value) {
148
+ const lower = name.toLowerCase();
149
+ if (lower.startsWith("on")) return true;
150
+ if (lower === "href" || lower.endsWith(":href")) return !value.trim().startsWith("#");
151
+ if (lower === "src" || lower === "xml:base" || lower === "formaction" || lower === "action") return true;
152
+ return /javascript:/i.test(value) || loadsOutside(value);
153
+ }
154
+ function usesOutside(element) {
155
+ return [...element.attributes].some(({ name, value }) => (name === "href" || name.endsWith(":href")) && !value.trim().startsWith("#"));
156
+ }
157
+ function cleanAttributes(element) {
158
+ for (const { name, value } of [...element.attributes]) {
159
+ if (name.toLowerCase() === "style") element.setAttribute(name, cleanDeclarations(value));
160
+ else if (isUnsafeAttribute(name, value)) element.removeAttribute(name);
161
+ }
162
+ }
163
+ function unwrap(element) {
164
+ element.replaceWith(...element.childNodes);
165
+ }
166
+ function cleanSvg(markup) {
167
+ const doc = new DOMParser().parseFromString(markup, "text/html");
168
+ const root = doc.querySelector("svg");
169
+ if (!root || root.namespaceURI !== SVG_NS) return "";
170
+ for (const element of [root, ...root.querySelectorAll("*")]) {
171
+ if (!root.contains(element)) continue;
172
+ const name = element.localName.toLowerCase();
173
+ if (element.namespaceURI === SVG_NS && name === "a") {
174
+ unwrap(element);
175
+ continue;
176
+ }
177
+ if (element.namespaceURI !== SVG_NS || !ALLOWED_ELEMENTS.has(name) || name === "use" && usesOutside(element)) {
178
+ element.remove();
179
+ continue;
180
+ }
181
+ cleanAttributes(element);
182
+ if (name === "style") element.textContent = cleanCss(element.textContent ?? "", root.getAttribute("id"));
183
+ }
184
+ return root.outerHTML;
185
+ }
186
+
187
+ // src/diagrams-client.ts
188
+ var MAX_SOURCE_LENGTH = 2e4;
189
+ var MAX_DIAGRAM_NODES = 200;
190
+ var CACHE_SIZE = 100;
191
+ var LINK = /<?(?:-{2,}|={2,}|-\.+-|~{3,}|->>)[>ox]?/g;
192
+ function countDiagramNodes(source) {
193
+ const statements = source.split(/[\n;]/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("%%")).slice(1);
194
+ return statements.reduce((total, statement) => total + Math.max(1, statement.match(LINK)?.length ?? 0), 0);
195
+ }
196
+ function mermaidConfig(colors) {
197
+ return {
198
+ startOnLoad: false,
199
+ securityLevel: "strict",
200
+ // Подписи — текстом SVG, а не HTML: без foreignObject, и рисунок остаётся векторным.
201
+ htmlLabels: false,
202
+ flowchart: { htmlLabels: false },
203
+ // Ошибка разбора не рисуется на странице: скин сам покажет исходник.
204
+ suppressErrorRendering: true,
205
+ maxTextSize: MAX_SOURCE_LENGTH,
206
+ theme: "base",
207
+ themeVariables: {
208
+ darkMode: colors.dark,
209
+ background: colors.background,
210
+ fontFamily: "inherit",
211
+ textColor: colors.text,
212
+ primaryColor: colors.node,
213
+ primaryTextColor: colors.text,
214
+ primaryBorderColor: colors.line,
215
+ secondaryColor: colors.background,
216
+ secondaryTextColor: colors.text,
217
+ secondaryBorderColor: colors.accent,
218
+ tertiaryColor: colors.background,
219
+ tertiaryTextColor: colors.text,
220
+ tertiaryBorderColor: colors.line,
221
+ mainBkg: colors.node,
222
+ nodeBorder: colors.line,
223
+ lineColor: colors.line,
224
+ clusterBkg: colors.background,
225
+ clusterBorder: colors.accent,
226
+ edgeLabelBackground: colors.background,
227
+ actorBkg: colors.node,
228
+ actorBorder: colors.line,
229
+ actorTextColor: colors.text,
230
+ signalColor: colors.line,
231
+ signalTextColor: colors.text,
232
+ noteBkgColor: colors.background,
233
+ noteTextColor: colors.text,
234
+ noteBorderColor: colors.accent
235
+ }
236
+ };
237
+ }
238
+ function createDiagramsClient() {
239
+ let mermaid = null;
240
+ let queue = Promise.resolve();
241
+ let renderCount = 0;
242
+ const cache = /* @__PURE__ */ new Map();
243
+ const load = () => mermaid ??= import("mermaid").then((module) => module.default);
244
+ async function draw(source, colors) {
245
+ if (source.length > MAX_SOURCE_LENGTH) return { error: `\u0421\u0445\u0435\u043C\u0430 \u0434\u043B\u0438\u043D\u043D\u0435\u0435 ${MAX_SOURCE_LENGTH} \u0437\u043D\u0430\u043A\u043E\u0432` };
246
+ if (countDiagramNodes(source) > MAX_DIAGRAM_NODES) return { error: `\u0412 \u0441\u0445\u0435\u043C\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${MAX_DIAGRAM_NODES} \u0443\u0437\u043B\u043E\u0432` };
247
+ try {
248
+ const api = await load();
249
+ api.initialize(mermaidConfig(colors));
250
+ const { svg } = await api.render(`agent-chat-diagram-${++renderCount}`, source);
251
+ const clean = cleanSvg(svg);
252
+ return clean ? { svg: clean } : { error: "\u0420\u0438\u0441\u043E\u0432\u0430\u043B\u044C\u0449\u0438\u043A \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B \u0440\u0438\u0441\u0443\u043D\u043E\u043A" };
253
+ } catch (error) {
254
+ return { error: error instanceof Error ? error.message : String(error) };
255
+ }
256
+ }
257
+ return {
258
+ render(source, colors) {
259
+ const key = `${JSON.stringify(colors)}
260
+ ${source}`;
261
+ const cached = cache.get(key);
262
+ if (cached) return cached;
263
+ const result = queue.then(() => draw(source, colors));
264
+ queue = result;
265
+ if (cache.size >= CACHE_SIZE) cache.delete(cache.keys().next().value);
266
+ cache.set(key, result);
267
+ return result;
268
+ }
269
+ };
270
+ }
271
+
272
+ // src/index.ts
273
+ var diagramsClient = /* @__PURE__ */ createDiagramsClient();
274
+ export {
275
+ cleanSvg,
276
+ createDiagramsClient,
277
+ diagramsClient
278
+ };
279
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/clean-svg.ts","../src/diagrams-client.ts","../src/index.ts"],"sourcesContent":["/*\n * Рисунок схемы скин кладёт в страницу разметкой, как есть: так схема остаётся векторной. Поэтому всё, что может\n * исполниться или куда-то сходить, вычищается здесь (FR-027). Разрешён короткий список элементов SVG, которыми рисует\n * mermaid; остальное удаляется вместе с содержимым. Ссылка `<a>` снимается, а её текст остаётся.\n */\n\nconst SVG_NS = \"http://www.w3.org/2000/svg\";\n\n/** Элементы, которыми рисует mermaid, в нижнем регистре. `a` сюда не входит: она снимается отдельно. */\nconst ALLOWED_ELEMENTS = new Set([\n \"svg\", \"g\", \"defs\", \"style\", \"title\", \"desc\", \"symbol\", \"use\", \"marker\", \"clippath\", \"mask\", \"pattern\",\n \"lineargradient\", \"radialgradient\", \"stop\", \"path\", \"rect\", \"circle\", \"ellipse\", \"line\", \"polyline\", \"polygon\",\n \"text\", \"tspan\", \"textpath\", \"filter\", \"feblend\", \"fecolormatrix\", \"fecomponenttransfer\", \"fecomposite\",\n \"fedropshadow\", \"feflood\", \"fefunca\", \"fefuncb\", \"fefuncg\", \"fefuncr\", \"fegaussianblur\", \"femerge\", \"femergenode\",\n \"femorphology\", \"feoffset\",\n]);\n\n/*\n * CSS чистится белым списком. Остаются только правила вида «#<id корня> …{свойство:значение}» без вложенных блоков;\n * любые @-правила (@import, @font-face, @media, @keyframes) уходят целиком. Объявление или селектор с обратной косой\n * чертой (экранирование: «\\\\75rl(» браузер читает как «url(»), кавычкой без пары, скобкой или комментарием\n * отбрасывается: в рисунке mermaid таких нет, а собранная заново таблица читается браузером ровно так, как здесь.\n */\n\n/** `url(` не на свою метку `#…` — после нормализации, в нижнем регистре. */\nconst OUTSIDE_URL = /url\\(\\s*(?!['\"]?\\s*#)/;\n/** Функции CSS, которые сами грузят адрес: `image-set(\"…\")`, `-webkit-image-set`, `image()`, `cross-fade()`, `src()`. */\nconst LOADING_FUNCTION = /(?:^|[^a-z0-9_-])(?:-[a-z]+-)?(?:image-set|image|cross-fade|element|src)\\s*\\(/;\n/** Свойства, которые исполнили бы код. */\nconst BLOCKED_PROPERTIES = new Set([\"behavior\", \"-moz-binding\"]);\n/** `position: absolute | fixed | sticky` вынес бы рисунок из его рамки поверх страницы. */\nconst escapesFrame = (property: string, value: string) => property === \"position\" && !/^(?:static|relative)$/i.test(value);\nconst PROPERTY_NAME = /^(?:--|-)?[a-z][a-z0-9-]*$/;\n\n/** Значение, как его прочтёт браузер: экранирование раскрыто, комментарии убраны. Только для проверок. */\nfunction decodeCss(value: string): string {\n return value\n .replace(/\\\\([0-9a-f]{1,6})\\s?/gi, (_whole, hex: string) => String.fromCodePoint(Math.min(Number.parseInt(hex, 16), 0x10ffff) || 0xfffd))\n .replace(/\\\\([\\s\\S])/g, \"$1\")\n .replace(/\\/\\*[\\s\\S]*?(?:\\*\\/|$)/g, \"\");\n}\n\n/** Может ли значение что-то загрузить или исполнить. */\nfunction loadsOutside(value: string): boolean {\n const plain = decodeCss(value).toLowerCase();\n return OUTSIDE_URL.test(plain) || LOADING_FUNCTION.test(plain) || /expression\\(|javascript:|@import/.test(plain);\n}\n\n/** Части строки, разделённые `separator` вне кавычек и скобок. */\nfunction splitTopLevel(text: string, separator: string): string[] {\n const parts: string[] = [];\n let quote: string | null = null;\n let depth = 0;\n let start = 0;\n for (let index = 0; index < text.length; index++) {\n const char = text[index];\n if (quote) {\n if (char === quote || char === \"\\n\") quote = null;\n } else if (char === '\"' || char === \"'\") quote = char;\n else if (char === \"(\" || char === \"[\") depth++;\n else if (char === \")\" || char === \"]\") depth = Math.max(0, depth - 1);\n else if (char === separator && depth === 0) {\n parts.push(text.slice(start, index));\n start = index + 1;\n }\n }\n parts.push(text.slice(start));\n return parts;\n}\n\n/** Блоки верхнего уровня таблицы: `prelude{body}`; у правила без блока (`@import …;`) `body` — null. */\nfunction cssBlocks(css: string): Array<{ prelude: string; body: string | null }> {\n const blocks: Array<{ prelude: string; body: string | null }> = [];\n let quote: string | null = null;\n let depth = 0;\n let start = 0;\n let bodyStart = 0;\n let prelude = \"\";\n for (let index = 0; index < css.length; index++) {\n const char = css[index];\n if (quote) {\n if (char === quote || char === \"\\n\") quote = null;\n } else if (char === '\"' || char === \"'\") quote = char;\n else if (char === \"{\") {\n if (depth++ === 0) {\n prelude = css.slice(start, index).trim();\n bodyStart = index + 1;\n }\n } else if (char === \"}\") {\n if (depth === 0) start = index + 1;\n else if (--depth === 0) {\n blocks.push({ prelude, body: css.slice(bodyStart, index) });\n start = index + 1;\n }\n } else if (char === \";\" && depth === 0) {\n blocks.push({ prelude: css.slice(start, index).trim(), body: null });\n start = index + 1;\n }\n }\n return blocks;\n}\n\n/** Каждая кавычка закрыта на той же строке. */\nfunction quotesClosed(text: string): boolean {\n let quote: string | null = null;\n for (const char of text) {\n if (quote) {\n if (char === \"\\n\") return false;\n if (char === quote) quote = null;\n } else if (char === '\"' || char === \"'\") quote = char;\n }\n return quote === null;\n}\n\n/** Знаки, после которых собранная заново таблица прочиталась бы браузером иначе, чем здесь. */\nconst AMBIGUOUS_CSS = /[\\\\{};<@]|\\/\\*|\\*\\//;\n\n/**\n * Селекторы правила, ограниченные корнем рисунка: каждый начинается с `#<id>`, без соседей (`+`, `~`), которые\n * дотянулись бы до страницы рядом. Не ограниченные отбрасываются; не осталось ни одного — null.\n */\nfunction scopedSelectors(prelude: string, rootId: string): string | null {\n const scope = `#${rootId}`;\n const kept = splitTopLevel(prelude, \",\")\n .map((selector) => selector.trim())\n .filter(\n (selector) =>\n selector.startsWith(scope) &&\n /^(?:$|[\\s.:[>])/.test(selector.slice(scope.length)) &&\n !/[~+]/.test(selector) &&\n quotesClosed(selector) &&\n !AMBIGUOUS_CSS.test(selector),\n );\n return kept.length > 0 ? kept.join(\",\") : null;\n}\n\n/** Объявления `свойство:значение` без загрузок, вложенных блоков и опасных свойств. */\nfunction cleanDeclarations(body: string): string {\n return splitTopLevel(body, \";\")\n .flatMap((declaration) => {\n const colon = declaration.indexOf(\":\");\n if (colon < 0) return [];\n const property = declaration.slice(0, colon).trim().toLowerCase();\n const value = declaration.slice(colon + 1).trim();\n const safe =\n PROPERTY_NAME.test(property) &&\n !BLOCKED_PROPERTIES.has(property) &&\n !escapesFrame(property, value) &&\n value.length > 0 &&\n !AMBIGUOUS_CSS.test(value) &&\n quotesClosed(value) &&\n !loadsOutside(value);\n return safe ? [`${declaration.slice(0, colon).trim()}:${value}`] : [];\n })\n .join(\";\");\n}\n\n/** Таблица стилей рисунка: только простые правила внутри корня `#<rootId>`; нет id — пусто. */\nfunction cleanCss(css: string, rootId: string | null): string {\n if (!rootId || !/^[\\w-]+$/.test(rootId)) return \"\";\n const kept: string[] = [];\n for (const { prelude, body } of cssBlocks(css)) {\n if (body === null || prelude.includes(\"@\")) continue;\n const selectors = scopedSelectors(prelude, rootId);\n const declarations = selectors && cleanDeclarations(body);\n if (selectors && declarations) kept.push(`${selectors}{${declarations}}`);\n }\n return kept.join(\"\");\n}\n\nfunction isUnsafeAttribute(name: string, value: string): boolean {\n const lower = name.toLowerCase();\n if (lower.startsWith(\"on\")) return true;\n if (lower === \"href\" || lower.endsWith(\":href\")) return !value.trim().startsWith(\"#\");\n if (lower === \"src\" || lower === \"xml:base\" || lower === \"formaction\" || lower === \"action\") return true;\n return /javascript:/i.test(value) || loadsOutside(value);\n}\n\n/** `<use>` с чужим адресом удаляется целиком: без адреса он ничего не рисует. */\nfunction usesOutside(element: Element): boolean {\n return [...element.attributes].some(({ name, value }) => (name === \"href\" || name.endsWith(\":href\")) && !value.trim().startsWith(\"#\"));\n}\n\nfunction cleanAttributes(element: Element): void {\n for (const { name, value } of [...element.attributes]) {\n if (name.toLowerCase() === \"style\") element.setAttribute(name, cleanDeclarations(value));\n else if (isUnsafeAttribute(name, value)) element.removeAttribute(name);\n }\n}\n\n/** Снять ссылку: её содержимое встаёт на её место. */\nfunction unwrap(element: Element): void {\n element.replaceWith(...element.childNodes);\n}\n\n/**\n * Чистая разметка корневого `<svg>` из ответа рисовальщика. Всё вне корневого `<svg>` отбрасывается; нет `<svg>` —\n * пустая строка. Разбор — без исполнения: у документа `DOMParser` нет окна, скрипты и картинки в нём не грузятся.\n */\nexport function cleanSvg(markup: string): string {\n const doc = new DOMParser().parseFromString(markup, \"text/html\");\n const root = doc.querySelector(\"svg\");\n if (!root || root.namespaceURI !== SVG_NS) return \"\";\n for (const element of [root, ...root.querySelectorAll(\"*\")]) {\n if (!root.contains(element)) continue;\n const name = element.localName.toLowerCase();\n if (element.namespaceURI === SVG_NS && name === \"a\") {\n unwrap(element);\n continue;\n }\n if (element.namespaceURI !== SVG_NS || !ALLOWED_ELEMENTS.has(name) || (name === \"use\" && usesOutside(element))) {\n element.remove();\n continue;\n }\n cleanAttributes(element);\n if (name === \"style\") element.textContent = cleanCss(element.textContent ?? \"\", root.getAttribute(\"id\"));\n }\n return root.outerHTML;\n}\n","import { cleanSvg } from \"./clean-svg\";\nimport type { DiagramColors, DiagramsClient } from \"./diagram-types\";\n\n/** Длиннее — не рисуем: тяжёлая схема подвесила бы страницу. */\nexport const MAX_SOURCE_LENGTH = 20_000;\n/** Больше узлов и связей — не рисуем. */\nexport const MAX_DIAGRAM_NODES = 200;\n/** Столько готовых рисунков помнится; самый старый забывается первым. */\nconst CACHE_SIZE = 100;\n\ntype Mermaid = typeof import(\"mermaid\").default;\ntype RenderResult = { svg: string } | { error: string };\n\n// Связь в схеме: «-->», «---», «==>», «-.->», «~~~», «->>» и подобные.\nconst LINK = /<?(?:-{2,}|={2,}|-\\.+-|~{3,}|->>)[>ox]?/g;\n\n/**\n * Оценка числа узлов и связей: каждая строка-утверждение — узел, каждая связь сверх первой в строке — ещё узел\n * («A-->B-->C» — три). Заголовок («graph TD») и комментарии («%%») не считаются.\n */\nexport function countDiagramNodes(source: string): number {\n const statements = source\n .split(/[\\n;]/)\n .map((line) => line.trim())\n .filter((line) => line.length > 0 && !line.startsWith(\"%%\"))\n .slice(1);\n return statements.reduce((total, statement) => total + Math.max(1, statement.match(LINK)?.length ?? 0), 0);\n}\n\nfunction mermaidConfig(colors: DiagramColors) {\n return {\n startOnLoad: false,\n securityLevel: \"strict\" as const,\n // Подписи — текстом SVG, а не HTML: без foreignObject, и рисунок остаётся векторным.\n htmlLabels: false,\n flowchart: { htmlLabels: false },\n // Ошибка разбора не рисуется на странице: скин сам покажет исходник.\n suppressErrorRendering: true,\n maxTextSize: MAX_SOURCE_LENGTH,\n theme: \"base\" as const,\n themeVariables: {\n darkMode: colors.dark,\n background: colors.background,\n fontFamily: \"inherit\",\n textColor: colors.text,\n primaryColor: colors.node,\n primaryTextColor: colors.text,\n primaryBorderColor: colors.line,\n secondaryColor: colors.background,\n secondaryTextColor: colors.text,\n secondaryBorderColor: colors.accent,\n tertiaryColor: colors.background,\n tertiaryTextColor: colors.text,\n tertiaryBorderColor: colors.line,\n mainBkg: colors.node,\n nodeBorder: colors.line,\n lineColor: colors.line,\n clusterBkg: colors.background,\n clusterBorder: colors.accent,\n edgeLabelBackground: colors.background,\n actorBkg: colors.node,\n actorBorder: colors.line,\n actorTextColor: colors.text,\n signalColor: colors.line,\n signalTextColor: colors.text,\n noteBkgColor: colors.background,\n noteTextColor: colors.text,\n noteBorderColor: colors.accent,\n },\n };\n}\n\n/**\n * Рисовальщик схем: mermaid грузится при первом рисунке (FR-024), рисует в строгом режиме, рисунок чистится (FR-027).\n * Рисунки идут по одному: настройки mermaid общие, и схема в другой теме не должна взять чужие цвета.\n */\nexport function createDiagramsClient(): DiagramsClient {\n let mermaid: Promise<Mermaid> | null = null;\n let queue: Promise<unknown> = Promise.resolve();\n let renderCount = 0;\n const cache = new Map<string, Promise<RenderResult>>();\n\n const load = () => (mermaid ??= import(\"mermaid\").then((module) => module.default));\n\n async function draw(source: string, colors: DiagramColors): Promise<RenderResult> {\n if (source.length > MAX_SOURCE_LENGTH) return { error: `Схема длиннее ${MAX_SOURCE_LENGTH} знаков` };\n if (countDiagramNodes(source) > MAX_DIAGRAM_NODES) return { error: `В схеме больше ${MAX_DIAGRAM_NODES} узлов` };\n try {\n const api = await load();\n api.initialize(mermaidConfig(colors));\n const { svg } = await api.render(`agent-chat-diagram-${++renderCount}`, source);\n const clean = cleanSvg(svg);\n return clean ? { svg: clean } : { error: \"Рисовальщик не вернул рисунок\" };\n } catch (error) {\n return { error: error instanceof Error ? error.message : String(error) };\n }\n }\n\n return {\n render(source, colors) {\n const key = `${JSON.stringify(colors)}\\n${source}`;\n const cached = cache.get(key);\n if (cached) return cached;\n const result = queue.then(() => draw(source, colors));\n queue = result;\n if (cache.size >= CACHE_SIZE) cache.delete(cache.keys().next().value!);\n cache.set(key, result);\n return result;\n },\n };\n}\n","// Пакет схем (specs/024-agent-widgets, contracts/diagrams.md): рисовальщик блоков ```mermaid для экрана чата.\nimport { createDiagramsClient } from \"./diagrams-client\";\n\nexport type { DiagramColors, DiagramsClient } from \"./diagram-types\";\nexport { createDiagramsClient } from \"./diagrams-client\";\nexport { cleanSvg } from \"./clean-svg\";\n\n/** Готовый рисовальщик для `<AgentChat diagrams={diagramsClient} />`: mermaid грузится при первой схеме. */\nexport const diagramsClient = /* @__PURE__ */ createDiagramsClient();\n"],"mappings":"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","names":[]}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Чистая разметка корневого `<svg>` из ответа рисовальщика. Всё вне корневого `<svg>` отбрасывается; нет `<svg>` —
3
+ * пустая строка. Разбор — без исполнения: у документа `DOMParser` нет окна, скрипты и картинки в нём не грузятся.
4
+ */
5
+ export declare function cleanSvg(markup: string): string;
@@ -0,0 +1,17 @@
1
+ /** Цвета схемы: их отдаёт скин (`ChatSkin.diagramColors`), по ним рисовальщик красит схему. */
2
+ export interface DiagramColors {
3
+ background: string;
4
+ text: string;
5
+ line: string;
6
+ node: string;
7
+ accent: string;
8
+ dark: boolean;
9
+ }
10
+ /** Рисовальщик схем для свойства `diagrams` экрана чата. */
11
+ export interface DiagramsClient {
12
+ render(source: string, colors: DiagramColors): Promise<{
13
+ svg: string;
14
+ } | {
15
+ error: string;
16
+ }>;
17
+ }
@@ -0,0 +1,15 @@
1
+ import type { DiagramsClient } from "./diagram-types.js";
2
+ /** Длиннее — не рисуем: тяжёлая схема подвесила бы страницу. */
3
+ export declare const MAX_SOURCE_LENGTH = 20000;
4
+ /** Больше узлов и связей — не рисуем. */
5
+ export declare const MAX_DIAGRAM_NODES = 200;
6
+ /**
7
+ * Оценка числа узлов и связей: каждая строка-утверждение — узел, каждая связь сверх первой в строке — ещё узел
8
+ * («A-->B-->C» — три). Заголовок («graph TD») и комментарии («%%») не считаются.
9
+ */
10
+ export declare function countDiagramNodes(source: string): number;
11
+ /**
12
+ * Рисовальщик схем: mermaid грузится при первом рисунке (FR-024), рисует в строгом режиме, рисунок чистится (FR-027).
13
+ * Рисунки идут по одному: настройки mermaid общие, и схема в другой теме не должна взять чужие цвета.
14
+ */
15
+ export declare function createDiagramsClient(): DiagramsClient;
@@ -0,0 +1,5 @@
1
+ export type { DiagramColors, DiagramsClient } from "./diagram-types.js";
2
+ export { createDiagramsClient } from "./diagrams-client.js";
3
+ export { cleanSvg } from "./clean-svg.js";
4
+ /** Готовый рисовальщик для `<AgentChat diagrams={diagramsClient} />`: mermaid грузится при первой схеме. */
5
+ export declare const diagramsClient: import("./diagram-types.js").DiagramsClient;
package/package.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
2
  "name": "@ykurilov/agent-chat-diagrams",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.55.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "files": [
8
+ "dist",
9
+ "README.md",
10
+ "CHANGELOG.md"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "@ykurilov/agent-chat-source": "./src/index.ts",
15
+ "types": "./dist/types/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
19
+ "engines": {
20
+ "node": ">=22"
21
+ },
22
+ "publishConfig": {
23
+ "registry": "https://registry.npmjs.org",
24
+ "access": "public"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/ykurilov/agent-chat-sdk.git",
29
+ "directory": "packages/diagrams"
30
+ },
31
+ "dependencies": {
32
+ "mermaid": "^12.1.0"
33
+ },
34
+ "devDependencies": {
35
+ "jsdom": "^30.1.1"
36
+ },
37
+ "scripts": {
38
+ "build": "node ../../scripts/build-package.mjs",
39
+ "typecheck": "tsc -p tsconfig.json"
40
+ }
6
41
  }