@graphorin/provider-llamacpp-node 0.6.0 → 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 CHANGED
@@ -1,5 +1,35 @@
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
+
23
+ ## 0.6.1
24
+
25
+ ### Patch Changes
26
+
27
+ - [#142](https://github.com/o-stepper/graphorin/pull/142) [`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430) Thanks [@o-stepper](https://github.com/o-stepper)! - Version constants and version-bearing strings now derive from each package's manifest at build time (`VERSION = pkg.version`; writer ids, client/server info, OTLP framework attributes, build-info metrics interpolate it). No behavioral change at the current version: the rendered strings are byte-identical. A release bump no longer edits source; the new `check-version-consistency` gate fails any reintroduced hardcoded framework version.
28
+
29
+ - Updated dependencies [[`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430)]:
30
+ - @graphorin/core@0.6.1
31
+ - @graphorin/provider@0.6.1
32
+
3
33
  ## 0.6.0
4
34
 
5
35
  ### 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.6.0 · MIT License · © 2026 Oleksiy Stepurenko
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
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","names":[],"sources":["../src/adapter.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;UA8CiB,0BAAA;;;;;;;;;;0BAUS,QAAQ;;gCAEF,cAAc;;;;;8BAKhB;;;;;;2BAMH;;;;;;;;;oCAUhB,wCAEJ,QAAQ;;;;;;;;;iBAsBC,mBAAA,UAA6B,6BAA6B"}
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 sessionFactory = options.sessionFactory ?? options.runtimeOverrides?.createSession ?? ((model, system) => defaultSessionFactory(model, system, options));
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, sessionFactory, providerName, options, applyLlamaCppNodePreflight(req, capabilities));
61
+ return streamLlamaCppNode(ensureModel, acquireSession, providerName, options, applyLlamaCppNodePreflight(req, capabilities));
62
62
  },
63
63
  async generate(req) {
64
- const events = streamLlamaCppNode(ensureModel, sessionFactory, providerName, options, applyLlamaCppNodePreflight(req, capabilities));
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, sessionFactory, providerName, options, req) {
110
+ async function* streamLlamaCppNode(ensureModel, acquireSession, providerName, options, req) {
111
111
  const { model } = await ensureModel();
112
- const session = await sessionFactory(model, typeof req.systemMessage === "string" ? req.systemMessage : void 0);
113
- const prompt = renderPrompt(req);
114
- const promptTokens = lengthOf(model.tokenize(prompt));
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
- let completionTokens = 0;
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
- completionTokens += lengthOf(model.tokenize(piece));
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
- try {
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,
@@ -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/index.d.ts CHANGED
@@ -25,8 +25,7 @@ import { LlamaCppNativeCounter, LlamaCppNativeCounterOptions } from "./counter.j
25
25
  *
26
26
  * @packageDocumentation
27
27
  */
28
- /** Canonical version constant. Mirrors the `package.json` version. */
29
- declare const VERSION = "0.6.0";
28
+ declare const VERSION: string;
30
29
  //#endregion
31
30
  export { LlamaCppNativeCounter, type LlamaCppNativeCounterOptions, type LlamaCppNodeAdapterOptions, type LlamaCppNodeRuntimeOverrides, type LlamaInstance, type LlamaModelInstance, type LlamaSessionInstance, VERSION, llamaCppNodeAdapter };
32
31
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAuBA;;;;;;;;;;;;;;;;;cAAa,OAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAyBA;;;;;;;;;;;;;;;;cAAa"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { version } from "./package.js";
1
2
  import { llamaCppNodeAdapter } from "./adapter.js";
2
3
  import { LlamaCppNativeCounter } from "./counter.js";
3
4
 
@@ -23,8 +24,8 @@ import { LlamaCppNativeCounter } from "./counter.js";
23
24
  *
24
25
  * @packageDocumentation
25
26
  */
26
- /** Canonical version constant. Mirrors the `package.json` version. */
27
- const VERSION = "0.6.0";
27
+ /** Canonical version constant, derived from `package.json` at build time. */
28
+ const VERSION = version;
28
29
 
29
30
  //#endregion
30
31
  export { LlamaCppNativeCounter, VERSION, llamaCppNodeAdapter };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @graphorin/provider-llamacpp-node - in-process GGUF execution\n * adapter for the Graphorin framework. The package wraps\n * `node-llama-cpp@^3.5` to load `.gguf` model files directly into the\n * same Node process - no daemon, no port to manage, no GPU contention\n * with other processes.\n *\n * The adapter declares `trust: 'loopback'` permanently because the\n * model lives in the same trust boundary as the host process; the\n * symmetry mirrors `@graphorin/embedder-transformersjs` (in-process\n * embedder; same trust boundary).\n *\n * The companion package is operationally simpler than the HTTP-shaped\n * adapters but does NOT survive a process restart mid-stream - the\n * model context lives in the process and is lost on exit. For HITL\n * durable mid-stream resume, one of the HTTP-shaped adapters\n * (`ollamaAdapter`, `llamaCppServerAdapter`, `openAICompatibleAdapter`)\n * is the better choice.\n *\n * @packageDocumentation\n */\n\n/** Canonical version constant. Mirrors the `package.json` version. */\nexport const VERSION = '0.6.0';\n\nexport { type LlamaCppNodeAdapterOptions, llamaCppNodeAdapter } from './adapter.js';\nexport {\n LlamaCppNativeCounter,\n type LlamaCppNativeCounterOptions,\n} from './counter.js';\nexport type {\n LlamaCppNodeRuntimeOverrides,\n LlamaInstance,\n LlamaModelInstance,\n LlamaSessionInstance,\n} from './runtime.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,UAAU"}
1
+ {"version":3,"file":"index.js","names":["VERSION: string","pkg.version"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @graphorin/provider-llamacpp-node - in-process GGUF execution\n * adapter for the Graphorin framework. The package wraps\n * `node-llama-cpp@^3.5` to load `.gguf` model files directly into the\n * same Node process - no daemon, no port to manage, no GPU contention\n * with other processes.\n *\n * The adapter declares `trust: 'loopback'` permanently because the\n * model lives in the same trust boundary as the host process; the\n * symmetry mirrors `@graphorin/embedder-transformersjs` (in-process\n * embedder; same trust boundary).\n *\n * The companion package is operationally simpler than the HTTP-shaped\n * adapters but does NOT survive a process restart mid-stream - the\n * model context lives in the process and is lost on exit. For HITL\n * durable mid-stream resume, one of the HTTP-shaped adapters\n * (`ollamaAdapter`, `llamaCppServerAdapter`, `openAICompatibleAdapter`)\n * is the better choice.\n *\n * @packageDocumentation\n */\n\n/** Canonical version constant, derived from `package.json` at build time. */\nimport pkg from '../package.json' with { type: 'json' };\n\nexport const VERSION: string = pkg.version;\n\nexport { type LlamaCppNodeAdapterOptions, llamaCppNodeAdapter } from './adapter.js';\nexport {\n LlamaCppNativeCounter,\n type LlamaCppNativeCounterOptions,\n} from './counter.js';\nexport type {\n LlamaCppNodeRuntimeOverrides,\n LlamaInstance,\n LlamaModelInstance,\n LlamaSessionInstance,\n} from './runtime.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAaA,UAAkBC"}
@@ -0,0 +1,6 @@
1
+ //#region package.json
2
+ var version = "0.7.0";
3
+
4
+ //#endregion
5
+ export { version };
6
+ //# sourceMappingURL=package.js.map
@@ -0,0 +1 @@
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: {
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","names":[],"sources":["../src/runtime.ts"],"sourcesContent":[],"mappings":";;AAaA;AASA;;;;;;AAeA;AAuBA;;;AASW,UAxDM,aAAA,CAwDN;EAEI,SAAA,CAAA,IAAA,EAAA;IAAR,SAAA,EAAA,MAAA;IAMuB,SAAA,CAAA,EAAA,MAAA,GAAA,MAAA;EAAoB,CAAA,CAAA,EA/DqB,OA+DrB,CA/D6B,kBA+D7B,CAAA;AAWlD;AAaA;;;;;UA/EiB,kBAAA;;8CAE6B,cAAc;;;MACV;;;;;;cAIpC;;;;;;;UAQG,oBAAA;;sBAIO;;;MAInB;;;;;;;;;;;;;;UAeY,4BAAA;;4BAEW,QAAQ;;;;;;mCAOzB,wCAEJ,QAAQ;;;;;;8BAMe;;;;;;;;;;UAWb,oBAAA;;sBAIO;;;;MAKnB;;;KAIO,oBAAA;;;MAGN"}
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"}
@@ -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":";AA0GA,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"}
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.6.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.0.0"
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
- "import": "./dist/index.js"
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.6.0",
52
- "@graphorin/provider": "0.6.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"