@7n/llm-lib 2.8.8 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.9.0] - 2026-07-25
4
+
5
+ ### Added
6
+
7
+ - acp: публічний session-API (create_session/prompt/cancel, стрім SessionEvent, зовнішній permission-responder, опційний post-session config-крок для Pi); one_shot_acp — фасад над session
8
+ - acp: пресети агентів — AcpAgentKind::Pi (npx -y pi-acp), тір-мапи Codex (CODEX_CONFIG luna/terra/sol), Cursor (--model з ефорт-суфіксами), Pi (post-session provider/modelId), UI-лейбли; one_shot_acp_with_tier
9
+ - napi/JS: oneShotAcp(kind, prompt, cwd, {tier}) з kind 'pi', getAcpPresets(), oneShotLocalCloud (Тип 2a, модуль ./local-cloud); model-tiers.mjs: resolveModel — napi-делегація в tiers.rs
10
+ - llm_lib::batch — емуляція Типу 2b (submit → progress → results, чанк 35/конкурентність 2, помилка item не валить batch); napi submitBatch з ThreadsafeFunction-прогресом; JS-модуль ./batch
11
+
12
+ ### Changed
13
+
14
+ - acp: транспортний шар spawn/init/session виділено в acp/transport.rs (build_acp_args: env-префікси + extra-args), one_shot_acp — тонкий фасад без зміни поведінки
15
+ - Rust-крейти перейменовано: llm-cascade → llm-lib, llm-cascade-napi → llm-lib-napi, CascadeError → LlmError; napi-артефакти llm-lib-napi.`triple`.node; git-споживачам — dependency-alias llm-cascade = { package = "llm-lib" }
16
+
17
+ ### Fixed
18
+
19
+ - Виправлено profile генерації тестів для survived Stryker-мутантів
20
+
3
21
  ## [2.8.8] - 2026-07-24
4
22
 
5
23
  ### Changed
package/lib/acp.mjs CHANGED
@@ -1,28 +1,36 @@
1
1
  /**
2
- * ACP (Agent Client Protocol, Zed) — доступ до `cursor`/`codex` через
2
+ * ACP (Agent Client Protocol, Zed) — доступ до `cursor`/`codex`/`pi` через
3
3
  * особисту підписку (вже залогінений локально CLI), не API-ключ.
4
4
  *
5
- * Тонкий JS-клієнт до Rust-крейта `llm_cascade::acp` через napi FFI
6
- * in-process (`llm-lib/crates/llm-cascade-napi`) — жодного власного
5
+ * Тонкий JS-клієнт до Rust-крейта `llm_lib::acp` через napi FFI
6
+ * in-process (`llm-lib/crates/llm-lib-napi`) — жодного власного
7
7
  * ACP JSON-RPC/`ClientSideConnection` тут; уся протокольна логіка (спавн
8
- * агента, `session/prompt`, автоапрув `session/request_permission`) живе
9
- * в Rust, разом з watchdog-поведінкою на мертвий/незапущений дочірній процес.
8
+ * агента, `session/prompt`, автоапрув `session/request_permission`,
9
+ * тір→env/args/post-session-config резолвінг) живе в Rust, разом з
10
+ * watchdog-поведінкою на мертвий/незапущений дочірній процес.
10
11
  *
11
- * `claude` тут немає — Rust-крейт моделює лише `cursor`/`codex`
12
+ * `claude` тут немає — Rust-крейт моделює лише `cursor`/`codex`/`pi`
12
13
  * (`AcpAgentKind`); deprecated `claude`-раннер лишається окремим
13
14
  * JS-шимом у `@7n/rules` (`npm/scripts/lib/acp-runner.mjs`).
14
15
  */
15
16
  import { loadNative } from './internal/native.mjs'
16
17
 
17
18
  /**
18
- * Один виклик через ACP-агента з особистою підпискою.
19
- * @param {'cursor' | 'codex'} kind провайдер
19
+ * Один виклик через ACP-агента з особистою підпискою. `tier` (задача T5,
20
+ * рішення И) опційний абстрактний тир (`min`/`avg`/`max`): якщо заданий,
21
+ * Rust сам резолвить tier→env/args/post-session-config з пресету агента
22
+ * (`one_shot_acp_with_tier`) — жодного JS-хелпера "пресет→env" тут немає.
23
+ * Без `tier` — стара поведінка (модель = персональний конфіг CLI на машині).
24
+ * @param {'cursor' | 'codex' | 'pi'} kind провайдер
20
25
  * @param {string} prompt промпт
21
26
  * @param {string} cwd робочий каталог сесії агента (каталог проєкту-викликача)
22
- * @param {{ native?: { oneShotAcp: (kind: string, prompt: string, cwd: string) => Promise<string> } }} [deps] інжект для тестів
27
+ * @param {{
28
+ * tier?: 'min' | 'avg' | 'max',
29
+ * native?: { oneShotAcp: (kind: string, prompt: string, cwd: string, tier?: string) => Promise<string> }
30
+ * }} [options] тир + інжект `native` для тестів (той самий 4-й аргумент, що й раніше — сумісність зі старим `{ native }`-викликом збережена)
23
31
  * @returns {Promise<string>} повний текст відповіді до кінця ходу
24
32
  */
25
- export function runAcpAgent(kind, prompt, cwd, deps = {}) {
26
- const native = deps.native ?? loadNative()
27
- return native.oneShotAcp(kind, prompt, cwd)
33
+ export function runAcpAgent(kind, prompt, cwd, { tier, native } = {}) {
34
+ const nativeImpl = native ?? loadNative()
35
+ return nativeImpl.oneShotAcp(kind, prompt, cwd, tier)
28
36
  }
package/lib/agent-fix.mjs CHANGED
@@ -150,34 +150,61 @@ async function runVerifyLoop({ session, verify, verifyMax, timeoutMs, startedAt,
150
150
  * правкою (хардкод значення, симуляція поведінки) — промпт явно це забороняє, а
151
151
  * verdict-veto consumer-а (re-check) відхиляє такі правки поза target-файлами.
152
152
  * @param {{ ruleId: string, violation: string, ruleText?: string, feedback?: object,
153
- * targetFiles?: string[] }} args параметри промпта; `targetFiles` — файли порушення,
154
- * єдині наявні файли, які дозволено редагувати (порожньо/відсутнє — без переліку).
153
+ * targetFiles?: string[], sourceFiles?: string[], editMode?: 'generic'|'test-generation' }} args параметри промпта.
154
+ * У generic-режимі `targetFiles` — єдині наявні файли, які дозволено редагувати.
155
+ * `test-generation` відділяє read-only source-контекст від test-файлів, які агент
156
+ * може знайти, створити або змінити.
155
157
  * @returns {string} промпт
156
158
  */
157
- export function buildFixPrompt({ ruleId, violation, ruleText, feedback, targetFiles, anchoredEdits = false }) {
159
+ export function buildFixPrompt({
160
+ ruleId,
161
+ violation,
162
+ ruleText,
163
+ feedback,
164
+ targetFiles,
165
+ sourceFiles,
166
+ editMode = 'generic',
167
+ anchoredEdits = false
168
+ }) {
158
169
  const parts = [`Виправ порушення правила "${ruleId}" у цьому проєкті.`]
159
170
  if (ruleText) parts.push(`## Правило\n${ruleText}`)
160
171
  parts.push(`## Порушення\n${violation}`)
161
- if (Array.isArray(targetFiles) && targetFiles.length > 0) {
172
+ if (editMode === 'generic' && Array.isArray(targetFiles) && targetFiles.length > 0) {
162
173
  parts.push(
163
174
  '## Target-файли (єдині наявні файли, які дозволено редагувати)\n' + targetFiles.map(f => `- ${f}`).join('\n')
164
175
  )
165
176
  }
177
+ if (editMode === 'test-generation' && Array.isArray(sourceFiles) && sourceFiles.length > 0) {
178
+ parts.push('## Source-файли (лише для читання)\n' + sourceFiles.map(f => `- ${f}`).join('\n'))
179
+ }
166
180
  if (feedback?.previousError) {
167
181
  parts.push(`## Попередня спроба не спрацювала\n${feedback.previousError}\nСпробуй інший підхід.`)
168
182
  }
169
- parts.push(
170
- '## Обмеження (обовʼязкові)\n' +
171
- 'Дозволені ЛИШЕ механічні зміни, що прямо усувають наведене порушення правила:\n' +
172
- '- НЕ змінюй бізнес-логіку і поведінку коду.\n' +
173
- '- НЕ хардкодь значення замість викликів функцій чи обчислень.\n' +
174
- '- НЕ симулюй і не заглушуй поведінку (stub/mock/"simulate").\n' +
175
- '- НЕ редагуй наявні файли поза порушенням; нові файли створюй лише якщо цього прямо вимагає правило.\n' +
176
- 'Якщо порушення не усувається механічною правкою — зупинись, нічого не змінюючи.',
177
- 'Перед редагуванням JS/TS-файлу спершу виклич `ast_facts` на ньому. ' +
178
- 'Після правок виклич `self_check`, щоб підтвердити, що порушення зникло. ' +
179
- 'Редагуй лише потрібне, не чіпай стороннє.'
180
- )
183
+ if (editMode === 'test-generation') {
184
+ parts.push(
185
+ '## Обмеження test-generation (обовʼязкові)\n' +
186
+ '- Source-файли — лише контекст для читання; НЕ редагуй production source або його поведінку.\n' +
187
+ '- Знайди, створи або зміни лише повʼязані `*.test.*` / `*.spec.*` файли, зокрема у test-каталогах.\n' +
188
+ '- Для ізоляції unit-тесту дозволені звичайні Vitest mock/stub, якщо вони не змінюють production-код.\n' +
189
+ '- Новий тест мусить перевіряти поведінку, що вбиває описаний мутант; не маскуй його ignore/exclude.\n' +
190
+ 'Якщо неможливо написати коректний тест без зміни production source — зупинись, нічого не змінюючи.',
191
+ 'Перед редагуванням JS/TS test-файлу спершу прочитай потрібний контекст. ' +
192
+ 'Після правок виклич `self_check` і запусти релевантні Vitest-тести. Редагуй лише потрібні test-файли.'
193
+ )
194
+ } else {
195
+ parts.push(
196
+ '## Обмеження (обовʼязкові)\n' +
197
+ 'Дозволені ЛИШЕ механічні зміни, що прямо усувають наведене порушення правила:\n' +
198
+ '- НЕ змінюй бізнес-логіку і поведінку коду.\n' +
199
+ '- НЕ хардкодь значення замість викликів функцій чи обчислень.\n' +
200
+ '- НЕ симулюй і не заглушуй поведінку (stub/mock/"simulate").\n' +
201
+ '- НЕ редагуй наявні файли поза порушенням; нові файли створюй лише якщо цього прямо вимагає правило.\n' +
202
+ 'Якщо порушення не усувається механічною правкою — зупинись, нічого не змінюючи.',
203
+ 'Перед редагуванням JS/TS-файлу спершу виклич `ast_facts` на ньому. ' +
204
+ 'Після правок виклич `self_check`, щоб підтвердити, що порушення зникло. ' +
205
+ 'Редагуй лише потрібне, не чіпай стороннє.'
206
+ )
207
+ }
181
208
  if (anchoredEdits) {
182
209
  parts.push(
183
210
  'Наявні файли читай і редагуй ЛИШЕ через `read_anchored` → `edit_anchored` ' +
@@ -283,7 +310,7 @@ async function defaultCreateSession({
283
310
  * @param {{
284
311
  * model: string, tier?: string, feedback?: object, caller?: string, timeoutMs?: number, ruleText?: string,
285
312
  * chain?: object,
286
- * targetFiles?: string[],
313
+ * targetFiles?: string[], sourceFiles?: string[], editMode?: 'generic'|'test-generation',
287
314
  * verify?: (args: { touchedFiles: string[] }) => Promise<{ ok: boolean, output?: string }> | { ok: boolean, output?: string },
288
315
  * verifyMax?: number,
289
316
  * anchoredEdits?: boolean,
@@ -306,6 +333,8 @@ export async function runAgentFix(ruleId, violation, cwd, opts = {}) {
306
333
  ruleText,
307
334
  chain = null,
308
335
  targetFiles,
336
+ sourceFiles,
337
+ editMode = 'generic',
309
338
  verify = null,
310
339
  verifyMax = VERIFY_MAX_DEFAULT,
311
340
  anchoredEdits = false,
@@ -418,7 +447,16 @@ export async function runAgentFix(ruleId, violation, cwd, opts = {}) {
418
447
  }
419
448
  })
420
449
 
421
- const fixPrompt = buildFixPrompt({ ruleId, violation, ruleText, feedback, targetFiles, anchoredEdits })
450
+ const fixPrompt = buildFixPrompt({
451
+ ruleId,
452
+ violation,
453
+ ruleText,
454
+ feedback,
455
+ targetFiles,
456
+ sourceFiles,
457
+ editMode,
458
+ anchoredEdits
459
+ })
422
460
  const pHash = promptHash(fixPrompt)
423
461
  const startedAt = clock()
424
462
  let error = null
@@ -458,6 +496,9 @@ export async function runAgentFix(ruleId, violation, cwd, opts = {}) {
458
496
  stepUsage.output += t.usage?.output ?? 0
459
497
  stepUsage.totalTokens += t.usage?.totalTokens ?? 0
460
498
  }
499
+ // Повна відповідь без tool-call і записів — окремо позначаємо: usage не є
500
+ // єдиним критерієм, бо деякі провайдери його не віддають.
501
+ const emptyCompletion = toolCallCount === 0 && touchedFiles.length === 0
461
502
  chain?.note({ model: modelSpec, usage: stepUsage, error })
462
503
  trace({
463
504
  caller,
@@ -478,6 +519,8 @@ export async function runAgentFix(ruleId, violation, cwd, opts = {}) {
478
519
  turnCount,
479
520
  toolCallCount,
480
521
  touchedFiles,
522
+ usage: stepUsage,
523
+ emptyCompletion,
481
524
  backstopHit,
482
525
  verifyAttempts: verifyAttempts.length,
483
526
  verifyOk: verifyAttempts.length > 0 ? verifyAttempts.at(-1).ok : null,
@@ -494,9 +537,11 @@ export async function runAgentFix(ruleId, violation, cwd, opts = {}) {
494
537
  model: modelSpec,
495
538
  promptHash: pHash,
496
539
  prompt: fixPrompt,
497
- output: { touchedFiles, edits: guard.state.editLog },
540
+ output: { touchedFiles, edits: guard.state.editLog, emptyCompletion },
498
541
  usage: stepUsage,
499
542
  error
500
543
  })
544
+ telemetry.emptyCompletion = emptyCompletion
545
+ telemetry.usage = stepUsage
501
546
  return { applied: touchedFiles.length > 0, touchedFiles, telemetry, error, rollback: guard.rollback }
502
547
  }
package/lib/batch.mjs ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Тип 2b (OpenAI-сумісний API, batch) — **лише емуляція** у v1 (рішення Р,
3
+ * задача T6): чанкований конкурентний прогін через Тип 2a
4
+ * (`llm_lib::local_cloud`) під інтерфейсом `submit → progress → results` —
5
+ * той самий інтерфейс, яким говорив би й справжній OpenAI Batch API
6
+ * (`/v1/batches`, v2), якому локальний omlx (перший споживач) не має.
7
+ *
8
+ * Тонкий JS-клієнт до Rust-крейта `llm_lib::batch` через napi FFI
9
+ * in-process (`llm-lib/crates/llm-lib-napi`) — жодного власного чанкінгу
10
+ * тут (анти-приклад, якого це узагальнює: `mlmail/use-summary.js` чанкує
11
+ * переклади проти omlx вручну, з вистражданими лімітами).
12
+ */
13
+ import { loadNative } from './internal/native.mjs'
14
+
15
+ /**
16
+ * Один item вхідного batch-у.
17
+ * @typedef {{ customId: string, prompt: string, system?: string }} BatchItem
18
+ */
19
+
20
+ /**
21
+ * Результат одного item — рівно одне з `ok`/`error` заповнене.
22
+ * @typedef {{ customId: string, ok?: string, error?: string }} BatchResult
23
+ */
24
+
25
+ /**
26
+ * Емуляція batch-виклику Типу 2b. `modelSpecOrTier` — той самий контракт,
27
+ * що й у [`oneShotLocalCloud`] з `local-cloud.mjs`: явний
28
+ * `"provider/model-id"` або абстрактний тир (`min`/`avg`/`max`).
29
+ * @param {string} modelSpecOrTier `"provider/model-id"` або `'min'|'avg'|'max'`
30
+ * @param {BatchItem[]} items вхідні items (`customId` — унікальний у межах виклику)
31
+ * @param {{
32
+ * localProviders?: Record<string, { baseUrl: string, apiKey?: string | null }>,
33
+ * system?: string,
34
+ * chunkSize?: number,
35
+ * concurrency?: number,
36
+ * onProgress?: (completed: number, total: number) => void,
37
+ * native?: {
38
+ * submitBatch: (
39
+ * modelSpecOrTier: string,
40
+ * items: Array<{ customId: string, prompt: string, system?: string }>,
41
+ * options?: object,
42
+ * config?: object,
43
+ * onProgress?: (completed: number, total: number) => void
44
+ * ) => Promise<BatchResult[]>
45
+ * }
46
+ * }} [options] конфіг локальних провайдерів, ліміти чанка/конкурентності, progress-колбек, інжект `native` для тестів
47
+ * @returns {Promise<BatchResult[]>} результати в тому самому порядку, що й вхідні `items`
48
+ */
49
+ export function submitBatch(
50
+ modelSpecOrTier,
51
+ items,
52
+ { localProviders, system, chunkSize, concurrency, onProgress, native } = {}
53
+ ) {
54
+ const nativeImpl = native ?? loadNative()
55
+ return nativeImpl.submitBatch(
56
+ modelSpecOrTier,
57
+ items.map(item => ({
58
+ customId: item.customId,
59
+ prompt: item.prompt,
60
+ system: item.system ?? undefined
61
+ })),
62
+ {
63
+ localProviders: localProviders ?? undefined,
64
+ system: system ?? undefined
65
+ },
66
+ {
67
+ chunkSize: chunkSize ?? undefined,
68
+ concurrency: concurrency ?? undefined
69
+ },
70
+ onProgress ?? undefined
71
+ )
72
+ }
package/lib/docs/acp.md CHANGED
@@ -3,29 +3,33 @@ type: JS Module
3
3
  title: acp.mjs
4
4
  resource: llm-lib/lib/acp.mjs
5
5
  docgen:
6
- crc: 5c32e90c
6
+ crc: 587f1966
7
7
  model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
8
9
  score: 100
9
- issues: judge:inaccurate:0.97
10
10
  judgeModel: openai-codex/gpt-5.4-mini
11
11
  ---
12
12
 
13
13
  ## Огляд
14
14
 
15
- Файл надає тонкий JS-доступ до `cursor` або `codex` через публічну `runAcpAgent`, покладаючись на вже авторизовану локальну CLI-сесію без API-ключів. Уся ACP-логіка живе в нативному Rust-шарі `llm_cascade::acp`, який викликається in-process через `napi FFI` у `llm-lib/crates/llm-cascade-napi`; тут немає власного `ClientSideConnection` чи JSON-RPC. Саме Rust запускає агента, обробляє `session/prompt`, автоматично погоджує `session/request_permission` і стежить за живістю дочірнього процесу. Крейт підтримує лише `cursor`/`codex`; `claude`-runner лишається окремим JS-шимом у `@7n/rules` (`npm/scripts/lib/acp-runner.mjs`).
15
+ Публічна точка входу `runAcpAgent` для запуску ACP-агента `cursor`, `codex` або `pi` через локально залогінений CLI без API-ключа. Це тонкий JS-міст до `llm_lib::acp` у `llm-lib/crates/llm-lib-napi`, без власної ACP JSON-RPC чи `ClientSideConnection` логіки; протокольна поведінка, `session/prompt`, `session/request_permission`, `tier→env/args/post-session-config` resolving і watchdog на мертвий або незапущений дочірній процес зосереджені в Rust. `AcpAgentKind` охоплює лише `cursor`/`codex`/`pi`; `claude` тут відсутній, а deprecated `claude`-runner лишається окремим JS-шимом у `@7n/rules` (`npm/scripts/lib/acp-runner.mjs`).
16
16
 
17
17
  ## Поведінка
18
18
 
19
- 1. `runAcpAgent` запускає один ACP-хід для `cursor` або `codex` через вже авторизовану локальну CLI-сесію, без API-ключів.
20
- 2. Вона звертається до нативного Rust-шару `llm_cascade::acp`, який бере на себе весь протокол взаємодії: старт агента, обмін `session/prompt`, автоапрув запитів на дозвіл і контроль живості дочірнього процесу.
21
- 3. Вона передає робочий каталог поточного проєкту як контекст сесії, щоб агент працював у межах каталогу викликача.
22
- 4. Вона повертає повний текст відповіді агента за один хід.
23
- 5. Вона не виконує власну протокольну логіку, не працює з `claude`, і не покладається на `ClientSideConnection`; підтримка `claude` живе окремо в JS-шимі `npm/scripts/lib/acp-runner.mjs`.
19
+ 1. `runAcpAgent` запускає один запит до ACP-агента з особистою підпискою для `cursor`, `codex` або `pi` у межах поточного робочого каталогу.
20
+ 2. Якщо задано `tier`, передає цю абстракцію в нативний шар, щоб далі саме Rust визначив відповідні параметри сесії для вибраного агента.
21
+ 3. Якщо `tier` не задано, використовує стандартну поведінку персонально залогіненого CLI без окремого вибору рівня.
22
+ 4. Для виконання звертається до нативної реалізації в процесі, яка вже містить протокольну логіку, запуск сесії та обробку дозволів; цей файл не реалізує власний ACP-обмін і не працює з `claude`.
23
+ 5. Повертає повний текст відповіді агента після завершення одного ходу.
24
24
 
25
25
  ## Публічний API
26
26
 
27
- - runAcpAgent — запускає один запит через ACP-агента з власною підпискою
27
+ - runAcpAgent — Один виклик через ACP-агента з особистою підпискою. `tier` (задача T5,
28
+ рішення И) — опційний абстрактний тир (`min`/`avg`/`max`): якщо заданий,
29
+ Rust сам резолвить tier→env/args/post-session-config з пресету агента
30
+ (`one_shot_acp_with_tier`) — жодного JS-хелпера "пресет→env" тут немає.
31
+ Без `tier` — стара поведінка (модель = персональний конфіг CLI на машині).
28
32
 
29
33
  ## Гарантії поведінки
30
34
 
31
- - Read-only: не виконує операцій запису (ФС/БД).
35
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
@@ -3,32 +3,26 @@ type: JS Module
3
3
  title: agent-fix.mjs
4
4
  resource: llm-lib/lib/agent-fix.mjs
5
5
  docgen:
6
- crc: 83a189d4
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ crc: 907ba309
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 55
10
+ issues: no-overview,short-behavior,best-of-2:retry-lost
8
11
  ---
9
12
 
10
- ## Огляд
11
-
12
- Модуль ініціює процес виправлення порушень правил. Він створює відповідний промпт для агента через функцію `buildFixPrompt` та запускає цикл взаємодії з агентом за допомогою `runAgentFix`. Процес працює з механізмом перехоплення помилок (fail-safe), гарантуючи відсутність винятків на виході.
13
-
14
- ## Поведінка
15
-
16
- Поведінка:
17
- buildFixPrompt готує текстовий промпт, що містить інструкції для агента щодо виправлення порушення правила.
18
- runAgentFix виконує повний агентний цикл для спроби виправлення порушення правила, включаючи взаємодію з інструментами, застосування патча та фіксацію телеметрії.
19
- Виклик сесії огорнутий timeout-гонкою: `opts.timeoutMs` (дефолт 300s, коли consumer не передав значення) на спрацюванні abort-ить сесію і повертає помилку `fix timeout …` — зависла LLM-сесія (напр. мертва SSE) не блокує виклик назавжди.
20
-
21
- Web-профіль (опційний `opts.webTools`, Фаза A3): додає read-only tools `web_search`/`web_fetch` (див. web-tools.md; SSRF-guard, ліміти розміру) — для правил із зовнішнім знанням на cloud-тирах; прапорець у трейсі, дефолт вимкнено.
22
-
23
- Anchored-профіль (опційний `opts.anchoredEdits`, Фаза A2): toolset сесії заміняє built-in `read`/`edit` на строгі `read_anchored`/`edit_anchored` (див. anchored-edit.md; `write` лишається для нових файлів, під тим самим write-guard), а промпт отримує інструкцію anchored-циклу. Прапорець фіксується у трейсі (`anchoredEdits`) для A/B-аналізу; дефолт вимкнено.
24
-
25
- Evidence-гейт (опційний `opts.verify`, Фаза A1 run-harness): після prompt-у модуль сам запускає canonical-перевірку consumer-а; провал інʼєктиться фідбеком у ту саму сесію — до `opts.verifyMax` додаткових ітерацій (дефолт 2), у межах того самого `timeoutMs` (залишок бюджету < 5s — чесна зупинка без ітерації). Гейт структурний: заяви агента про успіх не важать, джерелом правди лишається зовнішня перевірка. Помилка самої перевірки — інфраструктурна: ітерації не витрачаються, повертається `error` з префіксом `verify:`. Без `verify` — поведінка попередня (один прохід). Спроби фіксуються у `telemetry.verifyAttempts` і у trace (`verifyAttempts`, `verifyOk`).
26
-
27
13
  ## Публічний API
28
14
 
29
- buildFixPrompt — формує інструкцію для виправлення проблеми, включаючи відповідне правило, описане порушення, опційний перелік target-файлів (єдині наявні файли, які дозволено редагувати) та можливий відгук з попередньої невдалої спроби; містить обовʼязковий блок обмежень semantic-collateral guard (лише механічні зміни: без зміни бізнес-логіки, без хардкоду значень, без симуляції поведінки spec pi-migration §12, addendum 2026-07-05) і директиву щодо кроків перед редагуванням та самоперевірки.
30
- buildVerifyFeedbackPrompt формує фідбек-повідомлення verify-ітерації: точний вивід canonical-перевірки + нагадування обмежень semantic-collateral guard.
31
- runAgentFixвиконує єдину спробу виправлення коду з боку агента для заданого правила, використовуючи вказані параметри (модель, рівень, зворотний зв'язок, verify-гейт, тощо) та ін'єкції для тестування.
15
+ - buildVerifyFeedbackPromptБудує фідбек-prompt verify-ітерації: точний вивід canonical-перевірки + нагадування
16
+ обмежень (той самий semantic-collateral guard, що й у buildFixPrompt).
17
+ - buildFixPrompt Будує fix-промпт для рунга: правило + порушення + (опц.) target-файли + (опц.) feedback
18
+ попереднього провалу + жорсткий блок обмежень (лише механічні зміни) + інструкція
19
+ «ast_facts перед edit, self_check після».
20
+
21
+ Блок обмежень — перший шар semantic-collateral guard (спека pi-migration §12,
22
+ addendum 2026-07-05): слабкі локальні моделі схильні «виправляти» правило семантичною
23
+ правкою (хардкод значення, симуляція поведінки) — промпт явно це забороняє, а
24
+ verdict-veto consumer-а (re-check) відхиляє такі правки поза target-файлами.
25
+ - runAgentFix — Проводить ОДНУ агентну fix-спробу (рунг) для правила.
32
26
 
33
27
  ## Гарантії поведінки
34
28
 
@@ -0,0 +1,34 @@
1
+ ---
2
+ type: JS Module
3
+ title: batch.mjs
4
+ resource: llm-lib/lib/batch.mjs
5
+ docgen:
6
+ crc: 4412e8c2
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ judgeModel: openai-codex/gpt-5.4-mini
11
+ ---
12
+
13
+ ## Огляд
14
+
15
+ Тонкий JS-клієнт до `llm_lib::batch` у `llm-lib/crates/llm-lib-napi`, який через in-process `napi FFI` лише емулює Type 2b у v1: під одним `submit → progress → results` інтерфейсом він прокидає batch-запит у `llm_lib::local_cloud` і повертає результат як сумісний OpenAI Batch API-контракт для майбутнього `/v1/batches`. Єдина публічна точка входу — `submitBatch`. Це узагальнення анти-прикладу на кшталт `mlmail/use-summary.js`, де чанкінг доводиться робити вручну під обмеження провайдера.
16
+
17
+ ## Поведінка
18
+
19
+ 1. `submitBatch` приймає batch-запит для Type 2b і передає його в native-реалізацію, щоб отримати той самий бізнес-інтерфейс `submit → progress → results`, який очікується від batch-потоку поверх локальних провайдерів.
20
+ 2. `submitBatch` зберігає порядок вхідних items у результатах, щоб кожен результат можна було зіставити з початковим `customId`.
21
+ 3. `submitBatch` нормалізує вхідні items перед передачею далі: бере `customId` і `prompt` як є, а відсутній `system` не підміняє значенням.
22
+ 4. `submitBatch` передає конфіг локальних провайдерів, загальний `system`, а також ліміти chunking і concurrency у native-шар, щоб контроль виконання залишався в реалізації batch-крейта.
23
+ 5. `submitBatch` підтримує `onProgress`, щоб викликати повідомлення про хід виконання під час обробки batch-у.
24
+ 6. `submitBatch` дозволяє підмінити native-реалізацію для тестів, не змінюючи зовнішню поведінку публічного API.
25
+
26
+ ## Публічний API
27
+
28
+ - submitBatch — Емуляція batch-виклику Типу 2b. `modelSpecOrTier` — той самий контракт,
29
+ що й у [`oneShotLocalCloud`] з `local-cloud.mjs`: явний
30
+ `"provider/model-id"` або абстрактний тир (`min`/`avg`/`max`).
31
+
32
+ ## Гарантії поведінки
33
+
34
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
package/lib/docs/index.md CHANGED
@@ -10,10 +10,12 @@ resource: llm-lib/lib/
10
10
  | [agent-fix.mjs](agent-fix.md) | JS Module |
11
11
  | [agent-skill.mjs](agent-skill.md) | JS Module |
12
12
  | [anchored-edit.mjs](anchored-edit.md) | JS Module |
13
+ | [batch.mjs](batch.md) | JS Module |
13
14
  | [body-capture.mjs](body-capture.md) | JS Module |
14
15
  | [chain.mjs](chain.md) | JS Module |
15
16
  | [chains-report.mjs](chains-report.md) | JS Module |
16
17
  | [harness.mjs](harness.md) | JS Module |
18
+ | [local-cloud.mjs](local-cloud.md) | JS Module |
17
19
  | [model-tiers.mjs](model-tiers.md) | JS Module |
18
20
  | [one-shot.mjs](one-shot.md) | JS Module |
19
21
  | [prompt-budget.mjs](prompt-budget.md) | JS Module |
@@ -0,0 +1,34 @@
1
+ ---
2
+ type: JS Module
3
+ title: local-cloud.mjs
4
+ resource: llm-lib/lib/local-cloud.mjs
5
+ docgen:
6
+ crc: fc09aee2
7
+ model: openai-codex/gpt-5.5
8
+ tier: cloud-avg
9
+ score: 100
10
+ judgeModel: openai-codex/gpt-5.4-mini
11
+ ---
12
+
13
+ ## Огляд
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 і без агентського циклу.
16
+
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. Повертає текст відповіді моделі як результат одного синхронного за сценарієм запиту.
24
+
25
+ ## Публічний API
26
+
27
+ - oneShotLocalCloud — Один chat-виклик Типу 2a. `modelSpecOrTier` — або явний `"provider/model-id"`,
28
+ або абстрактний тир (`min`/`avg`/`max`, рішення К), що резолвиться в Rust
29
+ через ту саму [`llm_lib::resolve_model`], що й `resolveModel` з
30
+ `model-tiers.mjs`.
31
+
32
+ ## Гарантії поведінки
33
+
34
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
@@ -3,39 +3,61 @@ type: JS Module
3
3
  title: model-tiers.mjs
4
4
  resource: llm-lib/lib/model-tiers.mjs
5
5
  docgen:
6
- crc: ba67ab5c
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ crc: 13970c1f
7
+ model: openai-codex/gpt-5.5
8
+ tier: cloud-avg
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
- Цей модуль відповідає за тир-політику моделей: env-параметри для локальних (`LOCAL_MIN` по `LOCAL_MAX`) та хмарних (`CLOUD_MIN` по `CLOUD_MAX`) моделей, каскадну резолюцію тиру в специфікатор (`resolveModel`), визначення рівня мислення за rung-тиром (`thinkingLevelForTier`) і розбір специфікаторів (`parseModelId`). Модуль повністю substrate-free і pure; доступ до pi ModelRegistry живе окремо в internal-модулі [registry](../internal/docs/registry.md).
16
+ Модуль централізує вибір і нормалізацію LLM-моделей для local та cloud tier значень. Він дає спільну точку для розбору і форматування model spec, визначення локальності моделі та зіставлення tier із рівнем thinking.
13
17
 
14
18
  ## Поведінка
15
19
 
16
- Поведінка:
17
- LOCAL_MIN — надає конфігураційне значення для мінімальної локальної моделі.
18
- LOCAL_AVG надає конфігураційне значення для середньої локальної моделі.
19
- LOCAL_MAX — надає конфігураційне значення для максимальної локальної моделі.
20
- CLOUD_MIN надає конфігураційне значення для мінімальної хмарної моделі.
21
- CLOUD_AVG — надає конфігураційне значення для середньої хмарної моделі.
22
- CLOUD_MAX надає конфігураційне значення для максимальної хмарної моделі.
23
- resolveModel — визначає шлях до моделі на основі вибраного рівня (min, avg, max) за каскадною політикою.
24
- thinkingLevelForTier визначає відповідний дискретний рівень мислення залежно від rung-тиру моделі.
25
- parseModelId — розбиває специфікатор моделі у форматі `"provider/model-id"` на окремі частини.
20
+ Модуль задає спільну env-політику вибору моделей через LOCAL_MIN, LOCAL_AVG, LOCAL_MAX, CLOUD_MIN, CLOUD_AVG і CLOUD_MAX. Ці значення є вхідним станом для подальшого резолву та класифікації: порожнє значення означає, що відповідний tier не заданий явно.
21
+
22
+ resolveModel приймає абстрактний tier і повертає фактичний model spec у форматі pi. Вибір делегується нативному шару, щоб JavaScript-споживачі отримували той самий каскад, що й Rust-частина. Якщо каскад не знаходить явної моделі, результатом стає порожній рядок, який залишає вибір дефолтної моделі нижчому шару.
23
+
24
+ parseModelId і formatModelSpec підтримують єдиний формат обміну між конфігурацією, результатами resolveModel і pi-моделями. parseModelId відкидає некоректні або неповні model spec, а formatModelSpec перетворює фактично вибрану pi-модель назад у той самий текстовий формат для подальшого порівняння чи логування.
25
+
26
+ isLocalModel використовує спільні LOCAL_* значення та список локальних провайдерів з оточення, щоб визначити, чи фактично вибрана або явно задана модель є локальною. Для цього результат resolveModel або formatModelSpec може бути переданий у isLocalModel після нормалізації через спільний формат.
27
+
28
+ thinkingLevelForTier працює з rung-рівнями escalation-ланцюжка й повертає дискретний рівень thinking для виконання запиту. Це рішення незалежне від env-конфігурації моделей, але використовується поруч із resolveModel у потоках, де одночасно обираються модельний tier і інтенсивність міркування.
29
+
30
+ Файл не виконує власних операцій запису у ФС чи БД; результати передаються назовні як значення для споживачів LLM-шару. Імпортовані модулі не аналізувались.
26
31
 
27
32
  ## Публічний API
28
33
 
29
- LOCAL_MIN — Забезпечує швидке виконання моделей без використання хмарних сервісів.
30
- LOCAL_AVG — Надає середні можливості локальних моделей для виконання завдань.
31
- LOCAL_MAX — Дозволяє використовувати найпотужніші доступні локальні моделі.
32
- CLOUD_MIN — Забезпечує мінімальний рівень продуктивності від хмарних моделей, вимагає підключення через `pi auth`.
33
- CLOUD_AVG — Використовує середній за можливостями хмарний двигун.
34
- CLOUD_MAX — Дозволяє отримати максимальну якість від хмарних моделей.
35
- resolveModel — Визначає найкращу модель для виконання, обираючи між локальними та хмарними варіантами залежно від запитуваного рівня (`min`, `avg`, `max`).
36
- thinkingLevelForTier Призначає відповідний рівень обробки (`low`, `medium`, `high`) на основі обраного рівня моделі для коректної роботи з пам'яттю та ресурсами.
37
- parseModelId Розбирає загальний рядок ідентифікатора моделі у дві частини: назву постачальника та його ідентифікатор.
34
+ - LOCAL_MIN — Швидкий локальний inference. Напр.: omlx/gemma-4-e4b-it-OptiQ-4bit
35
+ - LOCAL_AVG — Середній локальний.
36
+ - LOCAL_MAX — Максимальний локальний.
37
+ - CLOUD_MIN — Мінімальний хмарний (потрібен ключ у pi auth). Напр.: openai/gpt-5.4-mini
38
+ - CLOUD_AVG — Середній хмарний. Напр.: openai/gpt-5.4
39
+ - CLOUD_MAX — Максимальний хмарний. Напр.: openai/gpt-5.5
40
+ - resolveModel — Каскадне розв'язання абстрактного тиру в `"provider/model-id"`
41
+ napi-делегація в `llm_lib::resolve_model` (задача T5, рішення Е): та сама
42
+ логіка, що й Rust-каскад у `tiers.rs`:
43
+ 'min' → LOCAL_MIN → LOCAL_AVG → LOCAL_MAX → CLOUD_MIN
44
+ 'avg' → LOCAL_AVG → LOCAL_MAX → CLOUD_AVG
45
+ 'max' → LOCAL_MAX → CLOUD_MAX
46
+ Тир валідується тут (не в Rust) — щоб зберегти контракт `TypeError` для
47
+ невідомого тиру без потреби мапити помилку з napi-боку.
48
+ - thinkingLevelForTier — `thinkingLevel` за rung-тиром fix-драбини: слабка локальна — `low`,
49
+ cloud-min — `medium`, cloud-avg — `high`, cloud-max (experiment-only tier,
50
+ не в production ladder) — `xhigh`.
51
+ - parseModelId — Розбирає `"provider/model-id"` у пару. Перший `/` — роздільник (model-id може
52
+ містити власні `/`). Порожній провайдер чи id → `null` (malformed).
53
+ - formatModelSpec — Форматує pi `Model`-об'єкт (`{provider, id}`) назад у `"provider/model-id"`.
54
+ Інверсія {@link parseModelId} — застосовується до фактично резолвленої
55
+ pi-моделі (`session.model`), коли consumer лишив `modelSpec` порожнім і pi
56
+ сам вибрав дефолт (локальний чи хмарний).
57
+ - isLocalModel — Чи model-spec вказує на локальну модель: збіг з одним із LOCAL_* тирів
58
+ АБО провайдер з `N_LLM_LOCAL_PROVIDERS` (дефолт `omlx`). Використовується
59
+ для local/cloud-агрегатів ланцюжків і рішення про chain-заголовки.
38
60
 
39
61
  ## Гарантії поведінки
40
62
 
41
- - (специфічних машинно-виведених гарантій немає)
63
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
@@ -3,28 +3,31 @@ type: JS Module
3
3
  title: native.mjs
4
4
  resource: llm-lib/lib/internal/native.mjs
5
5
  docgen:
6
- crc: 205418d5
6
+ crc: 655cb048
7
7
  model: openai-codex/gpt-5.5
8
+ tier: cloud-avg
8
9
  score: 100
9
- issues: judge:inaccurate:0.98
10
10
  judgeModel: openai-codex/gpt-5.4-mini
11
11
  ---
12
12
 
13
13
  ## Огляд
14
14
 
15
- Файл знаходить і завантажує native-аддон `llm-cascade`, щоб JavaScript-код використовував Rust-ядро через napi-артефакт. `resolveNativeAddon` визначає шлях за єдиним порядком: явний override `N_LLM_LIB_NATIVE_ADDON`, platform-підпакет `@7n/llm-lib-<platform>-<arch>` з артефактом `llm-cascade-napi.<triple>.node`, dev-fallback у `target/release|debug/` після `cargo build -p llm-cascade-napi` або вивід у `llm-lib/crates/llm-cascade-napi/`. `loadNative` завантажує знайдений аддон через `process.dlopen` і кешує результат у межах процесу. На непідтриманих платформах запуск зупиняється зрозумілою помилкою без JS-fallback.
15
+ Файл підʼєднує Rust/NAPI native addon для `llm-lib` і є єдиною точкою вибору джерела: явний override через `N_LLM_LIB_NATIVE_ADDON`, платформний npm-пакет `@7n/llm-lib-<platform>-<arch>` з артефактом `llm-lib-napi.<triple>.node` або локальна dev-збірка. Публічні API `resolveNativeAddon` і `loadNative` потрібні, щоб споживачі отримували native exports з однаковою поведінкою в інсталяції, CI, тестах і локальній розробці. Завантаження виконується через `process.dlopen`, тому підтримуються і `.node`, і сирі cdylib (`.dylib`/`.so`); результат кешується як одне завантаження на процес. Непідтримані платформи завершуються зрозумілою помилкою без прихованого JS fallback.
16
16
 
17
17
  ## Поведінка
18
18
 
19
- - `resolveNativeAddon` визначає шлях до native-аддона `llm-cascade`: спершу бере явний override, далі шукає platform-підпакет, потім локальні dev-збірки; для непідтриманої або незібраної платформи завершується зрозумілою помилкою без JS-fallback.
20
- - `loadNative` завантажує native-аддон один раз за процес і повертає закешовані exports для повторних викликів.
19
+ `loadNative` отримує шлях від `resolveNativeAddon`, завантажує знайдений native addon і повертає його exports споживачам `llm-lib`. Результат завантаження зберігається в памʼяті процесу, тому наступні звернення повторно використовують той самий addon без нового пошуку та відкриття файлу.
20
+
21
+ `resolveNativeAddon` визначає джерело native addon за єдиним порядком пріоритетів: явний шлях із середовища для dev/CI/тестів, платформний npm-підпакет для підтримуваної платформи, локальні dev-збірки Rust/NAPI, а потім помилка з інструкцією для користувача. Це дає однакову поведінку для встановленого пакета, локальної розробки й тестових сценаріїв.
22
+
23
+ Якщо платформа не входить до свідомо підтриманих у v1 комбінацій, JavaScript fallback не використовується: потік завершується hard error. Це фіксує межу підтримки native-ядра замість прихованої деградації поведінки.
21
24
 
22
25
  ## Публічний API
23
26
 
24
- - resolveNativeAddon — знаходить файл native addon `llm-cascade` для поточного середовища або тестових підмін.
25
- - loadNative — повертає native addon із кешу, щоб завантажувати його лише один раз за час роботи процесу.
27
+ - resolveNativeAddon — Резолвить шлях до napi-аддона `llm-lib`.
28
+ - loadNative — Кешований доступ до аддона (одне завантаження на процес).
26
29
 
27
30
  ## Гарантії поведінки
28
31
 
29
- - Read-only: не виконує операцій запису (ФС/БД).
32
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
30
33
  - Кешує результати в межах одного прогону.
@@ -1,14 +1,14 @@
1
1
  /**
2
- * Loader napi-аддона `llm-cascade` (Rust-ядро `llm-lib/crates/llm-cascade-napi`
3
- * → `llm-cascade`) — за зразком `mt/npm/lib/core/native.mjs`.
2
+ * Loader napi-аддона `llm-lib` (Rust-ядро `llm-lib/crates/llm-lib-napi`
3
+ * → `llm-lib`) — за зразком `mt/npm/lib/core/native.mjs`.
4
4
  *
5
5
  * Порядок пошуку:
6
6
  * 1. N_LLM_LIB_NATIVE_ADDON — явний override шляху до аддона (dev / CI / тести).
7
7
  * 2. Platform-підпакет `@7n/llm-lib-<platform>-<arch>` (napi-артефакт
8
- * `llm-cascade-napi.<triple>.node`).
8
+ * `llm-lib-napi.<triple>.node`).
9
9
  * 3. Dev-fallback: `<repoRoot>/target/release|debug/` (сирий cdylib з
10
- * `cargo build -p llm-cascade-napi`) та вивід `napi build` у
11
- * `llm-lib/crates/llm-cascade-napi/`.
10
+ * `cargo build -p llm-lib-napi`) та вивід `napi build` у
11
+ * `llm-lib/crates/llm-lib-napi/`.
12
12
  * 4. Інакше — зрозуміла помилка з підказкою.
13
13
  *
14
14
  * Аддон завантажується через `process.dlopen` — працює і для `.node`, і для
@@ -48,16 +48,16 @@ function dlopenAddon(p) {
48
48
  }
49
49
 
50
50
  /**
51
- * Ім'я cdylib-файлу для платформи (вивід `cargo build -p llm-cascade-napi`).
51
+ * Ім'я cdylib-файлу для платформи (вивід `cargo build -p llm-lib-napi`).
52
52
  * @param {string} platform process.platform
53
53
  * @returns {string} ім'я бібліотеки
54
54
  */
55
55
  function cdylibName(platform) {
56
- return platform === 'darwin' ? 'libllm_cascade_napi.dylib' : 'libllm_cascade_napi.so'
56
+ return platform === 'darwin' ? 'libllm_lib_napi.dylib' : 'libllm_lib_napi.so'
57
57
  }
58
58
 
59
59
  /**
60
- * Резолвить шлях до napi-аддона `llm-cascade`.
60
+ * Резолвить шлях до napi-аддона `llm-lib`.
61
61
  * @param {{
62
62
  * env?: Record<string, string | undefined>,
63
63
  * platform?: string,
@@ -86,7 +86,7 @@ export function resolveNativeAddon(deps = {}) {
86
86
  // 2. Platform-підпакет.
87
87
  if (suffix) {
88
88
  try {
89
- return requireResolve(`@7n/llm-lib-${key}/llm-cascade-napi.${suffix}.node`)
89
+ return requireResolve(`@7n/llm-lib-${key}/llm-lib-napi.${suffix}.node`)
90
90
  } catch {
91
91
  // не встановлено — пробуємо dev-fallback
92
92
  }
@@ -97,7 +97,7 @@ export function resolveNativeAddon(deps = {}) {
97
97
  join(repoRoot, 'target', profile, cdylibName(platform))
98
98
  )
99
99
  if (suffix) {
100
- candidates.push(join(repoRoot, 'llm-lib', 'crates', 'llm-cascade-napi', `llm-cascade-napi.${suffix}.node`))
100
+ candidates.push(join(repoRoot, 'llm-lib', 'crates', 'llm-lib-napi', `llm-lib-napi.${suffix}.node`))
101
101
  }
102
102
  for (const candidate of candidates) {
103
103
  if (exists(candidate)) return candidate
@@ -105,9 +105,9 @@ export function resolveNativeAddon(deps = {}) {
105
105
 
106
106
  // 4. Помилка з підказкою.
107
107
  throw new Error(
108
- `llm-cascade native addon: немає збірки для "${key}". ` +
108
+ `llm-lib native addon: немає збірки для "${key}". ` +
109
109
  `Постав N_LLM_LIB_NATIVE_ADDON=/шлях/до/аддона, додай підпакет @7n/llm-lib-${key}, ` +
110
- `або збери локально: cargo build --release -p llm-cascade-napi`
110
+ `або збери локально: cargo build --release -p llm-lib-napi`
111
111
  )
112
112
  }
113
113
 
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Тип 2a (OpenAI-сумісний API, sync) для Node — прямий HTTP до OpenAI-compatible
3
+ * ендпоінта (`chat/completions`): локальні провайдери (напр. omlx) і хмарні
4
+ * (стандартна автентифікація провайдера) — без агентського циклу.
5
+ *
6
+ * Тонкий JS-клієнт до Rust-крейта `llm_lib::local_cloud` через napi FFI
7
+ * in-process (`llm-lib/crates/llm-lib-napi`) — жодного власного HTTP-клієнта
8
+ * тут (анти-приклад, якого це уникає: `mlmail` читає `~/.omlx/settings.json`
9
+ * і б'є в ендпоінт напряму замість спільної точки, задача T5/рішення Н).
10
+ */
11
+ import { loadNative } from './internal/native.mjs'
12
+
13
+ /**
14
+ * Один chat-виклик Типу 2a. `modelSpecOrTier` — або явний `"provider/model-id"`,
15
+ * або абстрактний тир (`min`/`avg`/`max`, рішення К), що резолвиться в Rust
16
+ * через ту саму [`llm_lib::resolve_model`], що й `resolveModel` з
17
+ * `model-tiers.mjs`.
18
+ * @param {string} modelSpecOrTier `"provider/model-id"` або `'min'|'avg'|'max'`
19
+ * @param {string} prompt user-репліка
20
+ * @param {{
21
+ * localProviders?: Record<string, { baseUrl: string, apiKey?: string | null }>,
22
+ * system?: string,
23
+ * native?: { oneShotLocalCloud: (modelSpecOrTier: string, prompt: string, options?: object) => Promise<string> }
24
+ * }} [options] конфіг локальних провайдерів (`omlx` тощо), system-репліка, інжект `native` для тестів
25
+ * @returns {Promise<string>} текст відповіді моделі
26
+ */
27
+ export function oneShotLocalCloud(modelSpecOrTier, prompt, { localProviders, system, native } = {}) {
28
+ const nativeImpl = native ?? loadNative()
29
+ return nativeImpl.oneShotLocalCloud(modelSpecOrTier, prompt, {
30
+ localProviders: localProviders ?? undefined,
31
+ system: system ?? undefined
32
+ })
33
+ }
@@ -3,9 +3,12 @@
3
3
  /**
4
4
  * Тир-конфіг моделей для LLM-шару (@7n/llm-lib) і його consumers.
5
5
  *
6
- * Тири політика споживача (які env-моделі = який тир); резолвінг у Model-обʼєкт
7
- * substrate-у живе окремо в [internal/registry] — цей модуль substrate-free і
8
- * повністю pure, юніт-тестується без pi.
6
+ * Каскадне розв'язання тиру ({@link resolveModel}) задача T5, рішення Е
7
+ * (ОНОВЛЕНЕ 2026-07-24): канон живе в Rust-крейті `llm_lib::tiers`
8
+ * (`llm-lib/crates/llm-lib/src/tiers.rs`), тут тонка napi-делегація
9
+ * (`internal/native.mjs`), жодного власного каскаду. Інші утиліти модуля
10
+ * (парсинг spec, класифікація local/cloud, thinkingLevel) не мають
11
+ * Rust-відповідника — лишаються pure JS, substrate-free.
9
12
  *
10
13
  * Формат значень env — `"provider/model-id"` (pi-формат), напр.:
11
14
  * N_LOCAL_MIN_MODEL=omlx/gemma-4-e4b-it-OptiQ-4bit
@@ -14,6 +17,7 @@
14
17
  */
15
18
 
16
19
  import { env } from 'node:process'
20
+ import { loadNative } from './internal/native.mjs'
17
21
 
18
22
  // ── Тири (env-політика) ──────────────────────────────────────────────────────
19
23
 
@@ -30,20 +34,29 @@ export const CLOUD_AVG = env.N_CLOUD_AVG_MODEL ?? ''
30
34
  /** Максимальний хмарний. Напр.: openai/gpt-5.5 */
31
35
  export const CLOUD_MAX = env.N_CLOUD_MAX_MODEL ?? ''
32
36
 
37
+ /** Валідні тири — та сама множина, що й `parse_tier` у napi-крейті. */
38
+ const KNOWN_TIERS = new Set(['min', 'avg', 'max'])
39
+
33
40
  /**
34
- * Каскадне розв'язання абстрактного тиру в `"provider/model-id"`:
41
+ * Каскадне розв'язання абстрактного тиру в `"provider/model-id"` —
42
+ * napi-делегація в `llm_lib::resolve_model` (задача T5, рішення Е): та сама
43
+ * логіка, що й Rust-каскад у `tiers.rs`:
35
44
  * 'min' → LOCAL_MIN → LOCAL_AVG → LOCAL_MAX → CLOUD_MIN
36
45
  * 'avg' → LOCAL_AVG → LOCAL_MAX → CLOUD_AVG
37
46
  * 'max' → LOCAL_MAX → CLOUD_MAX
47
+ * Тир валідується тут (не в Rust) — щоб зберегти контракт `TypeError` для
48
+ * невідомого тиру без потреби мапити помилку з napi-боку.
38
49
  * @param {'min'|'avg'|'max'} tier тир
50
+ * @param {{ native?: { resolveModel: (tier: string) => string | null } }} [deps] інжект `native` для тестів
39
51
  * @returns {string} `"provider/model-id"` або `''` (дефолт провайдера substrate)
40
52
  * @throws {TypeError} якщо tier невідомий
41
53
  */
42
- export function resolveModel(tier) {
43
- if (tier === 'min') return LOCAL_MIN || LOCAL_AVG || LOCAL_MAX || CLOUD_MIN
44
- if (tier === 'avg') return LOCAL_AVG || LOCAL_MAX || CLOUD_AVG
45
- if (tier === 'max') return LOCAL_MAX || CLOUD_MAX
46
- throw new TypeError(`resolveModel: unknown tier "${tier}". Use 'min', 'avg', or 'max'.`)
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'.`)
57
+ }
58
+ const native = deps.native ?? loadNative()
59
+ return native.resolveModel(tier) ?? ''
47
60
  }
48
61
 
49
62
  // ── Escalation-rung → thinkingLevel ──────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/llm-lib",
3
- "version": "2.8.8",
3
+ "version": "2.9.0",
4
4
  "description": "Тонкий шар роботи з LLM (локальні omlx + хмарні провайдери) поверх pi: model tiers, one-shot, agentic-раннери, write-guard, trace, telemetry, prompt-budget",
5
5
  "keywords": [
6
6
  "nitra",
@@ -37,6 +37,8 @@
37
37
  "./web-tools": "./lib/web-tools.mjs",
38
38
  "./model-tiers": "./lib/model-tiers.mjs",
39
39
  "./acp": "./lib/acp.mjs",
40
+ "./local-cloud": "./lib/local-cloud.mjs",
41
+ "./batch": "./lib/batch.mjs",
40
42
  "./chain": "./lib/chain.mjs",
41
43
  "./chains-report": "./lib/chains-report.mjs",
42
44
  "./one-shot": "./lib/one-shot.mjs",
@@ -53,8 +55,8 @@
53
55
  "access": "public"
54
56
  },
55
57
  "optionalDependencies": {
56
- "@7n/llm-lib-darwin-arm64": "2.8.8",
57
- "@7n/llm-lib-linux-x64": "2.8.8"
58
+ "@7n/llm-lib-darwin-arm64": "2.9.0",
59
+ "@7n/llm-lib-linux-x64": "2.9.0"
58
60
  },
59
61
  "peerDependencies": {
60
62
  "@earendil-works/pi-ai": "~0.80.10",