@thinkingos/vsl-sdk 0.1.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/LICENSE.txt +12 -0
- package/README.md +84 -0
- package/dist/index.d.mts +1280 -0
- package/dist/index.d.ts +1280 -0
- package/dist/index.js +1912 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1850 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +44 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM extraction (T1.1.2, ROADMAP.md M1.1).
|
|
3
|
+
*
|
|
4
|
+
* Обходит дерево от корня (по умолчанию document.body) и извлекает все видимые
|
|
5
|
+
* элементы с координатами (getBoundingClientRect), собственным текстом, атрибутами и CSS-подмножеством (Level 3, T1.5.1).
|
|
6
|
+
* Покрывает тот же набор элементов, что и root.querySelectorAll('*'), минус отфильтрованные.
|
|
7
|
+
*
|
|
8
|
+
* Фильтр невидимых (семантика ARCHITECTURE.md; CSS-уровень 3 вне scope M1.1):
|
|
9
|
+
* - непрендеримые теги (script/style/link/meta/noscript/template/head/title/base) —
|
|
10
|
+
* никогда не отрисовываются → пропуск поддерева;
|
|
11
|
+
* - display: none → элемент и всё поддерево не отрисованы → пропуск поддерева;
|
|
12
|
+
* - visibility: hidden → сам элемент невидим, но дети могут быть видимы
|
|
13
|
+
* (visibility: visible) → элемент не включается, поддерево обходится;
|
|
14
|
+
* - нулевой прямоугольник (width <= 0 || height <= 0) → нет видимого бокса, но дети
|
|
15
|
+
* (например, overflow) могут быть видимы → элемент не включается, поддерево обходится.
|
|
16
|
+
*
|
|
17
|
+
* indexPath — индексы среди ЭЛЕМЕНТНЫХ детей на каждом уровне, вычисляются по
|
|
18
|
+
* структуре DOM ДО фильтрации: позиция стабильна независимо от фильтров — основа
|
|
19
|
+
* детерминированных ID VSL (решение note_1789916091535: без Date.now()/Math.random(),
|
|
20
|
+
* база для diffing в M1.2).
|
|
21
|
+
*/
|
|
22
|
+
interface Rect {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
}
|
|
28
|
+
interface ExtractedElement {
|
|
29
|
+
/** Имя тега в нижнем регистре (например, 'button'). */
|
|
30
|
+
tag: string;
|
|
31
|
+
/** Путь от корня обхода: индексы среди элементных детей каждого уровня (до фильтрации). */
|
|
32
|
+
indexPath: number[];
|
|
33
|
+
/** Координаты и размеры из getBoundingClientRect (px, viewport). */
|
|
34
|
+
rect: Rect;
|
|
35
|
+
/** Собственный текст (только прямые текстовые узлы), нормализованный; null если пуст. */
|
|
36
|
+
text: string | null;
|
|
37
|
+
/** Все атрибуты элемента (verbatim). */
|
|
38
|
+
attributes: Record<string, string>;
|
|
39
|
+
/** CSS-подмножество для Level 3 (computed styles в момент extraction, T1.5.1). */
|
|
40
|
+
css?: ElementCss;
|
|
41
|
+
/** Видимые дочерние элементы. */
|
|
42
|
+
children: ExtractedElement[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* CSS-подмножество для Segmentation Level 3 (T1.5.1): computed styles,
|
|
46
|
+
* захваченные в момент extraction (viewOf/getComputedStyle). Все поля
|
|
47
|
+
* optional: отсутствие значения или поддержки свойства средой → поле
|
|
48
|
+
* просто не задано.
|
|
49
|
+
*/
|
|
50
|
+
interface ElementCss {
|
|
51
|
+
cursor?: string;
|
|
52
|
+
position?: string;
|
|
53
|
+
top?: string;
|
|
54
|
+
bottom?: string;
|
|
55
|
+
display?: string;
|
|
56
|
+
gap?: string;
|
|
57
|
+
fontWeight?: string;
|
|
58
|
+
fontSize?: string;
|
|
59
|
+
opacity?: string;
|
|
60
|
+
pointerEvents?: string;
|
|
61
|
+
overflow?: string;
|
|
62
|
+
height?: string;
|
|
63
|
+
}
|
|
64
|
+
/** Собственный текст элемента: только прямые текстовые узлы, whitespace-нормализация. */
|
|
65
|
+
declare function ownText(el: Element): string;
|
|
66
|
+
/**
|
|
67
|
+
* Извлекает видимое поддерево DOM, начиная с root (по умолчанию document.body).
|
|
68
|
+
* Сам root не включается — возвращается лес его видимых потомков.
|
|
69
|
+
*/
|
|
70
|
+
declare function extractDomTree(root?: Element): ExtractedElement[];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Типы VSL JSON (DESIGN_SYSTEM.md §4, ARCHITECTURE.md §2.3).
|
|
74
|
+
*/
|
|
75
|
+
/**
|
|
76
|
+
* Семантический тип объекта VSL (поле `t`): L1-теги + L2-роли (T1.1.3/T1.1.4),
|
|
77
|
+
* L3-CSS (T1.5.1), L4-структурные (T1.5.2), L5-vision (T1.5.4).
|
|
78
|
+
*/
|
|
79
|
+
type VslType = 'button' | 'input' | 'link' | 'nav' | 'header' | 'main' | 'container' | 'image' | 'select' | 'textarea' | 'modal' | 'tab' | 'heading' | 'footer' | 'scrollable_container' | 'toolbar' | 'list' | 'grid' | 'form_field' | 'tab_bar' | 'layout' | 'icon' | 'chart' | 'custom_widget' | 'unknown';
|
|
80
|
+
/** Состояние объекта VSL (поле `st`) — из ARIA-атрибутов (T1.1.4). */
|
|
81
|
+
type VslState = 'checked' | 'expanded' | 'disabled';
|
|
82
|
+
/** Тип визуального фрагмента — выход классификации vision (§2.2.2 Шаг 2). */
|
|
83
|
+
type VisualFragmentType = 'image' | 'icon' | 'chart' | 'custom_widget' | 'unknown';
|
|
84
|
+
/** Метаданные visual fragment (DESIGN_SYSTEM.md §4.4). */
|
|
85
|
+
interface VslFragmentMeta$1 {
|
|
86
|
+
type?: string;
|
|
87
|
+
format?: string;
|
|
88
|
+
size?: [number, number];
|
|
89
|
+
hash?: string;
|
|
90
|
+
cached_at?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Запись реестра visual_fragments документа (§2.2.2, ARCHITECTURE.md:L265-284).
|
|
94
|
+
* ТОЛЬКО метаданные: base64-данные изображений живут в VisualFragmentStore
|
|
95
|
+
* (src/llm/types.ts, DEC-015) и подаются по запросу (lazy loading §4.4) —
|
|
96
|
+
* документ остаётся в бюджете 10-100 KB (ARCHITECTURE.md:L16-18).
|
|
97
|
+
*/
|
|
98
|
+
interface VisualFragment {
|
|
99
|
+
/** Тип фрагмента (§2.2.2 Шаг 2). */
|
|
100
|
+
type: VisualFragmentType;
|
|
101
|
+
/** Формат закодированных данных (например, 'webp'). */
|
|
102
|
+
format: string;
|
|
103
|
+
/** Размер [ширина, высота] в px. */
|
|
104
|
+
size: [number, number];
|
|
105
|
+
/** Хэш кэша hash(bounding_box + pixel_content) — ключ инвалидации (§5.2). */
|
|
106
|
+
hash?: string;
|
|
107
|
+
/** ISO 8601 — время кэширования. */
|
|
108
|
+
cached_at?: string;
|
|
109
|
+
/** Эмбеддинг (512-dim CLIP) — контракт §2.2.2; в M1.5 не заполняется. */
|
|
110
|
+
embedding?: number[];
|
|
111
|
+
}
|
|
112
|
+
/** Версия формата VSL (DESIGN_SYSTEM.md §4.1). */
|
|
113
|
+
declare const VSL_VERSION: "1.0.0";
|
|
114
|
+
interface VslViewport {
|
|
115
|
+
width: number;
|
|
116
|
+
height: number;
|
|
117
|
+
unit: 'px';
|
|
118
|
+
}
|
|
119
|
+
interface VslCanvas {
|
|
120
|
+
viewport: VslViewport;
|
|
121
|
+
background: string;
|
|
122
|
+
scale: number;
|
|
123
|
+
orientation: 'landscape' | 'portrait';
|
|
124
|
+
/** ISO 8601; для детерминизма тестов фиксируется через BuildOptions. */
|
|
125
|
+
timestamp: string;
|
|
126
|
+
url?: string;
|
|
127
|
+
title?: string;
|
|
128
|
+
}
|
|
129
|
+
interface VslObject {
|
|
130
|
+
/** Детерминированный ID из DOM-пути (note_1789916091535, база M1.2 diff). */
|
|
131
|
+
id: string;
|
|
132
|
+
t: VslType;
|
|
133
|
+
/** Роль (атрибут role), уточняет семантику. */
|
|
134
|
+
r?: string;
|
|
135
|
+
/** Позиция [x, y] — относительные координаты 0.0–1.0 от viewport. */
|
|
136
|
+
p: [number, number];
|
|
137
|
+
/** Размер [width, height] в px. */
|
|
138
|
+
s: [number, number];
|
|
139
|
+
st?: VslState;
|
|
140
|
+
txt?: string;
|
|
141
|
+
/** Preview длинного текста (первые ~50 символов) — lazy text loading (M1.7, DEC-026). */
|
|
142
|
+
txt_preview?: string;
|
|
143
|
+
/** Ссылка на полный текст в text_blocks документа — lazy text loading (M1.7, DEC-026). */
|
|
144
|
+
txt_ref?: string;
|
|
145
|
+
act?: string[];
|
|
146
|
+
ch?: VslObject[];
|
|
147
|
+
/** Ссылка на визуальный фрагмент (§2.2.2) — ключ в visual_fragments документа. */
|
|
148
|
+
vf?: string;
|
|
149
|
+
/** Метаданные фрагмента (§4.4). */
|
|
150
|
+
vf_meta?: VslFragmentMeta$1;
|
|
151
|
+
}
|
|
152
|
+
interface VslDocument {
|
|
153
|
+
vsl_version: string;
|
|
154
|
+
canvas: VslCanvas;
|
|
155
|
+
objects: VslObject[];
|
|
156
|
+
/**
|
|
157
|
+
* Реестр визуальных фрагментов (§2.2.2): ключ — vf-ссылка объекта
|
|
158
|
+
* (инвариант: каждый vf объекта — ключ этой map). Только метаданные:
|
|
159
|
+
* base64-данные изображений живут в VisualFragmentStore (src/llm/types.ts,
|
|
160
|
+
* DEC-015) и подаются по запросу (lazy loading §4.4) — документ остаётся
|
|
161
|
+
* в бюджете 10-100 KB (ARCHITECTURE.md:L16-18). Optional: документы
|
|
162
|
+
* без фрагментов валидны (обратно-совместимость).
|
|
163
|
+
*/
|
|
164
|
+
visual_fragments?: Record<string, VisualFragment>;
|
|
165
|
+
/**
|
|
166
|
+
* Кэш полных текстов для lazy text loading (M1.7, DEC-026): ключ — txt_ref
|
|
167
|
+
* объекта (формат tb_xxx, инкрементный счётчик). Значение — полный текст.
|
|
168
|
+
* Только для элементов с txt > 200 символов. Optional: документы без
|
|
169
|
+
* текстовых блоков валидны (обратно-совместимость).
|
|
170
|
+
*/
|
|
171
|
+
text_blocks?: Record<string, string>;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Segmentation Level 1: семантические теги (T1.1.3, ROADMAP.md M1.1).
|
|
176
|
+
*
|
|
177
|
+
* Контракт — таблица «Уровень 1: Семантические теги» из ARCHITECTURE.md:
|
|
178
|
+
* ровно 10 тегов, покрывающих ~40% элементов типичной страницы. Теги вне
|
|
179
|
+
* таблицы (div/span/p/footer/…) НЕ классифицируются на этом уровне —
|
|
180
|
+
* их семантика, если есть, добавляется Level 2 (ARIA) в T1.1.4.
|
|
181
|
+
*
|
|
182
|
+
* Функция чистая и детерминированная: (тег) → тип VSL | null.
|
|
183
|
+
*/
|
|
184
|
+
|
|
185
|
+
/** Таблица маппинга HTML-тегов (нижний регистр) → семантические типы VSL. */
|
|
186
|
+
declare const LEVEL1_TAG_MAP: Readonly<Record<string, VslType>>;
|
|
187
|
+
/**
|
|
188
|
+
* Возвращает семантический тип VSL для HTML-тега по таблице Level 1
|
|
189
|
+
* или null, если тег на этом уровне не классифицируется.
|
|
190
|
+
* Регистр тега не учитывается (защита от tagName в любом регистре).
|
|
191
|
+
*/
|
|
192
|
+
declare function resolveLevel1Type(tag: string): VslType | null;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Segmentation Level 2: ARIA-атрибуты (T1.1.4, ROADMAP.md M1.1).
|
|
196
|
+
*
|
|
197
|
+
* Контракт — таблица «Уровень 2: ARIA-атрибуты» из ARCHITECTURE.md:
|
|
198
|
+
* - role → тип VSL (button / dialog→modal / tab / tabpanel→container);
|
|
199
|
+
* - aria-label → txt (доступное имя элемента);
|
|
200
|
+
* - aria-pressed / aria-expanded / aria-disabled = "true" → st
|
|
201
|
+
* (checked / expanded / disabled);
|
|
202
|
+
* - aria-hidden="true" → элемент декоративный (обрезается в segmenter.ts).
|
|
203
|
+
*
|
|
204
|
+
* L2 дополняет L1: тип из ARIA-роли применяется только если тег не дал типа.
|
|
205
|
+
*/
|
|
206
|
+
|
|
207
|
+
/** Таблица маппинга ARIA-ролей (нижний регистр) → типы VSL. */
|
|
208
|
+
declare const ARIA_ROLE_TYPE_MAP: Readonly<Record<string, VslType>>;
|
|
209
|
+
/**
|
|
210
|
+
* Возвращает тип VSL по ARIA-роли или null, если роль не из контракта
|
|
211
|
+
* (или роли нет). Регистр не учитывается.
|
|
212
|
+
*/
|
|
213
|
+
declare function resolveAriaRoleType(role: string | undefined): VslType | null;
|
|
214
|
+
/**
|
|
215
|
+
* Состояние VSL из ARIA-атрибутов. По контракту маппируется только точное
|
|
216
|
+
* значение "true" ("false"/"mixed"/прочее → null). При нескольких состояниях
|
|
217
|
+
* приоритет — порядок таблицы контракта: checked → expanded → disabled.
|
|
218
|
+
*/
|
|
219
|
+
declare function resolveSt(attributes: Record<string, string>): VslState | null;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Segmentation Engine: объединение уровней 1-4 (T1.1.3/T1.1.4/T1.5.1/T1.5.2).
|
|
223
|
+
*
|
|
224
|
+
* Обходит ExtractedElement-дерево (domExtractor) и аннотирует каждый элемент:
|
|
225
|
+
* - t: приоритет L1 (тег) → L2 (ARIA-роль) → L3 (CSS-паттерны) → L4
|
|
226
|
+
* (структурные паттерны по геометрии уже аннотированных детей); null —
|
|
227
|
+
* не классифицирован (включение и типизация контейнеров — решение
|
|
228
|
+
* VSL Builder, T1.1.5);
|
|
229
|
+
* - txt: aria-label (приоритет по семантике доступного имени) ?? собственный текст;
|
|
230
|
+
* - st: состояние из aria-pressed/expanded/disabled ("true" only).
|
|
231
|
+
*
|
|
232
|
+
* Skip-поддеревья (элемент не попадает в VSL вместе с поддеревом):
|
|
233
|
+
* - aria-hidden="true" — декоративный по семантике accessibility-дерева;
|
|
234
|
+
* - opacity:0 + pointer-events:none — визуально невидимый (L3,
|
|
235
|
+
* isPointerInvisible).
|
|
236
|
+
*
|
|
237
|
+
* L4 применяется после рекурсивной обработки детей: структурные паттерны
|
|
238
|
+
* (toolbar/list/grid/form_field/tab_bar/layout) анализируют t детей.
|
|
239
|
+
*/
|
|
240
|
+
|
|
241
|
+
interface SegmentedElement {
|
|
242
|
+
tag: string;
|
|
243
|
+
indexPath: number[];
|
|
244
|
+
rect: Rect;
|
|
245
|
+
/** Атрибуты verbatim (нужны Builder для умных дефолтов act и валидации). */
|
|
246
|
+
attributes: Record<string, string>;
|
|
247
|
+
/** Тип VSL: приоритет L1 > L2 > L3 > L4; null — не классифицирован. */
|
|
248
|
+
t: VslType | null;
|
|
249
|
+
/** Текст: aria-label ?? собственный текст; null, если нет ни того, ни другого. */
|
|
250
|
+
txt: string | null;
|
|
251
|
+
/** Состояние из ARIA (checked/expanded/disabled) или null. */
|
|
252
|
+
st: VslState | null;
|
|
253
|
+
ch: SegmentedElement[];
|
|
254
|
+
/** Ссылка на визуальный фрагмент (заполняется enrichWithVision, T1.5.4). */
|
|
255
|
+
vf?: string;
|
|
256
|
+
/** Метаданные фрагмента (§4.4). */
|
|
257
|
+
vf_meta?: VslFragmentMeta$1;
|
|
258
|
+
}
|
|
259
|
+
/** Элемент декоративен (aria-hidden="true") → в VSL не включается. */
|
|
260
|
+
declare function isAriaHidden(attributes: Record<string, string>): boolean;
|
|
261
|
+
/**
|
|
262
|
+
* Аннотирует видимое дерево (результат extractDomTree) семантикой L1-L4,
|
|
263
|
+
* обрезая skip-поддеревья (aria-hidden, isPointerInvisible). Порядок
|
|
264
|
+
* элементов сохраняется. L4 анализирует геометрию детей ПОСЛЕ их
|
|
265
|
+
* аннотирования (post-order по t).
|
|
266
|
+
*/
|
|
267
|
+
declare function segmentTree(elements: readonly ExtractedElement[]): SegmentedElement[];
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* VSL Builder (T1.1.5, ROADMAP.md M1.1; контракт ARCHITECTURE.md §2.3).
|
|
271
|
+
*
|
|
272
|
+
* Вход: семантическое дерево от Segmentation Engine (segmentTree).
|
|
273
|
+
* Выход: VSL JSON по DESIGN_SYSTEM.md §4 (vsl_version, canvas, objects tree).
|
|
274
|
+
*
|
|
275
|
+
* Отбор элементов — эвристика Semantic Density (ARCHITECTURE.md) в адаптации
|
|
276
|
+
* к L1/L2-сегментации:
|
|
277
|
+
* - t !== null (типизирован L1/L2) → семантический объект, включается;
|
|
278
|
+
* - t === null → score = role(3) + aria-label(2) + interactive-атрибуты(2) +
|
|
279
|
+
* txt(1) + включённые дети(1); score >= 3 → контейнер;
|
|
280
|
+
* - t === null и есть включённые потомки → контейнер-обёртка (обобщение
|
|
281
|
+
* контракта «== 0 с семантическими детьми → grouping» на зазор 1–2);
|
|
282
|
+
* - иначе — декоративный, пропускается вместе с поддеревом.
|
|
283
|
+
*
|
|
284
|
+
* Детерминизм (решение для M1.2 Cache/Diff): id = tag_indexPath из DOM-пути,
|
|
285
|
+
* без Date.now()/Math.random(); timestamp/url/title фиксируются через
|
|
286
|
+
* BuildOptions (в тестах — обязательно).
|
|
287
|
+
*/
|
|
288
|
+
|
|
289
|
+
/** Опции сборки: для детерминированных тестов фиксируйте timestamp/url/title. */
|
|
290
|
+
interface BuildOptions {
|
|
291
|
+
viewport?: {
|
|
292
|
+
width: number;
|
|
293
|
+
height: number;
|
|
294
|
+
};
|
|
295
|
+
background?: string;
|
|
296
|
+
timestamp?: string;
|
|
297
|
+
url?: string;
|
|
298
|
+
title?: string;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Строит VSL JSON из семантического дерева (segmentTree) по DESIGN_SYSTEM §4.
|
|
302
|
+
* Viewport фиксируется один раз на всю сборку — p-нормализация консистентна.
|
|
303
|
+
*/
|
|
304
|
+
declare function buildVslDocument(elements: readonly SegmentedElement[], options?: BuildOptions): VslDocument;
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Cache Store (T1.2.1, ROADMAP.md M1.2; контракт ARCHITECTURE.md §2.4/§5).
|
|
308
|
+
*
|
|
309
|
+
* In-memory Map id → CacheEntry. Запись кэша содержит VslObject и два
|
|
310
|
+
* детерминированных хэша (решение dc_4):
|
|
311
|
+
* - contentHash — содержимое {t, r?, st?, txt?, act?} в порядке канона §4;
|
|
312
|
+
* - coordHash — координаты {p, s}.
|
|
313
|
+
* id в хэш не входит (он ключ Map), ch не входит (дети отслеживаются
|
|
314
|
+
* собственными записями). Хэш — sha256 (node:crypto) поверх JSON.stringify
|
|
315
|
+
* объекта с фиксированным порядком ключей.
|
|
316
|
+
*
|
|
317
|
+
* Invalidation (ARCHITECTURE.md §5.2, решение dc_5):
|
|
318
|
+
* - invalidate(id) — точечная инвалидация записи;
|
|
319
|
+
* - invalidateBySelector — MVP-семантика: '*' → полный сброс; точный id;
|
|
320
|
+
* иначе селектор трактуется как тег id ('button' → все 'button_*'), т.к.
|
|
321
|
+
* CSS-селекторы к кэшированным VslObject неприменимы (id = tag_indexPath);
|
|
322
|
+
* - invalidateCoordinates() — «сброс координат» при viewport resize:
|
|
323
|
+
* coordHash всех записей обнуляется, содержимое сохраняется.
|
|
324
|
+
*/
|
|
325
|
+
|
|
326
|
+
/** Запись кэша: объект VSL + хэши содержимого и координат. */
|
|
327
|
+
interface CacheEntry {
|
|
328
|
+
/** Кэшированный VslObject (поддерево ch не хранится — дети в своих записях). */
|
|
329
|
+
object: VslObject;
|
|
330
|
+
/** sha256 от {t, r?, st?, txt?, act?} в фиксированном порядке ключей. */
|
|
331
|
+
contentHash: string;
|
|
332
|
+
/** sha256 от {p, s}; пустая строка — координаты инвалидированы (resize). */
|
|
333
|
+
coordHash: string;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* contentHash: только смысловые поля, фиксированный порядок t→r→st→txt→act.
|
|
337
|
+
* Опциональные поля включаются только при наличии — сериализация детерминирована.
|
|
338
|
+
*/
|
|
339
|
+
declare function computeContentHash(object: VslObject): string;
|
|
340
|
+
/** coordHash: только координаты {p, s}. */
|
|
341
|
+
declare function computeCoordHash(object: VslObject): string;
|
|
342
|
+
/** Публичный контракт Cache Store (ROADMAP T1.2.1). */
|
|
343
|
+
interface CacheStore {
|
|
344
|
+
/** Сохранить объект под явным id (ROADMAP T1.2.1: cache.set(id, element)). */
|
|
345
|
+
set(id: string, object: VslObject): void;
|
|
346
|
+
/** Запись по id или undefined. */
|
|
347
|
+
get(id: string): CacheEntry | undefined;
|
|
348
|
+
/** Наличие записи. */
|
|
349
|
+
has(id: string): boolean;
|
|
350
|
+
/** Точечная инвалидация (§5.2 cache.invalidate). */
|
|
351
|
+
invalidate(id: string): void;
|
|
352
|
+
/** MVP-семантика (решение dc_5): '*' | точный id | 'tag' → префикс 'tag_'. */
|
|
353
|
+
invalidateBySelector(selector: string): void;
|
|
354
|
+
/** Viewport resize (§5.2 «сброс координат»): обнулить coordHash, объекты сохранить. */
|
|
355
|
+
invalidateCoordinates(): void;
|
|
356
|
+
/** Полный сброс (§5.2 cache.clear). */
|
|
357
|
+
clear(): void;
|
|
358
|
+
/** Все id в порядке вставки. */
|
|
359
|
+
keys(): string[];
|
|
360
|
+
/** Число записей. */
|
|
361
|
+
readonly size: number;
|
|
362
|
+
}
|
|
363
|
+
declare function createCacheStore(): CacheStore;
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Cache invalidation через MutationObserver (T1.2.2, ROADMAP.md M1.2; ARCHITECTURE.md §5.2).
|
|
367
|
+
*
|
|
368
|
+
* attachMutationObserver(root, store) подписывается на мутации поддерева root
|
|
369
|
+
* и точечно инвалидирует записи Cache Store:
|
|
370
|
+
* - childList (изменился состав детей) → родительский элемент (его поле ch);
|
|
371
|
+
* - attributes → сам целевой элемент;
|
|
372
|
+
* - characterData → owner-элемент текстового узла.
|
|
373
|
+
*
|
|
374
|
+
* Маппинг element → id воспроизводит арифметику domExtractor + vslBuilder
|
|
375
|
+
* (id = tag_indexPath): подъём от элемента до root, на каждом уровне — индекс
|
|
376
|
+
* среди ЭЛЕМЕНТНЫХ детей (Array.from(parent.children)); domExtractor считает
|
|
377
|
+
* indexPath по живому DOM ДО фильтрации, поэтому индексы совпадают 1:1.
|
|
378
|
+
* Контракт: root наблюдателя === корень extraction (document.body по умолчанию).
|
|
379
|
+
*
|
|
380
|
+
* Если id элемента мутации нет в store — инвалидируется ближайший предок из
|
|
381
|
+
* store (подъём вверх до root). Root сам не имеет VSL-записи (extractDomTree
|
|
382
|
+
* не включает корень) → childList на прямых детях root — no-op в MVP.
|
|
383
|
+
*
|
|
384
|
+
* Известное MVP-ограничение (соответствует дизайну фикстур dc_6: добавления/
|
|
385
|
+
* удаления только в конец списков детей): вставка/удаление в середину сдвигает
|
|
386
|
+
* indexPath последующих сиблингов — их id меняются; точечная инвалидация
|
|
387
|
+
* «переехавших» записей вне MVP, итоговую консистентность гарантирует diff
|
|
388
|
+
* по полному переизвлечению (T1.2.4).
|
|
389
|
+
*/
|
|
390
|
+
|
|
391
|
+
/** Результат attachMutationObserver: наблюдатель + ручная отписка. */
|
|
392
|
+
interface MutationObserverHandle {
|
|
393
|
+
/** Живой MutationObserver (для тестов и расширенного контроля). */
|
|
394
|
+
observer: MutationObserver;
|
|
395
|
+
/** Отписаться от мутаций. */
|
|
396
|
+
disconnect(): void;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Подписывает store на мутации поддерева root (§5.2 «DOM mutation observer →
|
|
400
|
+
* invalidation изменённых элементов»). Возвращает handle с disconnect().
|
|
401
|
+
*/
|
|
402
|
+
declare function attachMutationObserver(root: Element, store: CacheStore): MutationObserverHandle;
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Diff Engine (T1.2.3, ROADMAP.md M1.2; формат ARCHITECTURE.md §6.2).
|
|
406
|
+
*
|
|
407
|
+
* diffVslDocuments(prev, next, options) сравнивает два VslDocument по ПЛОСКОМУ
|
|
408
|
+
* индексу id (id = tag_indexPath глобально уникальны — база M1.1) и выдаёт
|
|
409
|
+
* дифф строго в формате §6.2:
|
|
410
|
+
* - added — новые объекты полными поддеревьями (tree order next);
|
|
411
|
+
* - modified — id + только изменившиеся поля в порядке канона §4
|
|
412
|
+
* (t→r→p→s→st→txt→act); УДАЛЁННОЕ опциональное поле передаётся явным null
|
|
413
|
+
* (r/st/txt/act; решение dc_5: отсутствие в Partial амбивалентно —
|
|
414
|
+
* «не изменилось» vs «удалено»); t/p/s/id никогда не null;
|
|
415
|
+
* - removed — {id} в tree order prev;
|
|
416
|
+
* - unchanged_refs — id неизменённых объектов (tree order next).
|
|
417
|
+
*
|
|
418
|
+
* Равенство объектов — по паре хэшей Cache Store (решение dc_4):
|
|
419
|
+
* contentHash {t,r?,st?,txt?,act?} + coordHash {p,s}, sha256 по фиксированному
|
|
420
|
+
* порядку ключей. Поле ch в сравнении НЕ участвует — diff плоский, дети
|
|
421
|
+
* отслеживаются собственными id: изменение состава/содержимого детей проявляется
|
|
422
|
+
* их собственными added/removed/modified записями. Canvas не диффуется —
|
|
423
|
+
* сравнивается только дерево objects.
|
|
424
|
+
*
|
|
425
|
+
* Пустой дифф валиден: идентичные документы → все id в unchanged_refs,
|
|
426
|
+
* остальные списки пусты.
|
|
427
|
+
*
|
|
428
|
+
* Предусловие: id уникальны в пределах каждого документа (гарантия M1.1).
|
|
429
|
+
*/
|
|
430
|
+
|
|
431
|
+
/** Изменившийся объект: id + только изменившиеся поля (порядок канона §4). */
|
|
432
|
+
interface VslModifiedObject {
|
|
433
|
+
id: string;
|
|
434
|
+
t?: VslType;
|
|
435
|
+
/** null = опциональное поле r удалено в next. */
|
|
436
|
+
r?: string | null;
|
|
437
|
+
p?: [number, number];
|
|
438
|
+
s?: [number, number];
|
|
439
|
+
/** null = состояние st удалено в next. */
|
|
440
|
+
st?: VslState | null;
|
|
441
|
+
/** null = текст txt удалён в next. */
|
|
442
|
+
txt?: string | null;
|
|
443
|
+
/** null = список действий act удалён в next. */
|
|
444
|
+
act?: string[] | null;
|
|
445
|
+
}
|
|
446
|
+
/** Удалённый объект: только ссылка на id (§6.2). */
|
|
447
|
+
interface VslRemovedObject {
|
|
448
|
+
id: string;
|
|
449
|
+
}
|
|
450
|
+
/** Блок changes формата §6.2. */
|
|
451
|
+
interface VslDiffChanges {
|
|
452
|
+
/** Новые объекты — полные поддеревья, tree order next. */
|
|
453
|
+
added: VslObject[];
|
|
454
|
+
/** Изменившиеся объекты, tree order next. */
|
|
455
|
+
modified: VslModifiedObject[];
|
|
456
|
+
/** Удалённые объекты, tree order prev. */
|
|
457
|
+
removed: VslRemovedObject[];
|
|
458
|
+
/** id неизменённых объектов, tree order next. */
|
|
459
|
+
unchanged_refs: string[];
|
|
460
|
+
}
|
|
461
|
+
/** Дифф двух VslDocument (формат ARCHITECTURE.md §6.2). */
|
|
462
|
+
interface VslDiff {
|
|
463
|
+
/** Версия нового состояния (счётчик session). */
|
|
464
|
+
diff_version: number;
|
|
465
|
+
/** Версия базового (предыдущего) состояния. */
|
|
466
|
+
base_version: number;
|
|
467
|
+
/** ISO 8601. */
|
|
468
|
+
timestamp: string;
|
|
469
|
+
changes: VslDiffChanges;
|
|
470
|
+
}
|
|
471
|
+
/** Опции diffVslDocuments: версии заголовка обязательны (явный контракт session). */
|
|
472
|
+
interface DiffOptions {
|
|
473
|
+
diffVersion: number;
|
|
474
|
+
baseVersion: number;
|
|
475
|
+
/** ISO 8601; по умолчанию — текущее время (недетерминированно; в тестах фиксировать). */
|
|
476
|
+
timestamp?: string;
|
|
477
|
+
}
|
|
478
|
+
/** Сравнивает два VSL-документа и возвращает дифф формата §6.2 (см. шапку модуля). */
|
|
479
|
+
declare function diffVslDocuments(prev: VslDocument, next: VslDocument, options: DiffOptions): VslDiff;
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Типы LLM Integration (M1.3, ROADMAP.md T1.3.1–T1.3.5).
|
|
483
|
+
*
|
|
484
|
+
* Архитектурная база:
|
|
485
|
+
* - ARCHITECTURE.md §2.5 — LLM Integration Layer, промпт-шаблон {vsl_json}+{goal};
|
|
486
|
+
* - ARCHITECTURE.md §7 — Action Model, формат ответа LLM (§7.4);
|
|
487
|
+
* - ARCHITECTURE.md §9.4 — API адаптеров: decide({vslJson, goal});
|
|
488
|
+
* - ARCHITECTURE.md §10.3 — retry 3× exponential backoff;
|
|
489
|
+
* - DESIGN_SYSTEM.md §4.4 — visual fragments (vf/vf_meta, forward-compatible);
|
|
490
|
+
* - DEC-002 — model-agnostic: адаптеры провайдеров без SDK-зависимостей.
|
|
491
|
+
*
|
|
492
|
+
* Транспорт (нативный fetch, node>=18) инъецируется через LlmTransport —
|
|
493
|
+
* тесты работают на моках без сети (решение пользователя, clarification M1.3).
|
|
494
|
+
* Ноль runtime-зависимостей: официальные SDK провайдеров НЕ используются.
|
|
495
|
+
*/
|
|
496
|
+
|
|
497
|
+
/** Вход LLM-адаптера: полный документ или дифф (§2.5, §11.2 — diff-first). */
|
|
498
|
+
type VslInput = VslDocument | VslDiff;
|
|
499
|
+
/** Действие LLM — формат ответа модели (ARCHITECTURE.md §7.4). */
|
|
500
|
+
interface LlmAction {
|
|
501
|
+
/** Имя действия из Action Model (§7.1–7.3; см. VALID_ACTIONS в llm/actions). */
|
|
502
|
+
action: string;
|
|
503
|
+
/** ID элемента-цели в VSL JSON — обязателен для действий с целью. */
|
|
504
|
+
target_id?: string;
|
|
505
|
+
/** Значение: текст ввода для type, option для select и т.п. */
|
|
506
|
+
value?: string | null;
|
|
507
|
+
/** Объяснение выбора модели (свободный текст). */
|
|
508
|
+
reasoning?: string;
|
|
509
|
+
}
|
|
510
|
+
/** Нормализованное использование токенов (маппинг из usage провайдера). */
|
|
511
|
+
interface LlmUsage {
|
|
512
|
+
inputTokens?: number;
|
|
513
|
+
outputTokens?: number;
|
|
514
|
+
}
|
|
515
|
+
/** Ответ провайдера на sendPrompt (T1.3.1): raw-данные БЕЗ валидации. */
|
|
516
|
+
interface LlmResponse {
|
|
517
|
+
/** Текстовое содержимое ответа (если провайдер его вернул). */
|
|
518
|
+
text: string | null;
|
|
519
|
+
/** Raw-объект действия из tool call (валидацию выполняет decide). */
|
|
520
|
+
action?: unknown;
|
|
521
|
+
/** Идентификатор модели, сформировавшей ответ. */
|
|
522
|
+
model: string;
|
|
523
|
+
usage?: LlmUsage;
|
|
524
|
+
}
|
|
525
|
+
/** Метаданные visual fragment (DESIGN_SYSTEM.md §4.4). */
|
|
526
|
+
interface VslFragmentMeta {
|
|
527
|
+
type?: string;
|
|
528
|
+
format?: string;
|
|
529
|
+
size?: [number, number];
|
|
530
|
+
hash?: string;
|
|
531
|
+
cached_at?: string;
|
|
532
|
+
}
|
|
533
|
+
/** Объект VSL с forward-compatible полями visual fragments (§4.4). */
|
|
534
|
+
type VslObjectWithVf = VslObject & {
|
|
535
|
+
/** Ссылка на визуальный фрагмент (например, «emb_abc123»). */
|
|
536
|
+
vf?: string;
|
|
537
|
+
/** Метаданные фрагмента. */
|
|
538
|
+
vf_meta?: VslFragmentMeta;
|
|
539
|
+
};
|
|
540
|
+
/** Данные изображения фрагмента для мультимодальной подачи (DEC-015). */
|
|
541
|
+
interface VisualFragmentData {
|
|
542
|
+
/** MIME-тип изображения, например «image/webp». */
|
|
543
|
+
mediaType: string;
|
|
544
|
+
/** Base64-данные изображения (без data:-префикса). */
|
|
545
|
+
data: string;
|
|
546
|
+
}
|
|
547
|
+
/** Стор данных фрагментов: ключ — vf-ссылка объекта («emb_abc123»). */
|
|
548
|
+
type VisualFragmentStore = ReadonlyMap<string, VisualFragmentData>;
|
|
549
|
+
/**
|
|
550
|
+
* Провайдер-независимая content-часть user-сообщения (T1.5.4, вариант C):
|
|
551
|
+
* адаптеры маппят её в формат провайдера (OpenAI image_url / Anthropic image
|
|
552
|
+
* base64-блок); форма image-части совпадает с VisualFragmentData (DEC-015).
|
|
553
|
+
*/
|
|
554
|
+
type LlmContentPart = {
|
|
555
|
+
type: 'text';
|
|
556
|
+
text: string;
|
|
557
|
+
} | {
|
|
558
|
+
type: 'image';
|
|
559
|
+
mediaType: string;
|
|
560
|
+
data: string;
|
|
561
|
+
};
|
|
562
|
+
/**
|
|
563
|
+
* Провайдер-независимое определение tool: одна JSON Schema на оба провайдера
|
|
564
|
+
* (паттерн schema.ts: OpenAI function.parameters / Anthropic input_schema).
|
|
565
|
+
*/
|
|
566
|
+
interface LlmToolDef {
|
|
567
|
+
/** Имя tool (execute_action / classify_fragment). */
|
|
568
|
+
name: string;
|
|
569
|
+
/** Описание для модели. */
|
|
570
|
+
description: string;
|
|
571
|
+
/** JSON Schema аргументов. */
|
|
572
|
+
schema: Record<string, unknown>;
|
|
573
|
+
}
|
|
574
|
+
/** Опции sendPrompt сверх базовых аргументов. */
|
|
575
|
+
interface SendPromptOptions {
|
|
576
|
+
/**
|
|
577
|
+
* Данные изображений для vf-ссылок входа: image-блоки включаются
|
|
578
|
+
* только для ссылок, присутствующих в сторе (lazy loading, §4.4);
|
|
579
|
+
* без стора подача остаётся текстовой.
|
|
580
|
+
*/
|
|
581
|
+
visualFragments?: VisualFragmentStore;
|
|
582
|
+
}
|
|
583
|
+
/** Вход decide (ARCHITECTURE.md §9.4). */
|
|
584
|
+
interface DecideInput {
|
|
585
|
+
/** VSL JSON: VslDocument или VslDiff от VslSnapshotSession. */
|
|
586
|
+
vslJson: VslInput;
|
|
587
|
+
/** Цель пользователя на естественном языке. */
|
|
588
|
+
goal: string;
|
|
589
|
+
/** Данные изображений для vf-ссылок (см. SendPromptOptions). */
|
|
590
|
+
visualFragments?: VisualFragmentStore;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Инъекция HTTP-транспорта: подмножество сигнатуры fetch.
|
|
594
|
+
* Прод — нативный fetch; тесты — мок (ноль runtime-зависимостей).
|
|
595
|
+
*/
|
|
596
|
+
type LlmTransport = (url: string, init: RequestInit) => Promise<Response>;
|
|
597
|
+
/** Функция задержки (инъекция для детерминированных тестов retry). */
|
|
598
|
+
type Sleep = (ms: number) => Promise<void>;
|
|
599
|
+
/** Конфиг адаптера провайдера (§9.4: new OpenAIAdapter({apiKey, model})). */
|
|
600
|
+
interface LlmAdapterConfig {
|
|
601
|
+
/** API-ключ провайдера. */
|
|
602
|
+
apiKey: string;
|
|
603
|
+
/** Модель; дефолт задаёт адаптер (gpt-4o / claude-sonnet-4-20250514). */
|
|
604
|
+
model?: string;
|
|
605
|
+
/** База API; по умолчанию — официальный endpoint провайдера. */
|
|
606
|
+
baseUrl?: string;
|
|
607
|
+
/** Попыток при сбоях API (§10.3); по умолчанию 3. */
|
|
608
|
+
maxRetries?: number;
|
|
609
|
+
/** Транспорт; по умолчанию — нативный fetch. */
|
|
610
|
+
transport?: LlmTransport;
|
|
611
|
+
/** Задержка между retry-попытками (инъекция для детерминированных тестов; §10.3). */
|
|
612
|
+
sleep?: Sleep;
|
|
613
|
+
}
|
|
614
|
+
/** Абстрактный интерфейс LLM Adapter (ROADMAP.md T1.3.1). */
|
|
615
|
+
interface LlmAdapter {
|
|
616
|
+
/** Низкий уровень: VSL JSON + задача → ответ провайдера (raw action). */
|
|
617
|
+
sendPrompt(vslJson: VslInput, task: string, options?: SendPromptOptions): Promise<LlmResponse>;
|
|
618
|
+
/** Convenience (§9.4): решение для цели с валидацией действия (§7.4). */
|
|
619
|
+
decide(input: DecideInput): Promise<LlmAction>;
|
|
620
|
+
}
|
|
621
|
+
/** Базовая ошибка LLM-слоя (транспорт/HTTP/формат ответа провайдера). */
|
|
622
|
+
declare class LlmError extends Error {
|
|
623
|
+
/** HTTP-статус, если ошибка пришла от API (для классификации retry). */
|
|
624
|
+
readonly status?: number;
|
|
625
|
+
constructor(message: string, status?: number);
|
|
626
|
+
}
|
|
627
|
+
/** Ответ модели не прошёл валидацию: неизвестное действие, невалидный target_id. */
|
|
628
|
+
declare class LlmValidationError extends LlmError {
|
|
629
|
+
constructor(message: string);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Vision-подсистема (T1.5.3/T1.5.4): порты и типы (§2.2.2).
|
|
634
|
+
*
|
|
635
|
+
* Порты (Dependency Inversion) — SDK не зависит от платформы:
|
|
636
|
+
* - ViewportCapture: скриншот текущего viewport (chrome.tabs.captureVisibleTab
|
|
637
|
+
* в extension background, Playwright screenshot в e2e; jsdom не рендерит —
|
|
638
|
+
* в unit-тестах инъекция фейка);
|
|
639
|
+
* - FragmentCropper: кроп скриншота по прямоугольнику → WebP base64
|
|
640
|
+
* (дефолтная реализация — Image + offscreen canvas в браузере;
|
|
641
|
+
* getContext('2d') в jsdom возвращает null — в unit-тестах инъекция фейка);
|
|
642
|
+
* - VisionClassifier: классификация визуального фрагмента — реализация M1.5 —
|
|
643
|
+
* LlmVisionClassifier через LLM vision API (решение пользователя 24.09.2026);
|
|
644
|
+
* CLIP-эмбеддинги — будущие реализации порта (контракт §2.2.2 Шаг 3).
|
|
645
|
+
*
|
|
646
|
+
* Кэш фрагментов — по hash(bounding_box + pixel_content) (§2.2.2 Шаг 4):
|
|
647
|
+
* sync sha256 через createHash (node:crypto / nodeCryptoShim в браузере),
|
|
648
|
+
* как в cacheStore — переиспользование готового механизма.
|
|
649
|
+
*/
|
|
650
|
+
|
|
651
|
+
/** Результат классификации фрагмента vision-моделью (§2.2.2 Шаг 2). */
|
|
652
|
+
interface VisionClassification {
|
|
653
|
+
/** Тип фрагмента: image | icon | chart | custom_widget | unknown. */
|
|
654
|
+
type: VisualFragmentType;
|
|
655
|
+
/** Уверенность модели 0..1. */
|
|
656
|
+
confidence: number;
|
|
657
|
+
/** Опциональное описание, например «blue submit button with white text». */
|
|
658
|
+
description?: string;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Порт классификатора визуальных фрагментов (§2.2.2 Шаг 2).
|
|
662
|
+
* Реализация M1.5 — LlmVisionClassifier (LLM vision API); unit-тесты — моки.
|
|
663
|
+
*/
|
|
664
|
+
interface VisionClassifier {
|
|
665
|
+
/**
|
|
666
|
+
* Классифицирует изображение фрагмента.
|
|
667
|
+
* @param image Данные изображения (mediaType + base64 без data:-префикса).
|
|
668
|
+
* @param hint Контекст элемента (тег/атрибуты) — подсказка модели.
|
|
669
|
+
*/
|
|
670
|
+
classify(image: VisualFragmentData, hint?: string): Promise<VisionClassification>;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Порт скриншота текущего viewport (§2.2.2 Шаг 1). Возвращает полный скриншот
|
|
674
|
+
* как data-URL (например, 'data:image/png;base64,...').
|
|
675
|
+
* Extension — chrome.tabs.captureVisibleTab; unit-тесты — инъекция фейка.
|
|
676
|
+
*/
|
|
677
|
+
type ViewportCapture = () => Promise<string>;
|
|
678
|
+
/** Результат кропа: данные фрагмента + фактический размер (после клампинга). */
|
|
679
|
+
interface CroppedFragment {
|
|
680
|
+
/** MIME-тип фактически закодированных данных, например 'image/webp'. */
|
|
681
|
+
mediaType: string;
|
|
682
|
+
/** Base64-данные (без data:-префикса). */
|
|
683
|
+
data: string;
|
|
684
|
+
/** Фактический размер кропа в px [width, height] — после клампинга к границам скриншота. */
|
|
685
|
+
size: [number, number];
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Кроп скриншота по прямоугольнику (§2.2.2 Шаг 1: +2px margin применяется
|
|
689
|
+
* вызывающей стороной; здесь — вырезание области и кодирование WebP q~0.6).
|
|
690
|
+
* Дефолтная реализация — Image + offscreen canvas (браузер).
|
|
691
|
+
*/
|
|
692
|
+
type FragmentCropper = (screenshot: string, rect: Rect) => Promise<CroppedFragment>;
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* FragmentExtractor (T1.5.3): извлечение визуальных фрагментов по bounding box.
|
|
696
|
+
*
|
|
697
|
+
* Конвейер §2.2.2 Шаг 1 + Шаг 4:
|
|
698
|
+
* 1. Расширить rect на 2px с каждой стороны (захват border) — expandRect;
|
|
699
|
+
* 2. capture() — полный скриншот viewport (порт, chrome.tabs.captureVisibleTab);
|
|
700
|
+
* 3. crop(screenshot, rect) — вырезание области → WebP base64 (порт/дефолт);
|
|
701
|
+
* 4. hash = sha256('x,y,w,h|data') — hash(bounding_box + pixel_content);
|
|
702
|
+
* 5. Кэш: hit → та же запись (vfId/cached_at исходные, cached=true —
|
|
703
|
+
* классификатор не вызывается повторно, AC[3]); miss → новая запись.
|
|
704
|
+
*
|
|
705
|
+
* vfId детерминированный: 'emb_' + первые 12 hex-символов hash — одинаковые
|
|
706
|
+
* фрагменты (позиция+пиксели) получают один vfId (стабильность в diff-режиме).
|
|
707
|
+
*
|
|
708
|
+
* Инъекции (Dependency Inversion): capture обязателен; crop опционален
|
|
709
|
+
* (дефолт — Image + offscreen canvas браузера; в jsdom getContext('2d')
|
|
710
|
+
* возвращает null → в unit-тестах инъекция фейка); now опционален
|
|
711
|
+
* (детерминизм cached_at в тестах). createHash — прямой импорт node:crypto
|
|
712
|
+
* как в cacheStore (esbuild alias → nodeCryptoShim в extension-сборке).
|
|
713
|
+
*/
|
|
714
|
+
|
|
715
|
+
/** Метаданные фрагмента в кэше (заполняются полностью при extract). */
|
|
716
|
+
interface FragmentMeta {
|
|
717
|
+
/** Формат закодированных данных без префикса 'image/' (например, 'webp'). */
|
|
718
|
+
format: string;
|
|
719
|
+
/** Фактический размер кропа в px [width, height] (после клампинга). */
|
|
720
|
+
size: [number, number];
|
|
721
|
+
/** sha256(expanded_rect + '|' + base64-данные) — ключ кэша (§2.2.2 Шаг 4). */
|
|
722
|
+
hash: string;
|
|
723
|
+
/** ISO 8601 — время первого кэширования (сохраняется при cache hit). */
|
|
724
|
+
cached_at: string;
|
|
725
|
+
}
|
|
726
|
+
/** Запись кэша фрагментов: данные + метаданные + классификация (после enrich). */
|
|
727
|
+
interface FragmentCacheEntry {
|
|
728
|
+
vfId: string;
|
|
729
|
+
meta: FragmentMeta;
|
|
730
|
+
data: VisualFragmentData;
|
|
731
|
+
/** Классификация — дозаполняется enrichWithVision после classify. */
|
|
732
|
+
classification?: VisionClassification;
|
|
733
|
+
}
|
|
734
|
+
/** Результат extract: запись кэша + признак «уже была в кэше». */
|
|
735
|
+
interface FragmentExtraction {
|
|
736
|
+
entry: FragmentCacheEntry;
|
|
737
|
+
/** true — фрагмент уже был в кэше: классификатор вызывать не нужно (AC[3]). */
|
|
738
|
+
cached: boolean;
|
|
739
|
+
}
|
|
740
|
+
/** Опции конструктора FragmentExtractor. */
|
|
741
|
+
interface FragmentExtractorOptions {
|
|
742
|
+
/** Порт скриншота viewport (data-URL), например chrome.tabs.captureVisibleTab. */
|
|
743
|
+
capture: ViewportCapture;
|
|
744
|
+
/** Кроп скриншота; дефолт — Image + offscreen canvas (браузер). */
|
|
745
|
+
crop?: FragmentCropper;
|
|
746
|
+
/** Часы для cached_at (инъекция для детерминированных тестов). */
|
|
747
|
+
now?: () => string;
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Извлекатель визуальных фрагментов (T1.5.3). Stateful: держит кэш
|
|
751
|
+
* hash → запись (§2.2.2 Шаг 4). Экземпляр живёт столько же, сколько сессия
|
|
752
|
+
* снапшотов (кэш переживает повторные snapshot() той же страницы).
|
|
753
|
+
*/
|
|
754
|
+
declare class FragmentExtractor {
|
|
755
|
+
private readonly capture;
|
|
756
|
+
private readonly crop;
|
|
757
|
+
private readonly now;
|
|
758
|
+
private readonly cache;
|
|
759
|
+
constructor(options: FragmentExtractorOptions);
|
|
760
|
+
/** Число записей в кэше (для тестов и диагностики). */
|
|
761
|
+
get size(): number;
|
|
762
|
+
/**
|
|
763
|
+
* Извлекает визуальный фрагмент по прямоугольнику: capture → crop(+2px
|
|
764
|
+
* margin) → hash → кэш. Возвращает null для пустой геометрии (нулевые
|
|
765
|
+
* размеры или кроп полностью вне скриншота). Ошибки capture/crop не
|
|
766
|
+
* глушатся — их обрабатывает enrichWithVision (vision — best-effort).
|
|
767
|
+
*/
|
|
768
|
+
extract(rect: Rect): Promise<FragmentExtraction | null>;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* enrichWithVision (T1.5.4, dev_8): async-шаг конвейера ДО buildVslDocument —
|
|
773
|
+
* Level 5 vision fallback (§2.2.1, ARCHITECTURE.md:L174-202).
|
|
774
|
+
*
|
|
775
|
+
* Отбор кандидатов: элементы, которые builder ОТБРОСИТ (t === null и
|
|
776
|
+
* !isIncludedInVsl — нетипизированный, score < 3, без включённых детей).
|
|
777
|
+
* Для каждого: extract (bounding box → WebP base64, кэш по hash) → classify
|
|
778
|
+
* (порт VisionClassifier) → аннотация t + vf/vf_meta НА МЕСТЕ (мутирует
|
|
779
|
+
* дерево, как segmentTree) — после этого builder включает элемент в VSL
|
|
780
|
+
* (типизирован), а convert переносит vf/vf_meta в объект (AC[5]).
|
|
781
|
+
*
|
|
782
|
+
* Best-effort (vision не должен ломать снапшот): ошибки extract/classify
|
|
783
|
+
* одного элемента не роняют обогащение — элемент остаётся отброшенным
|
|
784
|
+
* (поведение как до vision), ошибка учитывается в failedCount.
|
|
785
|
+
*
|
|
786
|
+
* Кэш и AC[3]: запись FragmentCacheEntry возвращается extract по ссылке и
|
|
787
|
+
* хранит classification — повторный extract того же hash даёт тот же entry,
|
|
788
|
+
* поэтому классификатор вызывается один раз на фрагмент. Обход СТРОГО
|
|
789
|
+
* последовательный: параллельный запуск классифицировал бы одинаковые
|
|
790
|
+
* фрагменты дважды (оба extract завершились бы до первой записи classification).
|
|
791
|
+
*
|
|
792
|
+
* Данные изображений НЕ попадают в VslDocument (бюджет 10-100 KB) —
|
|
793
|
+
* возвращаются в VisualFragmentStore (DEC-015, lazy loading §4.4) для
|
|
794
|
+
* DecideInput.visualFragments.
|
|
795
|
+
*/
|
|
796
|
+
|
|
797
|
+
/** Зависимости enrichWithVision (Dependency Inversion — фейки в unit-тестах). */
|
|
798
|
+
interface EnrichWithVisionDeps {
|
|
799
|
+
/** Классификатор фрагментов (LlmVisionClassifier; в тестах — фейк). */
|
|
800
|
+
classifier: VisionClassifier;
|
|
801
|
+
/** Извлекатель фрагментов с кэшем по hash (AC[3]). */
|
|
802
|
+
extractor: FragmentExtractor;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* VSL Snapshot Session (T1.2.4, ROADMAP.md M1.2; ARCHITECTURE.md §2.4/§5.2/§6.3).
|
|
807
|
+
*
|
|
808
|
+
* Stateful-фасад над пайплайном capture→segment→build→cache→diff:
|
|
809
|
+
* - первый вызов или смена URL → полный VslDocument; кэш очищается и
|
|
810
|
+
* заполняется объектами документа; счётчик версий сбрасывается в 1;
|
|
811
|
+
* - тот же URL → VslDiff последнего документа против нового (формат §6.2):
|
|
812
|
+
* diff_version = version + 1, base_version = version;
|
|
813
|
+
* - viewport изменился при том же URL → дополнительно
|
|
814
|
+
* store.invalidateCoordinates() — «сброс координат» §5.2, НЕ полный сброс.
|
|
815
|
+
*
|
|
816
|
+
* Состояние {lastUrl, lastViewport, lastDocument, version} — в памяти
|
|
817
|
+
* экземпляра; VslDocument (канон §4) версиями НЕ расширяется.
|
|
818
|
+
*
|
|
819
|
+
* Политика кэша — store отражает текущий документ: full → clear + заполнение;
|
|
820
|
+
* diff → удалённые invalidate, добавленные (поддеревья) и изменённые set из
|
|
821
|
+
* нового документа; неизменённые остаются кэшированными (после resize — с
|
|
822
|
+
* обнулённым coordHash: наблюдаемый эффект invalidateCoordinates).
|
|
823
|
+
*
|
|
824
|
+
* input.url/viewport/timestamp/background передаются в BuildOptions ЯВНО —
|
|
825
|
+
* дефолты builder (window.innerWidth/innerHeight/location.href/document.title)
|
|
826
|
+
* вычислялись бы от глобалов: недетерминированны в тестах и недоступны в Node;
|
|
827
|
+
* title — из input, иначе root.ownerDocument.title (портативный фолбэк — фикс
|
|
828
|
+
* packaging-smoke demo:cache-diff).
|
|
829
|
+
*/
|
|
830
|
+
|
|
831
|
+
/** Результат snapshot(): полный документ (1-й вызов / URL change) или дифф. */
|
|
832
|
+
type SnapshotResult = VslDocument | VslDiff;
|
|
833
|
+
/** Type guard: различение диффа и полного документа (контракт T1.2.4). */
|
|
834
|
+
declare function isVslDiff(result: SnapshotResult): result is VslDiff;
|
|
835
|
+
/**
|
|
836
|
+
* Результат snapshotWithVision (T1.5.5): снапшот (документ/дифф) + ДАННЫЕ
|
|
837
|
+
* фрагментов (base64) — стора в документе нет по контракту (DEC-015):
|
|
838
|
+
* документ несёт только vf/vf_meta, данные идут в decide({visualFragments}).
|
|
839
|
+
*/
|
|
840
|
+
interface VisionSnapshotResult {
|
|
841
|
+
snapshot: SnapshotResult;
|
|
842
|
+
fragments: VisualFragmentStore;
|
|
843
|
+
}
|
|
844
|
+
/** Входные параметры снапшота; url/viewport обязательны (инвалидация §5.2). */
|
|
845
|
+
interface SnapshotInput {
|
|
846
|
+
/** URL страницы: изменение → полный сброс кэша и новый полный документ. */
|
|
847
|
+
url: string;
|
|
848
|
+
/** Текущий viewport: изменение при том же URL → сброс координат кэша. */
|
|
849
|
+
viewport: {
|
|
850
|
+
width: number;
|
|
851
|
+
height: number;
|
|
852
|
+
};
|
|
853
|
+
title?: string;
|
|
854
|
+
/** ISO 8601; фиксировать в тестах (детерминизм canvas.timestamp и диффа). */
|
|
855
|
+
timestamp?: string;
|
|
856
|
+
background?: string;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Session-фасад (§2.4 «первый вызов → полный JSON, последующие → дифф»).
|
|
860
|
+
* Store инъекцией — тесты наблюдают coordHash-инвалидацию напрямую.
|
|
861
|
+
*/
|
|
862
|
+
declare class VslSnapshotSession {
|
|
863
|
+
private readonly store;
|
|
864
|
+
private lastUrl;
|
|
865
|
+
private lastViewport;
|
|
866
|
+
private lastDocument;
|
|
867
|
+
private version;
|
|
868
|
+
constructor(store?: CacheStore);
|
|
869
|
+
/**
|
|
870
|
+
* Строит VSL из DOM поддерева root (extractDomTree → segmentTree →
|
|
871
|
+
* buildVslDocument) и возвращает полный документ либо дифф (см. шапку).
|
|
872
|
+
*/
|
|
873
|
+
snapshot(root: Element, input: SnapshotInput): SnapshotResult;
|
|
874
|
+
/**
|
|
875
|
+
* Vision-вариант конвейера (T1.5.5): segmentTree → enrichWithVision (Level 5
|
|
876
|
+
* fallback — аннотация t + vf/vf_meta на элементах ДО сборки) → buildVslDocument
|
|
877
|
+
* → cache/diff — та же логика состояния, что snapshot(). Синхронный snapshot()
|
|
878
|
+
* сохранён (существующие контракты/тесты не ломаются); данные фрагментов
|
|
879
|
+
* (base64) возвращаются ОТДЕЛЬНО — в документе только метаданные (DEC-015).
|
|
880
|
+
*/
|
|
881
|
+
snapshotWithVision(root: Element, input: SnapshotInput, vision: EnrichWithVisionDeps): Promise<VisionSnapshotResult>;
|
|
882
|
+
/** BuildOptions из input + портативный title (логика бывшего snapshot()). */
|
|
883
|
+
private buildOptions;
|
|
884
|
+
/** Сборка документа + кэш/дифф (логика бывшего snapshot(), см. шапку модуля). */
|
|
885
|
+
private commit;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* OpenAI adapter (T1.3.2): Chat Completions API + function calling
|
|
890
|
+
* (tools/tool_choice «execute_action») без официального SDK — нативный fetch
|
|
891
|
+
* через инъекцию транспорта (ноль runtime-зависимостей, DEC-002).
|
|
892
|
+
*
|
|
893
|
+
* Retry (AC[8], §10.3): executeJsonWithRetry — до 3 попыток, только
|
|
894
|
+
* сеть/429/5xx. Мультимодальность (AC[7], §4.4): vf-ссылки из входа при
|
|
895
|
+
* наличии данных в сторе превращаются в content-части
|
|
896
|
+
* {type: 'image_url', image_url: {url: 'data:<mediaType>;base64,<data>'}}.
|
|
897
|
+
*/
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* Адаптер OpenAI (gpt-4o / gpt-4o-mini): system prompt + VSL JSON (§2.5)
|
|
901
|
+
* → tool call «execute_action» с действием §7.4.
|
|
902
|
+
*/
|
|
903
|
+
declare class OpenAIAdapter implements LlmAdapter {
|
|
904
|
+
private readonly apiKey;
|
|
905
|
+
private readonly model;
|
|
906
|
+
private readonly baseUrl;
|
|
907
|
+
private readonly maxRetries;
|
|
908
|
+
private readonly transport;
|
|
909
|
+
private readonly sleep;
|
|
910
|
+
constructor(config: LlmAdapterConfig);
|
|
911
|
+
sendPrompt(vslJson: VslInput, task: string, options?: SendPromptOptions): Promise<LlmResponse>;
|
|
912
|
+
/**
|
|
913
|
+
* Низкоуровневый вызов (T1.5.4, вариант C): провайдер-независимые аргументы
|
|
914
|
+
* → Chat Completions → LlmResponse. Потребители: sendPrompt (tool
|
|
915
|
+
* «execute_action») и LlmVisionClassifier (tool «classify_fragment»).
|
|
916
|
+
* Retry (§10.3) и парсинг tool_calls живут здесь в одном месте.
|
|
917
|
+
*/
|
|
918
|
+
sendRaw(system: string, userContent: readonly LlmContentPart[], tool: LlmToolDef): Promise<LlmResponse>;
|
|
919
|
+
/** Convenience §9.4: решение для цели + валидация (whitelist + target_id). */
|
|
920
|
+
decide(input: DecideInput): Promise<LlmAction>;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Anthropic adapter (T1.3.3): Messages API (/v1/messages) + tool use
|
|
925
|
+
* (tools/tool_choice «execute_action») без официального SDK — нативный fetch
|
|
926
|
+
* через инъекцию транспорта (ноль runtime-зависимостей, DEC-002).
|
|
927
|
+
*
|
|
928
|
+
* Retry (AC[8], §10.3): executeJsonWithRetry — до 3 попыток, только
|
|
929
|
+
* сеть/429/5xx. Мультимодальность (AC[7], §4.4): vf-ссылки из входа при
|
|
930
|
+
* наличии данных в сторе превращаются в content-блоки
|
|
931
|
+
* {type: 'image', source: {type: 'base64', media_type, data}}.
|
|
932
|
+
*/
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Адаптер Anthropic (claude-sonnet-4-20250514): system prompt + VSL JSON (§2.5)
|
|
936
|
+
* → tool_use «execute_action» с действием §7.4.
|
|
937
|
+
*/
|
|
938
|
+
declare class AnthropicAdapter implements LlmAdapter {
|
|
939
|
+
private readonly apiKey;
|
|
940
|
+
private readonly model;
|
|
941
|
+
private readonly baseUrl;
|
|
942
|
+
private readonly maxTokens;
|
|
943
|
+
private readonly maxRetries;
|
|
944
|
+
private readonly transport;
|
|
945
|
+
private readonly sleep;
|
|
946
|
+
constructor(config: LlmAdapterConfig);
|
|
947
|
+
sendPrompt(vslJson: VslInput, task: string, options?: SendPromptOptions): Promise<LlmResponse>;
|
|
948
|
+
/**
|
|
949
|
+
* Низкоуровневый вызов (T1.5.4, вариант C): провайдер-независимые аргументы
|
|
950
|
+
* → Messages API → LlmResponse. Потребители: sendPrompt (tool
|
|
951
|
+
* «execute_action») и LlmVisionClassifier (tool «classify_fragment»).
|
|
952
|
+
* tool_use-блоки фильтруются по tool.name (ранее — константа ACTION_TOOL_NAME):
|
|
953
|
+
* параметризация безопасна, sendPrompt передаёт ACTION_TOOL_NAME.
|
|
954
|
+
*/
|
|
955
|
+
sendRaw(system: string, userContent: readonly LlmContentPart[], tool: LlmToolDef): Promise<LlmResponse>;
|
|
956
|
+
/** Convenience §9.4: решение для цели + валидация (whitelist + target_id). */
|
|
957
|
+
decide(input: DecideInput): Promise<LlmAction>;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Alibaba/Qwen adapter (DashScope API, OpenAI-compatible): Chat Completions API
|
|
962
|
+
* + function calling (tools/tool_choice «execute_action») без официального SDK —
|
|
963
|
+
* нативный fetch через инъекцию транспорта (ноль runtime-зависимостей, DEC-002).
|
|
964
|
+
*
|
|
965
|
+
* DashScope compatible-mode endpoint: https://dashscope.aliyuncs.com/compatible-mode/v1
|
|
966
|
+
* Формат запроса/ответа идентичен OpenAI (chat/completions, tool_calls).
|
|
967
|
+
*
|
|
968
|
+
* Модель по умолчанию: qwen3.8-flash — самая быстрая и дешёвая модель Qwen (09.2026).
|
|
969
|
+
* Используется для vision-backend (классификация visual fragments).
|
|
970
|
+
*
|
|
971
|
+
* Retry (AC[8], §10.3): executeJsonWithRetry — до 3 попыток, только
|
|
972
|
+
* сеть/429/5xx. Мультимодальность (AC[7], §4.4): vf-ссылки из входа при
|
|
973
|
+
* наличии данных в сторе превращаются в content-части
|
|
974
|
+
* {type: 'image_url', image_url: {url: 'data:<mediaType>;base64,<data>'}}.
|
|
975
|
+
*/
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* Адаптер Alibaba/Qwen (qwen3.8-flash): system prompt + VSL JSON (§2.5)
|
|
979
|
+
* → tool call «execute_action» с действием §7.4.
|
|
980
|
+
*
|
|
981
|
+
* DashScope compatible-mode — OpenAI-compatible API, формат запроса/ответа
|
|
982
|
+
* идентичен OpenAI (chat/completions, tools, tool_choice, tool_calls).
|
|
983
|
+
*/
|
|
984
|
+
declare class AlibabaAdapter implements LlmAdapter {
|
|
985
|
+
private readonly apiKey;
|
|
986
|
+
private readonly model;
|
|
987
|
+
private readonly baseUrl;
|
|
988
|
+
private readonly maxRetries;
|
|
989
|
+
private readonly transport;
|
|
990
|
+
private readonly sleep;
|
|
991
|
+
constructor(config: LlmAdapterConfig);
|
|
992
|
+
sendPrompt(vslJson: VslInput, task: string, options?: SendPromptOptions): Promise<LlmResponse>;
|
|
993
|
+
/**
|
|
994
|
+
* Низкоуровневый вызов (T1.5.4, вариант C): провайдер-независимые аргументы
|
|
995
|
+
* → DashScope Chat Completions → LlmResponse. Потребители: sendPrompt (tool
|
|
996
|
+
* «execute_action») и LlmVisionClassifier (tool «classify_fragment»).
|
|
997
|
+
* Retry (§10.3) и парсинг tool_calls живут здесь в одном месте.
|
|
998
|
+
*/
|
|
999
|
+
sendRaw(system: string, userContent: readonly LlmContentPart[], tool: LlmToolDef): Promise<LlmResponse>;
|
|
1000
|
+
/** Convenience §9.4: решение для цели + валидация (whitelist + target_id). */
|
|
1001
|
+
decide(input: DecideInput): Promise<LlmAction>;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Action Model (ARCHITECTURE.md §7) для M1.3: канонический список действий,
|
|
1006
|
+
* классификация по цели и валидация ответа LLM (§7.4, T1.3.5).
|
|
1007
|
+
*
|
|
1008
|
+
* Валидация (§7.4 «Action Executor», п.1): action — валидное действие,
|
|
1009
|
+
* target_id существует в поданном VSL JSON. Поля value/reasoning проходят
|
|
1010
|
+
* сквозь с мягкой нормализацией типов (string | null).
|
|
1011
|
+
*/
|
|
1012
|
+
|
|
1013
|
+
/**
|
|
1014
|
+
* Канонический список действий Action Model (§7.1–7.3) — 24 действия:
|
|
1015
|
+
* базовые (10) + расширенные (9) + навигационные (4) + download (1).
|
|
1016
|
+
*/
|
|
1017
|
+
declare const VALID_ACTIONS: readonly ["click", "type", "clear", "scroll", "hover", "focus", "blur", "select", "check", "uncheck", "drag", "drop", "submit", "reset", "open", "close", "expand", "collapse", "wait", "navigate", "go_back", "go_forward", "refresh", "download"];
|
|
1018
|
+
/** Имя валидного действия Action Model. */
|
|
1019
|
+
type ValidAction = (typeof VALID_ACTIONS)[number];
|
|
1020
|
+
/**
|
|
1021
|
+
* Собирает множество валидных target_id из входа LLM (§7.4 валидация):
|
|
1022
|
+
* - VslDocument — все id рекурсивно;
|
|
1023
|
+
* - VslDiff — added (рекурсивно) + modified + unchanged_refs;
|
|
1024
|
+
* removed НЕ включаются: элементы удалены и не могут быть целью.
|
|
1025
|
+
*/
|
|
1026
|
+
declare function collectIds(vslJson: VslInput): ReadonlySet<string>;
|
|
1027
|
+
/**
|
|
1028
|
+
* Валидирует raw-объект действия из tool call модели (§7.4):
|
|
1029
|
+
* - action — строка из VALID_ACTIONS;
|
|
1030
|
+
* - для TARGET_ACTIONS target_id обязателен и должен присутствовать в validIds;
|
|
1031
|
+
* - value/reasoning — passthrough с мягкой нормализацией (string | null);
|
|
1032
|
+
* target_id у действий без цели игнорируется.
|
|
1033
|
+
* Бросает LlmValidationError при нарушении любого условия.
|
|
1034
|
+
*/
|
|
1035
|
+
declare function validateAction(raw: unknown, validIds: ReadonlySet<string>): LlmAction;
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* JSON Schema tool «execute_action» (T1.3.4) — одна схема для обоих провайдеров:
|
|
1039
|
+
* - OpenAI function calling: tools[0].function.parameters;
|
|
1040
|
+
* - Anthropic tool use: tools[0].input_schema.
|
|
1041
|
+
* Список действий — enum из VALID_ACTIONS (actions.ts): единый источник правды
|
|
1042
|
+
* с system prompt. Формат аргументов — §7.4 {action, target_id, value, reasoning}.
|
|
1043
|
+
*/
|
|
1044
|
+
/** Имя tool, через которое модель возвращает действие. */
|
|
1045
|
+
declare const ACTION_TOOL_NAME: "execute_action";
|
|
1046
|
+
/** Описание tool для моделей. */
|
|
1047
|
+
declare const ACTION_TOOL_DESCRIPTION = "Execute exactly one next browser action toward the user goal.";
|
|
1048
|
+
/**
|
|
1049
|
+
* JSON Schema аргументов tool (подмножество Draft 2020-12, совместимое
|
|
1050
|
+
* с OpenAI function calling и Anthropic tool use).
|
|
1051
|
+
*/
|
|
1052
|
+
declare const ACTION_TOOL_SCHEMA: {
|
|
1053
|
+
type: string;
|
|
1054
|
+
properties: {
|
|
1055
|
+
action: {
|
|
1056
|
+
type: string;
|
|
1057
|
+
description: string;
|
|
1058
|
+
enum: ("select" | "click" | "close" | "type" | "check" | "uncheck" | "clear" | "scroll" | "hover" | "focus" | "blur" | "drag" | "drop" | "submit" | "reset" | "open" | "expand" | "collapse" | "wait" | "navigate" | "go_back" | "go_forward" | "refresh" | "download")[];
|
|
1059
|
+
};
|
|
1060
|
+
target_id: {
|
|
1061
|
+
type: string;
|
|
1062
|
+
description: string;
|
|
1063
|
+
};
|
|
1064
|
+
value: {
|
|
1065
|
+
type: string[];
|
|
1066
|
+
description: string;
|
|
1067
|
+
};
|
|
1068
|
+
reasoning: {
|
|
1069
|
+
type: string;
|
|
1070
|
+
description: string;
|
|
1071
|
+
};
|
|
1072
|
+
};
|
|
1073
|
+
required: string[];
|
|
1074
|
+
additionalProperties: boolean;
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
/**
|
|
1078
|
+
* Промпты LLM (T1.3.4): system prompt (VSL-формат + Action Model + 3 few-shot)
|
|
1079
|
+
* и user-промпт по каноническому шаблону ARCHITECTURE.md §2.5.
|
|
1080
|
+
*
|
|
1081
|
+
* Промпты провайдер-независимы: few-shot включены ТЕКСТОМ в system prompt,
|
|
1082
|
+
* поэтому OpenAI (function calling) и Anthropic (tool use) получают идентичную
|
|
1083
|
+
* инструкцию; само действие модель возвращает через tool «execute_action»
|
|
1084
|
+
* (см. schema.ts). Список действий рендерится из VALID_ACTIONS (actions.ts) —
|
|
1085
|
+
* единый источник правды для промпта и enum в JSON Schema.
|
|
1086
|
+
*
|
|
1087
|
+
* Примечание (neg note f48a9bda4bd5): llm_prompts_examples.md из корня —
|
|
1088
|
+
* примеры домена scene editing (Phase 4), НЕ заготовка для этого промпта;
|
|
1089
|
+
* текст построен с нуля из ARCHITECTURE §2.5 + §7.
|
|
1090
|
+
*/
|
|
1091
|
+
|
|
1092
|
+
/** Few-shot пример: заголовок, компактный VSL-фрагмент, цель, действие. */
|
|
1093
|
+
interface FewShotExample {
|
|
1094
|
+
title: string;
|
|
1095
|
+
/** Компактный VSL JSON (иллюстративный фрагмент). */
|
|
1096
|
+
vsl: string;
|
|
1097
|
+
goal: string;
|
|
1098
|
+
/** Ожидаемый ответ модели — JSON действия (формат §7.4). */
|
|
1099
|
+
action: string;
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Строит system prompt: описание VSL-формата (включая семантику диффа §6.2),
|
|
1103
|
+
* правила ответа через tool «execute_action», Action Model и 3 few-shot.
|
|
1104
|
+
*/
|
|
1105
|
+
declare function buildSystemPrompt(): string;
|
|
1106
|
+
/** Строит user-промпт по шаблону §2.5: Current screen (VSL JSON) + User goal. */
|
|
1107
|
+
declare function buildUserPrompt(vslJson: VslInput, goal: string): string;
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* HTTP-транспорт и retry для LLM-адаптеров (T1.3.2/T1.3.3, ARCHITECTURE §10.1/§10.3):
|
|
1111
|
+
* - defaultTransport — нативный fetch (node>=18, ноль runtime-зависимостей);
|
|
1112
|
+
* - retryWithBackoff — до 3 попыток с exponential backoff (§10.3, 2^attempt секунд);
|
|
1113
|
+
* - executeJsonWithRetry — запрос + статус-проверка + парс JSON, с retry.
|
|
1114
|
+
*
|
|
1115
|
+
* Retry-политика (уточнение §10.1/§10.3): повторяются ТОЛЬКО сетевые ошибки,
|
|
1116
|
+
* HTTP 429 и 5xx; прочие 4xx — fast-fail (401/403 повтором не чинятся).
|
|
1117
|
+
* Паузы: 1s, 2s перед 2-й и 3-й попыткой (форма 2^attempt секунд из §10.3).
|
|
1118
|
+
* sleep инъецируется (RetryOptions.sleep / LlmAdapterConfig.sleep) —
|
|
1119
|
+
* тесты идут без реальных задержек.
|
|
1120
|
+
*/
|
|
1121
|
+
|
|
1122
|
+
interface RetryOptions {
|
|
1123
|
+
/** Всего попыток, включая первую (§10.3: 3). Значения <1 приводятся к 1. */
|
|
1124
|
+
maxRetries?: number;
|
|
1125
|
+
/** Задержка между попытками (инъекция для тестов). */
|
|
1126
|
+
sleep?: Sleep;
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Исполняет operation с retry до maxRetries попыток и exponential backoff
|
|
1130
|
+
* 1s/2s/… (§10.3). После исчерпания попыток (или на неремрабатой ошибке)
|
|
1131
|
+
* исключение уходит наружу.
|
|
1132
|
+
*/
|
|
1133
|
+
declare function retryWithBackoff<T>(operation: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Типы Action Executor (ARCHITECTURE.md §7.4 «Action Executor»,
|
|
1137
|
+
* ROADMAP.md M1.4: T1.4.1–T1.4.3).
|
|
1138
|
+
*
|
|
1139
|
+
* Executor исполняет действия Action Model M1.3 (VALID_ACTIONS, 24 действия)
|
|
1140
|
+
* на живом DOM. Контракты:
|
|
1141
|
+
* - вход — LlmAction (llm/types.ts): {action, target_id?, value?, reasoning?};
|
|
1142
|
+
* - value-кодирование — строго по PARAMETER_CONVENTIONS (llm/prompt.ts),
|
|
1143
|
+
* единый источник правды с LLM (prompt/schema согласованы в M1.3);
|
|
1144
|
+
* - резолв target_id — чисто вычислительный, executor не аннотирует DOM;
|
|
1145
|
+
* - executeAction — soft-fail: сбои возвращаются в ActionResult (error), а не
|
|
1146
|
+
* бросаются, чтобы шаг агента не прерывал агентный цикл (extension background).
|
|
1147
|
+
*/
|
|
1148
|
+
/** Информация о загруженном файле (заполняется при download действии). */
|
|
1149
|
+
interface DownloadInfo {
|
|
1150
|
+
/** Уникальный идентификатор загрузки. */
|
|
1151
|
+
downloadId: string;
|
|
1152
|
+
/** Имя файла. */
|
|
1153
|
+
filename: string;
|
|
1154
|
+
/** URL источника. */
|
|
1155
|
+
url: string;
|
|
1156
|
+
/** Статус загрузки: pending | completed | cancelled | failed. */
|
|
1157
|
+
status: 'pending' | 'completed' | 'cancelled' | 'failed';
|
|
1158
|
+
}
|
|
1159
|
+
/** Результат исполнения одного действия (возврат executeAction агенту). */
|
|
1160
|
+
interface ActionResult {
|
|
1161
|
+
/** true — действие исполнено; false — отклонено (детали в error). */
|
|
1162
|
+
success: boolean;
|
|
1163
|
+
/** Имя исполненного действия (эхо LlmAction.action). */
|
|
1164
|
+
action: string;
|
|
1165
|
+
/** target_id, если действие было с целью (эхо из входа). */
|
|
1166
|
+
targetId?: string;
|
|
1167
|
+
/** Машиночитаемое описание сбоя при success=false; обратная связь агенту. */
|
|
1168
|
+
error?: string;
|
|
1169
|
+
/** Информация о загрузке (заполняется при action='download' и success=true). */
|
|
1170
|
+
download?: DownloadInfo;
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Ошибка исполнения действия: невалидные параметры, целевой элемент не найден
|
|
1174
|
+
* (DOM изменился с момента снапшота) или расхождение tag при резолве id.
|
|
1175
|
+
* Бросается resolveTarget и внутренними шагами executeAction; снаружи
|
|
1176
|
+
* executeAction конвертирует её в ActionResult{success:false, error}.
|
|
1177
|
+
*/
|
|
1178
|
+
declare class ActionExecutionError extends Error {
|
|
1179
|
+
constructor(message: string);
|
|
1180
|
+
}
|
|
1181
|
+
/** Опции исполнения; все опциональны (дефолты рассчитаны на прод в браузере). */
|
|
1182
|
+
interface ExecutorOptions {
|
|
1183
|
+
/**
|
|
1184
|
+
* Корень обхода DOM при резолве target_id; по умолчанию document.body.
|
|
1185
|
+
* Обязан совпадать с root, переданным в extractDomTree/VslSnapshotSession:
|
|
1186
|
+
* indexPath в VSL id считается от корня обхода (domExtractor, §5.1).
|
|
1187
|
+
*/
|
|
1188
|
+
root?: Element;
|
|
1189
|
+
/** Таймаут wait (мс) — для селектора или "idle"; по умолчанию 5000. */
|
|
1190
|
+
waitTimeout?: number;
|
|
1191
|
+
/**
|
|
1192
|
+
* Амплитуда скролла (px) для bare-направления ("down" без amount);
|
|
1193
|
+
* по умолчанию 300. Явный "down:300" перекрывает дефолт.
|
|
1194
|
+
*/
|
|
1195
|
+
defaultScrollAmount?: number;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
/**
|
|
1199
|
+
* Action Executor — исполнение действий Action Model M1.3 на живом DOM
|
|
1200
|
+
* (ARCHITECTURE.md §7.4 «Action Executor», ROADMAP.md M1.4: T1.4.1–T1.4.3).
|
|
1201
|
+
*
|
|
1202
|
+
* Контракт (spec M1.4; value-кодирование — строго по PARAMETER_CONVENTIONS
|
|
1203
|
+
* llm/prompt.ts, единый источник правды с LLM — решение dc_4/dc_7):
|
|
1204
|
+
* - вход — LlmAction (llm/types.ts): {action, target_id?, value?, reasoning?};
|
|
1205
|
+
* - target_id резолвится resolveTarget (tag_indexPath → обход DOM от корня
|
|
1206
|
+
* снапшота: ExecutorOptions.root ?? document.body) — executor не аннотирует
|
|
1207
|
+
* DOM и не хранит ссылок на элементы между действиями;
|
|
1208
|
+
* - executeAction — soft-fail: всегда возвращает Promise<ActionResult>, не
|
|
1209
|
+
* бросает; ActionExecutionError (и любой неожиданный error) перехватывается
|
|
1210
|
+
* и отражается в result.error — сбой шага не роняет агентный цикл
|
|
1211
|
+
* (extension background);
|
|
1212
|
+
* - значения input/select/checkbox выставляются программно + dispatch
|
|
1213
|
+
* input/change с bubbles — React-совместимость (AC[2]): React обновляет
|
|
1214
|
+
* controlled-компоненты только по событиям, а не от прямой смены .value;
|
|
1215
|
+
* - type ЗАМЕНЯЕТ значение поля (не дополняет) — детерминизм: повторный type
|
|
1216
|
+
* даёт то же состояние; очистка поля — отдельным действием clear;
|
|
1217
|
+
* - сообщения ошибок — на английском (публичный API @thinkingos/vsl-sdk и вход для
|
|
1218
|
+
* LLM-агента, решение 22.09.2026).
|
|
1219
|
+
*/
|
|
1220
|
+
|
|
1221
|
+
/**
|
|
1222
|
+
* Исполняет одно действие LLM на живом DOM (§7.4, T1.4.1–T1.4.3).
|
|
1223
|
+
*
|
|
1224
|
+
* Soft-fail (решение dev_1): любые сбои — невалидные параметры, целевой
|
|
1225
|
+
* элемент не найден, DOM изменился с момента снапшота — возвращаются в
|
|
1226
|
+
* ActionResult.error, исключение агентному циклу не бросается.
|
|
1227
|
+
*
|
|
1228
|
+
* @param action — действие от LLM (после validateAction в M1.3; состав VSL
|
|
1229
|
+
* здесь повторно не валидируется — executor проверяет только то, что нужно
|
|
1230
|
+
* для исполнения).
|
|
1231
|
+
* @param options — ExecutorOptions; root обязан совпадать с корнем снапшота.
|
|
1232
|
+
* @returns ActionResult с эхом action/targetId.
|
|
1233
|
+
*/
|
|
1234
|
+
declare function executeAction(action: LlmAction, options?: ExecutorOptions): Promise<ActionResult>;
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* Резолв target_id → DOM-элемент (ARCHITECTURE.md §7.4 «Action Executor»,
|
|
1238
|
+
* ROADMAP.md M1.4, T1.4.1).
|
|
1239
|
+
*
|
|
1240
|
+
* Контракт (spec M1.4 п.4; формат id — vslBuilder.ts:100):
|
|
1241
|
+
* - target_id = `tag_index1_index2_...`: tag — имя тега в нижнем регистре,
|
|
1242
|
+
* индексы — indexPath элемента (id = `${el.tag}_${el.indexPath.join('_')}`);
|
|
1243
|
+
* - indexPath — индексы среди ЭЛЕМЕНТНЫХ детей каждого уровня, вычисляются по
|
|
1244
|
+
* структуре DOM ДО фильтрации (domExtractor.collectVisibleChildren) — позиция
|
|
1245
|
+
* стабильна независимо от фильтров видимости/сегментации;
|
|
1246
|
+
* - extractDomTree возвращает ЛЕС потомков root (сам root не включается), поэтому
|
|
1247
|
+
* каждый VSL id содержит ≥1 индекс, а обход стартует с root.children[index];
|
|
1248
|
+
* - обход от корня снапшота: ExecutorOptions.root ?? document.body. Root обязан
|
|
1249
|
+
* совпадать с root, переданным в extractDomTree/VslSnapshotSession, иначе
|
|
1250
|
+
* indexPath укажет не на тот элемент;
|
|
1251
|
+
* - резолв чисто вычислительный: executor не аннотирует DOM и не хранит ссылок;
|
|
1252
|
+
* - при невалидном формате id, отсутствии элемента по indexPath или несовпадении
|
|
1253
|
+
* tag бросается ActionExecutionError (DOM изменился с момента снапшота).
|
|
1254
|
+
* Сообщения ошибок — на английском: runtime-строки — публичный API @thinkingos/vsl-sdk и
|
|
1255
|
+
* вход для LLM-агента (решение 22.09.2026).
|
|
1256
|
+
*/
|
|
1257
|
+
|
|
1258
|
+
/**
|
|
1259
|
+
* Резолвит target_id в DOM-элемент обходом от корня снапшота (§7.4, T1.4.1).
|
|
1260
|
+
*
|
|
1261
|
+
* @param targetId — id элемента из VSL JSON, формат "tag_index1_index2_...".
|
|
1262
|
+
* @param options — ExecutorOptions; root обязан совпадать с корнем снапшота
|
|
1263
|
+
* (extractDomTree/§5.1), по умолчанию document.body.
|
|
1264
|
+
* @returns найденный DOM-элемент.
|
|
1265
|
+
* @throws ActionExecutionError — невалидный формат id, элемент по indexPath не
|
|
1266
|
+
* найден или tag не совпал (DOM изменился с момента снапшота).
|
|
1267
|
+
*/
|
|
1268
|
+
declare function resolveTarget(targetId: string, options?: ExecutorOptions): Element;
|
|
1269
|
+
|
|
1270
|
+
/**
|
|
1271
|
+
* VSL SDK — публичное API. Точка входа:
|
|
1272
|
+
* - Snapshot generation (M1.1): capture → segment → build;
|
|
1273
|
+
* - Cache & Diff (M1.2): Cache Store, invalidation, Diff Engine (§6.2),
|
|
1274
|
+
* Snapshot Session (первый вызов → VslDocument, далее → VslDiff).
|
|
1275
|
+
* - LLM Integration (M1.3): LLM Adapter (OpenAI/Anthropic), Action Model, промпты и retry.
|
|
1276
|
+
*/
|
|
1277
|
+
|
|
1278
|
+
declare const VSL_SDK_VERSION = "0.1.0";
|
|
1279
|
+
|
|
1280
|
+
export { ACTION_TOOL_DESCRIPTION, ACTION_TOOL_NAME, ACTION_TOOL_SCHEMA, ARIA_ROLE_TYPE_MAP, ActionExecutionError, type ActionResult, AlibabaAdapter, AnthropicAdapter, type BuildOptions, type CacheEntry, type CacheStore, type DecideInput, type DiffOptions, type ExecutorOptions, type ExtractedElement, type FewShotExample, LEVEL1_TAG_MAP, type LlmAction, type LlmAdapter, type LlmAdapterConfig, LlmError, type LlmResponse, type LlmTransport, type LlmUsage, LlmValidationError, type MutationObserverHandle, OpenAIAdapter, type Rect, type RetryOptions, type SegmentedElement, type SendPromptOptions, type Sleep, type SnapshotInput, type SnapshotResult, VALID_ACTIONS, VSL_SDK_VERSION, VSL_VERSION, type ValidAction, type VisualFragment, type VisualFragmentData, type VisualFragmentStore, type VisualFragmentType, type VslCanvas, type VslDiff, type VslDiffChanges, type VslDocument, type VslFragmentMeta, type VslInput, type VslModifiedObject, type VslObject, type VslObjectWithVf, type VslRemovedObject, VslSnapshotSession, type VslState, type VslType, type VslViewport, attachMutationObserver, buildSystemPrompt, buildUserPrompt, buildVslDocument, collectIds, computeContentHash, computeCoordHash, createCacheStore, diffVslDocuments, executeAction, extractDomTree, isAriaHidden, isVslDiff, ownText, resolveAriaRoleType, resolveLevel1Type, resolveSt, resolveTarget, retryWithBackoff, segmentTree, validateAction };
|