@graphorin/provider-llamacpp-node 0.6.1 → 0.7.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 +20 -0
- package/README.md +1 -1
- package/dist/adapter.d.ts +13 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +115 -12
- package/dist/adapter.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/dist/runtime.d.ts +27 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js.map +1 -1
- package/package.json +7 -5
- package/src/adapter.ts +543 -0
- package/src/counter.ts +79 -0
- package/src/index.ts +38 -0
- package/src/runtime.ts +202 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @graphorin/provider-llamacpp-node
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-072: every export map's `import` condition becomes `default`, and the Node floor rises to `>=22.12.0`.
|
|
8
|
+
|
|
9
|
+
CJS consumers previously hit a bewildering `ERR_PACKAGE_PATH_NOT_EXPORTED` instead of a clear ESM-only signal. With the `default` condition, plain `require('@graphorin/core')` works via Node's stable `require(esm)` - which shipped in 22.12, hence the engines bump across every workspace manifest (packages, examples, benchmarks, docs; enforced by the widened mvp-readiness sweep). No dual-instance hazard: there is no CJS build, `require()` returns the same ESM module instance. ESM consumers are unaffected (`default` serves both paths; `types` stays first). The pack gate now runs attw under the full `node16` profile (was `esm-only`) and adds a runtime `require(esm)` smoke against the packed tarballs. Installs on Node 22.0-22.11 with `engine-strict` will refuse - upgrade Node (see the migration guide).
|
|
10
|
+
|
|
11
|
+
- [#160](https://github.com/o-stepper/graphorin/pull/160) [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156) Thanks [@o-stepper](https://github.com/o-stepper)! - W-096: `llamaCppNodeAdapter` speaks real chat history. When the session exposes node-llama-cpp v3's `setChatHistory` (the default factory's sessions do), a multi-turn transcript is fed as structured history turns (system included - `setChatHistory` replaces the whole history, so relying on the constructor `systemPrompt` alone would drop it) and only the LAST user turn is prompted - the pre-fix pseudo-prompt serialised the entire conversation into one `[user]`-framed string, double-templating it and degrading chat-tuned GGUF models. Sessions without the setter (older fixtures, custom factories) keep the legacy renderPrompt path byte-identically. `completionTokens` now tokenize the ASSEMBLED response once instead of per streamed chunk (BPE merges span chunk seams, so per-chunk counting over-billed). New opt-in `persistentSession: true` caches one session (context + KV cache) per adapter instance, serialised by a promise mutex with per-request `setChatHistory` re-sync, so an agent loop stops re-prefilling the growing transcript every step; the default per-request create/dispose lifecycle is unchanged.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - Tarballs now ship `src/` so the published `dist/**/*.d.ts.map` files actually work (W-136): the maps referenced `../src/*.ts` that the `files` whitelist excluded, so go-to-definition fell back into `.d.ts` and the shipped maps were dead weight. The pack gate gains a `map-integrity` leg: every source referenced by a shipped map must resolve inside the tarball (or be embedded via `sourcesContent`), with an anti-vacuous guard - a package whose tsdown config emits declaration maps must contain a non-zero number of `.d.ts.map` files, so a cache-restored dist that silently dropped maps fails the gate instead of passing vacuously. `mvp-readiness` now requires `src` in every publishable `files` array.
|
|
16
|
+
|
|
17
|
+
- [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - Every published package now declares its tree-shaking contract via `sideEffects` (W-137): 18 packages audited to a pure module scope get `false`, the CLI declares its bin entry (`["./dist/bin/*"]`), and `@graphorin/security` gets an explicit `true` - its secrets subsystem registers built-in resolvers and the SecretValue caller-context provider at import time, so marking it pure would let bundlers drop those registrations. `mvp-readiness` now fails any publishable manifest without a declared `sideEffects`, closing the drift for future packages.
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [[`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04)]:
|
|
20
|
+
- @graphorin/core@0.7.0
|
|
21
|
+
- @graphorin/provider@0.7.0
|
|
22
|
+
|
|
3
23
|
## 0.6.1
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -78,5 +78,5 @@ discipline rather than enforcing it at runtime.
|
|
|
78
78
|
|
|
79
79
|
## Project metadata
|
|
80
80
|
|
|
81
|
-
- **Project Graphorin** · v0.
|
|
81
|
+
- **Project Graphorin** · v0.7.0 · MIT License · © 2026 Oleksiy Stepurenko
|
|
82
82
|
- Repository: <https://github.com/o-stepper/graphorin>
|
package/dist/adapter.d.ts
CHANGED
|
@@ -41,6 +41,19 @@ interface LlamaCppNodeAdapterOptions {
|
|
|
41
41
|
* seam.
|
|
42
42
|
*/
|
|
43
43
|
readonly sessionFactory?: (model: LlamaModelInstance, system?: string) => Promise<LlamaSessionInstance>;
|
|
44
|
+
/**
|
|
45
|
+
* W-096: reuse ONE session (context + KV cache) across requests
|
|
46
|
+
* instead of creating and disposing a fresh one per call - an agent
|
|
47
|
+
* loop then avoids re-prefilling the growing transcript on every
|
|
48
|
+
* step. Requests serialise through a promise mutex (a llama context
|
|
49
|
+
* sequence is single-threaded), and the chat history re-syncs via
|
|
50
|
+
* `setChatHistory` before each prompt. Strictly opt-in: the default
|
|
51
|
+
* per-request lifecycle stays memory-safe and concurrency-safe; the
|
|
52
|
+
* cached session also skips per-request disposal (it lives until the
|
|
53
|
+
* process / instance is released). Sessions WITHOUT `setChatHistory`
|
|
54
|
+
* cannot re-sync and silently degrade to per-request behaviour.
|
|
55
|
+
*/
|
|
56
|
+
readonly persistentSession?: boolean;
|
|
44
57
|
}
|
|
45
58
|
/**
|
|
46
59
|
* Build a Graphorin {@link Provider} backed by an in-process GGUF
|
package/dist/adapter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","names":[],"sources":["../src/adapter.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","names":[],"sources":["../src/adapter.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;UA+CiB,0BAAA;;;;;;;;;;0BAUS,QAAQ;;gCAEF,cAAc;;;;;8BAKhB;;;;;;2BAMH;;;;;;;;;oCAUhB,wCAEJ,QAAQ;;;;;;;;;;;;;;;;;;;;;;iBAmCC,mBAAA,UAA6B,6BAA6B"}
|
package/dist/adapter.js
CHANGED
|
@@ -51,17 +51,17 @@ function llamaCppNodeAdapter(options) {
|
|
|
51
51
|
})();
|
|
52
52
|
return resolving;
|
|
53
53
|
};
|
|
54
|
-
const
|
|
54
|
+
const acquireSession = buildSessionManager(options, options.sessionFactory ?? options.runtimeOverrides?.createSession ?? ((model, system) => defaultSessionFactory(model, system, options)));
|
|
55
55
|
return {
|
|
56
56
|
name: providerName,
|
|
57
57
|
modelId: options.modelPath,
|
|
58
58
|
capabilities,
|
|
59
59
|
acceptsSensitivity,
|
|
60
60
|
stream(req) {
|
|
61
|
-
return streamLlamaCppNode(ensureModel,
|
|
61
|
+
return streamLlamaCppNode(ensureModel, acquireSession, providerName, options, applyLlamaCppNodePreflight(req, capabilities));
|
|
62
62
|
},
|
|
63
63
|
async generate(req) {
|
|
64
|
-
const events = streamLlamaCppNode(ensureModel,
|
|
64
|
+
const events = streamLlamaCppNode(ensureModel, acquireSession, providerName, options, applyLlamaCppNodePreflight(req, capabilities));
|
|
65
65
|
const collected = [];
|
|
66
66
|
let usage = {
|
|
67
67
|
promptTokens: 0,
|
|
@@ -107,11 +107,18 @@ function applyLlamaCppNodePreflight(req, capabilities) {
|
|
|
107
107
|
messages: filtered
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
|
-
async function* streamLlamaCppNode(ensureModel,
|
|
110
|
+
async function* streamLlamaCppNode(ensureModel, acquireSession, providerName, options, req) {
|
|
111
111
|
const { model } = await ensureModel();
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
const
|
|
112
|
+
const lease = await acquireSession(model, typeof req.systemMessage === "string" ? req.systemMessage : void 0);
|
|
113
|
+
const session = lease.session;
|
|
114
|
+
const historic = typeof session.setChatHistory === "function";
|
|
115
|
+
let prompt;
|
|
116
|
+
if (historic) {
|
|
117
|
+
const { priorTurns, lastUserText } = buildChatHistory(req);
|
|
118
|
+
session.setChatHistory?.(priorTurns);
|
|
119
|
+
prompt = lastUserText;
|
|
120
|
+
} else prompt = renderPrompt(req);
|
|
121
|
+
const promptTokens = lengthOf(model.tokenize(renderPrompt(req)));
|
|
115
122
|
yield {
|
|
116
123
|
type: "stream-start",
|
|
117
124
|
metadata: {
|
|
@@ -120,7 +127,7 @@ async function* streamLlamaCppNode(ensureModel, sessionFactory, providerName, op
|
|
|
120
127
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
121
128
|
}
|
|
122
129
|
};
|
|
123
|
-
|
|
130
|
+
const pieces = [];
|
|
124
131
|
let errored = false;
|
|
125
132
|
let aborted = false;
|
|
126
133
|
try {
|
|
@@ -134,7 +141,7 @@ async function* streamLlamaCppNode(ensureModel, sessionFactory, providerName, op
|
|
|
134
141
|
break;
|
|
135
142
|
}
|
|
136
143
|
if (typeof piece !== "string" || piece.length === 0) continue;
|
|
137
|
-
|
|
144
|
+
pieces.push(piece);
|
|
138
145
|
yield {
|
|
139
146
|
type: "text-delta",
|
|
140
147
|
delta: piece
|
|
@@ -154,10 +161,9 @@ async function* streamLlamaCppNode(ensureModel, sessionFactory, providerName, op
|
|
|
154
161
|
}
|
|
155
162
|
};
|
|
156
163
|
} finally {
|
|
157
|
-
|
|
158
|
-
session.dispose?.();
|
|
159
|
-
} catch {}
|
|
164
|
+
lease.release();
|
|
160
165
|
}
|
|
166
|
+
const completionTokens = lengthOf(model.tokenize(pieces.join("")));
|
|
161
167
|
yield {
|
|
162
168
|
type: "finish",
|
|
163
169
|
finishReason: errored ? "error" : aborted ? "aborted" : "stop",
|
|
@@ -168,6 +174,103 @@ async function* streamLlamaCppNode(ensureModel, sessionFactory, providerName, op
|
|
|
168
174
|
}
|
|
169
175
|
};
|
|
170
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* W-096: per-request sessions by default (create -> use -> dispose, the
|
|
179
|
+
* memory-safe core-provider-08 lifecycle); with `persistentSession:
|
|
180
|
+
* true` ONE cached session serialised by a promise mutex (a llama
|
|
181
|
+
* context sequence is single-threaded) whose history re-syncs via
|
|
182
|
+
* `setChatHistory` on every request. A cached session that turns out
|
|
183
|
+
* not to support `setChatHistory` degrades to per-request behaviour -
|
|
184
|
+
* without the setter its history cannot be made consistent.
|
|
185
|
+
*/
|
|
186
|
+
function buildSessionManager(options, sessionFactory) {
|
|
187
|
+
if (options.persistentSession !== true) return async (model, system) => {
|
|
188
|
+
const session = await sessionFactory(model, system);
|
|
189
|
+
return {
|
|
190
|
+
session,
|
|
191
|
+
release: () => {
|
|
192
|
+
try {
|
|
193
|
+
session.dispose?.();
|
|
194
|
+
} catch {}
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
let cached = null;
|
|
199
|
+
let queueTail = Promise.resolve();
|
|
200
|
+
return async (model, system) => {
|
|
201
|
+
let unlock;
|
|
202
|
+
const gate = new Promise((resolve) => {
|
|
203
|
+
unlock = resolve;
|
|
204
|
+
});
|
|
205
|
+
const priorTail = queueTail;
|
|
206
|
+
queueTail = queueTail.then(() => gate);
|
|
207
|
+
await priorTail;
|
|
208
|
+
if (cached === null) cached = await sessionFactory(model, system);
|
|
209
|
+
const session = cached;
|
|
210
|
+
if (typeof session.setChatHistory !== "function") {
|
|
211
|
+
cached = null;
|
|
212
|
+
return {
|
|
213
|
+
session,
|
|
214
|
+
release: () => {
|
|
215
|
+
try {
|
|
216
|
+
session.dispose?.();
|
|
217
|
+
} catch {}
|
|
218
|
+
unlock();
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
session,
|
|
224
|
+
release: () => unlock()
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* W-096: split the request into node-llama-cpp chat-history turns plus
|
|
230
|
+
* the trailing user text (the prompt). The system message is included
|
|
231
|
+
* as a history item because `setChatHistory` REPLACES the session's
|
|
232
|
+
* whole history - including the slot the constructor `systemPrompt`
|
|
233
|
+
* seeded - so relying on the constructor alone would drop it. Tool
|
|
234
|
+
* roles cannot occur (`toolCalling: false`); a defensive skip keeps a
|
|
235
|
+
* stray one from corrupting the template.
|
|
236
|
+
*/
|
|
237
|
+
function buildChatHistory(req) {
|
|
238
|
+
const turns = [];
|
|
239
|
+
if (typeof req.systemMessage === "string" && req.systemMessage.length > 0) turns.push({
|
|
240
|
+
type: "system",
|
|
241
|
+
text: req.systemMessage
|
|
242
|
+
});
|
|
243
|
+
for (const msg of req.messages) {
|
|
244
|
+
const text = textOf(msg);
|
|
245
|
+
if (msg.role === "user") turns.push({
|
|
246
|
+
type: "user",
|
|
247
|
+
text
|
|
248
|
+
});
|
|
249
|
+
else if (msg.role === "assistant") turns.push({
|
|
250
|
+
type: "model",
|
|
251
|
+
response: [text]
|
|
252
|
+
});
|
|
253
|
+
else if (msg.role === "system") turns.push({
|
|
254
|
+
type: "system",
|
|
255
|
+
text
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
const last = turns[turns.length - 1];
|
|
259
|
+
if (last !== void 0 && last.type === "user") {
|
|
260
|
+
turns.pop();
|
|
261
|
+
return {
|
|
262
|
+
priorTurns: turns,
|
|
263
|
+
lastUserText: last.text
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
priorTurns: turns,
|
|
268
|
+
lastUserText: ""
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function textOf(msg) {
|
|
272
|
+
return typeof msg.content === "string" ? msg.content : msg.content.map((p) => p.type === "text" ? p.text : p.type === "reasoning" ? p.text : "").join("");
|
|
273
|
+
}
|
|
171
274
|
async function resolveModel(options) {
|
|
172
275
|
return (await loadLlamaModule(options.runtimeOverrides)).loadModel({
|
|
173
276
|
modelPath: options.modelPath,
|
package/dist/adapter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","names":["LLAMA_CPP_NODE_ACCEPTS_SENSITIVITY: ReadonlyArray<Sensitivity>","DEFAULT_CAPABILITIES: ProviderCapabilities","capabilities: ProviderCapabilities","resolved: { model: LlamaModelInstance } | null","resolving: Promise<{ model: LlamaModelInstance }> | null","collected: string[]","usage: Usage","finishReason: ProviderResponse['finishReason']","streamError: { message: string } | undefined","streamOptions: { signal?: AbortSignal; maxTokens?: number; temperature?: number }","queue: string[]","failure: unknown","wake: (() => void) | null","parts: string[]"],"sources":["../src/adapter.ts"],"sourcesContent":["/**\n * `llamaCppNodeAdapter` - in-process GGUF adapter built on\n * `node-llama-cpp@^3.5`. The adapter declares `trust: 'loopback'`\n * permanently because the model lives in the same trust boundary as\n * the host process.\n *\n * @packageDocumentation\n */\n\nimport type {\n Provider,\n ProviderCapabilities,\n ProviderEvent,\n ProviderRequest,\n ProviderResponse,\n Sensitivity,\n Usage,\n} from '@graphorin/core';\nimport { ProviderHttpError } from '@graphorin/provider/errors';\nimport { applyReasoningPolicy, resolveReasoningRetention } from '@graphorin/provider/reasoning';\nimport {\n type LlamaChatSessionPeer,\n type LlamaCppNodeRuntimeOverrides,\n type LlamaModelInstance,\n type LlamaSessionInstance,\n loadLlamaChatSessionCtor,\n loadLlamaModule,\n} from './runtime.js';\n\n/**\n * Default sensitivity envelope for the in-process adapter - same as\n * the loopback HTTP path.\n *\n * @stable\n */\nexport const LLAMA_CPP_NODE_ACCEPTS_SENSITIVITY: ReadonlyArray<Sensitivity> = Object.freeze([\n 'public',\n 'internal',\n 'secret',\n]);\n\n/**\n * Options accepted by {@link llamaCppNodeAdapter}.\n *\n * @stable\n */\nexport interface LlamaCppNodeAdapterOptions {\n /** Filesystem path to the `.gguf` model file. */\n readonly modelPath: string;\n /** Number of layers to offload to the GPU. Default `'auto'`. */\n readonly gpuLayers?: number | 'auto';\n /** Optional context-window override. */\n readonly contextSize?: number;\n /** Provider name attached to spans / log lines. */\n readonly name?: string;\n /** Capability declaration. Merged on top of the defaults table. */\n readonly capabilities?: Partial<ProviderCapabilities>;\n /** Sensitivity override (defaults to the loopback envelope). */\n readonly acceptsSensitivity?: ReadonlyArray<Sensitivity>;\n /**\n * Test-only runtime override. When unset the adapter loads\n * `node-llama-cpp` lazily on first call.\n */\n readonly runtimeOverrides?: LlamaCppNodeRuntimeOverrides;\n /**\n * Optional `model` override that short-circuits the\n * `loadLlamaModule(...).loadModel(...)` flow. Tests pass a fixture\n * shaped instance.\n */\n readonly modelOverride?: LlamaModelInstance;\n /**\n * Optional session factory override. When unset, the adapter builds a\n * real session from the peer (PS-3): `model.createContext()` →\n * `new LlamaChatSession({ contextSequence })`, streaming through\n * `prompt(text, { onTextChunk })`. Overrides\n * (`runtimeOverrides.createSession` or this option) keep the test\n * seam.\n */\n readonly sessionFactory?: (\n model: LlamaModelInstance,\n system?: string,\n ) => Promise<LlamaSessionInstance>;\n}\n\nconst DEFAULT_CAPABILITIES: ProviderCapabilities = {\n streaming: true,\n toolCalling: false,\n parallelToolCalls: false,\n multimodal: false,\n structuredOutput: false,\n reasoning: false,\n contextWindow: 8_192,\n maxOutput: 4_096,\n reasoningContract: 'optional',\n};\n\n/**\n * Build a Graphorin {@link Provider} backed by an in-process GGUF\n * model. The first call lazily loads the `node-llama-cpp` peer + the\n * model file; subsequent calls reuse the cached instances.\n *\n * @stable\n */\nexport function llamaCppNodeAdapter(options: LlamaCppNodeAdapterOptions): Provider {\n const providerName = options.name ?? `llama-cpp-node-${basename(options.modelPath)}`;\n const capabilities: ProviderCapabilities = {\n ...DEFAULT_CAPABILITIES,\n ...options.capabilities,\n };\n const acceptsSensitivity = options.acceptsSensitivity ?? LLAMA_CPP_NODE_ACCEPTS_SENSITIVITY;\n let resolved: { model: LlamaModelInstance } | null = null;\n let resolving: Promise<{ model: LlamaModelInstance }> | null = null;\n const ensureModel = async (): Promise<{ model: LlamaModelInstance }> => {\n if (resolved !== null) return resolved;\n if (resolving !== null) return resolving;\n resolving = (async () => {\n const model = options.modelOverride ?? (await resolveModel(options));\n resolved = { model };\n resolving = null;\n return resolved;\n })();\n return resolving;\n };\n const sessionFactory =\n options.sessionFactory ??\n options.runtimeOverrides?.createSession ??\n ((model: LlamaModelInstance, system?: string) => defaultSessionFactory(model, system, options));\n return {\n name: providerName,\n modelId: options.modelPath,\n capabilities,\n acceptsSensitivity,\n stream(req) {\n return streamLlamaCppNode(\n ensureModel,\n sessionFactory,\n providerName,\n options,\n applyLlamaCppNodePreflight(req, capabilities),\n );\n },\n async generate(req) {\n const events = streamLlamaCppNode(\n ensureModel,\n sessionFactory,\n providerName,\n options,\n applyLlamaCppNodePreflight(req, capabilities),\n );\n const collected: string[] = [];\n let usage: Usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };\n let finishReason: ProviderResponse['finishReason'] = 'stop';\n let streamError: { message: string } | undefined;\n for await (const event of events) {\n if (event.type === 'text-delta') collected.push(event.delta);\n if (event.type === 'error') streamError = event.error;\n if (event.type === 'finish') {\n usage = event.usage;\n finishReason = event.finishReason;\n }\n }\n // PS-4: a swallowed mid-stream error returned truncated text\n // indistinguishable from success - and a never-throwing\n // generate() bypasses withRetry / withFallback entirely.\n if (streamError !== undefined) {\n throw new ProviderHttpError({\n providerName,\n status: 0,\n message: streamError.message,\n });\n }\n return {\n text: collected.join(''),\n usage,\n finishReason,\n };\n },\n };\n}\n\nfunction applyLlamaCppNodePreflight(\n req: ProviderRequest,\n capabilities: ProviderCapabilities,\n): ProviderRequest {\n const retention = resolveReasoningRetention({\n ...(req.reasoningRetention !== undefined ? { requested: req.reasoningRetention } : {}),\n ...(capabilities.reasoningContract !== undefined\n ? { contract: capabilities.reasoningContract }\n : {}),\n });\n if (retention === 'pass-through-all') return req;\n const filtered = applyReasoningPolicy({ messages: req.messages, retention });\n if (filtered === req.messages) return req;\n return { ...req, messages: filtered };\n}\n\nasync function* streamLlamaCppNode(\n ensureModel: () => Promise<{ model: LlamaModelInstance }>,\n sessionFactory: (model: LlamaModelInstance, system?: string) => Promise<LlamaSessionInstance>,\n providerName: string,\n options: LlamaCppNodeAdapterOptions,\n req: ProviderRequest,\n): AsyncIterable<ProviderEvent> {\n const { model } = await ensureModel();\n const session = await sessionFactory(\n model,\n typeof req.systemMessage === 'string' ? req.systemMessage : undefined,\n );\n const prompt = renderPrompt(req);\n const promptTokens = lengthOf(model.tokenize(prompt));\n yield {\n type: 'stream-start',\n metadata: {\n providerName,\n modelId: options.modelPath,\n createdAt: new Date().toISOString(),\n },\n };\n let completionTokens = 0;\n let errored = false;\n let aborted = false;\n try {\n const streamOptions: { signal?: AbortSignal; maxTokens?: number; temperature?: number } = {};\n if (req.signal !== undefined) streamOptions.signal = req.signal;\n if (req.maxTokens !== undefined) streamOptions.maxTokens = req.maxTokens;\n if (req.temperature !== undefined) streamOptions.temperature = req.temperature;\n for await (const piece of session.promptStreamingResponse(prompt, streamOptions)) {\n if (req.signal?.aborted) {\n aborted = true;\n break;\n }\n if (typeof piece !== 'string' || piece.length === 0) continue;\n completionTokens += lengthOf(model.tokenize(piece));\n yield { type: 'text-delta', delta: piece };\n if (req.signal?.aborted) {\n aborted = true;\n break;\n }\n }\n } catch (err) {\n errored = true;\n yield { type: 'error', error: { kind: 'unknown', message: (err as Error).message } };\n } finally {\n // Release the per-request context / KV cache (core-provider-08).\n try {\n session.dispose?.();\n } catch {\n // Disposal failures must never mask the stream outcome.\n }\n }\n yield {\n type: 'finish',\n // PS-4: a mid-stream failure must not masquerade as a clean stop -\n // partial text would be indistinguishable from success. PS-12: an\n // aborted stream reports 'aborted', mirroring the HTTP adapters.\n finishReason: errored ? 'error' : aborted ? 'aborted' : 'stop',\n usage: {\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n },\n };\n}\n\nasync function resolveModel(options: LlamaCppNodeAdapterOptions): Promise<LlamaModelInstance> {\n const llama = await loadLlamaModule(options.runtimeOverrides);\n return llama.loadModel({\n modelPath: options.modelPath,\n ...(options.gpuLayers !== undefined ? { gpuLayers: options.gpuLayers } : {}),\n });\n}\n\n/**\n * The REAL default session factory (PS-3): `model.createContext()` →\n * `context.getSequence()` → `new LlamaChatSession({ contextSequence })`\n * from the lazily-loaded peer, adapting its callback-streaming\n * `prompt(text, { onTextChunk })` to the adapter's\n * `promptStreamingResponse` AsyncIterable contract.\n */\nasync function defaultSessionFactory(\n model: LlamaModelInstance,\n system: string | undefined,\n options: LlamaCppNodeAdapterOptions,\n): Promise<LlamaSessionInstance> {\n const ChatSession = await loadLlamaChatSessionCtor(options.runtimeOverrides);\n const context = await model.createContext(\n options.contextSize !== undefined ? { contextSize: options.contextSize } : undefined,\n );\n const sequence = context.getSequence();\n const session = new ChatSession({\n contextSequence: sequence,\n ...(system !== undefined ? { systemPrompt: system } : {}),\n });\n return {\n promptStreamingResponse(prompt, streamOptions) {\n return promptToIterable(session, prompt, streamOptions);\n },\n dispose() {\n // Sequence first, then its owning context (core-provider-08).\n try {\n sequence.dispose?.();\n } catch {\n // fall through to the context\n }\n context.dispose?.();\n },\n };\n}\n\n/**\n * Bridge the peer's callback-streaming `prompt(...)` into the\n * AsyncIterable the adapter consumes. A rejection from `prompt`\n * rejects the pending/next iteration so the stream's error path\n * (PS-4) observes it.\n */\nfunction promptToIterable(\n session: LlamaChatSessionPeer,\n prompt: string,\n streamOptions?: {\n readonly signal?: AbortSignal;\n readonly maxTokens?: number;\n readonly temperature?: number;\n },\n): AsyncIterable<string> {\n const queue: string[] = [];\n let done = false;\n let failure: unknown;\n let wake: (() => void) | null = null;\n const notify = (): void => {\n if (wake !== null) {\n const w = wake;\n wake = null;\n w();\n }\n };\n void session\n .prompt(prompt, {\n ...(streamOptions?.signal !== undefined ? { signal: streamOptions.signal } : {}),\n ...(streamOptions?.maxTokens !== undefined ? { maxTokens: streamOptions.maxTokens } : {}),\n ...(streamOptions?.temperature !== undefined\n ? { temperature: streamOptions.temperature }\n : {}),\n onTextChunk: (chunk: string) => {\n if (typeof chunk === 'string' && chunk.length > 0) queue.push(chunk);\n notify();\n },\n })\n .then(\n () => {\n done = true;\n notify();\n },\n (err: unknown) => {\n failure = err instanceof Error ? err : new Error(String(err));\n done = true;\n notify();\n },\n );\n return {\n [Symbol.asyncIterator](): AsyncIterator<string> {\n return {\n async next(): Promise<IteratorResult<string>> {\n for (;;) {\n const head = queue.shift();\n if (head !== undefined) return { done: false, value: head };\n if (failure !== undefined) throw failure;\n if (done) return { done: true, value: undefined };\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n }\n },\n };\n },\n };\n}\n\nfunction renderPrompt(req: ProviderRequest): string {\n const parts: string[] = [];\n // `req.systemMessage` is deliberately NOT rendered here: the session\n // factory already sets it as the chat-template `systemPrompt`, and\n // rendering it again would show the model the system prompt twice\n // (core-provider-08).\n for (const msg of req.messages) {\n const text =\n typeof msg.content === 'string'\n ? msg.content\n : msg.content\n .map((p) => (p.type === 'text' ? p.text : p.type === 'reasoning' ? p.text : ''))\n .join('');\n parts.push(`[${msg.role}] ${text}`);\n }\n return parts.join('\\n');\n}\n\nfunction basename(path: string): string {\n const idx = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\\\'));\n return idx === -1 ? path : path.slice(idx + 1);\n}\n\nfunction lengthOf(tokens: readonly number[] | Uint32Array | Uint8Array | null | undefined): number {\n if (tokens === null || tokens === undefined) return 0;\n return (tokens as { length?: number }).length ?? 0;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"adapter.js","names":["LLAMA_CPP_NODE_ACCEPTS_SENSITIVITY: ReadonlyArray<Sensitivity>","DEFAULT_CAPABILITIES: ProviderCapabilities","capabilities: ProviderCapabilities","resolved: { model: LlamaModelInstance } | null","resolving: Promise<{ model: LlamaModelInstance }> | null","collected: string[]","usage: Usage","finishReason: ProviderResponse['finishReason']","streamError: { message: string } | undefined","prompt: string","pieces: string[]","streamOptions: { signal?: AbortSignal; maxTokens?: number; temperature?: number }","cached: LlamaSessionInstance | null","queueTail: Promise<void>","unlock!: () => void","turns: LlamaChatHistoryItem[]","queue: string[]","failure: unknown","wake: (() => void) | null","parts: string[]"],"sources":["../src/adapter.ts"],"sourcesContent":["/**\n * `llamaCppNodeAdapter` - in-process GGUF adapter built on\n * `node-llama-cpp@^3.5`. The adapter declares `trust: 'loopback'`\n * permanently because the model lives in the same trust boundary as\n * the host process.\n *\n * @packageDocumentation\n */\n\nimport type {\n Provider,\n ProviderCapabilities,\n ProviderEvent,\n ProviderRequest,\n ProviderResponse,\n Sensitivity,\n Usage,\n} from '@graphorin/core';\nimport { ProviderHttpError } from '@graphorin/provider/errors';\nimport { applyReasoningPolicy, resolveReasoningRetention } from '@graphorin/provider/reasoning';\nimport {\n type LlamaChatHistoryItem,\n type LlamaChatSessionPeer,\n type LlamaCppNodeRuntimeOverrides,\n type LlamaModelInstance,\n type LlamaSessionInstance,\n loadLlamaChatSessionCtor,\n loadLlamaModule,\n} from './runtime.js';\n\n/**\n * Default sensitivity envelope for the in-process adapter - same as\n * the loopback HTTP path.\n *\n * @stable\n */\nexport const LLAMA_CPP_NODE_ACCEPTS_SENSITIVITY: ReadonlyArray<Sensitivity> = Object.freeze([\n 'public',\n 'internal',\n 'secret',\n]);\n\n/**\n * Options accepted by {@link llamaCppNodeAdapter}.\n *\n * @stable\n */\nexport interface LlamaCppNodeAdapterOptions {\n /** Filesystem path to the `.gguf` model file. */\n readonly modelPath: string;\n /** Number of layers to offload to the GPU. Default `'auto'`. */\n readonly gpuLayers?: number | 'auto';\n /** Optional context-window override. */\n readonly contextSize?: number;\n /** Provider name attached to spans / log lines. */\n readonly name?: string;\n /** Capability declaration. Merged on top of the defaults table. */\n readonly capabilities?: Partial<ProviderCapabilities>;\n /** Sensitivity override (defaults to the loopback envelope). */\n readonly acceptsSensitivity?: ReadonlyArray<Sensitivity>;\n /**\n * Test-only runtime override. When unset the adapter loads\n * `node-llama-cpp` lazily on first call.\n */\n readonly runtimeOverrides?: LlamaCppNodeRuntimeOverrides;\n /**\n * Optional `model` override that short-circuits the\n * `loadLlamaModule(...).loadModel(...)` flow. Tests pass a fixture\n * shaped instance.\n */\n readonly modelOverride?: LlamaModelInstance;\n /**\n * Optional session factory override. When unset, the adapter builds a\n * real session from the peer (PS-3): `model.createContext()` →\n * `new LlamaChatSession({ contextSequence })`, streaming through\n * `prompt(text, { onTextChunk })`. Overrides\n * (`runtimeOverrides.createSession` or this option) keep the test\n * seam.\n */\n readonly sessionFactory?: (\n model: LlamaModelInstance,\n system?: string,\n ) => Promise<LlamaSessionInstance>;\n /**\n * W-096: reuse ONE session (context + KV cache) across requests\n * instead of creating and disposing a fresh one per call - an agent\n * loop then avoids re-prefilling the growing transcript on every\n * step. Requests serialise through a promise mutex (a llama context\n * sequence is single-threaded), and the chat history re-syncs via\n * `setChatHistory` before each prompt. Strictly opt-in: the default\n * per-request lifecycle stays memory-safe and concurrency-safe; the\n * cached session also skips per-request disposal (it lives until the\n * process / instance is released). Sessions WITHOUT `setChatHistory`\n * cannot re-sync and silently degrade to per-request behaviour.\n */\n readonly persistentSession?: boolean;\n}\n\nconst DEFAULT_CAPABILITIES: ProviderCapabilities = {\n streaming: true,\n toolCalling: false,\n parallelToolCalls: false,\n multimodal: false,\n structuredOutput: false,\n reasoning: false,\n contextWindow: 8_192,\n maxOutput: 4_096,\n reasoningContract: 'optional',\n};\n\n/**\n * Build a Graphorin {@link Provider} backed by an in-process GGUF\n * model. The first call lazily loads the `node-llama-cpp` peer + the\n * model file; subsequent calls reuse the cached instances.\n *\n * @stable\n */\nexport function llamaCppNodeAdapter(options: LlamaCppNodeAdapterOptions): Provider {\n const providerName = options.name ?? `llama-cpp-node-${basename(options.modelPath)}`;\n const capabilities: ProviderCapabilities = {\n ...DEFAULT_CAPABILITIES,\n ...options.capabilities,\n };\n const acceptsSensitivity = options.acceptsSensitivity ?? LLAMA_CPP_NODE_ACCEPTS_SENSITIVITY;\n let resolved: { model: LlamaModelInstance } | null = null;\n let resolving: Promise<{ model: LlamaModelInstance }> | null = null;\n const ensureModel = async (): Promise<{ model: LlamaModelInstance }> => {\n if (resolved !== null) return resolved;\n if (resolving !== null) return resolving;\n resolving = (async () => {\n const model = options.modelOverride ?? (await resolveModel(options));\n resolved = { model };\n resolving = null;\n return resolved;\n })();\n return resolving;\n };\n const sessionFactory =\n options.sessionFactory ??\n options.runtimeOverrides?.createSession ??\n ((model: LlamaModelInstance, system?: string) => defaultSessionFactory(model, system, options));\n const acquireSession = buildSessionManager(options, sessionFactory);\n return {\n name: providerName,\n modelId: options.modelPath,\n capabilities,\n acceptsSensitivity,\n stream(req) {\n return streamLlamaCppNode(\n ensureModel,\n acquireSession,\n providerName,\n options,\n applyLlamaCppNodePreflight(req, capabilities),\n );\n },\n async generate(req) {\n const events = streamLlamaCppNode(\n ensureModel,\n acquireSession,\n providerName,\n options,\n applyLlamaCppNodePreflight(req, capabilities),\n );\n const collected: string[] = [];\n let usage: Usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };\n let finishReason: ProviderResponse['finishReason'] = 'stop';\n let streamError: { message: string } | undefined;\n for await (const event of events) {\n if (event.type === 'text-delta') collected.push(event.delta);\n if (event.type === 'error') streamError = event.error;\n if (event.type === 'finish') {\n usage = event.usage;\n finishReason = event.finishReason;\n }\n }\n // PS-4: a swallowed mid-stream error returned truncated text\n // indistinguishable from success - and a never-throwing\n // generate() bypasses withRetry / withFallback entirely.\n if (streamError !== undefined) {\n throw new ProviderHttpError({\n providerName,\n status: 0,\n message: streamError.message,\n });\n }\n return {\n text: collected.join(''),\n usage,\n finishReason,\n };\n },\n };\n}\n\nfunction applyLlamaCppNodePreflight(\n req: ProviderRequest,\n capabilities: ProviderCapabilities,\n): ProviderRequest {\n const retention = resolveReasoningRetention({\n ...(req.reasoningRetention !== undefined ? { requested: req.reasoningRetention } : {}),\n ...(capabilities.reasoningContract !== undefined\n ? { contract: capabilities.reasoningContract }\n : {}),\n });\n if (retention === 'pass-through-all') return req;\n const filtered = applyReasoningPolicy({ messages: req.messages, retention });\n if (filtered === req.messages) return req;\n return { ...req, messages: filtered };\n}\n\nasync function* streamLlamaCppNode(\n ensureModel: () => Promise<{ model: LlamaModelInstance }>,\n acquireSession: SessionManager,\n providerName: string,\n options: LlamaCppNodeAdapterOptions,\n req: ProviderRequest,\n): AsyncIterable<ProviderEvent> {\n const { model } = await ensureModel();\n const lease = await acquireSession(\n model,\n typeof req.systemMessage === 'string' ? req.systemMessage : undefined,\n );\n const session = lease.session;\n // W-096: feed the transcript as REAL chat history when the session\n // supports it (node-llama-cpp v3 `setChatHistory`) and prompt ONLY\n // the last user turn - the pre-fix pseudo-prompt double-templated the\n // whole conversation inside one user turn, degrading chat-tuned GGUF\n // models. Sessions without the setter (older fixtures, custom\n // factories) keep the legacy renderPrompt path unchanged.\n const historic = typeof session.setChatHistory === 'function';\n let prompt: string;\n if (historic) {\n const { priorTurns, lastUserText } = buildChatHistory(req);\n session.setChatHistory?.(priorTurns);\n prompt = lastUserText;\n } else {\n prompt = renderPrompt(req);\n }\n // Prompt-token accounting stays the full-transcript proxy in both\n // modes so the figure is comparable regardless of the wire path.\n const promptTokens = lengthOf(model.tokenize(renderPrompt(req)));\n yield {\n type: 'stream-start',\n metadata: {\n providerName,\n modelId: options.modelPath,\n createdAt: new Date().toISOString(),\n },\n };\n const pieces: string[] = [];\n let errored = false;\n let aborted = false;\n try {\n const streamOptions: { signal?: AbortSignal; maxTokens?: number; temperature?: number } = {};\n if (req.signal !== undefined) streamOptions.signal = req.signal;\n if (req.maxTokens !== undefined) streamOptions.maxTokens = req.maxTokens;\n if (req.temperature !== undefined) streamOptions.temperature = req.temperature;\n for await (const piece of session.promptStreamingResponse(prompt, streamOptions)) {\n if (req.signal?.aborted) {\n aborted = true;\n break;\n }\n if (typeof piece !== 'string' || piece.length === 0) continue;\n pieces.push(piece);\n yield { type: 'text-delta', delta: piece };\n if (req.signal?.aborted) {\n aborted = true;\n break;\n }\n }\n } catch (err) {\n errored = true;\n yield { type: 'error', error: { kind: 'unknown', message: (err as Error).message } };\n } finally {\n lease.release();\n }\n // W-096: tokenize the ASSEMBLED response once - per-chunk tokenization\n // over-counted at every chunk boundary (BPE merges span chunk seams).\n const completionTokens = lengthOf(model.tokenize(pieces.join('')));\n yield {\n type: 'finish',\n // PS-4: a mid-stream failure must not masquerade as a clean stop -\n // partial text would be indistinguishable from success. PS-12: an\n // aborted stream reports 'aborted', mirroring the HTTP adapters.\n finishReason: errored ? 'error' : aborted ? 'aborted' : 'stop',\n usage: {\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n },\n };\n}\n\n/** Lease over a session: per-request leases dispose on release. */\ninterface SessionLease {\n readonly session: LlamaSessionInstance;\n release(): void;\n}\n\ntype SessionManager = (model: LlamaModelInstance, system?: string) => Promise<SessionLease>;\n\n/**\n * W-096: per-request sessions by default (create -> use -> dispose, the\n * memory-safe core-provider-08 lifecycle); with `persistentSession:\n * true` ONE cached session serialised by a promise mutex (a llama\n * context sequence is single-threaded) whose history re-syncs via\n * `setChatHistory` on every request. A cached session that turns out\n * not to support `setChatHistory` degrades to per-request behaviour -\n * without the setter its history cannot be made consistent.\n */\nfunction buildSessionManager(\n options: LlamaCppNodeAdapterOptions,\n sessionFactory: (model: LlamaModelInstance, system?: string) => Promise<LlamaSessionInstance>,\n): SessionManager {\n if (options.persistentSession !== true) {\n return async (model, system) => {\n const session = await sessionFactory(model, system);\n return {\n session,\n release: () => {\n // Release the per-request context / KV cache (core-provider-08).\n try {\n session.dispose?.();\n } catch {\n // Disposal failures must never mask the stream outcome.\n }\n },\n };\n };\n }\n let cached: LlamaSessionInstance | null = null;\n let queueTail: Promise<void> = Promise.resolve();\n return async (model, system) => {\n let unlock!: () => void;\n const gate = new Promise<void>((resolve) => {\n unlock = resolve;\n });\n const priorTail = queueTail;\n queueTail = queueTail.then(() => gate);\n await priorTail;\n if (cached === null) cached = await sessionFactory(model, system);\n const session = cached;\n if (typeof session.setChatHistory !== 'function') {\n // Cannot re-sync history across requests - fall back to the\n // per-request lifecycle for THIS session and try again next call.\n cached = null;\n return {\n session,\n release: () => {\n try {\n session.dispose?.();\n } catch {\n // best-effort\n }\n unlock();\n },\n };\n }\n return { session, release: () => unlock() };\n };\n}\n\n/**\n * W-096: split the request into node-llama-cpp chat-history turns plus\n * the trailing user text (the prompt). The system message is included\n * as a history item because `setChatHistory` REPLACES the session's\n * whole history - including the slot the constructor `systemPrompt`\n * seeded - so relying on the constructor alone would drop it. Tool\n * roles cannot occur (`toolCalling: false`); a defensive skip keeps a\n * stray one from corrupting the template.\n */\nfunction buildChatHistory(req: ProviderRequest): {\n readonly priorTurns: ReadonlyArray<LlamaChatHistoryItem>;\n readonly lastUserText: string;\n} {\n const turns: LlamaChatHistoryItem[] = [];\n if (typeof req.systemMessage === 'string' && req.systemMessage.length > 0) {\n turns.push({ type: 'system', text: req.systemMessage });\n }\n for (const msg of req.messages) {\n const text = textOf(msg);\n if (msg.role === 'user') turns.push({ type: 'user', text });\n else if (msg.role === 'assistant') turns.push({ type: 'model', response: [text] });\n else if (msg.role === 'system') turns.push({ type: 'system', text });\n }\n const last = turns[turns.length - 1];\n if (last !== undefined && last.type === 'user') {\n turns.pop();\n return { priorTurns: turns, lastUserText: last.text };\n }\n // No trailing user turn (continuation) - full history, empty prompt.\n return { priorTurns: turns, lastUserText: '' };\n}\n\nfunction textOf(msg: ProviderRequest['messages'][number]): string {\n return typeof msg.content === 'string'\n ? msg.content\n : msg.content\n .map((p) => (p.type === 'text' ? p.text : p.type === 'reasoning' ? p.text : ''))\n .join('');\n}\n\nasync function resolveModel(options: LlamaCppNodeAdapterOptions): Promise<LlamaModelInstance> {\n const llama = await loadLlamaModule(options.runtimeOverrides);\n return llama.loadModel({\n modelPath: options.modelPath,\n ...(options.gpuLayers !== undefined ? { gpuLayers: options.gpuLayers } : {}),\n });\n}\n\n/**\n * The REAL default session factory (PS-3): `model.createContext()` →\n * `context.getSequence()` → `new LlamaChatSession({ contextSequence })`\n * from the lazily-loaded peer, adapting its callback-streaming\n * `prompt(text, { onTextChunk })` to the adapter's\n * `promptStreamingResponse` AsyncIterable contract.\n */\nasync function defaultSessionFactory(\n model: LlamaModelInstance,\n system: string | undefined,\n options: LlamaCppNodeAdapterOptions,\n): Promise<LlamaSessionInstance> {\n const ChatSession = await loadLlamaChatSessionCtor(options.runtimeOverrides);\n const context = await model.createContext(\n options.contextSize !== undefined ? { contextSize: options.contextSize } : undefined,\n );\n const sequence = context.getSequence();\n const session = new ChatSession({\n contextSequence: sequence,\n ...(system !== undefined ? { systemPrompt: system } : {}),\n });\n return {\n promptStreamingResponse(prompt, streamOptions) {\n return promptToIterable(session, prompt, streamOptions);\n },\n dispose() {\n // Sequence first, then its owning context (core-provider-08).\n try {\n sequence.dispose?.();\n } catch {\n // fall through to the context\n }\n context.dispose?.();\n },\n };\n}\n\n/**\n * Bridge the peer's callback-streaming `prompt(...)` into the\n * AsyncIterable the adapter consumes. A rejection from `prompt`\n * rejects the pending/next iteration so the stream's error path\n * (PS-4) observes it.\n */\nfunction promptToIterable(\n session: LlamaChatSessionPeer,\n prompt: string,\n streamOptions?: {\n readonly signal?: AbortSignal;\n readonly maxTokens?: number;\n readonly temperature?: number;\n },\n): AsyncIterable<string> {\n const queue: string[] = [];\n let done = false;\n let failure: unknown;\n let wake: (() => void) | null = null;\n const notify = (): void => {\n if (wake !== null) {\n const w = wake;\n wake = null;\n w();\n }\n };\n void session\n .prompt(prompt, {\n ...(streamOptions?.signal !== undefined ? { signal: streamOptions.signal } : {}),\n ...(streamOptions?.maxTokens !== undefined ? { maxTokens: streamOptions.maxTokens } : {}),\n ...(streamOptions?.temperature !== undefined\n ? { temperature: streamOptions.temperature }\n : {}),\n onTextChunk: (chunk: string) => {\n if (typeof chunk === 'string' && chunk.length > 0) queue.push(chunk);\n notify();\n },\n })\n .then(\n () => {\n done = true;\n notify();\n },\n (err: unknown) => {\n failure = err instanceof Error ? err : new Error(String(err));\n done = true;\n notify();\n },\n );\n return {\n [Symbol.asyncIterator](): AsyncIterator<string> {\n return {\n async next(): Promise<IteratorResult<string>> {\n for (;;) {\n const head = queue.shift();\n if (head !== undefined) return { done: false, value: head };\n if (failure !== undefined) throw failure;\n if (done) return { done: true, value: undefined };\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n }\n },\n };\n },\n };\n}\n\nfunction renderPrompt(req: ProviderRequest): string {\n const parts: string[] = [];\n // `req.systemMessage` is deliberately NOT rendered here: the session\n // factory already sets it as the chat-template `systemPrompt`, and\n // rendering it again would show the model the system prompt twice\n // (core-provider-08).\n for (const msg of req.messages) {\n const text =\n typeof msg.content === 'string'\n ? msg.content\n : msg.content\n .map((p) => (p.type === 'text' ? p.text : p.type === 'reasoning' ? p.text : ''))\n .join('');\n parts.push(`[${msg.role}] ${text}`);\n }\n return parts.join('\\n');\n}\n\nfunction basename(path: string): string {\n const idx = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\\\'));\n return idx === -1 ? path : path.slice(idx + 1);\n}\n\nfunction lengthOf(tokens: readonly number[] | Uint32Array | Uint8Array | null | undefined): number {\n if (tokens === null || tokens === undefined) return 0;\n return (tokens as { length?: number }).length ?? 0;\n}\n"],"mappings":"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"}
|
package/dist/package.js
CHANGED
package/dist/package.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package.js","names":[],"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"@graphorin/provider-llamacpp-node\",\n \"version\": \"0.
|
|
1
|
+
{"version":3,"file":"package.js","names":[],"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"@graphorin/provider-llamacpp-node\",\n \"version\": \"0.7.0\",\n \"description\": \"In-process GGUF execution adapter for the Graphorin framework. Wraps node-llama-cpp@^3.5 to load .gguf model files directly into the same Node process - no daemon, no port to manage, no GPU contention with other processes. Declares trust: 'loopback' permanently because the model lives in the same trust boundary as the host process. Ships LlamaCppNativeCounter that wraps model.tokenize() from the loaded GGUF for byte-exact token counts.\",\n \"license\": \"MIT\",\n \"author\": \"Oleksiy Stepurenko\",\n \"homepage\": \"https://github.com/o-stepper/graphorin/tree/main/packages/provider-llamacpp-node\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/o-stepper/graphorin.git\",\n \"directory\": \"packages/provider-llamacpp-node\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/o-stepper/graphorin/issues\"\n },\n \"keywords\": [\n \"graphorin\",\n \"ai\",\n \"agents\",\n \"framework\",\n \"provider\",\n \"llm\",\n \"llama-cpp\",\n \"node-llama-cpp\",\n \"gguf\",\n \"in-process\",\n \"local-first\",\n \"loopback\"\n ],\n \"type\": \"module\",\n \"sideEffects\": false,\n \"engines\": {\n \"node\": \">=22.12.0\"\n },\n \"main\": \"./dist/index.js\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"src\",\n \"README.md\",\n \"CHANGELOG.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsdown\",\n \"typecheck\": \"tsc --noEmit && tsc -p tsconfig.tests.json\",\n \"test\": \"vitest run\",\n \"lint\": \"biome check .\",\n \"clean\": \"rimraf dist .turbo *.tsbuildinfo\"\n },\n \"dependencies\": {\n \"@graphorin/core\": \"workspace:*\",\n \"@graphorin/provider\": \"workspace:*\"\n },\n \"peerDependencies\": {\n \"node-llama-cpp\": \"^3.5.0\"\n },\n \"peerDependenciesMeta\": {\n \"node-llama-cpp\": {\n \"optional\": false\n }\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n },\n \"devDependencies\": {}\n}\n"],"mappings":";cAEa"}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -35,6 +35,22 @@ interface LlamaModelInstance {
|
|
|
35
35
|
}>;
|
|
36
36
|
dispose?(): Promise<void>;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Structural mirror of node-llama-cpp v3's `ChatHistoryItem` (W-096).
|
|
40
|
+
* A `'model'` turn carries its text as `response: string[]`.
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
type LlamaChatHistoryItem = {
|
|
45
|
+
readonly type: 'system';
|
|
46
|
+
readonly text: string;
|
|
47
|
+
} | {
|
|
48
|
+
readonly type: 'user';
|
|
49
|
+
readonly text: string;
|
|
50
|
+
} | {
|
|
51
|
+
readonly type: 'model';
|
|
52
|
+
readonly response: ReadonlyArray<string>;
|
|
53
|
+
};
|
|
38
54
|
/**
|
|
39
55
|
* Loaded chat session capable of streaming responses.
|
|
40
56
|
*
|
|
@@ -46,6 +62,15 @@ interface LlamaSessionInstance {
|
|
|
46
62
|
readonly maxTokens?: number;
|
|
47
63
|
readonly temperature?: number;
|
|
48
64
|
}): AsyncIterable<string>;
|
|
65
|
+
/**
|
|
66
|
+
* W-096: replace the session's chat history (node-llama-cpp v3
|
|
67
|
+
* `setChatHistory`). When present, the adapter feeds multi-turn
|
|
68
|
+
* transcripts as REAL chat history + prompts only the last user turn
|
|
69
|
+
* - instead of serialising the whole conversation into one
|
|
70
|
+
* pseudo-prompt string. Optional: fixtures / custom factories without
|
|
71
|
+
* it keep the legacy render-prompt path.
|
|
72
|
+
*/
|
|
73
|
+
setChatHistory?(history: ReadonlyArray<LlamaChatHistoryItem>): void;
|
|
49
74
|
/**
|
|
50
75
|
* Release the per-request context / sequence backing this session.
|
|
51
76
|
* node-llama-cpp contexts hold KV-cache memory (hundreds of MB at
|
|
@@ -90,6 +115,8 @@ interface LlamaChatSessionPeer {
|
|
|
90
115
|
readonly temperature?: number;
|
|
91
116
|
readonly onTextChunk?: (chunk: string) => void;
|
|
92
117
|
}): Promise<string>;
|
|
118
|
+
/** W-096: node-llama-cpp v3 chat-history setter (optional slice). */
|
|
119
|
+
setChatHistory?(history: ReadonlyArray<LlamaChatHistoryItem>): void;
|
|
93
120
|
}
|
|
94
121
|
/** @internal */
|
|
95
122
|
type LlamaChatSessionCtor = new (args: {
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","names":[],"sources":["../src/runtime.ts"],"sourcesContent":[],"mappings":";;AAaA;AASA;;;;;;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","names":[],"sources":["../src/runtime.ts"],"sourcesContent":[],"mappings":";;AAaA;AASA;;;;;;AAgBA;AAUA;;;AAiByC,UApDxB,aAAA,CAoDwB;EAAd,SAAA,CAAA,IAAA,EAAA;IAAa,SAAA,EAAA,MAAA;IAevB,SAAA,CAAA,EAAA,MAAA,GAAA,MAA4B;EAET,CAAA,CAAA,EApEmC,OAoEnC,CApE2C,kBAoE3C,CAAA;;;;;;;AA0BnB,UAtFA,kBAAA,CAsFoB;EAIb,SAAA,gBAAA,CAAA,EAAA,MAAA;EAKnB,QAAA,CAAA,IAAA,EAAA,MAAA,CAAA,EAAA,SAAA,MAAA,EAAA,GA7FyC,WA6FzC,GA7FuD,UA6FvD;EAEoC,aAAA,CAAA,IAAD,CAAC,EAAA;IAAd,WAAA,CAAA,EAAA,MAAA;EAAa,CAAA,CAAA,EA9FU,OA8FV,CAAA;IAI5B,WAAA,EAAA,EAAA;;;;;cA9FE;;;;;;;;KASF,oBAAA;;;;;;;;qBAGqC;;;;;;;UAOhC,oBAAA;;sBAIO;;;MAInB;;;;;;;;;2BASsB,cAAc;;;;;;;;;;;;;;UAexB,4BAAA;;4BAEW,QAAQ;;;;;;mCAOzB,wCAEJ,QAAQ;;;;;;8BAMe;;;;;;;;;;UAWb,oBAAA;;sBAIO;;;;MAKnB;;2BAEsB,cAAc;;;KAI7B,oBAAA;;;MAGN"}
|
package/dist/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","names":["cachedLlama: LlamaInstance | null","cachedChatSessionCtor: LlamaChatSessionCtor | null","mod: { getLlama?: unknown }","mod: { LlamaChatSession?: unknown }"],"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * Loose structural shapes covering the slice of `node-llama-cpp` we\n * use. Re-declared here to avoid pulling the heavy native peer at\n * type-check time.\n *\n * @internal\n */\n\n/**\n * `Llama` engine instance (returned by `getLlama()`).\n *\n * @internal\n */\nexport interface LlamaInstance {\n loadModel(args: { modelPath: string; gpuLayers?: number | 'auto' }): Promise<LlamaModelInstance>;\n}\n\n/**\n * Loaded GGUF model.\n *\n * @internal\n */\nexport interface LlamaModelInstance {\n readonly trainContextSize?: number;\n tokenize(text: string): readonly number[] | Uint32Array | Uint8Array;\n createContext(args?: { contextSize?: number }): Promise<{\n getSequence(): { dispose?: () => void };\n dispose?: () => void;\n }>;\n dispose?(): Promise<void>;\n}\n\n/**\n * Loaded chat session capable of streaming responses.\n *\n * @internal\n */\nexport interface LlamaSessionInstance {\n promptStreamingResponse(\n prompt: string,\n options?: {\n readonly signal?: AbortSignal;\n readonly maxTokens?: number;\n readonly temperature?: number;\n },\n ): AsyncIterable<string>;\n /**\n * Release the per-request context / sequence backing this session.\n * node-llama-cpp contexts hold KV-cache memory (hundreds of MB at\n * large context sizes); the adapter calls this in a `finally` after\n * every stream so long-running agents do not leak until OOM.\n */\n dispose?(): void;\n}\n\n/**\n * Test-only shape for injecting fixture-driven runtime behaviour.\n *\n * @stable\n */\nexport interface LlamaCppNodeRuntimeOverrides {\n /** Returns a `LlamaInstance` (the result of `getLlama()`). */\n readonly getLlama?: () => Promise<LlamaInstance>;\n /**\n * Build a streaming chat session against an already-loaded model\n * instance. Used by the adapter to wire `model.tokenize` and\n * `session.promptStreamingResponse` to the per-test fixture.\n */\n readonly createSession?: (\n model: LlamaModelInstance,\n system?: string,\n ) => Promise<LlamaSessionInstance>;\n /**\n * Override the `LlamaChatSession` constructor used by the REAL\n * default session factory (PS-3). Tests stub it; production loads it\n * from the `node-llama-cpp` peer.\n */\n readonly LlamaChatSession?: LlamaChatSessionCtor;\n}\n\n/**\n * Structural slice of the peer's `LlamaChatSession` class used by the\n * default session factory (PS-3): `prompt(text, { onTextChunk })`\n * resolves with the full response while streaming chunks through the\n * callback.\n *\n * @internal\n */\nexport interface LlamaChatSessionPeer {\n prompt(\n text: string,\n options?: {\n readonly signal?: AbortSignal;\n readonly maxTokens?: number;\n readonly temperature?: number;\n readonly onTextChunk?: (chunk: string) => void;\n },\n ): Promise<string>;\n}\n\n/** @internal */\nexport type LlamaChatSessionCtor = new (args: {\n readonly contextSequence: unknown;\n readonly systemPrompt?: string;\n}) => LlamaChatSessionPeer;\n\nlet cachedLlama: LlamaInstance | null = null;\nlet cachedChatSessionCtor: LlamaChatSessionCtor | null = null;\n\n/**\n * Lazily load the `node-llama-cpp` peer and return the `Llama` engine\n * instance. Cached per process.\n *\n * @internal\n */\nexport async function loadLlamaModule(\n overrides: LlamaCppNodeRuntimeOverrides | undefined,\n): Promise<LlamaInstance> {\n if (overrides?.getLlama !== undefined) return overrides.getLlama();\n if (cachedLlama !== null) return cachedLlama;\n let mod: { getLlama?: unknown };\n try {\n mod = (await import('node-llama-cpp')) as { getLlama?: unknown };\n } catch (cause) {\n throw new Error(\n \"[graphorin/provider-llamacpp-node] missing peer dependency 'node-llama-cpp'. \" +\n 'Install it with `pnpm add node-llama-cpp`.',\n { cause },\n );\n }\n if (typeof mod.getLlama !== 'function') {\n throw new Error(\n '[graphorin/provider-llamacpp-node] installed node-llama-cpp does not expose getLlama().',\n );\n }\n const instance = (await (mod.getLlama as () => Promise<LlamaInstance>)()) as LlamaInstance;\n cachedLlama = instance;\n return instance;\n}\n\n/**\n * Lazily resolve the peer's `LlamaChatSession` constructor for the\n * real default session factory (PS-3). Cached per process; the\n * override wins for tests.\n *\n * @internal\n */\nexport async function loadLlamaChatSessionCtor(\n overrides: LlamaCppNodeRuntimeOverrides | undefined,\n): Promise<LlamaChatSessionCtor> {\n if (overrides?.LlamaChatSession !== undefined) return overrides.LlamaChatSession;\n if (cachedChatSessionCtor !== null) return cachedChatSessionCtor;\n let mod: { LlamaChatSession?: unknown };\n try {\n mod = (await import('node-llama-cpp')) as { LlamaChatSession?: unknown };\n } catch (cause) {\n throw new Error(\n \"[graphorin/provider-llamacpp-node] missing peer dependency 'node-llama-cpp'. \" +\n 'Install it with `pnpm add node-llama-cpp`.',\n { cause },\n );\n }\n if (typeof mod.LlamaChatSession !== 'function') {\n throw new Error(\n '[graphorin/provider-llamacpp-node] installed node-llama-cpp does not expose LlamaChatSession.',\n );\n }\n cachedChatSessionCtor = mod.LlamaChatSession as LlamaChatSessionCtor;\n return cachedChatSessionCtor;\n}\n\n/**\n * Test-only hook that resets the cached `getLlama()` result.\n *\n * @internal\n */\nexport function __resetLlamaCache(): void {\n cachedLlama = null;\n cachedChatSessionCtor = null;\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"runtime.js","names":["cachedLlama: LlamaInstance | null","cachedChatSessionCtor: LlamaChatSessionCtor | null","mod: { getLlama?: unknown }","mod: { LlamaChatSession?: unknown }"],"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * Loose structural shapes covering the slice of `node-llama-cpp` we\n * use. Re-declared here to avoid pulling the heavy native peer at\n * type-check time.\n *\n * @internal\n */\n\n/**\n * `Llama` engine instance (returned by `getLlama()`).\n *\n * @internal\n */\nexport interface LlamaInstance {\n loadModel(args: { modelPath: string; gpuLayers?: number | 'auto' }): Promise<LlamaModelInstance>;\n}\n\n/**\n * Loaded GGUF model.\n *\n * @internal\n */\nexport interface LlamaModelInstance {\n readonly trainContextSize?: number;\n tokenize(text: string): readonly number[] | Uint32Array | Uint8Array;\n createContext(args?: { contextSize?: number }): Promise<{\n getSequence(): { dispose?: () => void };\n dispose?: () => void;\n }>;\n dispose?(): Promise<void>;\n}\n\n/**\n * Structural mirror of node-llama-cpp v3's `ChatHistoryItem` (W-096).\n * A `'model'` turn carries its text as `response: string[]`.\n *\n * @internal\n */\nexport type LlamaChatHistoryItem =\n | { readonly type: 'system'; readonly text: string }\n | { readonly type: 'user'; readonly text: string }\n | { readonly type: 'model'; readonly response: ReadonlyArray<string> };\n\n/**\n * Loaded chat session capable of streaming responses.\n *\n * @internal\n */\nexport interface LlamaSessionInstance {\n promptStreamingResponse(\n prompt: string,\n options?: {\n readonly signal?: AbortSignal;\n readonly maxTokens?: number;\n readonly temperature?: number;\n },\n ): AsyncIterable<string>;\n /**\n * W-096: replace the session's chat history (node-llama-cpp v3\n * `setChatHistory`). When present, the adapter feeds multi-turn\n * transcripts as REAL chat history + prompts only the last user turn\n * - instead of serialising the whole conversation into one\n * pseudo-prompt string. Optional: fixtures / custom factories without\n * it keep the legacy render-prompt path.\n */\n setChatHistory?(history: ReadonlyArray<LlamaChatHistoryItem>): void;\n /**\n * Release the per-request context / sequence backing this session.\n * node-llama-cpp contexts hold KV-cache memory (hundreds of MB at\n * large context sizes); the adapter calls this in a `finally` after\n * every stream so long-running agents do not leak until OOM.\n */\n dispose?(): void;\n}\n\n/**\n * Test-only shape for injecting fixture-driven runtime behaviour.\n *\n * @stable\n */\nexport interface LlamaCppNodeRuntimeOverrides {\n /** Returns a `LlamaInstance` (the result of `getLlama()`). */\n readonly getLlama?: () => Promise<LlamaInstance>;\n /**\n * Build a streaming chat session against an already-loaded model\n * instance. Used by the adapter to wire `model.tokenize` and\n * `session.promptStreamingResponse` to the per-test fixture.\n */\n readonly createSession?: (\n model: LlamaModelInstance,\n system?: string,\n ) => Promise<LlamaSessionInstance>;\n /**\n * Override the `LlamaChatSession` constructor used by the REAL\n * default session factory (PS-3). Tests stub it; production loads it\n * from the `node-llama-cpp` peer.\n */\n readonly LlamaChatSession?: LlamaChatSessionCtor;\n}\n\n/**\n * Structural slice of the peer's `LlamaChatSession` class used by the\n * default session factory (PS-3): `prompt(text, { onTextChunk })`\n * resolves with the full response while streaming chunks through the\n * callback.\n *\n * @internal\n */\nexport interface LlamaChatSessionPeer {\n prompt(\n text: string,\n options?: {\n readonly signal?: AbortSignal;\n readonly maxTokens?: number;\n readonly temperature?: number;\n readonly onTextChunk?: (chunk: string) => void;\n },\n ): Promise<string>;\n /** W-096: node-llama-cpp v3 chat-history setter (optional slice). */\n setChatHistory?(history: ReadonlyArray<LlamaChatHistoryItem>): void;\n}\n\n/** @internal */\nexport type LlamaChatSessionCtor = new (args: {\n readonly contextSequence: unknown;\n readonly systemPrompt?: string;\n}) => LlamaChatSessionPeer;\n\nlet cachedLlama: LlamaInstance | null = null;\nlet cachedChatSessionCtor: LlamaChatSessionCtor | null = null;\n\n/**\n * Lazily load the `node-llama-cpp` peer and return the `Llama` engine\n * instance. Cached per process.\n *\n * @internal\n */\nexport async function loadLlamaModule(\n overrides: LlamaCppNodeRuntimeOverrides | undefined,\n): Promise<LlamaInstance> {\n if (overrides?.getLlama !== undefined) return overrides.getLlama();\n if (cachedLlama !== null) return cachedLlama;\n let mod: { getLlama?: unknown };\n try {\n mod = (await import('node-llama-cpp')) as { getLlama?: unknown };\n } catch (cause) {\n throw new Error(\n \"[graphorin/provider-llamacpp-node] missing peer dependency 'node-llama-cpp'. \" +\n 'Install it with `pnpm add node-llama-cpp`.',\n { cause },\n );\n }\n if (typeof mod.getLlama !== 'function') {\n throw new Error(\n '[graphorin/provider-llamacpp-node] installed node-llama-cpp does not expose getLlama().',\n );\n }\n const instance = (await (mod.getLlama as () => Promise<LlamaInstance>)()) as LlamaInstance;\n cachedLlama = instance;\n return instance;\n}\n\n/**\n * Lazily resolve the peer's `LlamaChatSession` constructor for the\n * real default session factory (PS-3). Cached per process; the\n * override wins for tests.\n *\n * @internal\n */\nexport async function loadLlamaChatSessionCtor(\n overrides: LlamaCppNodeRuntimeOverrides | undefined,\n): Promise<LlamaChatSessionCtor> {\n if (overrides?.LlamaChatSession !== undefined) return overrides.LlamaChatSession;\n if (cachedChatSessionCtor !== null) return cachedChatSessionCtor;\n let mod: { LlamaChatSession?: unknown };\n try {\n mod = (await import('node-llama-cpp')) as { LlamaChatSession?: unknown };\n } catch (cause) {\n throw new Error(\n \"[graphorin/provider-llamacpp-node] missing peer dependency 'node-llama-cpp'. \" +\n 'Install it with `pnpm add node-llama-cpp`.',\n { cause },\n );\n }\n if (typeof mod.LlamaChatSession !== 'function') {\n throw new Error(\n '[graphorin/provider-llamacpp-node] installed node-llama-cpp does not expose LlamaChatSession.',\n );\n }\n cachedChatSessionCtor = mod.LlamaChatSession as LlamaChatSessionCtor;\n return cachedChatSessionCtor;\n}\n\n/**\n * Test-only hook that resets the cached `getLlama()` result.\n *\n * @internal\n */\nexport function __resetLlamaCache(): void {\n cachedLlama = null;\n cachedChatSessionCtor = null;\n}\n"],"mappings":";AAgIA,IAAIA,cAAoC;AACxC,IAAIC,wBAAqD;;;;;;;AAQzD,eAAsB,gBACpB,WACwB;AACxB,KAAI,WAAW,aAAa,OAAW,QAAO,UAAU,UAAU;AAClE,KAAI,gBAAgB,KAAM,QAAO;CACjC,IAAIC;AACJ,KAAI;AACF,QAAO,MAAM,OAAO;UACb,OAAO;AACd,QAAM,IAAI,MACR,2HAEA,EAAE,OAAO,CACV;;AAEH,KAAI,OAAO,IAAI,aAAa,WAC1B,OAAM,IAAI,MACR,0FACD;CAEH,MAAM,WAAY,MAAO,IAAI,UAA2C;AACxE,eAAc;AACd,QAAO;;;;;;;;;AAUT,eAAsB,yBACpB,WAC+B;AAC/B,KAAI,WAAW,qBAAqB,OAAW,QAAO,UAAU;AAChE,KAAI,0BAA0B,KAAM,QAAO;CAC3C,IAAIC;AACJ,KAAI;AACF,QAAO,MAAM,OAAO;UACb,OAAO;AACd,QAAM,IAAI,MACR,2HAEA,EAAE,OAAO,CACV;;AAEH,KAAI,OAAO,IAAI,qBAAqB,WAClC,OAAM,IAAI,MACR,gGACD;AAEH,yBAAwB,IAAI;AAC5B,QAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphorin/provider-llamacpp-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "In-process GGUF execution adapter for the Graphorin framework. Wraps node-llama-cpp@^3.5 to load .gguf model files directly into the same Node process - no daemon, no port to manage, no GPU contention with other processes. Declares trust: 'loopback' permanently because the model lives in the same trust boundary as the host process. Ships LlamaCppNativeCounter that wraps model.tokenize() from the loaded GGUF for byte-exact token counts.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Oleksiy Stepurenko",
|
|
@@ -28,8 +28,9 @@
|
|
|
28
28
|
"loopback"
|
|
29
29
|
],
|
|
30
30
|
"type": "module",
|
|
31
|
+
"sideEffects": false,
|
|
31
32
|
"engines": {
|
|
32
|
-
"node": ">=22.
|
|
33
|
+
"node": ">=22.12.0"
|
|
33
34
|
},
|
|
34
35
|
"main": "./dist/index.js",
|
|
35
36
|
"module": "./dist/index.js",
|
|
@@ -37,19 +38,20 @@
|
|
|
37
38
|
"exports": {
|
|
38
39
|
".": {
|
|
39
40
|
"types": "./dist/index.d.ts",
|
|
40
|
-
"
|
|
41
|
+
"default": "./dist/index.js"
|
|
41
42
|
},
|
|
42
43
|
"./package.json": "./package.json"
|
|
43
44
|
},
|
|
44
45
|
"files": [
|
|
45
46
|
"dist",
|
|
47
|
+
"src",
|
|
46
48
|
"README.md",
|
|
47
49
|
"CHANGELOG.md",
|
|
48
50
|
"LICENSE"
|
|
49
51
|
],
|
|
50
52
|
"dependencies": {
|
|
51
|
-
"@graphorin/core": "0.
|
|
52
|
-
"@graphorin/provider": "0.
|
|
53
|
+
"@graphorin/core": "0.7.0",
|
|
54
|
+
"@graphorin/provider": "0.7.0"
|
|
53
55
|
},
|
|
54
56
|
"peerDependencies": {
|
|
55
57
|
"node-llama-cpp": "^3.5.0"
|