@7n/llm-lib 3.0.3 → 3.1.1

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.1.1] - 2026-08-09
4
+
5
+ ### Changed
6
+
7
+ - docs(llm-lib): регенерує застарілу файлову документацію (doc-files backlog) (#417)
8
+
9
+ ## [3.1.0] - 2026-08-08
10
+
11
+ ### Fixed
12
+
13
+ - runOneShot тепер конвертує stopReason='error' (pi проковтнула провал провайдера без винятку, напр. вичерпані внутрішні connection-retry) у справжній error з errorMessage провайдера — раніше consumers бачили error:null при порожньому content, невідрізниме від легітимної порожньої відповіді, і тихо продовжували, вважаючи виклик успішним
14
+
3
15
  ## [3.0.3] - 2026-08-08
4
16
 
5
17
  ### Changed
package/lib/docs/batch.md CHANGED
@@ -3,37 +3,26 @@ type: JS Module
3
3
  title: batch.mjs
4
4
  resource: llm-lib/lib/batch.mjs
5
5
  docgen:
6
- crc: ce0ba158
7
- model: openai-codex/gpt-5.4-mini
8
- tier: cloud-min
9
- score: 100
6
+ crc: 1f69a064
7
+ model: omlx/gemma-4-26b-a4b-it
8
+ tier: local-min
9
+ score: 80
10
10
  judgeModel: openai-codex/gpt-5.4-mini
11
11
  ---
12
12
 
13
13
  ## Огляд
14
14
 
15
- Тип 2b (OpenAI-сумісний API, batch) — `submitBatch` обирає між клієнтською
16
- емуляцією (v1, чанкований конкурентний прогін через Тип 2a
17
- `llm_lib::local_cloud`) і справжнім `/v1/batches` litellm batch-adapter-а
18
- (спека `docs/specs/2026-07-27-batch-local-avg-real-batches.md`), під тим
19
- самим інтерфейсом `submit progress → results` для обох. Вибір —
20
- `backend` (дефолт `'auto'`: реальний Batch API лише коли резолвлений
21
- провайдер `litellm` і кешована мережева проба адаптера пройшла; локальний
22
- omlx завжди йде емуляцією).
15
+ Тип 2b (OpenAI-сумісний API, batch) — `submitBatch` завжди йде через
16
+ справжній `/v1/batches` OpenAI-сумісний batch-adapter резолвленого
17
+ провайдера (спека `docs/specs/2026-07-27-batch-local-avg-real-batches.md`).
18
+ Клієнтську емуляцію (v1, чанкований прогін через Тип 2a) вилучено
19
+ провайдер без зареєстрованого `base_url`/`api_key` у `localProviders`
20
+ повертає явну помилку, без тихого фолбеку.
23
21
 
24
22
  Тонкий JS-клієнт до Rust-крейта `llm_lib::batch`/`llm_lib::remote_batch`
25
23
  через napi FFI in-process (`llm-lib/crates/llm-lib-napi`) — жодного
26
- власного чанкінгу чи HTTP тут (анти-приклад, якого це узагальнює:
27
- `mlmail/use-summary.js` чанкує переклади проти omlx вручну, з
28
- вистражданими лімітами).
29
-
30
- ## Поведінка
31
-
32
- submitBatch працює з набором batch-елементів як з одним запитом: для кожного елемента зберігається зв’язок між `customId` і відповіддю, а порядок результатів відповідає порядку вхідних `items`.
33
-
34
- Якщо для елемента є помилка, у результаті повертається запис з `error` замість `ok`; для одного елемента заповнюється рівно одне з цих полів. Це дає змогу обробляти частково успішні batch-и без втрати прив’язки до конкретного `customId`.
35
-
36
- Передані `system`, `localProviders`, `backend`, `chunkSize`, `concurrency`, `pollIntervalMs` і `pollTimeoutMs` впливають на поведінку batch-виклику, а `onProgress` дозволяє отримувати проміжний стан виконання.
24
+ власного HTTP тут (анти-приклад, якого це узагальнює: `mlmail/use-summary.js`
25
+ чанкує переклади проти omlx вручну, з вистражданими лімітами).
37
26
 
38
27
  ## Публічний API
39
28
 
@@ -43,7 +32,7 @@ submitBatch працює з набором batch-елементів як з од
43
32
 
44
33
  ## Сценарії використання
45
34
 
46
- - `llm-lib/tests/batch.test.mjs` (submitBatch) — делегує modelSpecOrTier/items у native.submitBatch і віддає його результат; явний; кожен item нормалізується до {customId, prompt, system}, навіть без власного system; localProviders/system/chunkSize/concurrency прокидаються в options/config; onProgress прокидається останнім аргументом; ще 2
35
+ - `llm-lib/tests/batch.test.mjs` (submitBatch) — делегує modelSpecOrTier/items у native.submitBatch і віддає його результат; явний; кожен item нормалізується до {customId, prompt, system}, навіть без власного system; localProviders/system/pollIntervalMs/pollTimeoutMs прокидаються в options/config; onProgress прокидається останнім аргументом; ще 2
47
36
 
48
37
  ## Гарантії поведінки
49
38
 
package/lib/docs/chain.md CHANGED
@@ -3,25 +3,32 @@ type: JS Module
3
3
  title: chain.mjs
4
4
  resource: llm-lib/lib/chain.mjs
5
5
  docgen:
6
- crc: 68b416b8
6
+ crc: 6d305cc5
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ judgeModel: openai-codex/gpt-5.4-mini
7
11
  ---
8
12
 
9
13
  ## Огляд
10
14
 
11
- Ланцюжок (chain) групує кілька LLM-викликів у одну задачу з фінальним результатом: виклики й перевиклики local/cloud моделей отримують спільний `chainId`, а `chain.end()` пише підсумковий запис `kind:'chain'` у глобальний trace. Основа аналітики: escalation-rate local→cloud, cloud-вартість per задача, кандидати на T0-дистиляцію. Явний handle без прихованого контексту; один chain = послідовне використання (по одному на одиницю роботи).
15
+ `promptHash` дає короткий ідентифікатор для останнього user-повідомлення, а `startChain` запускає ланцюжок від початкового контексту до фінального запису з підсумком і додатковими даними. Це потрібно, щоб у межах одного ланцюжка зберігати зв’язок між стартом і завершенням роботи та отримувати підсумок кроків.
12
16
 
13
17
  ## Поведінка
14
18
 
15
- startChain створює handle: id (hex16), nextStep (монотонний лічильник кроків; кличе раннер), note (акумуляція local/cloud лічильників, usage, usageCloud, errors, finalModel; local/cloud визначає isLocalModel), headers (X-Chain-Id/Step/Kind/Cwd для локального проксі myllm), traceFields (chainId/chainKind/chainUnit/chainStep у per-call trace-запис), end (ідемпотентний фінальний запис kind:'chain' з outcome/steps/localCalls/cloudCalls/escalated/wallMs/usage/usageCloud/meta/extra через writeTrace).
16
- promptHash — sha256 hex16 lowercase від trim(text) останнього user-повідомлення. КОНТРАКТ кореляції з myllm (дзеркальна реалізація у chains.rs) — не міняти односторонньо.
19
+ `promptHash` формує стабільний короткий ідентифікатор для останнього user-повідомлення: унікальність і зіставлення в межах ланцюжка залежать лише від нормалізованого тексту, а не від супровідного контексту.
20
+
21
+ `startChain` запускає й веде спільний стан ланцюжка: отримує початковий контекст, створює службові поля для кореляції, накопичує usage по кроках, а в кінці зводить результат у фінальний запис із підсумком і додатковими даними.
17
22
 
18
23
  ## Публічний API
19
24
 
20
- startChain({kind, unit, cwd?, meta?, deps?}) chain handle; deps.trace/clock/isLocal інжекти для тестів.
21
- promptHash(text)хеш за контрактом кореляції.
25
+ - promptHash Хеш промпта за спільним контрактом кореляції (див. шапку модуля).
26
+ - startChain Створює ланцюжок задачі.
27
+
28
+ ## Сценарії використання
29
+
30
+ - `llm-lib/tests/chain.test.mjs` (startChain; promptHash) — id — hex16, step монотонний; note агрегує local/cloud, usage і errors; фінал у end; end ідемпотентний — другий виклик без другого запису; headers: X-Chain-* з кроком і urlencoded cwd; без cwd — без X-Chain-Cwd; ще 4
22
31
 
23
32
  ## Гарантії поведінки
24
33
 
25
- - end ідемпотентний: рівно один фінальний запис на chain.
26
- - Раннери без opts.chain працюють як раніше — chain-поля в trace зʼявляються лише з chain.
27
- - escalated = localCalls>0 && cloudCalls>0.
34
+ - Підтримує локальні сценарії роботи ланцюжка та накопичення підсумкових даних.
@@ -3,25 +3,33 @@ type: JS Module
3
3
  title: chains-report.mjs
4
4
  resource: llm-lib/lib/chains-report.mjs
5
5
  docgen:
6
- crc: 21b5f666
6
+ crc: 6462c1b9
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ judgeModel: openai-codex/gpt-5.4-mini
7
11
  ---
8
12
 
9
13
  ## Огляд
10
14
 
11
- Pure-агрегатор аналітики ланцюжків з trace-записів: per-kind і per-rule метрики (chains, success/partial/fail, escalation-rate, cloud calls/tokens), топ кандидатів на T0-дистиляцію (юніти, що завжди ескалюють у cloud або взагалі cloud-only), незакриті ланцюжки (креші). Нічого не читає сам — читання файла в bin/chains-report.mjs (CLI n-llm-chains-report).
15
+ `parseTraceJsonl` читає trace-JSONL і виділяє придатні для аналізу записи. `buildChainsReport` перетворює ці дані на звіт про ланцюжки для подальшого розбору. Містить локальні fail-safe гілки; інші помилки можуть поширюватися назовні.
12
16
 
13
17
  ## Поведінка
14
18
 
15
- buildChainsReport фільтрує kind:'chain' записи (опц. sinceTs), агрегує perKind/perRule (для fix-concern правило = префікс unit до '/'), рахує t0Candidates (cloudCalls>0 і escalated+cloudOnly == chains, сорт за cloudTokens) і unclosed (step-записи з chainId без фінального запису).
16
- parseTraceJsonl — по-рядковий парс JSONL з пропуском сміття (best-effort writer).
19
+ parseTraceJsonl перетворює сирий JSONL-текст trace-файла на масив придатних до аналізу записів, тихо пропускаючи порожні та зіпсовані рядки; далі buildChainsReport працює вже тільки з таким масивом і зводить його в аналітичний звіт без власного читання файлів чи запису кудись назовні.
20
+
21
+ buildChainsReport розділяє потік на фінальні chain-записи й step-записи, фільтрує їх за нижньою межею часу за полем ts, після чого накопичує підсумки по типах ланцюжків, по правилах для fix-concern, а також по unit-комбінаціях для виявлення стабільно ескалюючих кандидатів на T0-дистиляцію. У результаті повертається структурований звіт із підсумками, списком T0-кандидатів і незакритими ланцюжками; локальні fail-safe гілки лише пом’якшують окремі пропуски в полях, але інші помилки можуть піти назовні.
17
22
 
18
23
  ## Публічний API
19
24
 
20
- buildChainsReport(records, {sinceTs?}) {perKind, perRule, t0Candidates, unclosed, totals}.
21
- parseTraceJsonl(text) object[].
25
+ - buildChainsReport Будує звіт по ланцюжках.
26
+ - parseTraceJsonl — Парсить JSONL-текст trace-файла у масив записів (сміттєві рядки пропускаються).
27
+
28
+ ## Сценарії використання
29
+
30
+ - `llm-lib/tests/chains-report.test.mjs` (buildChainsReport; parseTraceJsonl) — per-kind і per-rule агрегати з escalation-rate; T0-кандидати: лише units що завжди ескалюють або cloud-only, сорт за cloudTokens; unclosed: step-записи без фінального chain-запису; старі записи без chain-полів і sinceTs-фільтр; пропускає сміття й порожні рядки
22
31
 
23
32
  ## Гарантії поведінки
24
33
 
25
- - Read-only і pure: жодного IO.
26
- - Старі trace-записи без chain-полів не ламають звіт ігноруються.
27
- - Незакриті ланцюжки не входять у rate-метрики.
34
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
35
+ - Містить локальні fail-safe гілки; інші помилки можуть поширюватися назовні.
@@ -3,29 +3,36 @@ type: JS Module
3
3
  title: harness.mjs
4
4
  resource: llm-lib/lib/harness.mjs
5
5
  docgen:
6
- crc: 3ff88af8
6
+ crc: 388b9491
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ issues: judge-refine:kept-original,judge:inaccurate:0.95
11
+ judgeModel: openai-codex/gpt-5.4-mini
7
12
  ---
8
13
 
9
14
  ## Огляд
10
15
 
11
- Run-harness фасад (Фаза A4): єдиний декларативний вхід над трьома раннерами пакета (`runOneShot` / `runAgentFix` / `runAgentSkill`). Consumer описує ЩО запустити профілем-обʼєктом, а не набором позиційних opts; той самий профіль серіалізується у JSON це те, що дозволяє майбутньому MT-адаптеру мапити вузол графа на конфігурацію без коду. Фасад тонкий: резолвить профіль у opts і делегує в наявний раннер, не дублюючи їхньої логіки (write-guard, verify-loop, toolset-и лишаються в раннерах).
16
+ `HARNESS_SCHEMA_VERSION` фіксує версію контракту профілю для фасаду запуску, `validateProfile` відсіює профіль, що не відповідає цьому контракту, а `createHarness` збирає узгоджений запуск на основі перевірених даних. Це тримає перевірку профілю окремо від створення harness і дає один точковий вхід для роботи з профілем через `HARNESS_SCHEMA_VERSION`, `validateProfile`, `createHarness`.
12
17
 
13
18
  ## Поведінка
14
19
 
15
- Профіль обʼєкт `{ schema_version, kind, …налаштування }`, де `kind` (`fix`/`skill`/`one-shot`) привʼязує його до раннера, а решта полів (tier, model, timeoutMs, maxTokens, thinkingLevel, verifyMax, anchoredEdits, webTools) стають дефолтами opts. `schema_version` присутній з першої версії й перевіряється на сумісність несумісний або невідомий `kind` дає структуровану помилку валідації ще до раннера.
20
+ `HARNESS_SCHEMA_VERSION` задає спільну версію контракту для всього фасаду: `validateProfile` звіряє вхідний профіль саме з нею, а `createHarness` відкидає профілі з несумісною схемою ще до запуску раннера. Це тримає всі споживачі на одному форматі профілю й дає змогу безпечно еволюціонувати контракт через явний bump.
16
21
 
17
- `createHarness({ profiles })` повертає обʼєкт із `run(spec)` і `profileNames()`. У `run` профіль задається іменем (з мапи) або інлайн-обʼєктом, а per-виклик поля (динаміка: cwd, violation, verify, messages, prompt тощо) зливаються поверх дефолтів профілю й перекривають збіжні. Далі harness делегує з правильними позиційними аргументами кожного раннера: `fix` `(ruleId, violation, cwd, opts)`, `skill` `(prompt, opts)`, `one-shot` `(opts)`. Поля `kind`/`schema_version` у opts раннера не потрапляють.
22
+ `validateProfile` є єдиною точкою перевірки профілю перед виконанням: вона приймає або іменований профіль із набору, переданого в `createHarness`, або інлайн-обʼєкт із виклику, і гарантує лише дві речі сумісну версію схеми та відомий `kind`. Будь-яка помилка перетворюється на зрозумілий текстовий збій, який `createHarness` піднімає далі без спроб “виправити” дані.
18
23
 
19
- Раннери тягнуться lazy (динамічний import у гілці потрібного `kind`)top-level модуль лишається вільним від pi; у тестах раннери інжектуються через `deps`.
24
+ `createHarness` збирає декларативний профіль і поточні дані виклику в один набір opts, де профіль дає дефолти, а дані виклику їх перекривають. Після цього він обирає відповідний раннер за `kind` і передає туди вже готовий контракт: для fix і skill через їхній очікуваний спосіб виклику, для one-shot одним обʼєктом. Логіка виконання, включно з деталями раннерів і їхніми внутрішніми перевірками, лишається поза цим фасадом.
20
25
 
21
26
  ## Публічний API
22
27
 
23
- HARNESS_SCHEMA_VERSION — поточна версія схеми профілю.
24
- validateProfile — перевіряє `kind` і `schema_version`, повертає `{ok}` або `{ok:false, error}`.
25
- createHarness — будує harness із іменованих профілів; `run(spec)` запускає задачу, `profileNames()` перелічує профілі.
28
+ - HARNESS_SCHEMA_VERSION — Поточна версія схеми профілю. Несумісна зміна → bump + міграція consumer-ів.
29
+ - validateProfile — Валідує профіль: відомий `kind`, сумісний `schema_version`.
30
+ - createHarness — Створює harness із набором іменованих профілів.
31
+
32
+ ## Сценарії використання
33
+
34
+ - `llm-lib/tests/harness.test.mjs` (validateProfile; createHarness.run — делегація) — валідний fix/skill/one-shot профіль; невідомий kind → помилка; несумісний schema_version → помилка; не-обʼєкт → помилка; fix: профіль-дефолти + per-виклик поля → позиційні (ruleId, violation, cwd) + opts; ще 7
26
35
 
27
36
  ## Гарантії поведінки
28
37
 
29
- - Контракт раннерів не змінюється: harness лише перекладає профіль+виклик у їхні аргументи й повертає їхній результат як є.
30
- - Невалідний/невідомий профіль зупиняється до виклику раннера (жодного часткового ефекту).
31
- - Top-level pi-free: жодного pi-import, поки не викликано `run` відповідного kind.
38
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
@@ -3,36 +3,35 @@ type: JS Module
3
3
  title: local-providers.mjs
4
4
  resource: llm-lib/lib/local-providers.mjs
5
5
  docgen:
6
- crc: cd7b4cfa
7
- model: openai-codex/gpt-5.5
8
- tier: cloud-avg
6
+ crc: 15c41965
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
9
  score: 100
10
10
  judgeModel: openai-codex/gpt-5.4-mini
11
11
  ---
12
12
 
13
13
  ## Огляд
14
14
 
15
- Файл надає стандартну мапу local-провайдерів для `llm_lib::local_cloud` у формі `{ prefix: { baseUrl, apiKey } }`, щоб JS-частина могла передати Rust-крейту готові endpoints для `oneShotLocalCloud` і `submitBatch`. `defaultLocalProviders` завжди описує `omlx` і `litellm` одночасно, а фактичний мережевий запит отримує лише провайдер, чий префікс вибрано в model-spec, наприклад `N_LOCAL_MIN_MODEL`.
15
+ Повертає default map `defaultLocalProviders` для `llm_lib::local_cloud`, яка дає контракт `{ prefix: { baseUrl, apiKey } }` для `oneShotLocalCloud` і `submitBatch`.
16
16
 
17
- ## Поведінка
18
-
19
- 1. `defaultLocalProviders` формує стандартний набір local-провайдерів для `llm_lib::local_cloud`, щоб JS-частина передавала Rust-крейту готову мапу endpoint-ів у спільному форматі.
20
-
21
- 2. До мапи завжди входять `omlx` і `litellm`; наявність обох записів не означає одночасне використання обох провайдерів.
17
+ Головний слот `local-openai` призначений для будь-якого кастомного OpenAI-сумісного локального сервера через спільний `N_LOCAL_OPENAI_*`-env і `N_LOCAL_OPENAI_BASE_URL` для перемикання між серверами без окремих env-пар на кожен backend.
22
18
 
23
- 3. Активним стає лише провайдер, чий префікс вибрано в model-spec, тому запит спрямовується до одного відповідного клієнта.
19
+ Це свідомий breaking change: `omlx/...` більше не резолвиться, а всі конфіги мають мігрувати на `local-openai/...` (`nitra/7n-rules#374`).
24
20
 
25
- 4. Для `omlx` використовується локальна адреса за замовчуванням `http://127.0.0.1:8000/v1/`, щоб підтримати локальний LLM-сервер без обов’язкової конфігурації.
21
+ Запис саме `openai` тут не використовується, щоб не перехопити справжні хмарні виклики на кшталт `openai/gpt-5.4-mini` у `llm_lib::local_cloud` і genai, де цей prefix означає cloud OpenAI, а не локальний сервер.
26
22
 
27
- 5. Для `litellm` використовується віддалена адреса за замовчуванням `https://llm.7n.ai/v1/`, щоб мати готовий fallback-провайдер для централізованого LLM endpoint-а.
28
-
29
- 6. Значення адрес і ключів доступу можуть надходити з оточення, щоб одна й та сама логіка працювала в локальному, CI та production-середовищах без зміни коду.
23
+ ## Поведінка
30
24
 
31
- 7. Файл лише збирає конфігурацію провайдерів і не виконує власних операцій запису.
25
+ 1. `defaultLocalProviders` повертає стандартну мапу для локального OpenAI-сумісного провайдера `local-openai`, яку використовує `llm_lib::local_cloud` для викликів на локальні моделі.
26
+ 2. За замовчуванням вона спрямовує запити на локальний сервер без зовнішніх залежностей: `http://127.0.0.1:8000/v1/`.
27
+ 3. Якщо задано `N_LOCAL_OPENAI_BASE_URL`, функція підставляє його як цільовий endpoint; якщо задано `N_LOCAL_OPENAI_API_KEY`, функція передає його як ключ доступу.
28
+ 4. Якщо локальні змінні середовища не задані, функція залишає безпечні дефолти: локальний baseUrl і відсутній apiKey.
29
+ 5. `defaultLocalProviders` навмисно не реєструє окремі записи для інших локальних серверів і не підтримує паралельне перемикання між ними через різні tier-env; для цього використовується один спільний слот `local-openai`.
30
+ 6. `defaultLocalProviders` навмисно не використовує prefix `openai`, щоб не перехоплювати справжні cloud-виклики до хмарного OpenAI.
32
31
 
33
32
  ## Сценарії використання
34
33
 
35
- - `llm-lib/tests/local-providers.test.mjs` (defaultLocalProviders) — без env — дефолтні baseUrl для omlx і litellm, apiKey null; обидва провайдери завжди присутні одночасно (жоден не вимикається іншим); N_OMLX_BASE_URL/N_OMLX_API_KEY перекривають дефолт omlx; N_LITELLM_BASE_URL/N_LITELLM_API_KEY перекривають дефолт litellm
34
+ - `llm-lib/tests/local-providers.test.mjs` (defaultLocalProviders) — без env — один запис local-openai з дефолтним локальним baseUrl, apiKey null; N_LOCAL_OPENAI_BASE_URL/N_LOCAL_OPENAI_API_KEY перекривають дефолт незалежно від того, який сервер за ним стоїть (omlx, litellm, turbofieldfare, ...); лише один провайдер зареєстрований перемикання між серверами відбувається переналаштуванням N_LOCAL_OPENAI_BASE_URL, не одночасним співіснуванням
36
35
 
37
36
  ## Гарантії поведінки
38
37
 
@@ -3,21 +3,30 @@ type: JS Module
3
3
  title: model-tiers.mjs
4
4
  resource: llm-lib/lib/model-tiers.mjs
5
5
  docgen:
6
- crc: 1953f7a9
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- tier: local-min-retry
6
+ crc: 390f09c0
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
9
  score: 100
10
- issues: judge:error
11
10
  judgeModel: openai-codex/gpt-5.4-mini
12
11
  ---
13
12
 
14
13
  ## Огляд
15
14
 
16
- Файл відповідає за визначення та вибір моделі для виконання завдань. Він інтерпретує граничні значення, такі як `LOCAL_MIN`, `LOCAL_AVG`, `LOCAL_MAX` для локальних моделей та `CLOUD_MIN`, `CLOUD_AVG`, `CLOUD_MAX` для хмарних моделей. На основі цих значень відбувається пошук відповідної моделі через функцію `resolveModel`. Після вибору моделі, відповідно до її типу, визначається відповідний рівень обробки за допомогою `thinkingLevelForTier`.
15
+ Модуль визначає спільні правила для tier-орієнтованого вибору моделі: `LOCAL_MIN`, `LOCAL_AVG`, `LOCAL_MAX`, `CLOUD_MIN`, `CLOUD_AVG`, `CLOUD_MAX` задають доступні варіанти, `resolveModel` обирає модель для сценарію, `thinkingLevelForTier` узгоджує рівень thinking для tier, `parseModelId` і `formatModelSpec` працюють із поданням model spec, а `isLocalModel` відрізняє локальні моделі від cloud. Це потрібно, щоб вибір моделі, її формат і класифікація залишалися однаковими.
17
16
 
18
17
  ## Поведінка
19
18
 
20
- Значення `LOCAL_MIN`, `LOCAL_AVG`, `LOCAL_MAX`, `CLOUD_MIN`, `CLOUD_AVG`, `CLOUD_MAX` визначаються через змінні середовища, використовуючи префікси, які можуть бути налаштовані для вказівки на конкретні моделі. Ці значення слугують стартовими точками для визначення моделі та її рівні у ланцюжку. Коли викликається `resolveModel` з певною стартовою сходинкою, ця сходинка використовується для каскадного пошуку моделі, починаючи з локальних мінімальних та переходячи до хмарних, якщо необхідна модель не знайдена локально. Результат `resolveModel` повертає ідентифікатор моделі у форматі `"provider/model-id"`. Цей ідентифікатор може бути перетворений на пару `provider` та `id` за допомогою `parseModelId` або знову відформатований назад у `"provider/model-id"` за допомогою `formatModelSpec`, що корисно при отриманні дефолтної моделі від системи. `isLocalModel` приймає специфікатор моделі та визначає, чи належить вона до локальних моделей, спираючись на визначені стартові тири або на налаштування провайдерів у змінній середовища. Якщо ідентифікатор моделі відомий, `thinkingLevelForTier` визначає відповідний рівень складності (від `low` до `xhigh`) на основі того, який із визначених тирів був обраний.
19
+ LOCAL_MIN, LOCAL_AVG, LOCAL_MAX, CLOUD_MIN, CLOUD_AVG і CLOUD_MAX формують один набір tier-орієнтованих значень із env і задають спільну політику вибору моделі.
20
+
21
+ resolveModel бере вибір із цього tier-простору, нормалізує короткі alias-и min, avg і max до локальних стартових сходинок, а далі делегує фактичний вибір на substrate-резолвінг; якщо відповідь відсутня, результатом стає порожній рядок. Невідомий селектор не мовчить, а дає TypeError. Каскад починається з локальних тирових значень і може підніматися до cloud-сходинок за правилами, що задають один спільний маршрут.
22
+
23
+ parseModelId і formatModelSpec тримають один канон представлення: рядок у форматі provider/model-id розкладається на пару для внутрішньої роботи, а згодом збирається назад, коли потрібно відобразити фактично обрану модель. Якщо spec або модель неповні, результат відсутній, а не частково заповнений.
24
+
25
+ isLocalModel використовує той самий канон spec і той самий набір локальних провайдерів, щоб відрізняти локальні ланцюжки від cloud-ланцюжків. Для tier-значень пріоритет мають саме явні LOCAL_* значення; для інших spec рішення йде через provider, а не через наявність запису в конфігурації.
26
+
27
+ thinkingLevelForTier переводить rung-tier у дискретний рівень thinking без додаткових проміжних станів: локальні weak-paths лишаються нижче, cloud-шар підвищує рівень, а cloud-max займає окрему верхню позицію. Це дає єдине узгодження між вибраним tier і тим, скільки reasoning очікується від подальших consumers.
28
+
29
+ Усі функції працюють без власного запису стану: вони лише читають env, перетворюють model-spec і повертають похідні значення.
21
30
 
22
31
  ## Публічний API
23
32
 
@@ -41,10 +50,10 @@ cloud-min — `medium`, cloud-avg — `high`, cloud-max (experiment-only tier,
41
50
  - formatModelSpec — Форматує pi `Model`-об'єкт (`{provider, id}`) назад у `"provider/model-id"`.
42
51
  Інверсія {@link parseModelId} — застосовується до фактично резолвленої
43
52
  pi-моделі (`session.model`), коли consumer лишив `modelSpec` порожнім і pi
44
- сам вибрав дефолт (локальний чи хмарний).
53
+ сам вибрав дефолт.
45
54
  - isLocalModel — Чи model-spec вказує на локальну модель: збіг з одним із LOCAL_* тирів
46
- АБО провайдер з `N_LLM_LOCAL_PROVIDERS` (дефолт `omlx,litellm`). Обидва
47
- провайдери можуть бути зареєстровані в `localProviders`-конфізі одночасно
55
+ АБО провайдер з `N_LLM_LOCAL_PROVIDERS` (дефолт `local-openai`).
56
+ Провайдер може бути зареєстрований в `localProviders`-конфізі одночасно
48
57
  (див. `local-providers.mjs`) — "активний" завжди рівно один, бо
49
58
  `LocalCloud` викликає клієнта за провайдер-префіксом фактичного
50
59
  model-spec, не за наявністю запису в мапі. Використовується
@@ -52,7 +61,7 @@ model-spec, не за наявністю запису в мапі. Викори
52
61
 
53
62
  ## Сценарії використання
54
63
 
55
- - `llm-lib/tests/model-tiers.test.mjs` (isLocalModel; parseModelId) — omlx-провайдер — локальний (дефолт N_LLM_LOCAL_PROVIDERS); litellm-провайдертеж локальний за дефолтом (перемикач omlx/litellm через тир-env); порожній/malformed spec — не локальний; кастомний список провайдерів через env (ізольований re-import); звичайна пара; ще 9
64
+ - `llm-lib/tests/model-tiers.test.mjs` (isLocalModel; parseModelId) — local-openai-провайдер — локальний за дефолтом (generic-слот omlx/litellm/turbofieldfare/...); голий omlx-префікс більше не local злито в local-openai (свідомий breaking change); порожній/malformed spec — не локальний; кастомний список провайдерів через env (ізольований re-import); звичайна пара; ще 9
56
65
 
57
66
  ## Гарантії поведінки
58
67
 
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: one-shot.mjs
4
4
  resource: llm-lib/lib/one-shot.mjs
5
5
  docgen:
6
- crc: 3f964acb
6
+ crc: 5a59f042
7
7
  model: openai-codex/gpt-5.4-mini
8
8
  tier: cloud-min
9
9
  score: 100
@@ -12,18 +12,19 @@ docgen:
12
12
 
13
13
  ## Огляд
14
14
 
15
- `runOneShot` виконує одноразовий запит до вибраної моделі чи tier без agent-циклу і використовується як точка входу для сценаріїв, де потрібна одна відповідь без подальшої взаємодії. У файлі є локальні fail-safe гілки для окремих збоїв.
15
+ Модуль виконує одноразовий виклик LLM для зібраного з повідомлень запиту, щоб пройти ланцюжок без агентного циклу й отримати відповідь за один крок. Це потрібно для сценаріїв, де достатньо завершити обробку одразу після одного звернення.
16
16
 
17
17
  ## Поведінка
18
18
 
19
- 1. Приймає набір повідомлень, зводить їх в один запит і рахує його відбиток для подальшого трасування.
20
- 2. Визначає модель виконання: або за явним spec, або за tier; якщо вибір не задано, повертає контрольовану помилку без запуску запиту.
21
- 3. Перевіряє, чи існує запитана модель; якщо ні завершує виклик керованою помилкою.
22
- 4. Ініціює одноразову сесію без інструментів, щоб отримати plain completion без agent-циклу.
23
- 5. Відправляє запит із лімітом часу; якщо під час виконання виникає memory-guard відмова локального model-сервера, пробиває її назовні як fail-fast сценарій.
24
- 6. Акумулює текст відповіді та службову інформацію про завершення; якщо відповідь обрізана стелею, це фіксується у stop reason для рішення колером.
25
- 7. Після завершення повертає очищений текст відповіді, usage, помилку за потреби, фактичну модель, stop reason і caller.
26
- 8. Додатково пише trace і capture для спостережуваності, але не гарантує обробку всіх зовнішніх помилок.
19
+ 1. Приймає пакет повідомлень, зводить їх в один текст запиту й обчислює ідентифікатор цього запиту для трасування.
20
+ 2. Зсуває виконання ланцюжка на наступний крок, якщо він підключений.
21
+ 3. Обирає модель за вказаним рівнем або явним spec; якщо вибір не задано або модель не знайдено, завершує виклик з помилкою.
22
+ 4. Піднімає runtime-сесію для одноразового LLM-виклику без інструментів і без агентного циклу.
23
+ 5. Надсилає зібраний запит у модель з обмеженням часу очікування; під час відповіді накопичує текст, usage і stop reason.
24
+ 6. Якщо провайдер повернув помилку через завершення відповіді, але без винятку, перетворює це на явну помилку, щоб відрізнити збій від легітимно порожньої відповіді.
25
+ 7. Якщо спрацьовує memory guard локальної моделі, негайно завершує виконання fail-fast без м’якого відновлення.
26
+ 8. Фіксує результат у трасуванні та в ланцюжку, включно з моделлю, usage, stop reason, помилкою й ідентифікатором запиту.
27
+ 9. Повертає очищений текст відповіді, службову інформацію про виконання та ім’я ініціатора виклику.
27
28
 
28
29
  ## Публічний API
29
30
 
@@ -31,9 +32,8 @@ docgen:
31
32
 
32
33
  ## Сценарії використання
33
34
 
34
- - `llm-lib/tests/one-shot.test.mjs` (runOneShot) — happy path: збирає текст + usage; усі messages (system+user) зливаються в один prompt; без replaceInstructions; модель не знайдена → error, сесія не створюється; prompt кидає → error; timeout → error; ще 13
35
+ - `llm-lib/tests/one-shot.test.mjs` (runOneShot) — happy path: збирає текст + usage; усі messages (system+user) зливаються в один prompt; без replaceInstructions; модель не знайдена → error, сесія не створюється; prompt кидає → error, частковий текст збережено; timeout → error; ще 18
35
36
 
36
37
  ## Гарантії поведінки
37
38
 
38
39
  - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
39
- - Містить локальні fail-safe гілки.
@@ -3,29 +3,47 @@ type: JS Module
3
3
  title: prompt-budget.mjs
4
4
  resource: llm-lib/lib/prompt-budget.mjs
5
5
  docgen:
6
- crc: e20ea727
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ crc: 8e3d3feb
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ issues: judge-refine:kept-original,judge:inaccurate:0.97
11
+ judgeModel: openai-codex/gpt-5.4-mini
8
12
  ---
9
13
 
10
14
  ## Огляд
11
15
 
12
- Слугує єдиною точкою правди для визначення ліміту символів промпту та стелі відповіді (`maxTokens`) залежно від типу задачі (наприклад, `gen-tests`, `fix-tests`). Це механізм захисту від надмірного розширення промпту. Надає функції для внутрішнього обрізання тексту (`fitToBudget`), що виключає низькопріоритетні частини промпту, щоб він вмістився у встановлений ліміт, а також для ефективного групування цілих одиниць вмісту (`packBatch`) для подальшої пакетної обробки.
16
+ `budgetFor` обчислює доступний бюджет для тексту, `capText` урізає рядок до цього ліміту, `fitToBudget` підбирає вміст під заданий бюджет, а `packBatch` збирає набір елементів у порції, які в нього вміщуються. Це потрібно, щоб код, що викликає, узгоджено застосовував однакові межі скорочення й розбиття замість власних локальних правил.
13
17
 
14
18
  ## Поведінка
15
19
 
16
- Поведінка
17
- budgetFor повертає встановлений ліміт символів для промпту та максимальну кількість токенів для заданого типу LLM-задачі.
18
- capText обрізає вхідний текст до заданого максимального розміру, зберігаючи його структуру з головною та хвостовою частинами, розділеними маркером.
19
- fitToBudget збирає частини промпту, обрізаючи або відкидаючи нижчопріоритетні частини доти, доки сумарний обсяг не вкладеться у заданий ліміт символів, при цьому найвищий пріоритет завжди захищено.
20
- packBatch групує одиниці (файли) у батчи, сортуючи їх за розміром, щоб вмістити якомога більше у межах заданого бюджету; одиниці, що не вмістилися, відкладаються для наступного проходу.
20
+ `budgetFor` є джерелом спільних лімітів для всіх подальших кроків: з нього беруть єдиний бюджет для конкретного типу задачі, щоб інші частини не дублювали власні межі й не розходилися в поведінці.
21
+
22
+ `capText` застосовується там, де треба вмістити один великий фрагмент у символічний ліміт без втрати початку й кінця; результат цього скорочення може далі потрапляти до `fitToBudget`, якщо фрагмент є частиною складання більшого промпту.
23
+
24
+ `fitToBudget` збирає промпт із набору частин за спільним бюджетом: спершу намагається вкластися за рахунок менш пріоритетних фрагментів, потім остаточно прибирає зайве, якщо цього не вистачило. Найвищий пріоритет у наборі зберігається як недоторканний центр запиту: його не обрізають і не вилучають. На виході повертає готовий текст і перелік частин, що були скорочені або видалені, щоб викликальний код міг пояснити втрати.
25
+
26
+ `packBatch` працює на рівні цілих одиниць, коли треба вирішити, що піде в один виклик, а що буде відкладене. Він допомагає максимально заповнити бюджет найменшими одиницями першими, а те, що не вмістилося, явно переносить у наступний прохід замість мовчазно пропускати.
27
+
28
+ Разом ці функції утворюють один потік: бюджет визначається один раз через `budgetFor`, окремий великий текст може бути стислий через `capText`, далі `fitToBudget` збирає фінальний промпт із частин, а `packBatch` розкладає великі набори на порції для кількох проходів. У файлі немає власного запису в ФС чи БД; стан не зберігається між викликами, усе працює на вхідних даних і повертає нові значення назовні.
21
29
 
22
30
  ## Публічний API
23
31
 
24
- budgetFor — надає бюджет, необхідний для певної категорії завдання.
25
- capText — безпечно укорочує текст, зберігаючи його початок, маркер та кінець.
26
- fitToBudget — розміщує блоки у межах заданого бюджету: спочатку зменшує вміст, а потім відкидає найнижчі за пріоритетом, поки обсяг не втиснеться. Найважливіший елемент завжди залишається повним.
27
- packBatch згруповує файли для обробки: спочатку обирає найменші для оптимізації кількості виправлень за один раз. Якщо елемент занадто великий, він відкладається для окремої обробки, де застосовується жорсткіше обрізання.
32
+ - budgetFor — Повертає бюджет для типу задачі.
33
+ - capText — Символьно-безпечне обрізання середини: голова + маркер + хвіст.
34
+ - fitToBudget — Вкладає chunks у бюджет: спершу обрізає, потім дропає найнижчі
35
+ пріоритети, поки сумарний текст не влізе. Chunk із НАЙВИЩИМ
36
+ пріоритетом (сама задача / останній user-запит) захищений — його
37
+ текст не ріжеться і не дропається ніколи.
38
+ - packBatch — Пакує одиниці (файли) у бюджет: найменші першими, щоб максимізувати
39
+ кількість виправлень за один виклик. Одиниця, що сама-одна перевищує
40
+ бюджет, потрапляє в `deferred` — колер робить для неї соло-виклик із
41
+ жорсткішим внутрішнім обрізанням (`fitToBudget`), а не мовчазний skip.
42
+
43
+ ## Сценарії використання
44
+
45
+ - `llm-lib/tests/prompt-budget.test.mjs` (budgetFor; capText) — відомі типи задач повертають повний бюджет; повертає копію — мутація результату не псує наступні виклики; невідомий taskKind — помилка з назвою типу; текст коротший або рівний ліміту — без змін; на 1 символ понад ліміт — голова 70% + маркер + хвіст; ще 8
28
46
 
29
47
  ## Гарантії поведінки
30
48
 
31
- - Read-only: не виконує операцій запису (ФС/БД).
49
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
@@ -3,29 +3,44 @@ type: JS Module
3
3
  title: telemetry-store.mjs
4
4
  resource: llm-lib/lib/telemetry-store.mjs
5
5
  docgen:
6
- crc: c0a5e8ea
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ crc: 66e647ac
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ judgeModel: openai-codex/gpt-5.4-mini
8
11
  ---
9
12
 
10
13
  ## Огляд
11
14
 
12
- Я готовий створити огляд відповідно до ваших вимог. Надайте чорнетку секції "Overview".
15
+ `telemetryDir` задає місце для збереження стану виправлень, `signatureOf` спосіб отримати сигнатуру правки, `pruneNoopEdits` — відсіювання no-op змін, `recordFixTelemetry` — запис телеметрії виправлення, а `openCount` — підрахунок відкритих сигнатур. Файл тримає ці операції в одному контексті, щоб стан виправлень і читання його стану були узгоджені. Деякі локальні fail-safe гілки повертають порожнє значення, зокрема `null`, замість винятку; інші помилки можуть поширюватися назовні.
13
16
 
14
17
  ## Поведінка
15
18
 
16
- telemetryDir визначає абсолютний шлях до кореневого розташування для зберігання даних телеметрії, використовуючи змінну середовища `N_CURSOR_TELEMETRY_DIR` або шлях `~/.n-cursor/telemetry`.
17
- signatureOf генерує короткий хеш-ідентифікатор, що відображає унікальну комбінацію правила та структурної форми змін у наданому записі, для кластеризації.
18
- recordFixTelemetry зберігає або оновлює запис про спробу виправлення у відповідну директорію на основі правила, повторюючи або створюючи файл, ігноруючи будь-які помилки введення/виведення.
19
- openCount рахує кількість відкритих записів для заданого правила, перевіряючи відповідну директорію у сторі.
19
+ telemetryDir визначає спільний корінь сховища, від якого працюють recordFixTelemetry і openCount; через нього всі записи та підрахунки сходяться в один стор. Коли dir не задано явно, ці операції беруть базу з цього ж джерела, тому зміна кореня одразу змінює місце запису й читання.
20
+
21
+ signatureOf задає стабільний ключ для схлопування однакових записів, а pruneNoopEdits перед цим викидає правки без змістовної зміни, щоб у стор не потрапляв шум. Разом вони підтримують консистентність корпусу: до порівняння й збереження лишається лише те, що може впливати на дистиляцію.
22
+
23
+ recordFixTelemetry збирає запис, очищає його від no-op правок, перевіряє на ймовірні секрети й або редагує вміст, або зберігає повні дані для внутрішнього схлопування; після цього додає provenance без вмісту коду й пише результат у стор. Якщо під час обробки трапляється локальний fail-safe, функція повертає порожній результат замість падіння, але інші помилки можуть піти назовні.
24
+
25
+ openCount читає вже накопичені записи для конкретного правила в тому ж сховищі й рахує відкриті сигнатури як міру зрілості до дистиляції. Тобто записування через recordFixTelemetry і читання через openCount працюють на спільному просторі й спільній семантиці сигнатур, а provenance лишається метаданими походження без впливу на саму сигнатуру.
20
26
 
21
27
  ## Публічний API
22
28
 
23
- telemetryDir — визначення кореневої директорії для збору телеметрії, яку можна перевизначити через змінну середовища `N_CURSOR_TELEMETRY_DIR`.
24
- signatureOf — унікальний ідентифікатор запису, що складається з назви правила та структури змін (попереднього/нового стану, очищеної від зайвого).
25
- recordFixTelemetryфіксує спробу виправлення (fix-attempt) у сховищі телеметрії, ніколи не викликаючи помилок.
26
- openCountобчислює кількість відкритих записів для кожного правила, що слугує індикатором його зрілості для дистиляційного порогу.
29
+ - telemetryDir — Корінь стору (env-override `N_LLM_TELEMETRY_DIR`, legacy `N_CURSOR_TELEMETRY_DIR`).
30
+ - signatureOf — Стабільна сигнатура запису: rule + структурна форма правок (old/new, trimmed).
31
+ Колапслише точних дублікатів; семантичну кластеризацію робить cloud на дистиляції.
32
+ - pruneNoopEdits Відкидає no-op правки: пари з oldText === newText (після trim) і файлові
33
+ блоки, у яких після цього не лишилось ані значущих пар, ані content.
34
+ Клас «слабка модель переклеїла рядок сам на себе» (live e2e 2026-07-12) —
35
+ не знання для дистиляції; якість корпусу — відповідальність стора.
36
+ - recordFixTelemetry — Дописує/схлопує один fix-attempt запис у стор. Never throws.
37
+ - openCount — Лічильник open-записів правила (зрілість для дистиляційного порогу).
38
+
39
+ ## Сценарії використання
40
+
41
+ - `llm-lib/tests/telemetry-store.test.mjs` (signatureOf; recordFixTelemetry) — стабільна для однакових edits, різна для різних; whitespace в old/new не впливає (trim); перший запис → файл під <rule>/open/<sig>.json; ідентичні схлопуються з лічильником + provenance; секрет → redacted, повний вміст не зберігається; ще 6
27
42
 
28
43
  ## Гарантії поведінки
29
44
 
30
- - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
31
- - За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
45
+ - Містить локальні fail-safe гілки; інші помилки можуть поширюватися назовні.
46
+ - Деякі локальні fail-safe гілки повертають порожнє значення (напр. `null`) замість винятку.
package/lib/docs/trace.md CHANGED
@@ -3,24 +3,32 @@ type: JS Module
3
3
  title: trace.mjs
4
4
  resource: llm-lib/lib/trace.mjs
5
5
  docgen:
6
- crc: 75232da6
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ crc: 4c2e9b2a
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ judgeModel: openai-codex/gpt-5.4-mini
8
11
  ---
9
12
 
10
13
  ## Огляд
11
14
 
12
- Цей модуль управляє механізмом глобального трасування. Він дозволяє фіксувати деталі виконання операцій за допомогою функцій `tracePath` та `writeTrace`. Механізм працює з урахуванням помилок (`fail-safe`), що гарантує, що процес трасування не створює винятків на рівні системи.
15
+ `tracePath` і `writeTrace` утворюють спільний журнал для LLM wire-trace: `tracePath` задає місце запису, а `writeTrace` додає туди JSONL-рядки з міткою часу. Секція описує збереження трасування в append-only форматі та локальні fail-safe гілки, які обмежують збій у межах цього запису.
13
16
 
14
17
  ## Поведінка
15
18
 
16
- tracePath Визначає абсолютний шлях для збереження глобального журналу трасування, використовуючи змінну оточення `N_CURSOR_TRACE_PATH` або знаходяться в директорії користувача `~/.n-cursor/llm-trace.jsonl`.
17
- writeTrace — Додає один запис трасування до глобального журналу. Операція є "best-effort" (з найкращими зусиллями) і не генерує помилок, навіть якщо виникають проблеми з вводом/виводом.
19
+ `tracePath` визначає спільне місце запису для всього LLM wire-trace і повертає шлях, який далі використовує `writeTrace` як ціль для дописування JSONL-рядків. `writeTrace` бере готовий запис, додає службову мітку часу, створює потрібні каталоги для шляху призначення і дописує один рядок у trace; якщо під час IO стається помилка, вона приглушується, щоб трасування не впливало на основний виклик. Джерело йде з викликача, а результатом є append-only журнал у спільному файлі, який може бути перевизначений через змінні середовища для тестів або CI.
18
20
 
19
21
  ## Публічний API
20
22
 
21
- tracePath — Вказує на місце збереження загального логу про виконання.
22
- writeTrace — Записує окремий запис у лог у форматі JSONL.
23
+ - tracePath — Шлях глобального trace (env-override `N_LLM_TRACE_PATH`, legacy `N_CURSOR_TRACE_PATH`).
24
+ - writeTrace — Дописує один trace-запис (JSONL). Поля: `caller`, `rule`, `rung`, `model`,
25
+ `backend:"pi-ai"`, `kind:"agent"|"one-shot"|"skill"`, `cwd`, плюс довільна
26
+ корисна навантага. Ніколи не кидає.
27
+
28
+ ## Сценарії використання
29
+
30
+ - `llm-lib/tests/trace.test.mjs` (writeTrace; tracePath) — дописує JSONL-запис із ts і полями; best-effort: помилка IO не кидає; env-override N_CURSOR_TRACE_PATH; дефолт — під ~/.n-cursor/
23
31
 
24
32
  ## Гарантії поведінки
25
33
 
26
- - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
34
+ - Містить локальні fail-safe гілки; помилки IO не кидаються назовні.
@@ -3,35 +3,50 @@ type: JS Module
3
3
  title: web-tools.mjs
4
4
  resource: llm-lib/lib/web-tools.mjs
5
5
  docgen:
6
- crc: 4242c966
6
+ crc: 8ee0f788
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ issues: judge-refine:kept-original,judge:inaccurate:0.98
11
+ judgeModel: openai-codex/gpt-5.4-mini
7
12
  ---
8
13
 
9
14
  ## Огляд
10
15
 
11
- Web-доступ для cloud-профілів run-harness (Фаза A3): пара pi-tools `web_search`/`web_fetch` мінімальне ядро без нових залежностей (референс pi-web-access, без його fallback-ланцюгів провайдерів і browser-режимів). Вмикається лише явним профілем consumer-а (agent-fix `opts.webTools`, за дизайном cloud-тири); дефолт вимкнено.
16
+ `createWebTools` об’єднує `resolveSearchProvider`, `fetchPage`, `htmlToText`, `assertPublicHttpUrl` і публічний набір web-інструментів в один шар для роботи з вебом, щоб агент міг окремо шукати джерела, перевіряти адресу перед запитом і перетворювати HTML на текст для подальшої обробки.
17
+
18
+ `assertPublicHttpUrl` відсікає непридатні для зовнішнього доступу адреси, `fetchPage` отримує вміст сторінки з мережі, а `htmlToText` переводить HTML у читабельний текст, щоб у відповідь потрапляв саме вміст сторінки, а не сирий HTML.
12
19
 
13
20
  ## Поведінка
14
21
 
15
- `web_fetch` ходить лише на публічні http(s)-адреси: SSRF-guard блокує інші схеми, `localhost`/`*.local`/`*.internal` і літеральні приватні IP (v4-діапазони, v6 loopback/link-local/ULA); redirect-и проходяться вручну (до 3 hop-ів) з guard-перевіркою кожного hop-а. HTML зводиться до тексту власним мінімальним стрипером (script/style/noscript вирізаються ітеративно без regex-backtracking, блокові теги переноси, entity декодуються); json/plain віддаються як є. Відповідь обрізається лімітом (`maxChars`, дефолт 20k символів) з чесним прапорцем `truncated`; таймаут запиту 20s.
22
+ createWebTools збирає пару tool-об’єктів для пошуку й отримання сторінок, а resolveSearchProvider визначає, який зовнішній search-сервіс доступний у середовищі: або явно вказаний через N_LLM_SEARCH_PROVIDER, або перший знайдений ключ. Для пошуку це означає один узгоджений маршрут до провайдера, а не вибір у кожному виклику окремо; результати повертаються як текстовий tool-відгук із структурованими details.
23
+
24
+ fetchPage працює як захищений мережевий вхід: спочатку assertPublicHttpUrl відсікає небезпечні або непридатні адреси, далі запит іде з повторною перевіркою на кожному redirect-hop-і, щоб не вийти за межі дозволеного периметра під час переходів. Відповідь зводиться до тексту через htmlToText, тому назовні потрапляє не сирий HTML, а придатний для читання контент разом із метаданими про статус, content type та ознаку обрізання.
25
+
26
+ htmlToText використовує послідовне очищення HTML як спільний формат для подальшої роботи tool-ів: прибирає службові блоки, перетворює структуру сторінки на текст і нормалізує порожні рядки, щоб модель отримувала стислий зміст без залежності від DOM. Це робить fetchPage придатним для читання документації, README та changelog, а не для точного відтворення візуального рендеру.
16
27
 
17
- `web_search` працює через ОДНОГО провайдера: явний `N_LLM_SEARCH_PROVIDER` або перший наявний ключ (`BRAVE_API_KEY` `TAVILY_API_KEY` `EXA_API_KEY`); результати нормалізуються до `{title, url, snippet}`. Без жодного ключа tool чесно повертає структуровану відмову з інструкцією конфігурації не виняток.
28
+ assertPublicHttpUrl задає спільне правило безпеки для всієї мережевої частини: дозволяє лише публічні http-адреси й блокує локальні та приватні літерали, не покладаючись на DNS-резолюцію. Таким чином обробка входу з tool-input і подальші переходи лишаються в одному захищеному контурі, а довірена зона мережі визначається зовнішньою політикою споживача.
18
29
 
19
- Вміст сторінок повертається tool-result-ом (дані, не інструкції) prompt-injection зі сторінок не отримує системного рівня; помилки обох tools структурований JSON-текст із причиною.
30
+ Якщо потрібен search, createWebTools повертає інтерфейс до зовнішніх провайдерів, які очікують запити на кшталт https://api.search.brave.com/res/v1/web/search?q=, https://api.tavily.com/search і https://api.exa.ai/search; сам файл не виконує запису в файлову систему чи базу, а лише формує й передає дані далі. Усі результати, що повертаються назовні, мають однакову текстову упаковку через toolOk або toolFail, щоб виклик з боку агента був передбачуваним незалежно від джерела даних. Конфігураційна опора тут — r.json.
20
31
 
21
32
  ## Публічний API
22
33
 
23
- assertPublicHttpUrl — SSRF-guard: розбирає URL або кидає Error з причиною відмови.
24
- htmlToText мінімальна html→text екстракція (без DOM-залежностей).
25
- fetchPage fetch з guard-ом на кожному redirect-hop-і, таймаутом і лімітом розміру.
26
- resolveSearchProvider вибір search-провайдера за env.
27
- createWebToolsфабрика tool-дефініцій `web_search`/`web_fetch` (defineTool і fetch інжектяться — модуль pi-free).
34
+ - assertPublicHttpUrl — SSRF-guard: чи можна ходити на URL. Кидає Error з причиною при відмові.
35
+ Блокує не-http(s), localhost/*.local/*.internal і літеральні приватні IP
36
+ (v4-діапазони, v6 loopback/link-local/ULA). DNS-резолюція не робиться
37
+ захист від літералів; довірений периметр consumer-а лишається його політикою.
38
+ - htmlToText Мінімальна html→text екстракція: викидає script/style/noscript, блокові теги
39
+ зводить до переносів, решту тегів стрипає, декодує базові entity, стискає
40
+ порожні рядки. Це свідомо НЕ readability (без DOM-залежностей) — достатньо,
41
+ щоб агент прочитав документацію/README/чейнджлог.
42
+ - fetchPage — Fetch з SSRF-guard на кожному redirect-hop-і, таймаутом і лімітом розміру.
43
+ - resolveSearchProvider — Обирає search-провайдера: явний `N_LLM_SEARCH_PROVIDER` або перший наявний ключ.
44
+ - createWebTools — Фабрика пари pi-tools `web_search`/`web_fetch`.
28
45
 
29
- ## Де використовується
46
+ ## Сценарії використання
30
47
 
31
- `agent-fix.mjs`: `opts.webTools: true` додає обидва tools у сесію (перші споживачі правила з зовнішнім знанням: pin-перевірки ga, taze-подібні). Прапорець фіксується у trace для аналізу.
48
+ - `llm-lib/tests/web-tools.test.mjs` (assertPublicHttpUrl (SSRF-guard); htmlToText) публічні http/https проходять; викидає script/style, зводить блоки до переносів, декодує entity; незакритий script обрізається чесно, не ковтає весь документ у вивід; html → текст, contentType/status/truncated у відповіді; maxChars: обрізання + прапорець truncated; ще 8
32
49
 
33
50
  ## Гарантії поведінки
34
51
 
35
- - Жодного мережевого виклику без явного tool-виклику агента; лише http/https на публічні адреси.
36
- - Відповіді обмежені за розміром; guard-відмови детерміновані й пояснені.
37
- - Модуль pi-free; усі зовнішні ефекти (fetch, env) інжектовані — тести без мережі.
52
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
@@ -3,21 +3,33 @@ type: JS Module
3
3
  title: with-timeout.mjs
4
4
  resource: llm-lib/lib/with-timeout.mjs
5
5
  docgen:
6
- crc: b76e4669
6
+ crc: 0cee9644
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ judgeModel: openai-codex/gpt-5.4-mini
7
11
  ---
8
12
 
9
13
  ## Огляд
10
14
 
11
- Спільний abort-aware таймаут для pi-lib consumers (`one-shot`, `agent-skill`, `agent-fix`). Виносить ідентичний timeout-танець, що раніше тричі дублювався в цих модулях.
15
+ Допоміжний модуль для обмеження часу очікування результату: публічна функція withTimeout повертає значення в межах заданого ліміту або завершує очікування timeout-помилкою. Потрібен, щоб відокремити нормальний результат від перевищення часу очікування.
12
16
 
13
17
  ## Поведінка
14
18
 
15
- `withTimeout(promise, ms, opts)` гонить переданий `promise` з таймером на `ms` мілісекунд. Якщо `ms` — falsy або `≤ 0`, повертає `promise` без гонки (таймаут вимкнено).
19
+ 1. withTimeout приймає на себе очікування результату й або повертає його в межах ліміту, або зупиняє очікування з timeout-помилкою.
20
+ 2. Якщо ліміт не заданий або не додатний, withTimeout не втручається і просто передає результат очікуваного проміса.
21
+ 3. Якщо ліміт заданий, withTimeout одночасно чекає результат і окремий таймаут; перемагає той сценарій, що завершився раніше.
22
+ 4. Якщо першим спрацьовує таймаут, withTimeout викликає дію завершення, якщо вона передана, і повертає помилку з повідомленням про timeout для вказаного ліміту.
23
+ 5. Після завершення очікування withTimeout завжди прибирає пов’язане з таймаутом очікування, щоб не залишати фонових помилок після успішного завершення основного сценарію.
16
24
 
17
- Таймер-гілка чекає `sleep(ms)` під `AbortController`. На спрацювання вона кличе опційний `opts.onTimeout` (наприклад, `session.abort`) і реджектить помилкою `"<label> timeout <ms>ms"`, де `label` береться з `opts.label` (за замовчуванням `operation`).
25
+ ## Публічний API
18
26
 
19
- У `finally` `controller.abort()` скасовує таймер-`sleep`. Якщо переміг основний `promise` (не таймаут), його `AbortError` свідомо ковтається, щоб не спливти unhandled-реджектом після завершення гонки.
27
+ - withTimeout — Гонка `promise` з таймаутом `ms`. `ms 0` (або falsy) повертає `promise` без гонки.
20
28
 
21
- ## Публічний API
29
+ ## Сценарії використання
30
+
31
+ - `llm-lib/tests/with-timeout.test.mjs` (withTimeout) — falsy ms (0/undefined) — повертає promise без гонки, onTimeout не кличеться; від; promise встигає до таймауту — значення повертається, onTimeout не кличеться; таймаут — reject із label у повідомленні, onTimeout викликаний один раз; дефолтний label —; ще 2
32
+
33
+ ## Гарантії поведінки
22
34
 
23
- `withTimeout(promise, ms, { onTimeout, label })` повертає результат `promise` або реджектить timeout-помилкою.
35
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
@@ -3,28 +3,45 @@ type: JS Module
3
3
  title: write-guard.mjs
4
4
  resource: llm-lib/lib/write-guard.mjs
5
5
  docgen:
6
- crc: b5110048
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ crc: 6d1b3f35
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ issues: judge-refine:kept-original,judge:inaccurate:0.99
11
+ judgeModel: openai-codex/gpt-5.4-mini
8
12
  ---
9
13
 
10
14
  ## Огляд
11
15
 
12
- Цей файл надає механізми для взаємодії з файловою системою в межах репозиторію. Функція `gitRoot` визначає корінь репозиторію, а `NEW_FILE` дозволяє створювати нові файли. Механізм `createWriteGuard` захищає від неконтрольованих змін. При роботі з файлами автоматично ігнорується каталог `.git`. Система перехоплює помилки (`fail-safe`), не виводячи винятків назовні.
16
+ `NEW_FILE`, `gitRoot` і `createWriteGuard` разом визначають guard для безпечного запису в межах репозиторію під час fix-сесії: нові та вже існуючі шляхи оцінюються через один корінь, щоб уникнути хибних блокувань через різне подання шляху. Логіка свідомо пропускає шлях `.git`, щоб службові внутрішні файли не впливали на перевірку. У guard є локальні fail-safe гілки для контрольованого обходу окремих ситуацій, а решта помилок можуть поширюватися назовні.
13
17
 
14
18
  ## Поведінка
15
19
 
16
- NEW_FILE позначає файл, якого до запису не існувало.
17
- gitRoot визначає кореневий каталог репозиторію Git, якщо він є.
18
- createWriteGuard створює механізм захисту записів до файлової системи, який перевіряє, чи запис знаходиться в межах кореневого каталогу Git, чи не стосується він директорій `.git` або ігнорованих файлів Git, і робить резервні копії файлів перед будь-якою зміною.
19
- Під veto/snapshot підпадають tool-виклики `edit`, `write` і `edit_anchored` (anchored-профіль Фази A2, див. anchored-edit.md) — кастомний anchored-tool проходить той самий контроль, що й built-in write-tools.
20
+ NEW_FILE задає нормалізовану опору для нових шляхів, щоб подальша перевірка порівнювала записуваний файл із уже відомим коренем без хибних блокувань через відмінності в представленні шляху.
21
+
22
+ gitRoot визначає межу репозиторію для поточного робочого каталогу і стає джерелом істини для всієї подальшої перевірки; якщо git-root недоступний, подальший fix-прохід не має стартувати.
23
+
24
+ createWriteGuard збирає цей контекст в один guard для однієї fix-сесії: бере робочий каталог, за потреби обчислює git-root, звіряє шлях нових змін із межами репозиторію та готує стан, у якому фіксуються вже побачені передзображення файлів, заблоковані записи й факт підʼєднання до подій.
25
+
26
+ Під час роботи guard опирається на узгоджений абсолютний шлях, щоб однаково трактувати реальні файли, нові файли та шляхи через symlink; це зменшує ризик помилкового блокування того, що фактично лишається всередині репозиторію.
27
+
28
+ Свідомо пропускається `.git`, щоб службові внутрішні файли репозиторію не втручалися в захист запису.
29
+
30
+ Коли перевірка не може бути завершена безпечно, guard використовує локальні fail-safe гілки, а решта помилок може піти назовні, щоб не приховувати справжню проблему.
20
31
 
21
32
  ## Публічний API
22
33
 
23
- NEW_FILE — Зберігає попереднє зображення для файлу, який додається вперше до запису (при відкаті файл видаляється).
24
- gitRoot — Визначає корінь Git-репозиторію поточної директорії або повертає нуль, якщо це не Git-репозиторій.
25
- createWriteGuard Створює захист від запису для однієї сесії виправлення. Забезпечує контроль над робочою директорією, Git-коренем, інжекцією для тестів, надає функцію для обробки подій, стан захисту (включно з відмітками файлів та блокуваннями), функцію відкату та список змінених файлів.
34
+ - NEW_FILE — Sentinel pre-image для файлу, якого до запису не існувало (rollback = видалити).
35
+ - gitRoot — git-root для cwd (`git rev-parse --show-toplevel`) або null, якщо не git-репо.
36
+ Fix-шлях вимагає git caller на null **пропускає fix** (§12 precondition).
37
+ - createWriteGuard — Створює write-guard для однієї fix-сесії.
38
+ cwd — робоча директорія; root — git-root (за替замовч. обчислюється); checkIgnore — інжекція для тестів
39
+
40
+ ## Сценарії використання
41
+
42
+ - `llm-lib/tests/write-guard.test.mjs` (veto-логіка (інжектований root + checkIgnore); rollback) — запис у tracked-файл під root → allow + pre-image знятий; edit_anchored (A2) — той самий veto/snapshot, що й built-in edit; git-ignored → block, без pre-image; запис поза git-root (..-escape) → block; запис у .git/ → block; ще 7
26
43
 
27
44
  ## Гарантії поведінки
28
45
 
29
- - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
46
+ - Містить локальні fail-safe гілки; інші помилки можуть поширюватися назовні.
30
47
  - Свідомо пропускає шляхи: `.git`.
@@ -3,22 +3,37 @@ type: JS Module
3
3
  title: apply-compression.mjs
4
4
  resource: llm-lib/lib/internal/apply-compression.mjs
5
5
  docgen:
6
- crc: 90de77ca
6
+ crc: 46a50b9e
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ judgeModel: openai-codex/gpt-5.4-mini
7
11
  ---
8
12
 
9
13
  ## Огляд
10
14
 
11
- Streamfn-mixin (дзеркало max-tokens/chain-headers): домішує compressContext у кожен LLM-виклик pi-сесії safety-net проти prefill_memory_exceeded/context-window overflow, тепер на клієнті замість колишнього myllm-проксі (compress.rs), тож працює й напряму до omlx без залежності від запущеного myllm.
15
+ `applyCompression` застосовує ущільнення контексту перед генерацією, щоб передавати до моделі коротший вхід без зміни цілі сценарію.
16
+
17
+ Це корисно для викликів, де важливо зменшити обсяг тексту, але зберегти зміст для подальшої обробки.
12
18
 
13
19
  ## Поведінка
14
20
 
15
- applyCompression обгортає session.agent.streamFn, стискаючи context перед викликом оригінального streamFn; no-op без agent або коли N_LLM_COMPRESS=0 (дефолт увімкнено — це safety-net, не оптимізація).
21
+ 1. `applyCompression` перевіряє, чи в сесії є придатний для обгортання `streamFn`, і чи компресія не вимкнена через середовище.
22
+ 2. Якщо умови не виконані, вона повертає ту саму сесію без змін.
23
+ 3. Якщо умови виконані, вона підміняє виклик так, щоб кожне звернення до LLM проходило з ущільненим контекстом перед основною генерацією.
24
+ 4. Це зменшує ризик переповнення контексту у важких агентних сесіях і зберігає працездатність сценаріїв, що звертаються до LLM напряму без проміжного proxy.
25
+ 5. Функція працює як безпечне вмикання захисту: за замовчуванням увімкнена, а вимкнення призначене лише для порівняльного дебагу.
16
26
 
17
27
  ## Публічний API
18
28
 
19
- applyCompression(session)та сама сесія (для чейнінгу).
29
+ - applyCompression — Обгортає `session.agent.streamFn`, стискаючи `context` кожного LLM-виклику
30
+ сесії. Безпечний no-op для сесій без `agent` (інжектовані фейки в тестах)
31
+ або коли компресію вимкнено.
32
+
33
+ ## Сценарії використання
34
+
35
+ - `llm-lib/tests/apply-compression.test.mjs` (applyCompression) — стискає context перед оригінальним streamFn; no-op без agent і для сесій без streamFn; N_LLM_COMPRESS=0 вимикає стиснення (context проходить незмінним)
20
36
 
21
37
  ## Гарантії поведінки
22
38
 
23
- - Дефолт увімкнено; вимикається лише явним N_LLM_COMPRESS=0 (для дебагу різниці до/після).
24
- - Безпечний no-op для сесій без agent.streamFn (інжектовані фейки в тестах).
39
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
@@ -3,18 +3,35 @@ type: JS Module
3
3
  title: chain-headers.mjs
4
4
  resource: llm-lib/lib/internal/chain-headers.mjs
5
5
  docgen:
6
- crc: b9a48b25
6
+ crc: afee2cf2
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ judgeModel: openai-codex/gpt-5.4-mini
7
11
  ---
8
12
 
9
13
  ## Огляд
10
14
 
11
- INTERNAL streamFn-mixin (дзеркало max-tokens): домішує X-Chain-* заголовки chain в options кожного LLM-виклику pi-сесії pi StreamOptions.headers мерджаться останніми поверх дефолтів провайдера, тож заголовки долітають до локального проксі (myllm). Раннери передають chain сюди лише для локальних моделей (isLocalModel).
15
+ `applyChainHeaders` додає chain-заголовки до запиту LLM так, щоб у межах сесії зберігався потрібний контекст для кожного виклику. Це потрібно, щоб наступні запити в тій самій сесії отримували узгоджені заголовки.
12
16
 
13
17
  ## Поведінка
14
18
 
15
- applyChainHeaders — обгортає session.agent.streamFn; chain.headers() читається на момент кожного виклику (свіжий X-Chain-Step); зберігає наявні options.headers; no-op без chain або для сесій без agent (фейки в тестах).
19
+ 1. applyChainHeaders або залишає сесію без змін, або вмикає домішування chain-заголовків до кожного LLM-виклику через наявний streamFn.
20
+ 2. Якщо в сесії немає доступного streamFn або chain відсутній, функція нічого не змінює й повертає ту саму сесію.
21
+ 3. Якщо streamFn є, функція підміняє його так, щоб у кожному виклику до вже наявних headers додавалися актуальні chain-заголовки з chain.headers.
22
+ 4. Нові chain-заголовки мають пріоритет над попередніми значеннями в headers, тому сесія передає в запит саме свіжі значення.
23
+ 5. Функція повертає ту саму session, щоб її можна було далі ланцюжити без додаткових проміжних об’єктів.
24
+
25
+ ## Публічний API
26
+
27
+ - applyChainHeaders — Обгортає `session.agent.streamFn`, домішуючи chain-заголовки в options
28
+ кожного LLM-виклику сесії. Безпечний no-op без chain або для сесій без
29
+ `agent` (інжектовані фейки в тестах).
30
+
31
+ ## Сценарії використання
32
+
33
+ - Домішує заголовки, зберігаючи наявні options.headers; headers() читається на момент виклику; no-op без chain і для сесій без agent
16
34
 
17
35
  ## Гарантії поведінки
18
36
 
19
- - Чужі options.headers не губляться (мердж, chain-заголовки останні).
20
- - Безпечний no-op: без chain/agent session повертається незмінною.
37
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
@@ -3,23 +3,43 @@ type: JS Module
3
3
  title: compress-context.mjs
4
4
  resource: llm-lib/lib/internal/compress-context.mjs
5
5
  docgen:
6
- crc: 61b94b7d
6
+ crc: 5fde147b
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ issues: judge-refine:kept-original,judge:inaccurate:0.98
11
+ judgeModel: openai-codex/gpt-5.4-mini
7
12
  ---
8
13
 
9
14
  ## Огляд
10
15
 
11
- Стиснення pi-контексту (messages + systemPrompt) перед префілом клієнтський еквівалент колишньої проксі-компресії myllm (compress.rs), адаптований під форму pi Context (messages завжди array-parts, system живе окремо в systemPrompt, tool-виклик role toolResult / part type toolCall не 1:1 порт, а перевідображення тієї самої техніки, підтверджено спайком 2026-07-06). INTERNAL — приймає/повертає pi Context.
16
+ Скорочує контекст до компактнішого вигляду, щоб зменшити обсяг повідомлення перед подальшою обробкою. Зберігає захищені частини та допомагає підготувати вміст до передачі далі без втрати критично важливого змісту. Локальні fail-safe гілки можуть повернути порожнє значення замість винятку, а інші помилки можуть поширюватися назовні.
12
17
 
13
18
  ## Поведінка
14
19
 
15
- compressContext мінізує вбудований pretty-printed JSON у текстових частинах messages і systemPrompt; обрізає (truncate-middle) старі непротектовані блоки довші за поріг; захищає останні PROTECTED_TAIL_MESSAGES messages від truncation (лише minify); systemPrompt захищений від truncation, доки сумарний розмір контексту не перевищить SYSTEM_TRUNCATION_SIZE_THRESHOLD; повідомлення з tool-викликом (toolCall part / role toolResult) лишаються byte-exact.
20
+ 1. Стискає контекст перед подальшою обробкою, щоб зменшити обсяг тексту без зміни змісту там, де це критично.
21
+ 2. Обробляє `systemPrompt` обережно: якщо він є, зберігає його без обрізання доти, доки це потрібно для безпечного мінімального стиснення; за можливості лише прибирає зайве форматування.
22
+ 3. Окремо проходить по `messages` і для кожного повідомлення вирішує, чи можна його змінювати, чи треба залишити byte-exact.
23
+ 4. Якщо повідомлення пов’язане з tool-викликом або є результатом tool-роботи, залишає його точний вміст без обрізання, щоб не зламати зв’язок між запитом і відповіддю інструменту.
24
+ 5. Для звичайних текстових частин прибирає надлишкове форматування всередині embedded JSON, якщо це не змінює дані.
25
+ 6. Для не-захищених текстових частин додатково скорочує довгі фрагменти, зберігаючи початок і кінець та не розриваючи символи посередині.
26
+ 7. Для захищених повідомлень або частин виконує лише безпечне мінімізування, без обрізання змісту.
27
+ 8. Повертає той самий об’єкт, якщо нічого не змінилося, щоб викликальник міг дешево перевірити відсутність змін; інакше повертає новий стиснений контекст.
28
+ 9. У локальних fail-safe ситуаціях може відмовитися від окремого перетворення без падіння всього процесу; інші помилки можуть піти назовні.
16
29
 
17
30
  ## Публічний API
18
31
 
19
- compressContext(context)стиснений контекст (новий обʼєкт) або той самий, якщо нічого не змінилось.
32
+ - compressContext — Стискає pi Context: `systemPrompt` (захищений до порогу розміру) +
33
+ `messages` (tool-payload byte-exact, tail-messages лише minify, решта
34
+ minify+truncate). Повертає той самий обʼєкт, якщо нічого не змінилось
35
+ (щоб caller міг дешево перевірити `result === context`).
36
+
37
+ ## Сценарії використання
38
+
39
+ - `llm-lib/tests/compress-context.test.mjs` (minify (через compressContext, одне text-message); tool-payload лишається byte-exact) — мінізує вбудований pretty-printed JSON-блок; звичайний текст без дужок JSON лишається незмінним; ігнорує невалідний JSON-подібний текст у дужках; пропускає короткі JSON-блоки навіть без переносів рядків; toolCall (частина assistant-message) лишається незайманим, решта стискається; ще 7
20
40
 
21
41
  ## Гарантії поведінки
22
42
 
23
- - Ніколи не змінює tool-payload (toolCall/toolResult) byte-exact.
24
- - Не втрачає дані: тільки minify (без семантичних змін) + truncate-middle з явним маркером.
25
- - Pure-функція, без side-effects і без залежності від pi SDK.
43
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
44
+ - Містить локальні fail-safe гілки; інші помилки можуть поширюватися назовні.
45
+ - Деякі локальні fail-safe гілки повертають порожнє значення (напр. `null`) замість винятку.
@@ -3,27 +3,35 @@ type: JS Module
3
3
  title: max-tokens.mjs
4
4
  resource: llm-lib/lib/internal/max-tokens.mjs
5
5
  docgen:
6
- crc: b382cfb8
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ crc: 27cc0fa4
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ issues: judge-refine:kept-original,judge:inaccurate:0.98
11
+ judgeModel: openai-codex/gpt-5.4-mini
8
12
  ---
9
13
 
10
14
  ## Огляд
11
15
 
12
- Оновлений текст секції "Огляд":
13
-
14
- Конфігурація визначає максимальну кількість токенів відповіді, яка може бути надіслана для кожного окремого виклику агента в середовищі pi-сесій n-cursor. Механізм керування цим обмеженням забезпечує, що відповіді не перевищують заданий ліміт, використовуючи `options.maxTokens` у `streamFn`, при цьому для початкових викликів застосовується стеля, визначена у `models.json`.
16
+ `DEFAULT_MAX_TOKENS` задає стандартну верхню межу токенів для одноразових LLM-викликів у pi-сесіях, а `applyMaxTokens` застосовує цю межу там, де потрібне явне обмеження. Це потрібно, щоб поведінка викликів була передбачуваною і не залежала від неявних припущень у викликуючому коді.
15
17
 
16
18
  ## Поведінка
17
19
 
18
- Поведінка:
19
- DEFAULT_MAX_TOKENS надає значення за замовчуванням для максимальної кількості токенів відповіді в сесіях n-cursor, якщо не визначено окремо.
20
- applyMaxTokens модифікує функціонал потоку відповіді сесії, щоб обмежити максимальну кількість токенів для кожного LLM-виклику, використовуючи значення `maxTokens` або `DEFAULT_MAX_TOKENS`.
20
+ DEFAULT_MAX_TOKENS задає спільну верхню межу для одноразових LLM-викликів у pi-сесіях і бере значення з `N_LLM_MAX_TOKENS` або застарілого `N_PI_MAX_TOKENS`, а якщо обидва не задані — використовує безпечний дефолт. Це значення опирається на стелю моделі з `models.json`, тому без явного обмеження сесія успадковує модельний ліміт незалежно від фактичної потреби відповіді.
21
+
22
+ applyMaxTokens застосовує цю межу до вже створеної session так, щоб усі подальші LLM-виклики всередині тієї ж сесії отримували однаковий maxTokens. Якщо в session немає доступного agent streamFn або межа не задана, функція нічого не змінює і повертає ту саму session.
21
23
 
22
24
  ## Публічний API
23
25
 
24
- - DEFAULT_MAX_TOKENS — Встановлює стандартний максимальний обсяг відповіді для агентських та одноразових викликів n-cursor.
25
- - applyMaxTokens — Обмежує максимальну кількість токенів для кожного LLM-виклику в сесії, модифікуючи функції потоку сесії, і безпечно не робить нічого, якщо в сесії відсутній агент (наприклад, під час тестування).
26
+ - DEFAULT_MAX_TOKENS — Дефолтна стеля відповіді для агентних/one-shot викликів. Override: `N_LLM_MAX_TOKENS` (legacy-alias `N_PI_MAX_TOKENS`).
27
+ - applyMaxTokens — Обгортає `session.agent.streamFn`, домішуючи `maxTokens` в options
28
+ кожного LLM-виклику сесії. Безпечний no-op для сесій без `agent`
29
+ (напр. інжектовані фейки в тестах).
30
+
31
+ ## Сценарії використання
32
+
33
+ - `llm-lib/tests/max-tokens.test.mjs` (pi-max-tokens) — wraps agent.streamFn injecting the default maxTokens into stream options; respects an explicit maxTokens override; is a safe no-op for sessions without agent.streamFn (injected fakes); does not wrap when maxTokens is explicitly falsy
26
34
 
27
35
  ## Гарантії поведінки
28
36
 
29
- - Read-only: не виконує операцій запису (ФС/БД).
37
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: native.mjs
4
4
  resource: llm-lib/lib/internal/native.mjs
5
5
  docgen:
6
- crc: 35a583cb
6
+ crc: f912be12
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  tier: local-min
9
9
  score: 70
@@ -40,12 +40,21 @@ Loader napi-аддона `llm-lib` (Rust-ядро `llm-lib/crates/llm-lib-napi`
40
40
 
41
41
  ## Публічний API
42
42
 
43
- - resolveNativeAddonРезолвить шлях до napi-аддона `llm-lib`.
43
+ - nativeAddonChainЛанцюг кандидатів аддона в порядку пріоритету.
44
+
45
+ Повертає СПИСОК, а не один шлях, свідомо: `existsSync` — не доказ, що аддон
46
+ завантажиться (файл може бути з іншої платформи, побитий, або `existsSync`
47
+ підмінений моком у тесті, що не має до аддона стосунку — саме так
48
+ `gen-tests.test.mjs` валив увесь контур). Остаточний вибір робить
49
+ [`loadNative`], пробуючи кандидатів по черзі.
50
+ - resolveNativeAddon — Резолвить шлях до napi-аддона `llm-lib` — перший кандидат ланцюга
51
+ [`nativeAddonChain`]. Фактичний вибір з урахуванням невдалих dlopen
52
+ робить [`loadNative`].
44
53
  - loadNative — Кешований доступ до аддона (одне завантаження на процес).
45
54
 
46
55
  ## Сценарії використання
47
56
 
48
- - `llm-lib/tests/native.test.mjs` (resolveNativeAddon (порядок пошуку); resolveNativeAddon (вихідне дерево vs прод)) — N_LLM_LIB_NATIVE_ADDON має найвищий пріоритет; platform-підпакет: резолвиться @7n/llm-lib-<key> з napi-суфіксом; linux-x64 мапиться на суфікс linux-x64-gnu; dev-fallback: release-cdylib перемагає debug; dev-fallback: на linux шукається .so, а останній кандидат — вивід napi build; ще 6
57
+ - `llm-lib/tests/native.test.mjs` (resolveNativeAddon (порядок пошуку); resolveNativeAddon (вихідне дерево vs прод)) — N_LLM_LIB_NATIVE_ADDON має найвищий пріоритет; platform-підпакет: резолвиться @7n/llm-lib-<key> з napi-суфіксом; linux-x64 мапиться на суфікс linux-x64-gnu; dev-fallback: release-cdylib перемагає debug; dev-fallback: на linux шукається .so, а останній кандидат — вивід napi build; ще 8
49
58
 
50
59
  ## Гарантії поведінки
51
60
 
package/lib/one-shot.mjs CHANGED
@@ -77,7 +77,10 @@ async function defaultCreateSession({ registry, model, cwd, thinkingLevel, maxTo
77
77
  * }} args параметри; `maxTokens` — per-call стеля відповіді (undefined → дефолт пакета, 0 → без стелі);
78
78
  * `chain` — handle зі startChain: виклик стає кроком ланцюжка (chain-поля у trace, X-Chain-* заголовки локальним моделям)
79
79
  * @returns {Promise<{ content: string, usage: object|null, error: string|null, model: string|null, stopReason: string|null, caller: string }>} результат;
80
- * `stopReason` — фініш останнього assistant-повідомлення (`'length'` = відповідь обрізана стелею; політика повтору — за колером)
80
+ * `stopReason` — фініш останнього assistant-повідомлення (`'length'` = відповідь обрізана стелею; політика повтору — за колером);
81
+ * `stopReason === 'error'` завжди дає непорожній `error` (з `errorMessage` провайдера чи дефолтним текстом) —
82
+ * pi не завжди кидає виняток при провалі провайдера (вичерпані внутрішні retry), тож без цього `error: null`
83
+ * при порожньому `content` виглядало б так само, як легітимна порожня відповідь
81
84
  */
82
85
  export async function runOneShot({
83
86
  messages,
@@ -142,12 +145,14 @@ export async function runOneShot({
142
145
  let text = ''
143
146
  let usage = null
144
147
  let stopReason = null
148
+ let providerErrorMessage = null
145
149
  session.subscribe(event => {
146
150
  if (event.type === 'message_update' && event.assistantMessageEvent?.type === 'text_delta') {
147
151
  text += event.assistantMessageEvent.delta ?? ''
148
152
  } else if (event.type === 'message_end' && event.message) {
149
153
  if (event.message.usage) usage = event.message.usage
150
154
  stopReason = event.message.stopReason ?? null
155
+ if (event.message.errorMessage) providerErrorMessage = event.message.errorMessage
151
156
  }
152
157
  })
153
158
 
@@ -159,6 +164,18 @@ export async function runOneShot({
159
164
  failOnMemoryGuard(promptError, userText)
160
165
  }
161
166
 
167
+ // pi не завжди кидає при провалі провайдера (напр. вичерпані внутрішні
168
+ // retry на connection-error): `session.prompt()` резолвиться штатно, а
169
+ // єдиний слід — `stopReason: 'error'` на `message_end` (задокументований
170
+ // третій стан поруч із `stop`/`length`/`toolUse`/`aborted`, з `errorMessage`
171
+ // поряд — `docs/custom-provider.md` пакета pi-coding-agent). Без цього
172
+ // consumers (docgen callLlm: `if (res.error) throw ...`) бачать
173
+ // `error: null, content: ''` — не відрізнити від легітимної порожньої
174
+ // відповіді, і тихо продовжують, вважаючи виклик успішним.
175
+ if (!promptError && stopReason === 'error') {
176
+ promptError = providerErrorMessage ?? 'pi: stopReason=error (без деталей від провайдера)'
177
+ }
178
+
162
179
  // spec порожній/нерозв'язаний ('' → pi сам вибирає дефолт) — беремо фактично
163
180
  // резолвлену pi-модель із сесії, щоб chain.note()/trace не бачили порожній
164
181
  // model і не потрапляли в неявний cloud-бакет (isLocalModel('') === false).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/llm-lib",
3
- "version": "3.0.3",
3
+ "version": "3.1.1",
4
4
  "description": "Тонкий шар роботи з LLM (локальні omlx + хмарні провайдери) поверх pi: model tiers, one-shot, agentic-раннери, write-guard, trace, telemetry, prompt-budget",
5
5
  "keywords": [
6
6
  "nitra",