@7n/llm-lib 2.13.0 → 2.13.2

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.13.2] - 2026-07-29
4
+
5
+ ### Changed
6
+
7
+ - Уніфіковано вибір моделей через `resolveModel`: явні local/cloud selectors
8
+ використовують спільну Rust-драбину, а one-shot і agent runners вимагають
9
+ tier або explicit model policy.
10
+
11
+ ## [2.13.1] - 2026-07-29
12
+
13
+ ### Fixed
14
+
15
+ - local-providers.test: герметизація від ambient N_OMLX_*/N_LITELLM_* env (тест «без env» падав на машинах зі справжнім N_LITELLM_API_KEY)
16
+
3
17
  ## [2.13.0] - 2026-07-29
4
18
 
5
19
  ### Changed
package/lib/acp.mjs CHANGED
@@ -10,8 +10,7 @@
10
10
  * watchdog-поведінкою на мертвий/незапущений дочірній процес.
11
11
  *
12
12
  * `claude` тут немає — Rust-крейт моделює лише `cursor`/`codex`/`pi`
13
- * (`AcpAgentKind`); deprecated `claude`-раннер лишається окремим
14
- * JS-шимом у `@7n/rules` (`npm/scripts/lib/acp-runner.mjs`).
13
+ * (`AcpAgentKind`); `claude` тут навмисно не підтримується.
15
14
  */
16
15
  import { loadNative } from './internal/native.mjs'
17
16
 
@@ -20,17 +19,19 @@ import { loadNative } from './internal/native.mjs'
20
19
  * рішення И) — опційний абстрактний тир (`min`/`avg`/`max`): якщо заданий,
21
20
  * Rust сам резолвить tier→env/args/post-session-config з пресету агента
22
21
  * (`one_shot_acp_with_tier`) — жодного JS-хелпера "пресет→env" тут немає.
23
- * Без `tier` стара поведінка (модель = персональний конфіг CLI на машині).
22
+ * Без `tier` виклик заборонений, крім явного `mode:'interactive'` для персонального CLI-конфіга.
24
23
  * @param {'cursor' | 'codex' | 'pi'} kind провайдер
25
24
  * @param {string} prompt промпт
26
25
  * @param {string} cwd робочий каталог сесії агента (каталог проєкту-викликача)
27
26
  * @param {{
28
- * tier?: 'min' | 'avg' | 'max',
27
+ * tier?: 'min' | 'avg' | 'max', mode?: 'interactive',
29
28
  * native?: { oneShotAcp: (kind: string, prompt: string, cwd: string, tier?: string) => Promise<string> }
30
29
  * }} [options] тир + інжект `native` для тестів (той самий 4-й аргумент, що й раніше — сумісність зі старим `{ native }`-викликом збережена)
31
30
  * @returns {Promise<string>} повний текст відповіді до кінця ходу
32
31
  */
33
- export function runAcpAgent(kind, prompt, cwd, { tier, native } = {}) {
32
+ export function runAcpAgent(kind, prompt, cwd, { tier, mode, native } = {}) {
33
+ if (!tier && mode !== 'interactive') throw new TypeError('runAcpAgent: передай tier або явно mode:"interactive"')
34
+ if (tier && mode) throw new TypeError('runAcpAgent: tier і mode взаємовиключні')
34
35
  const nativeImpl = native ?? loadNative()
35
36
  return nativeImpl.oneShotAcp(kind, prompt, cwd, tier)
36
37
  }
@@ -6,7 +6,7 @@
6
6
  * Виконує один скіл як pi-агента: готовий промпт-рядок → `session.prompt`.
7
7
  * Повний user-trust набір вбудованих tools (`read/grep/find/edit/write/ls/bash`), БЕЗ
8
8
  * write-guard — скіл є явною user-invocation (паритет із `claude -p`, який теж без
9
- * обмежень). Модель з ОДНОГО тиру (дефолт `max`), без escalation-сходів fix-engine.
9
+ * обмежень). Модель задається явним тиром або `modelSpec`, без escalation-сходів fix-engine.
10
10
  * Runaway-backstop: turn-ceiling + per-call timeout. Асистентський текст стрімиться
11
11
  * у stdout (паритет із `claude -p`). Телеметрія `kind:"skill"` у глобальний trace.
12
12
  *
@@ -80,7 +80,7 @@ async function defaultCreateSession({ registry, model, cwd, thinkingLevel, maxTo
80
80
  export async function runAgentSkill(prompt, opts = {}) {
81
81
  const {
82
82
  skillId = 'skill',
83
- tier = 'max',
83
+ tier,
84
84
  modelSpec,
85
85
  cwd = process.cwd(),
86
86
  thinkingLevel,
@@ -122,7 +122,8 @@ export async function runAgentSkill(prompt, opts = {}) {
122
122
  let model
123
123
  try {
124
124
  registry = deps.registry ?? (await getReg())
125
- spec = modelSpec ?? resolveModel(tier) // '' допустимо дефолт провайдера pi
125
+ if (!modelSpec && !tier) return fail('model selection: передай tier або явний modelSpec', null)
126
+ spec = modelSpec || resolveModel(tier)
126
127
  model = spec ? resolveModelSpec(registry, spec) : null
127
128
  if (spec && !model) return fail(`модель не знайдена: ${spec}`, spec)
128
129
  } catch (error) {
package/lib/batch.mjs CHANGED
@@ -29,8 +29,8 @@ import { loadNative } from './internal/native.mjs'
29
29
  /**
30
30
  * Batch-виклик Типу 2b. `modelSpecOrTier` — той самий контракт, що й у
31
31
  * [`oneShotLocalCloud`] з `local-cloud.mjs`: явний `"provider/model-id"`
32
- * або абстрактний тир (`min`/`avg`/`max`).
33
- * @param {string} modelSpecOrTier `"provider/model-id"` або `'min'|'avg'|'max'`
32
+ * абстрактний тир (`min`/`avg`/`max`) або явний env-selector.
33
+ * @param {string} modelSpecOrTier `"provider/model-id"`, tier або env-selector
34
34
  * @param {BatchItem[]} items вхідні items (`customId` — унікальний у межах виклику)
35
35
  * @param {{
36
36
  * localProviders?: Record<string, { baseUrl: string, apiKey?: string | null }>,
package/lib/docs/acp.md CHANGED
@@ -3,9 +3,9 @@ type: JS Module
3
3
  title: acp.mjs
4
4
  resource: llm-lib/lib/acp.mjs
5
5
  docgen:
6
- crc: 1ed416a1
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- tier: local-min
6
+ crc: 022452f4
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
9
  score: 80
10
10
  ---
11
11
 
@@ -22,8 +22,7 @@ ACP JSON-RPC/`ClientSideConnection` тут; уся протокольна лог
22
22
  watchdog-поведінкою на мертвий/незапущений дочірній процес.
23
23
 
24
24
  `claude` тут немає — Rust-крейт моделює лише `cursor`/`codex`/`pi`
25
- (`AcpAgentKind`); deprecated `claude`-раннер лишається окремим
26
- JS-шимом у `@7n/rules` (`npm/scripts/lib/acp-runner.mjs`).
25
+ (`AcpAgentKind`); `claude` тут навмисно не підтримується.
27
26
 
28
27
  ## Публічний API
29
28
 
@@ -31,11 +30,11 @@ JS-шимом у `@7n/rules` (`npm/scripts/lib/acp-runner.mjs`).
31
30
  рішення И) — опційний абстрактний тир (`min`/`avg`/`max`): якщо заданий,
32
31
  Rust сам резолвить tier→env/args/post-session-config з пресету агента
33
32
  (`one_shot_acp_with_tier`) — жодного JS-хелпера "пресет→env" тут немає.
34
- Без `tier` стара поведінка (модель = персональний конфіг CLI на машині).
33
+ Без `tier` виклик заборонений, крім явного `mode:'interactive'` для персонального CLI-конфіга.
35
34
 
36
35
  ## Сценарії використання
37
36
 
38
- - `llm-lib/tests/acp.test.mjs` (runAcpAgent; getAcpPresets (smoke через реально збудований napi-аддон)) — делегує kind/prompt/cwd у native.oneShotAcp і віддає його результат; без опцій (старий виклик без 4-го аргументу) tier не заданий; tier прокидається в native.oneShotAcp четвертим аргументом; kind
37
+ - `llm-lib/tests/acp.test.mjs` (runAcpAgent; getAcpPresets (smoke через реально збудований napi-аддон)) — interactive mode делегує kind/prompt/cwd у native.oneShotAcp і віддає його результат; без tier або interactive modefail-closed; tier прокидається в native.oneShotAcp четвертим аргументом; kind
39
38
 
40
39
  ## Гарантії поведінки
41
40
 
@@ -3,33 +3,31 @@ type: JS Module
3
3
  title: agent-skill.mjs
4
4
  resource: llm-lib/lib/agent-skill.mjs
5
5
  docgen:
6
- crc: b7312bad
6
+ crc: 7c86fb8b
7
7
  model: openai-codex/gpt-5.4-mini
8
8
  tier: cloud-min
9
9
  score: 100
10
- issues: judge:error
10
+ issues: judge-refine:kept-original,judge:inaccurate:0.99
11
11
  judgeModel: openai-codex/gpt-5.4-mini
12
12
  ---
13
13
 
14
14
  ## Огляд
15
15
 
16
- Організовує один запуск skill і керує його виконанням у межах поточного контексту, щоб агент отримував потрібний стан для роботи. Має локальні fail-safe гілки для контрольованих збоїв; інші помилки можуть поширюватися назовні.
16
+ Файл запускає один агентський skill-run і передає виконання в існуючу сесію, щоб пов’язати його з обраним tier або modelSpec та зберегти трасування контексту. Це потрібно, щоб виклик або завершився успішно, або повернув діагностовану помилку; частина fail-safe гілок обробляється локально, а інші помилки можуть поширюватися назовні.
17
17
 
18
18
  ## Поведінка
19
19
 
20
- 1. Приймає готовий prompt для одного skill-запуску та фіксує контекст виконання: skill, tier, modelSpec, cwd, timeout, maxTokens, caller і chain.
21
- 2. Переходить у наступний крок chain, якщо ланцюжок передано, і готує кореляцію для подальшого обліку.
22
- 3. Обирає модель через registry; якщо модель явно задана, але не знаходиться, завершує запуск без виконання skill.
23
- 4. Створює pi-сесію з повним набором built-in tools, включно з bash, і прив’язує до неї поточний working directory та рівень thinking.
24
- 5. Для локальних моделей додає chain-кореляцію; для інших моделей цього не робить.
25
- 6. Запускає один skill-цикл і стрімить текст відповіді в stdout у міру надходження.
26
- 7. Рахує turns і tool calls; якщо turns перевищують аварійну стелю, зупиняє виконання як runaway-backstop.
27
- 8. Обмежує час виконання; при timeout перериває сесію.
28
- 9. Якщо модель або registry недоступні, повертає fail-safe результат із помилкою без продовження прогону.
29
- 10. Якщо під час prompt виникає memory-guard rejection для локального model-сервера, завершує як fail-fast і не маскує помилку.
30
- 11. Після завершення формує telemetry з фактичним model, turns, tool calls, backstop-станом і тривалістю.
31
- 12. Передає результат у chain і trace, а також зберігає capture для подальшого аналізу прогону.
32
- 13. Повертає ознаку успіху, telemetry і текст помилки; успіх можливий лише коли немає помилки й не спрацював backstop.
20
+ 1. Приймає готовий prompt для одного skill-run і фіксує контекст виконання: skill, tier, modelSpec, cwd, thinkingLevel, timeout, maxTokens, caller та chain.
21
+ 2. Перемикає ланцюжок на наступний крок, якщо chain передано, і готує контрольну позначку для трасування запиту.
22
+ 3. Перевіряє, чи задано спосіб вибору моделі: або tier, або явний modelSpec. Якщо ні — завершує невдачею без запуску сесії.
23
+ 4. Визначає цільову модель і перевіряє, чи вона доступна в registry. Якщо модель не знайдена або registry недоступний завершує невдачею з діагностикою.
24
+ 5. Створює агентську сесію з повним набором вбудованих tools і без write-guard; для локальних моделей може підключити chain-кореляцію, для інших ні.
25
+ 6. Підписується на події сесії, щоб рахувати turns і tool-виклики, стрімити текст відповіді у stdout та зберегти підсумкове usage з фінального повідомлення.
26
+ 7. Запускає виконання prompt із пер-call timeout і runaway-backstop на turns. Якщо ліміт turns перевищено або спрацьовує timeout, сесію зупиняє.
27
+ 8. Якщо під час запуску виникає memory-guard rejection для локального model-сервера, повертає failure-поведінку з друком prompt і помилкою замість structured result.
28
+ 9. Після завершення формує підсумок виконання: фактичну модель, кількість turns, кількість tool-викликів, факт спрацювання backstop і тривалість.
29
+ 10. Оновлює chain і trace, а також зберігає capture-дані для подальшого аналізу: prompt, output, usage, error і кореляційні поля.
30
+ 11. Повертає успіх лише тоді, коли немає помилки і не спрацював backstop; інакше повертає невдачу з telemetry, де це можливо.
33
31
 
34
32
  ## Публічний API
35
33
 
@@ -37,7 +35,7 @@ docgen:
37
35
 
38
36
  ## Сценарії використання
39
37
 
40
- - `llm-lib/tests/agent-skill.test.mjs` (runAgentSkill) — happy-path: ok, телеметрія, стрім тексту, trace kind:; createSession отримує тиру → thinkingLevel і cwd; maxTokens прокидається у createSession (0 = без стелі); з chain: step/note/chain-поля у trace; хмарна модель → chain:null у сесію; modelSpec порожній: telemetry.model — фактично резолвлена pi-модель, не echo spec; ще 5
38
+ - `llm-lib/tests/agent-skill.test.mjs` (runAgentSkill) — happy-path: ok, телеметрія, стрім тексту, trace kind:; createSession отримує тиру → thinkingLevel і cwd; maxTokens прокидається у createSession (0 = без стелі); з chain: step/note/chain-поля у trace; хмарна модель → chain:null у сесію; явний modelSpec зберігається у telemetry; ще 5
41
39
 
42
40
  ## Гарантії поведінки
43
41
 
package/lib/docs/batch.md CHANGED
@@ -3,31 +3,43 @@ type: JS Module
3
3
  title: batch.mjs
4
4
  resource: llm-lib/lib/batch.mjs
5
5
  docgen:
6
- crc: 10b603cc
7
- model: openai-codex/gpt-5.5
8
- tier: cloud-avg
9
- score: 80
6
+ crc: ce0ba158
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
10
  judgeModel: openai-codex/gpt-5.4-mini
11
11
  ---
12
12
 
13
13
  ## Огляд
14
14
 
15
- Тип 2b (OpenAI-сумісний API, batch) — **лише емуляція** у v1 (рішення Р,
16
- задача T6): чанкований конкурентний прогін через Тип 2a
17
- (`llm_lib::local_cloud`) під інтерфейсом `submit progress → results` —
18
- той самий інтерфейс, яким говорив би й справжній OpenAI Batch API
19
- (`/v1/batches`, v2), якому локальний omlx (перший споживач) не має.
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 завжди йде емуляцією).
20
23
 
21
- Тонкий JS-клієнт до Rust-крейта `llm_lib::batch` через napi FFI
22
- in-process (`llm-lib/crates/llm-lib-napi`) — жодного власного чанкінгу
23
- тут (анти-приклад, якого це узагальнює: `mlmail/use-summary.js` чанкує
24
- переклади проти omlx вручну, з вистражданими лімітами).
24
+ Тонкий JS-клієнт до Rust-крейта `llm_lib::batch`/`llm_lib::remote_batch`
25
+ через 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` дозволяє отримувати проміжний стан виконання.
25
37
 
26
38
  ## Публічний API
27
39
 
28
- - submitBatch — Емуляція batch-виклику Типу 2b. `modelSpecOrTier` — той самий контракт,
29
- що й у [`oneShotLocalCloud`] з `local-cloud.mjs`: явний
30
- `"provider/model-id"` або абстрактний тир (`min`/`avg`/`max`).
40
+ - submitBatch — Batch-виклик Типу 2b. `modelSpecOrTier` — той самий контракт, що й у
41
+ [`oneShotLocalCloud`] з `local-cloud.mjs`: явний `"provider/model-id"`
42
+ абстрактний тир (`min`/`avg`/`max`) або явний env-selector.
31
43
 
32
44
  ## Сценарії використання
33
45
 
@@ -3,32 +3,35 @@ type: JS Module
3
3
  title: local-cloud.mjs
4
4
  resource: llm-lib/lib/local-cloud.mjs
5
5
  docgen:
6
- crc: fc09aee2
6
+ crc: cedae6fe
7
7
  model: openai-codex/gpt-5.5
8
8
  tier: cloud-avg
9
- score: 100
9
+ score: 80
10
10
  judgeModel: openai-codex/gpt-5.4-mini
11
11
  ---
12
12
 
13
13
  ## Огляд
14
14
 
15
- Надає Node-доступ до одного OpenAI-сумісного запиту `chat/completions` для локального або хмарного провайдера через спільний Rust-шар. `oneShotLocalCloud` існує як тонкий JS-вхід до `llm_lib::local_cloud` через `napi FFI in-process` у `llm-lib/crates/llm-lib-napi`, щоб визначення моделей, конфігурація локальних провайдерів і HTTP-взаємодія залишалися в єдиній реалізації без окремого клієнта в JS і без агентського циклу.
15
+ Тип 2a (OpenAI-сумісний API, sync) для Node прямий HTTP до OpenAI-compatible
16
+ ендпоінта (`chat/completions`): локальні провайдери (напр. omlx) і хмарні
17
+ (стандартна автентифікація провайдера) — без агентського циклу.
16
18
 
17
- ## Поведінка
18
-
19
- 1. `oneShotLocalCloud` приймає запит на один OpenAI-сумісний chat-виклик для локального або хмарного провайдера без агентського циклу.
20
- 2. Визначає цільову модель як явну специфікацію провайдера або як абстрактний тир, щоб використовувати спільне правило резолву моделей із Rust-шару.
21
- 3. Передає текст користувача, optional system-повідомлення та конфігурацію локальних провайдерів до in-process Rust-клієнта через napi FFI.
22
- 4. Делегує HTTP-взаємодію з OpenAI-compatible ендпоінтом Rust-реалізації, щоб у JS-шарі не виникало окремого клієнта й розрізненого читання `settings.json`.
23
- 5. Повертає текст відповіді моделі як результат одного синхронного за сценарієм запиту.
19
+ Тонкий JS-клієнт до Rust-крейта `llm_lib::local_cloud` через napi FFI
20
+ in-process (`llm-lib/crates/llm-lib-napi`) — жодного власного HTTP-клієнта
21
+ тут (анти-приклад, якого це уникає: `mlmail` читає `~/.omlx/settings.json`
22
+ і б'є в ендпоінт напряму замість спільної точки, задача T5/рішення Н).
24
23
 
25
24
  ## Публічний API
26
25
 
27
- - oneShotLocalCloud — Один chat-виклик Типу 2a. `modelSpecOrTier` — або явний `"provider/model-id"`,
28
- або абстрактний тир (`min`/`avg`/`max`, рішення К), що резолвиться в Rust
26
+ - oneShotLocalCloud — Один chat-виклик Типу 2a. `modelSpecOrTier` — явний `"provider/model-id"`,
27
+ абстрактний tier або `N_LOCAL_*_MODEL`/`N_CLOUD_*_MODEL` selector
29
28
  через ту саму [`llm_lib::resolve_model`], що й `resolveModel` з
30
29
  `model-tiers.mjs`.
31
30
 
31
+ ## Сценарії використання
32
+
33
+ - `llm-lib/tests/local-cloud.test.mjs` (oneShotLocalCloud) — делегує modelSpecOrTier/prompt у native.oneShotLocalCloud і віддає його результат; явний; localProviders і system прокидаються в options; без опцій (лише modelSpecOrTier/prompt) — localProviders/system undefined
34
+
32
35
  ## Гарантії поведінки
33
36
 
34
37
  - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
@@ -3,26 +3,36 @@ type: JS Module
3
3
  title: local-providers.mjs
4
4
  resource: llm-lib/lib/local-providers.mjs
5
5
  docgen:
6
- crc: 6513b04f
7
- model: openai-codex/gpt-5.4-mini
8
- tier: cloud-min
6
+ crc: cd7b4cfa
7
+ model: openai-codex/gpt-5.5
8
+ tier: cloud-avg
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 } }`, яку споживають `oneShotLocalCloud` і `submitBatch`.
16
-
17
- `omlx` і `litellm` завжди присутні в мапі одночасно, а фактичний виклик іде рівно в один клієнт за `provider`-префіксом у model-spec. Якщо spec не вказує на певний префікс, відповідний запис у мапі не отримує запиту.
15
+ Файл надає стандартну мапу local-провайдерів для `llm_lib::local_cloud` у формі `{ prefix: { baseUrl, apiKey } }`, щоб JS-частина могла передати Rust-крейту готові endpoints для `oneShotLocalCloud` і `submitBatch`. `defaultLocalProviders` завжди описує `omlx` і `litellm` одночасно, а фактичний мережевий запит отримує лише провайдер, чий префікс вибрано в model-spec, наприклад `N_LOCAL_MIN_MODEL`.
18
16
 
19
17
  ## Поведінка
20
18
 
21
- 1. `defaultLocalProviders` формує єдиний дефолтний набір local-провайдерів для `llm_lib::local_cloud`, щоб обидва зареєстровані напрямки `omlx` і `litellm` були доступні одночасно в очікуваному `{ prefix: { baseUrl, apiKey } }` форматі для `oneShotLocalCloud` і `submitBatch`.
22
- 2. Для `omlx` функція бере `baseUrl` з `N_OMLX_BASE_URL`, а якщо його немає — підставляє `http://127.0.0.1:8000/v1/`; `apiKey` бере з `N_OMLX_API_KEY`, інакше лишає порожнім значенням.
23
- 3. Для `litellm` функція бере `baseUrl` з `N_LITELLM_BASE_URL`, а якщо його немає підставляє `https://llm.7n.ai/v1/`; `apiKey` бере з `N_LITELLM_API_KEY`, інакше лишає порожнім значенням.
24
- 4. Функція не вирішує, який провайдер “активний” сама по собі: вибір фактично визначається тим, який provider-префікс вказаний у model-spec на кшталт `N_LOCAL_MIN_MODEL`.
25
- 5. Коли model-spec вказує на один префікс, `LocalCloud::one_shot_with_spec` звертається рівно до відповідного клієнта; другий запис у мапі залишається запасним і не отримує запитів, доки жоден spec на нього не посилається.
19
+ 1. `defaultLocalProviders` формує стандартний набір local-провайдерів для `llm_lib::local_cloud`, щоб JS-частина передавала Rust-крейту готову мапу endpoint-ів у спільному форматі.
20
+
21
+ 2. До мапи завжди входять `omlx` і `litellm`; наявність обох записів не означає одночасне використання обох провайдерів.
22
+
23
+ 3. Активним стає лише провайдер, чий префікс вибрано в model-spec, тому запит спрямовується до одного відповідного клієнта.
24
+
25
+ 4. Для `omlx` використовується локальна адреса за замовчуванням `http://127.0.0.1:8000/v1/`, щоб підтримати локальний LLM-сервер без обов’язкової конфігурації.
26
+
27
+ 5. Для `litellm` використовується віддалена адреса за замовчуванням `https://llm.7n.ai/v1/`, щоб мати готовий fallback-провайдер для централізованого LLM endpoint-а.
28
+
29
+ 6. Значення адрес і ключів доступу можуть надходити з оточення, щоб одна й та сама логіка працювала в локальному, CI та production-середовищах без зміни коду.
30
+
31
+ 7. Файл лише збирає конфігурацію провайдерів і не виконує власних операцій запису.
32
+
33
+ ## Сценарії використання
34
+
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
26
36
 
27
37
  ## Гарантії поведінки
28
38
 
@@ -3,27 +3,21 @@ type: JS Module
3
3
  title: model-tiers.mjs
4
4
  resource: llm-lib/lib/model-tiers.mjs
5
5
  docgen:
6
- crc: 05c5eb6a
7
- model: openai-codex/gpt-5.4-mini
8
- tier: cloud-min
6
+ crc: 1953f7a9
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ tier: local-min-retry
9
9
  score: 100
10
- issues: judge-refine:kept-original,judge:inaccurate:0.97
10
+ issues: judge:error
11
11
  judgeModel: openai-codex/gpt-5.4-mini
12
12
  ---
13
13
 
14
14
  ## Огляд
15
15
 
16
- `LOCAL_MIN`, `LOCAL_AVG`, `LOCAL_MAX`, `CLOUD_MIN`, `CLOUD_AVG` і `CLOUD_MAX` задають спільні варіанти модельного рівня для локального та хмарного сценаріїв, щоб споживачі використовували однакові значення для вибору режиму роботи. `parseModelId` і `formatModelSpec` узгоджують подання модельного ідентифікатора між внутрішнім представленням і зовнішнім форматом, а `resolveModel` повертає уже погоджений варіант для подальшого використання. `thinkingLevelForTier` фіксує відповідність між tier і рівнем thinking, а `isLocalModel` дає змогу відрізнити локальні моделі від інших без дублювання цієї перевірки в різних місцях.
16
+ Файл відповідає за визначення та вибір моделі для виконання завдань. Він інтерпретує граничні значення, такі як `LOCAL_MIN`, `LOCAL_AVG`, `LOCAL_MAX` для локальних моделей та `CLOUD_MIN`, `CLOUD_AVG`, `CLOUD_MAX` для хмарних моделей. На основі цих значень відбувається пошук відповідної моделі через функцію `resolveModel`. Після вибору моделі, відповідно до її типу, визначається відповідний рівень обробки за допомогою `thinkingLevelForTier`.
17
17
 
18
18
  ## Поведінка
19
19
 
20
- LOCAL_MIN, LOCAL_AVG, LOCAL_MAX, CLOUD_MIN, CLOUD_AVG і CLOUD_MAX беруть значення з environment на старті модуля та задають єдину політику вибору моделі для локального й хмарного шарів. Ці значення далі слугують опорою для resolveModel, який повертає вже фактично обраний model spec у форматі provider/model-id або порожній рядок, якщо дефолт провайдера лишився substrate-рівню. Невідомий tier відсіюється на цьому рівні як помилка контракту.
21
-
22
- thinkingLevelForTier переводить rung-tier у дискретний рівень thinking, щоб downstream-логіка могла узгоджено трактувати силу моделі без повторного аналізу spec. local-min і local-min-retry зводяться до найнижчого рівня, cloud-min, cloud-avg і cloud-max піднімають рівень відповідно до потужності хмарного вибору.
23
-
24
- parseModelId і formatModelSpec утворюють парний обмін між рядковим model spec та об’єктом моделі: перший розбирає канонічний рядок на provider та id, другий збирає фактично резолвлену модель назад у той самий формат. Якщо spec або модель неповні, результатом є null, щоб не маскувати malformed або відсутній стан.
25
-
26
- isLocalModel використовує ту саму політику, що й resolveModel: спочатку звіряє явні локальні тири, а потім визначає локальність за provider із model spec. Це дає спільне правило для агрегатів local/cloud і для рішень, де потрібно відрізнити локальний шлях від хмарного без дублювання логіки в consumers.
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`) на основі того, який із визначених тирів був обраний.
27
21
 
28
22
  ## Публічний API
29
23
 
@@ -33,14 +27,12 @@ isLocalModel використовує ту саму політику, що й re
33
27
  - CLOUD_MIN — Мінімальний хмарний (потрібен ключ у pi auth). Напр.: openai/gpt-5.4-mini
34
28
  - CLOUD_AVG — Середній хмарний. Напр.: openai/gpt-5.4
35
29
  - CLOUD_MAX — Максимальний хмарний. Напр.: openai/gpt-5.5
36
- - resolveModel — Каскадне розв'язання абстрактного тиру в `"provider/model-id"` —
37
- napi-делегація в `llm_lib::resolve_model` (задача T5, рішення Е): та сама
38
- логіка, що й Rust-каскад у `tiers.rs`:
39
- 'min' → LOCAL_MIN → LOCAL_AVG → LOCAL_MAX → CLOUD_MIN
40
- 'avg' LOCAL_AVG LOCAL_MAX CLOUD_AVG
41
- 'max' LOCAL_MAX CLOUD_MAX
42
- Тир валідується тут (не в Rust) — щоб зберегти контракт `TypeError` для
43
- невідомого тиру без потреби мапити помилку з napi-боку.
30
+ - resolveModel — Універсально резолвить модель від явної env-сходинки:
31
+ - LOCAL_MIN LOCAL_AVG LOCAL_MAX CLOUD_MIN → CLOUD_AVG → CLOUD_MAX;
32
+ - LOCAL_AVG LOCAL_MAX CLOUD_AVG → CLOUD_MAX;
33
+ - LOCAL_MAX → CLOUD_MAX;
34
+ - cloud-старти проходять лише відповідну й сильніші cloud-сходинки.
35
+ `min`/`avg`/`max` лишаються alias-ами відповідних `N_LOCAL_*_MODEL`.
44
36
  - thinkingLevelForTier — `thinkingLevel` за rung-тиром fix-драбини: слабка локальна — `low`,
45
37
  cloud-min — `medium`, cloud-avg — `high`, cloud-max (experiment-only tier,
46
38
  не в production ladder) — `xhigh`.
@@ -60,7 +52,7 @@ model-spec, не за наявністю запису в мапі. Викори
60
52
 
61
53
  ## Сценарії використання
62
54
 
63
- - `llm-lib/tests/model-tiers.test.mjs` (isLocalModel; parseModelId) — omlx-провайдер — локальний (дефолт N_LLM_LOCAL_PROVIDERS); litellm-провайдер — теж локальний за дефолтом (перемикач omlx/litellm через тир-env); порожній/malformed spec — не локальний; кастомний список провайдерів через env (ізольований re-import); звичайна пара; ще 8
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
56
 
65
57
  ## Гарантії поведінки
66
58
 
@@ -3,32 +3,37 @@ type: JS Module
3
3
  title: one-shot.mjs
4
4
  resource: llm-lib/lib/one-shot.mjs
5
5
  docgen:
6
- crc: 3b62f8db
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ crc: 38adb9ce
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
- Цей механізм виконує одноразовий запит до мовної моделі (LLM) через публічну функцію `runOneShot`. Він збирає необхідну інформацію, ініціює взаємодію та консолідує вхідні дані у єдиний текстовий запит. Функція завжди перехоплює помилки (fail-safe) та не кидає винятків назовні. У разі успішного отримання відповіді від LLM, що є операцією лише для читання (read-only), повертається згенерований текст, дані про використання моделі та метадані.
15
+ `runOneShot` виконує одноразовий запит до вибраної моделі чи tier без agent-циклу і використовується як точка входу для сценаріїв, де потрібна одна відповідь без подальшої взаємодії. У файлі є локальні fail-safe гілки для окремих збоїв.
13
16
 
14
17
  ## Поведінка
15
18
 
16
- Поведінка:
17
-
18
- 1. Для виконання одноразового LLM-виклику спочатку збирається необхідна інформація для доступу до моделі, використовуючи надані рівні моделі.
19
- 2. Після визначення моделі створюється сесія для взаємодії з LLM, використовуючи налаштування, які гарантують виконання завдання без використання інструментів.
20
- 3. Всі вхідні повідомлення об'єднуються в єдиний текстовий запит.
21
- 4. Система чекає відповіді від LLM, обмеженої заданим часовим лімітом.
22
- 5. Під час очікування відбувається збір генерованого тексту та метаданих об'єднання (usage) відповіді.
23
- 6. У разі будь-якої помилки під час пошуку моделі, створення сесії чи самого виклику, виклик завершується з повідомленням про помилку.
24
- 7. У разі успіху, функція повертає згенерований текст, інформацію про використання моделі, статус помилки та метадані моделі, використовуючи інформацію, зібрану під час взаємодії з сесією.
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 для спостережуваності, але не гарантує обробку всіх зовнішніх помилок.
25
27
 
26
28
  ## Публічний API
27
29
 
28
- - runOneShot — виконує обмежений LLM-виклик одноразово; опція `maxTokens` задає per-call стелю відповіді (undefined → дефолт пакета, 0 → без стелі); у результаті додатково повертається `stopReason` (`'length'` = відповідь обрізана стелею — політика повтору за колером).
29
- - MEMORY_ERROR_RE — публічна частина fail-fast error-контракту: regex для класифікації memory-guard помилки локального model-сервера (пробити нагору й завершити процес, а не ковтати як per-item помилку).
30
+ - runOneShot — Виконує bounded one-shot LLM-виклик.
31
+
32
+ ## Сценарії використання
33
+
34
+ - `llm-lib/tests/one-shot.test.mjs` (runOneShot) — happy path: збирає текст + usage; усі messages (system+user) зливаються в один prompt; без replaceInstructions; модель не знайдена → error, сесія не створюється; prompt кидає → error; timeout → error; ще 13
30
35
 
31
36
  ## Гарантії поведінки
32
37
 
33
- - Read-only: не виконує операцій запису (ФС/БД).
34
- - Перехоплює помилки і не пропускає винятків назовні (fail-safe); виняток — memory-guard rejection, який свідомо кидає Error після друку тіла запиту.
38
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
39
+ - Містить локальні fail-safe гілки.
@@ -11,11 +11,11 @@
11
11
  import { loadNative } from './internal/native.mjs'
12
12
 
13
13
  /**
14
- * Один chat-виклик Типу 2a. `modelSpecOrTier` — або явний `"provider/model-id"`,
15
- * або абстрактний тир (`min`/`avg`/`max`, рішення К), що резолвиться в Rust
14
+ * Один chat-виклик Типу 2a. `modelSpecOrTier` — явний `"provider/model-id"`,
15
+ * абстрактний tier або `N_LOCAL_*_MODEL`/`N_CLOUD_*_MODEL` selector
16
16
  * через ту саму [`llm_lib::resolve_model`], що й `resolveModel` з
17
17
  * `model-tiers.mjs`.
18
- * @param {string} modelSpecOrTier `"provider/model-id"` або `'min'|'avg'|'max'`
18
+ * @param {string} modelSpecOrTier `"provider/model-id"`, tier або env-selector
19
19
  * @param {string} prompt user-репліка
20
20
  * @param {{
21
21
  * localProviders?: Record<string, { baseUrl: string, apiKey?: string | null }>,
@@ -34,29 +34,40 @@ export const CLOUD_AVG = env.N_CLOUD_AVG_MODEL ?? ''
34
34
  /** Максимальний хмарний. Напр.: openai/gpt-5.5 */
35
35
  export const CLOUD_MAX = env.N_CLOUD_MAX_MODEL ?? ''
36
36
 
37
- /** Валідні тири та сама множина, що й `parse_tier` у napi-крейті. */
38
- const KNOWN_TIERS = new Set(['min', 'avg', 'max'])
37
+ /** Абстрактний tier явна стартова env-сходинка. */
38
+ const TIER_START = {
39
+ min: 'N_LOCAL_MIN_MODEL',
40
+ avg: 'N_LOCAL_AVG_MODEL',
41
+ max: 'N_LOCAL_MAX_MODEL'
42
+ }
43
+ const MODEL_ENV_KEYS = new Set([
44
+ 'N_LOCAL_MIN_MODEL',
45
+ 'N_LOCAL_AVG_MODEL',
46
+ 'N_LOCAL_MAX_MODEL',
47
+ 'N_CLOUD_MIN_MODEL',
48
+ 'N_CLOUD_AVG_MODEL',
49
+ 'N_CLOUD_MAX_MODEL'
50
+ ])
39
51
 
40
52
  /**
41
- * Каскадне розв'язання абстрактного тиру в `"provider/model-id"` —
42
- * napi-делегація в `llm_lib::resolve_model` (задача T5, рішення Е): та сама
43
- * логіка, що й Rust-каскад у `tiers.rs`:
44
- * 'min' LOCAL_MIN → LOCAL_AVG → LOCAL_MAX → CLOUD_MIN
45
- * 'avg' LOCAL_AVG LOCAL_MAX CLOUD_AVG
46
- * 'max' LOCAL_MAX CLOUD_MAX
47
- * Тир валідується тут (не в Rust) — щоб зберегти контракт `TypeError` для
48
- * невідомого тиру без потреби мапити помилку з napi-боку.
49
- * @param {'min'|'avg'|'max'} tier тир
50
- * @param {{ native?: { resolveModel: (tier: string) => string | null } }} [deps] інжект `native` для тестів
53
+ * Універсально резолвить модель від явної env-сходинки:
54
+ * - LOCAL_MIN LOCAL_AVG LOCAL_MAX CLOUD_MIN → CLOUD_AVG → CLOUD_MAX;
55
+ * - LOCAL_AVG LOCAL_MAX CLOUD_AVG → CLOUD_MAX;
56
+ * - LOCAL_MAX → CLOUD_MAX;
57
+ * - cloud-старти проходять лише відповідну й сильніші cloud-сходинки.
58
+ * `min`/`avg`/`max` лишаються alias-ами відповідних `N_LOCAL_*_MODEL`.
59
+ * @param {'min'|'avg'|'max'|'N_LOCAL_MIN_MODEL'|'N_LOCAL_AVG_MODEL'|'N_LOCAL_MAX_MODEL'|'N_CLOUD_MIN_MODEL'|'N_CLOUD_AVG_MODEL'|'N_CLOUD_MAX_MODEL'} requested стартова сходинка
60
+ * @param {{ native?: { resolveModel: (start: string) => string | null } }} [deps] інжект `native` для тестів
51
61
  * @returns {string} `"provider/model-id"` або `''` (дефолт провайдера substrate)
52
62
  * @throws {TypeError} якщо tier невідомий
53
63
  */
54
- export function resolveModel(tier, deps = {}) {
55
- if (!KNOWN_TIERS.has(tier)) {
56
- throw new TypeError(`resolveModel: unknown tier "${tier}". Use 'min', 'avg', or 'max'.`)
64
+ export function resolveModel(requested, deps = {}) {
65
+ const start = TIER_START[requested] ?? requested
66
+ if (!MODEL_ENV_KEYS.has(start)) {
67
+ throw new TypeError(`resolveModel: unknown model selector ${JSON.stringify(requested)}.`)
57
68
  }
58
69
  const native = deps.native ?? loadNative()
59
- return native.resolveModel(tier) ?? ''
70
+ return native.resolveModel(start) ?? ''
60
71
  }
61
72
 
62
73
  // ── Escalation-rung → thinkingLevel ──────────────────────────────────────────
package/lib/one-shot.mjs CHANGED
@@ -81,7 +81,7 @@ async function defaultCreateSession({ registry, model, cwd, thinkingLevel, maxTo
81
81
  */
82
82
  export async function runOneShot({
83
83
  messages,
84
- modelTier = 'min',
84
+ modelTier,
85
85
  modelSpec,
86
86
  thinkingLevel,
87
87
  timeoutMs = DEFAULT_TIMEOUT_MS,
@@ -122,7 +122,8 @@ export async function runOneShot({
122
122
  let model
123
123
  try {
124
124
  registry = deps.registry ?? (await getReg())
125
- spec = modelSpec ?? resolveModel(modelTier)
125
+ if (!modelSpec && !modelTier) return fail('model selection: передай modelTier або явний modelSpec', null)
126
+ spec = modelSpec || resolveModel(modelTier)
126
127
  model = spec ? resolveModelSpec(registry, spec) : null
127
128
  if (spec && !model) return fail(`модель не знайдена: ${spec}`, spec)
128
129
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/llm-lib",
3
- "version": "2.13.0",
3
+ "version": "2.13.2",
4
4
  "description": "Тонкий шар роботи з LLM (локальні omlx + хмарні провайдери) поверх pi: model tiers, one-shot, agentic-раннери, write-guard, trace, telemetry, prompt-budget",
5
5
  "keywords": [
6
6
  "nitra",
@@ -56,8 +56,8 @@
56
56
  "access": "public"
57
57
  },
58
58
  "optionalDependencies": {
59
- "@7n/llm-lib-darwin-arm64": "2.9.7",
60
- "@7n/llm-lib-linux-x64": "2.9.7"
59
+ "@7n/llm-lib-darwin-arm64": "2.13.2",
60
+ "@7n/llm-lib-linux-x64": "2.13.2"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "@earendil-works/pi-ai": "~0.80.10",