@7n/llm-lib 2.8.7 → 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 +24 -0
- package/lib/acp.mjs +20 -12
- package/lib/agent-fix.mjs +64 -19
- package/lib/batch.mjs +72 -0
- package/lib/docs/acp.md +14 -10
- package/lib/docs/agent-fix.md +16 -22
- package/lib/docs/batch.md +34 -0
- package/lib/docs/index.md +2 -0
- package/lib/docs/local-cloud.md +34 -0
- package/lib/docs/model-tiers.md +45 -23
- package/lib/internal/docs/native.md +11 -8
- package/lib/internal/native.mjs +12 -12
- package/lib/local-cloud.mjs +33 -0
- package/lib/model-tiers.mjs +22 -9
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
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
|
+
|
|
21
|
+
## [2.8.8] - 2026-07-24
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- doc_comments rollout: header-JSDoc у vitest.config
|
|
26
|
+
|
|
3
27
|
## [2.8.7] - 2026-07-23
|
|
4
28
|
|
|
5
29
|
### 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-крейта `
|
|
6
|
-
* in-process (`llm-lib/crates/llm-
|
|
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, разом з
|
|
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
|
-
*
|
|
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 {{
|
|
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,
|
|
26
|
-
const
|
|
27
|
-
return
|
|
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 параметри
|
|
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({
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
'
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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({
|
|
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:
|
|
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
|
-
|
|
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
|
|
20
|
-
2.
|
|
21
|
-
3.
|
|
22
|
-
4.
|
|
23
|
-
5.
|
|
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 —
|
|
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
|
-
-
|
|
35
|
+
- Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
|
package/lib/docs/agent-fix.md
CHANGED
|
@@ -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:
|
|
7
|
-
model:
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
+
- Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
|
package/lib/docs/model-tiers.md
CHANGED
|
@@ -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:
|
|
7
|
-
model:
|
|
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
|
-
|
|
16
|
+
Модуль централізує вибір і нормалізацію LLM-моделей для local та cloud tier значень. Він дає спільну точку для розбору і форматування model spec, визначення локальності моделі та зіставлення tier із рівнем thinking.
|
|
13
17
|
|
|
14
18
|
## Поведінка
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
thinkingLevelForTier
|
|
25
|
-
|
|
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 —
|
|
33
|
-
CLOUD_AVG —
|
|
34
|
-
CLOUD_MAX —
|
|
35
|
-
resolveModel —
|
|
36
|
-
|
|
37
|
-
|
|
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:
|
|
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
|
-
Файл
|
|
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
|
-
|
|
20
|
-
|
|
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 —
|
|
25
|
-
- loadNative —
|
|
27
|
+
- resolveNativeAddon — Резолвить шлях до napi-аддона `llm-lib`.
|
|
28
|
+
- loadNative — Кешований доступ до аддона (одне завантаження на процес).
|
|
26
29
|
|
|
27
30
|
## Гарантії поведінки
|
|
28
31
|
|
|
29
|
-
-
|
|
32
|
+
- Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
|
|
30
33
|
- Кешує результати в межах одного прогону.
|
package/lib/internal/native.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Loader napi-аддона `llm-
|
|
3
|
-
* → `llm-
|
|
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-
|
|
8
|
+
* `llm-lib-napi.<triple>.node`).
|
|
9
9
|
* 3. Dev-fallback: `<repoRoot>/target/release|debug/` (сирий cdylib з
|
|
10
|
-
* `cargo build -p llm-
|
|
11
|
-
* `llm-lib/crates/llm-
|
|
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-
|
|
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' ? '
|
|
56
|
+
return platform === 'darwin' ? 'libllm_lib_napi.dylib' : 'libllm_lib_napi.so'
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* Резолвить шлях до napi-аддона `llm-
|
|
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-
|
|
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-
|
|
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-
|
|
108
|
+
`llm-lib native addon: немає збірки для "${key}". ` +
|
|
109
109
|
`Постав N_LLM_LIB_NATIVE_ADDON=/шлях/до/аддона, додай підпакет @7n/llm-lib-${key}, ` +
|
|
110
|
-
`або збери локально: cargo build --release -p llm-
|
|
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
|
+
}
|
package/lib/model-tiers.mjs
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Тир-конфіг моделей для LLM-шару (@7n/llm-lib) і його consumers.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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.
|
|
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.
|
|
57
|
-
"@7n/llm-lib-linux-x64": "2.
|
|
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",
|