@graphorin/provider-llamacpp-node 0.5.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 +6 -0
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/dist/adapter.d.ts +55 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +255 -0
- package/dist/adapter.js.map +1 -0
- package/dist/counter.d.ts +34 -0
- package/dist/counter.d.ts.map +1 -0
- package/dist/counter.js +50 -0
- package/dist/counter.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime.d.ts +94 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +47 -0
- package/dist/runtime.js.map +1 -0
- package/package.json +74 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oleksiy Stepurenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# `@graphorin/provider-llamacpp-node`
|
|
2
|
+
|
|
3
|
+
> Companion package to
|
|
4
|
+
> [`@graphorin/provider`](../provider/README.md) — in-process GGUF
|
|
5
|
+
> execution for the
|
|
6
|
+
> [**Graphorin**](https://github.com/o-stepper/graphorin) framework.
|
|
7
|
+
|
|
8
|
+
Wraps `node-llama-cpp@^3.5` to load `.gguf` model files directly into
|
|
9
|
+
the same Node process. No daemon, no port to manage, no GPU contention
|
|
10
|
+
with other processes. Trust class is permanent `loopback` because the
|
|
11
|
+
model lives in the same trust boundary as the host process.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @graphorin/provider-llamacpp-node node-llama-cpp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { llamaCppNodeAdapter } from '@graphorin/provider-llamacpp-node';
|
|
23
|
+
import { createProvider } from '@graphorin/provider';
|
|
24
|
+
|
|
25
|
+
const provider = createProvider(
|
|
26
|
+
llamaCppNodeAdapter({
|
|
27
|
+
modelPath: '/path/to/qwen2.5-7b-instruct-q4_k_m.gguf',
|
|
28
|
+
gpuLayers: 'auto',
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Native token counting
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { LlamaCppNativeCounter } from '@graphorin/provider-llamacpp-node';
|
|
37
|
+
import { setGlobalTokenCounter } from '@graphorin/provider/counters';
|
|
38
|
+
|
|
39
|
+
setGlobalTokenCounter(
|
|
40
|
+
new LlamaCppNativeCounter({
|
|
41
|
+
model: loadedGgufModel,
|
|
42
|
+
modelPath: '/path/to/qwen2.5-7b.gguf',
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The counter wraps the GGUF tokenizer directly, which is strictly
|
|
48
|
+
tighter than the cl100k_base proxy used by the HTTP-shaped adapters.
|
|
49
|
+
|
|
50
|
+
## HITL durable-resume tradeoff
|
|
51
|
+
|
|
52
|
+
The in-process adapter does **not** survive a process restart
|
|
53
|
+
mid-stream — the model context lives in the running process and is
|
|
54
|
+
lost on exit. For human-in-the-loop workflows that need durable
|
|
55
|
+
mid-stream resume across restarts, prefer one of the HTTP-shaped
|
|
56
|
+
adapters instead:
|
|
57
|
+
|
|
58
|
+
- `ollamaAdapter` — Ollama HTTP daemon
|
|
59
|
+
- `llamaCppServerAdapter` — upstream `llama-server` binary
|
|
60
|
+
- `openAICompatibleAdapter` — LMStudio / LocalAI / vLLM /
|
|
61
|
+
Together-style endpoints
|
|
62
|
+
|
|
63
|
+
## GGUF model provenance
|
|
64
|
+
|
|
65
|
+
`.gguf` model files are not signed by default. Pull only from trusted
|
|
66
|
+
publishers and verify the SHA-256 of the downloaded file against the
|
|
67
|
+
publisher's manifest:
|
|
68
|
+
|
|
69
|
+
- `huggingface.co/ggml-org`
|
|
70
|
+
- `huggingface.co/TheBloke`
|
|
71
|
+
- `huggingface.co/bartowski`
|
|
72
|
+
- `huggingface.co/unsloth`
|
|
73
|
+
- `huggingface.co/Qwen` (official Qwen distributions)
|
|
74
|
+
|
|
75
|
+
Full provenance enforcement (allowlist + Sigstore signature
|
|
76
|
+
verification) is a future Graphorin work item; v0.1 documents the
|
|
77
|
+
discipline rather than enforcing it at runtime.
|
|
78
|
+
|
|
79
|
+
## Project metadata
|
|
80
|
+
|
|
81
|
+
- **Project Graphorin** · v0.5.0 · MIT License · © 2026 Oleksiy Stepurenko
|
|
82
|
+
- Repository: <https://github.com/o-stepper/graphorin>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { LlamaCppNodeRuntimeOverrides, LlamaModelInstance, LlamaSessionInstance } from "./runtime.js";
|
|
2
|
+
import { Provider, ProviderCapabilities, Sensitivity } from "@graphorin/core";
|
|
3
|
+
|
|
4
|
+
//#region src/adapter.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options accepted by {@link llamaCppNodeAdapter}.
|
|
8
|
+
*
|
|
9
|
+
* @stable
|
|
10
|
+
*/
|
|
11
|
+
interface LlamaCppNodeAdapterOptions {
|
|
12
|
+
/** Filesystem path to the `.gguf` model file. */
|
|
13
|
+
readonly modelPath: string;
|
|
14
|
+
/** Number of layers to offload to the GPU. Default `'auto'`. */
|
|
15
|
+
readonly gpuLayers?: number | 'auto';
|
|
16
|
+
/** Optional context-window override. */
|
|
17
|
+
readonly contextSize?: number;
|
|
18
|
+
/** Provider name attached to spans / log lines. */
|
|
19
|
+
readonly name?: string;
|
|
20
|
+
/** Capability declaration. Merged on top of the defaults table. */
|
|
21
|
+
readonly capabilities?: Partial<ProviderCapabilities>;
|
|
22
|
+
/** Sensitivity override (defaults to the loopback envelope). */
|
|
23
|
+
readonly acceptsSensitivity?: ReadonlyArray<Sensitivity>;
|
|
24
|
+
/**
|
|
25
|
+
* Test-only runtime override. When unset the adapter loads
|
|
26
|
+
* `node-llama-cpp` lazily on first call.
|
|
27
|
+
*/
|
|
28
|
+
readonly runtimeOverrides?: LlamaCppNodeRuntimeOverrides;
|
|
29
|
+
/**
|
|
30
|
+
* Optional `model` override that short-circuits the
|
|
31
|
+
* `loadLlamaModule(...).loadModel(...)` flow. Tests pass a fixture
|
|
32
|
+
* shaped instance.
|
|
33
|
+
*/
|
|
34
|
+
readonly modelOverride?: LlamaModelInstance;
|
|
35
|
+
/**
|
|
36
|
+
* Optional session factory override. When unset, the adapter builds a
|
|
37
|
+
* real session from the peer (PS-3): `model.createContext()` →
|
|
38
|
+
* `new LlamaChatSession({ contextSequence })`, streaming through
|
|
39
|
+
* `prompt(text, { onTextChunk })`. Overrides
|
|
40
|
+
* (`runtimeOverrides.createSession` or this option) keep the test
|
|
41
|
+
* seam.
|
|
42
|
+
*/
|
|
43
|
+
readonly sessionFactory?: (model: LlamaModelInstance, system?: string) => Promise<LlamaSessionInstance>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build a Graphorin {@link Provider} backed by an in-process GGUF
|
|
47
|
+
* model. The first call lazily loads the `node-llama-cpp` peer + the
|
|
48
|
+
* model file; subsequent calls reuse the cached instances.
|
|
49
|
+
*
|
|
50
|
+
* @stable
|
|
51
|
+
*/
|
|
52
|
+
declare function llamaCppNodeAdapter(options: LlamaCppNodeAdapterOptions): Provider;
|
|
53
|
+
//#endregion
|
|
54
|
+
export { LlamaCppNodeAdapterOptions, llamaCppNodeAdapter };
|
|
55
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { loadLlamaChatSessionCtor, loadLlamaModule } from "./runtime.js";
|
|
2
|
+
import { ProviderHttpError } from "@graphorin/provider/errors";
|
|
3
|
+
import { applyReasoningPolicy, resolveReasoningRetention } from "@graphorin/provider/reasoning";
|
|
4
|
+
|
|
5
|
+
//#region src/adapter.ts
|
|
6
|
+
/**
|
|
7
|
+
* Default sensitivity envelope for the in-process adapter — same as
|
|
8
|
+
* the loopback HTTP path.
|
|
9
|
+
*
|
|
10
|
+
* @stable
|
|
11
|
+
*/
|
|
12
|
+
const LLAMA_CPP_NODE_ACCEPTS_SENSITIVITY = Object.freeze([
|
|
13
|
+
"public",
|
|
14
|
+
"internal",
|
|
15
|
+
"secret"
|
|
16
|
+
]);
|
|
17
|
+
const DEFAULT_CAPABILITIES = {
|
|
18
|
+
streaming: true,
|
|
19
|
+
toolCalling: false,
|
|
20
|
+
parallelToolCalls: false,
|
|
21
|
+
multimodal: false,
|
|
22
|
+
structuredOutput: false,
|
|
23
|
+
reasoning: false,
|
|
24
|
+
contextWindow: 8192,
|
|
25
|
+
maxOutput: 4096,
|
|
26
|
+
reasoningContract: "optional"
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Build a Graphorin {@link Provider} backed by an in-process GGUF
|
|
30
|
+
* model. The first call lazily loads the `node-llama-cpp` peer + the
|
|
31
|
+
* model file; subsequent calls reuse the cached instances.
|
|
32
|
+
*
|
|
33
|
+
* @stable
|
|
34
|
+
*/
|
|
35
|
+
function llamaCppNodeAdapter(options) {
|
|
36
|
+
const providerName = options.name ?? `llama-cpp-node-${basename(options.modelPath)}`;
|
|
37
|
+
const capabilities = {
|
|
38
|
+
...DEFAULT_CAPABILITIES,
|
|
39
|
+
...options.capabilities
|
|
40
|
+
};
|
|
41
|
+
const acceptsSensitivity = options.acceptsSensitivity ?? LLAMA_CPP_NODE_ACCEPTS_SENSITIVITY;
|
|
42
|
+
let resolved = null;
|
|
43
|
+
let resolving = null;
|
|
44
|
+
const ensureModel = async () => {
|
|
45
|
+
if (resolved !== null) return resolved;
|
|
46
|
+
if (resolving !== null) return resolving;
|
|
47
|
+
resolving = (async () => {
|
|
48
|
+
resolved = { model: options.modelOverride ?? await resolveModel(options) };
|
|
49
|
+
resolving = null;
|
|
50
|
+
return resolved;
|
|
51
|
+
})();
|
|
52
|
+
return resolving;
|
|
53
|
+
};
|
|
54
|
+
const sessionFactory = options.sessionFactory ?? options.runtimeOverrides?.createSession ?? ((model, system) => defaultSessionFactory(model, system, options));
|
|
55
|
+
return {
|
|
56
|
+
name: providerName,
|
|
57
|
+
modelId: options.modelPath,
|
|
58
|
+
capabilities,
|
|
59
|
+
acceptsSensitivity,
|
|
60
|
+
stream(req) {
|
|
61
|
+
return streamLlamaCppNode(ensureModel, sessionFactory, providerName, options, applyLlamaCppNodePreflight(req, capabilities));
|
|
62
|
+
},
|
|
63
|
+
async generate(req) {
|
|
64
|
+
const events = streamLlamaCppNode(ensureModel, sessionFactory, providerName, options, applyLlamaCppNodePreflight(req, capabilities));
|
|
65
|
+
const collected = [];
|
|
66
|
+
let usage = {
|
|
67
|
+
promptTokens: 0,
|
|
68
|
+
completionTokens: 0,
|
|
69
|
+
totalTokens: 0
|
|
70
|
+
};
|
|
71
|
+
let finishReason = "stop";
|
|
72
|
+
let streamError;
|
|
73
|
+
for await (const event of events) {
|
|
74
|
+
if (event.type === "text-delta") collected.push(event.delta);
|
|
75
|
+
if (event.type === "error") streamError = event.error;
|
|
76
|
+
if (event.type === "finish") {
|
|
77
|
+
usage = event.usage;
|
|
78
|
+
finishReason = event.finishReason;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (streamError !== void 0) throw new ProviderHttpError({
|
|
82
|
+
providerName,
|
|
83
|
+
status: 0,
|
|
84
|
+
message: streamError.message
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
text: collected.join(""),
|
|
88
|
+
usage,
|
|
89
|
+
finishReason
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function applyLlamaCppNodePreflight(req, capabilities) {
|
|
95
|
+
const retention = resolveReasoningRetention({
|
|
96
|
+
...req.reasoningRetention !== void 0 ? { requested: req.reasoningRetention } : {},
|
|
97
|
+
...capabilities.reasoningContract !== void 0 ? { contract: capabilities.reasoningContract } : {}
|
|
98
|
+
});
|
|
99
|
+
if (retention === "pass-through-all") return req;
|
|
100
|
+
const filtered = applyReasoningPolicy({
|
|
101
|
+
messages: req.messages,
|
|
102
|
+
retention
|
|
103
|
+
});
|
|
104
|
+
if (filtered === req.messages) return req;
|
|
105
|
+
return {
|
|
106
|
+
...req,
|
|
107
|
+
messages: filtered
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
async function* streamLlamaCppNode(ensureModel, sessionFactory, providerName, options, req) {
|
|
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));
|
|
115
|
+
yield {
|
|
116
|
+
type: "stream-start",
|
|
117
|
+
metadata: {
|
|
118
|
+
providerName,
|
|
119
|
+
modelId: options.modelPath,
|
|
120
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
let completionTokens = 0;
|
|
124
|
+
let errored = false;
|
|
125
|
+
try {
|
|
126
|
+
const streamOptions = {};
|
|
127
|
+
if (req.signal !== void 0) streamOptions.signal = req.signal;
|
|
128
|
+
if (req.maxTokens !== void 0) streamOptions.maxTokens = req.maxTokens;
|
|
129
|
+
if (req.temperature !== void 0) streamOptions.temperature = req.temperature;
|
|
130
|
+
for await (const piece of session.promptStreamingResponse(prompt, streamOptions)) {
|
|
131
|
+
if (typeof piece !== "string" || piece.length === 0) continue;
|
|
132
|
+
completionTokens += lengthOf(model.tokenize(piece));
|
|
133
|
+
yield {
|
|
134
|
+
type: "text-delta",
|
|
135
|
+
delta: piece
|
|
136
|
+
};
|
|
137
|
+
if (req.signal?.aborted) break;
|
|
138
|
+
}
|
|
139
|
+
} catch (err) {
|
|
140
|
+
errored = true;
|
|
141
|
+
yield {
|
|
142
|
+
type: "error",
|
|
143
|
+
error: {
|
|
144
|
+
kind: "unknown",
|
|
145
|
+
message: err.message
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
yield {
|
|
150
|
+
type: "finish",
|
|
151
|
+
finishReason: errored ? "error" : "stop",
|
|
152
|
+
usage: {
|
|
153
|
+
promptTokens,
|
|
154
|
+
completionTokens,
|
|
155
|
+
totalTokens: promptTokens + completionTokens
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
async function resolveModel(options) {
|
|
160
|
+
return (await loadLlamaModule(options.runtimeOverrides)).loadModel({
|
|
161
|
+
modelPath: options.modelPath,
|
|
162
|
+
...options.gpuLayers !== void 0 ? { gpuLayers: options.gpuLayers } : {}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The REAL default session factory (PS-3): `model.createContext()` →
|
|
167
|
+
* `context.getSequence()` → `new LlamaChatSession({ contextSequence })`
|
|
168
|
+
* from the lazily-loaded peer, adapting its callback-streaming
|
|
169
|
+
* `prompt(text, { onTextChunk })` to the adapter's
|
|
170
|
+
* `promptStreamingResponse` AsyncIterable contract.
|
|
171
|
+
*/
|
|
172
|
+
async function defaultSessionFactory(model, system, options) {
|
|
173
|
+
const session = new (await (loadLlamaChatSessionCtor(options.runtimeOverrides)))({
|
|
174
|
+
contextSequence: (await model.createContext(options.contextSize !== void 0 ? { contextSize: options.contextSize } : void 0)).getSequence(),
|
|
175
|
+
...system !== void 0 ? { systemPrompt: system } : {}
|
|
176
|
+
});
|
|
177
|
+
return { promptStreamingResponse(prompt, streamOptions) {
|
|
178
|
+
return promptToIterable(session, prompt, streamOptions);
|
|
179
|
+
} };
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Bridge the peer's callback-streaming `prompt(...)` into the
|
|
183
|
+
* AsyncIterable the adapter consumes. A rejection from `prompt`
|
|
184
|
+
* rejects the pending/next iteration so the stream's error path
|
|
185
|
+
* (PS-4) observes it.
|
|
186
|
+
*/
|
|
187
|
+
function promptToIterable(session, prompt, streamOptions) {
|
|
188
|
+
const queue = [];
|
|
189
|
+
let done = false;
|
|
190
|
+
let failure;
|
|
191
|
+
let wake = null;
|
|
192
|
+
const notify = () => {
|
|
193
|
+
if (wake !== null) {
|
|
194
|
+
const w = wake;
|
|
195
|
+
wake = null;
|
|
196
|
+
w();
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
session.prompt(prompt, {
|
|
200
|
+
...streamOptions?.signal !== void 0 ? { signal: streamOptions.signal } : {},
|
|
201
|
+
...streamOptions?.maxTokens !== void 0 ? { maxTokens: streamOptions.maxTokens } : {},
|
|
202
|
+
...streamOptions?.temperature !== void 0 ? { temperature: streamOptions.temperature } : {},
|
|
203
|
+
onTextChunk: (chunk) => {
|
|
204
|
+
if (typeof chunk === "string" && chunk.length > 0) queue.push(chunk);
|
|
205
|
+
notify();
|
|
206
|
+
}
|
|
207
|
+
}).then(() => {
|
|
208
|
+
done = true;
|
|
209
|
+
notify();
|
|
210
|
+
}, (err) => {
|
|
211
|
+
failure = err instanceof Error ? err : new Error(String(err));
|
|
212
|
+
done = true;
|
|
213
|
+
notify();
|
|
214
|
+
});
|
|
215
|
+
return { [Symbol.asyncIterator]() {
|
|
216
|
+
return { async next() {
|
|
217
|
+
for (;;) {
|
|
218
|
+
const head = queue.shift();
|
|
219
|
+
if (head !== void 0) return {
|
|
220
|
+
done: false,
|
|
221
|
+
value: head
|
|
222
|
+
};
|
|
223
|
+
if (failure !== void 0) throw failure;
|
|
224
|
+
if (done) return {
|
|
225
|
+
done: true,
|
|
226
|
+
value: void 0
|
|
227
|
+
};
|
|
228
|
+
await new Promise((resolve) => {
|
|
229
|
+
wake = resolve;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
} };
|
|
233
|
+
} };
|
|
234
|
+
}
|
|
235
|
+
function renderPrompt(req) {
|
|
236
|
+
const parts = [];
|
|
237
|
+
if (req.systemMessage !== void 0) parts.push(`[system] ${req.systemMessage}`);
|
|
238
|
+
for (const msg of req.messages) {
|
|
239
|
+
const text = typeof msg.content === "string" ? msg.content : msg.content.map((p) => p.type === "text" ? p.text : p.type === "reasoning" ? p.text : "").join("");
|
|
240
|
+
parts.push(`[${msg.role}] ${text}`);
|
|
241
|
+
}
|
|
242
|
+
return parts.join("\n");
|
|
243
|
+
}
|
|
244
|
+
function basename(path) {
|
|
245
|
+
const idx = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
246
|
+
return idx === -1 ? path : path.slice(idx + 1);
|
|
247
|
+
}
|
|
248
|
+
function lengthOf(tokens) {
|
|
249
|
+
if (tokens === null || tokens === void 0) return 0;
|
|
250
|
+
return tokens.length ?? 0;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
//#endregion
|
|
254
|
+
export { llamaCppNodeAdapter };
|
|
255
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +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 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 (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) break;\n }\n } catch (err) {\n errored = true;\n yield { type: 'error', error: { kind: 'unknown', message: (err as Error).message } };\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.\n finishReason: errored ? 'error' : '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 };\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 if (req.systemMessage !== undefined) parts.push(`[system] ${req.systemMessage}`);\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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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { LlamaModelInstance } from "./runtime.js";
|
|
2
|
+
import { Message, TokenCounter } from "@graphorin/core";
|
|
3
|
+
|
|
4
|
+
//#region src/counter.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options for {@link LlamaCppNativeCounter}.
|
|
8
|
+
*
|
|
9
|
+
* @stable
|
|
10
|
+
*/
|
|
11
|
+
interface LlamaCppNativeCounterOptions {
|
|
12
|
+
readonly model: LlamaModelInstance;
|
|
13
|
+
readonly modelPath?: string;
|
|
14
|
+
readonly id?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Counter that delegates to `model.tokenize(text)` from the loaded
|
|
18
|
+
* GGUF instance. Cache invalidation is keyed on the model file path
|
|
19
|
+
* (when supplied) so swapping models invalidates per-message caches
|
|
20
|
+
* upstream.
|
|
21
|
+
*
|
|
22
|
+
* @stable
|
|
23
|
+
*/
|
|
24
|
+
declare class LlamaCppNativeCounter implements TokenCounter {
|
|
25
|
+
#private;
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly version: string;
|
|
28
|
+
constructor(options: LlamaCppNativeCounterOptions);
|
|
29
|
+
count(messages: ReadonlyArray<Message>): Promise<number>;
|
|
30
|
+
countText(text: string): Promise<number>;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { LlamaCppNativeCounter, LlamaCppNativeCounterOptions };
|
|
34
|
+
//# sourceMappingURL=counter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"counter.d.ts","names":[],"sources":["../src/counter.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;UAmBiB,4BAAA;kBACC;;;;;;;;;;;;cAaL,qBAAA,YAAiC;;;;uBAKvB;kBAOC,cAAc,WAAW;2BAShB"}
|
package/dist/counter.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { serialiseMessageForCount, serializedToString } from "@graphorin/provider/counters";
|
|
2
|
+
|
|
3
|
+
//#region src/counter.ts
|
|
4
|
+
/**
|
|
5
|
+
* Counter that delegates to `model.tokenize(text)` from the loaded
|
|
6
|
+
* GGUF instance. Cache invalidation is keyed on the model file path
|
|
7
|
+
* (when supplied) so swapping models invalidates per-message caches
|
|
8
|
+
* upstream.
|
|
9
|
+
*
|
|
10
|
+
* @stable
|
|
11
|
+
*/
|
|
12
|
+
var LlamaCppNativeCounter = class {
|
|
13
|
+
id;
|
|
14
|
+
version;
|
|
15
|
+
#model;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.#model = options.model;
|
|
18
|
+
const fingerprint = options.modelPath !== void 0 ? hash(options.modelPath) : "unknown";
|
|
19
|
+
this.id = options.id ?? `llama-cpp-native@${fingerprint}`;
|
|
20
|
+
this.version = `llama-cpp-native-${fingerprint}-v1`;
|
|
21
|
+
}
|
|
22
|
+
async count(messages) {
|
|
23
|
+
let total = 0;
|
|
24
|
+
for (const msg of messages) {
|
|
25
|
+
const serialised = serialiseMessageForCount(msg);
|
|
26
|
+
total += this.#tokenLengthOf(serializedToString(serialised));
|
|
27
|
+
}
|
|
28
|
+
return total;
|
|
29
|
+
}
|
|
30
|
+
async countText(text) {
|
|
31
|
+
if (text.length === 0) return 0;
|
|
32
|
+
return this.#tokenLengthOf(text);
|
|
33
|
+
}
|
|
34
|
+
#tokenLengthOf(text) {
|
|
35
|
+
if (text.length === 0) return 0;
|
|
36
|
+
const tokens = this.#model.tokenize(text);
|
|
37
|
+
if (tokens === null || tokens === void 0) return 0;
|
|
38
|
+
if (typeof tokens.length === "number") return tokens.length;
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
function hash(value) {
|
|
43
|
+
let h = 0;
|
|
44
|
+
for (let i = 0; i < value.length; i++) h = h * 31 + value.charCodeAt(i) | 0;
|
|
45
|
+
return Math.abs(h).toString(16);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
export { LlamaCppNativeCounter };
|
|
50
|
+
//# sourceMappingURL=counter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"counter.js","names":["#model","#tokenLengthOf"],"sources":["../src/counter.ts"],"sourcesContent":["/**\n * `LlamaCppNativeCounter` — token counter that wraps the loaded\n * `node-llama-cpp` model's `tokenize(text)` function. The counter is\n * strictly tighter than the cl100k_base proxy because it uses the\n * exact tokenizer the GGUF file embeds.\n *\n * @packageDocumentation\n */\n\nimport type { Message, TokenCounter } from '@graphorin/core';\nimport { serialiseMessageForCount, serializedToString } from '@graphorin/provider/counters';\n\nimport type { LlamaModelInstance } from './runtime.js';\n\n/**\n * Options for {@link LlamaCppNativeCounter}.\n *\n * @stable\n */\nexport interface LlamaCppNativeCounterOptions {\n readonly model: LlamaModelInstance;\n readonly modelPath?: string;\n readonly id?: string;\n}\n\n/**\n * Counter that delegates to `model.tokenize(text)` from the loaded\n * GGUF instance. Cache invalidation is keyed on the model file path\n * (when supplied) so swapping models invalidates per-message caches\n * upstream.\n *\n * @stable\n */\nexport class LlamaCppNativeCounter implements TokenCounter {\n readonly id: string;\n readonly version: string;\n readonly #model: LlamaModelInstance;\n\n constructor(options: LlamaCppNativeCounterOptions) {\n this.#model = options.model;\n const fingerprint = options.modelPath !== undefined ? hash(options.modelPath) : 'unknown';\n this.id = options.id ?? `llama-cpp-native@${fingerprint}`;\n this.version = `llama-cpp-native-${fingerprint}-v1`;\n }\n\n async count(messages: ReadonlyArray<Message>): Promise<number> {\n let total = 0;\n for (const msg of messages) {\n const serialised = serialiseMessageForCount(msg);\n total += this.#tokenLengthOf(serializedToString(serialised));\n }\n return total;\n }\n\n async countText(text: string): Promise<number> {\n if (text.length === 0) return 0;\n return this.#tokenLengthOf(text);\n }\n\n #tokenLengthOf(text: string): number {\n if (text.length === 0) return 0;\n const tokens = this.#model.tokenize(text);\n if (tokens === null || tokens === undefined) return 0;\n if (typeof (tokens as { length?: number }).length === 'number') {\n return (tokens as { length: number }).length;\n }\n return 0;\n }\n}\n\nfunction hash(value: string): string {\n // Lightweight deterministic fingerprint — the framework only needs\n // it for cache invalidation, not for cryptographic purposes.\n let h = 0;\n for (let i = 0; i < value.length; i++) {\n h = (h * 31 + value.charCodeAt(i)) | 0;\n }\n return Math.abs(h).toString(16);\n}\n"],"mappings":";;;;;;;;;;;AAiCA,IAAa,wBAAb,MAA2D;CACzD,AAAS;CACT,AAAS;CACT,CAASA;CAET,YAAY,SAAuC;AACjD,QAAKA,QAAS,QAAQ;EACtB,MAAM,cAAc,QAAQ,cAAc,SAAY,KAAK,QAAQ,UAAU,GAAG;AAChF,OAAK,KAAK,QAAQ,MAAM,oBAAoB;AAC5C,OAAK,UAAU,oBAAoB,YAAY;;CAGjD,MAAM,MAAM,UAAmD;EAC7D,IAAI,QAAQ;AACZ,OAAK,MAAM,OAAO,UAAU;GAC1B,MAAM,aAAa,yBAAyB,IAAI;AAChD,YAAS,MAAKC,cAAe,mBAAmB,WAAW,CAAC;;AAE9D,SAAO;;CAGT,MAAM,UAAU,MAA+B;AAC7C,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,SAAO,MAAKA,cAAe,KAAK;;CAGlC,eAAe,MAAsB;AACnC,MAAI,KAAK,WAAW,EAAG,QAAO;EAC9B,MAAM,SAAS,MAAKD,MAAO,SAAS,KAAK;AACzC,MAAI,WAAW,QAAQ,WAAW,OAAW,QAAO;AACpD,MAAI,OAAQ,OAA+B,WAAW,SACpD,QAAQ,OAA8B;AAExC,SAAO;;;AAIX,SAAS,KAAK,OAAuB;CAGnC,IAAI,IAAI;AACR,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,IAChC,KAAK,IAAI,KAAK,MAAM,WAAW,EAAE,GAAI;AAEvC,QAAO,KAAK,IAAI,EAAE,CAAC,SAAS,GAAG"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { LlamaCppNodeRuntimeOverrides, LlamaInstance, LlamaModelInstance, LlamaSessionInstance } from "./runtime.js";
|
|
2
|
+
import { LlamaCppNodeAdapterOptions, llamaCppNodeAdapter } from "./adapter.js";
|
|
3
|
+
import { LlamaCppNativeCounter, LlamaCppNativeCounterOptions } from "./counter.js";
|
|
4
|
+
|
|
5
|
+
//#region src/index.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @graphorin/provider-llamacpp-node — in-process GGUF execution
|
|
9
|
+
* adapter for the Graphorin framework. The package wraps
|
|
10
|
+
* `node-llama-cpp@^3.5` to load `.gguf` model files directly into the
|
|
11
|
+
* same Node process — no daemon, no port to manage, no GPU contention
|
|
12
|
+
* with other processes.
|
|
13
|
+
*
|
|
14
|
+
* The adapter declares `trust: 'loopback'` permanently because the
|
|
15
|
+
* model lives in the same trust boundary as the host process; the
|
|
16
|
+
* symmetry mirrors `@graphorin/embedder-transformersjs` (in-process
|
|
17
|
+
* embedder; same trust boundary).
|
|
18
|
+
*
|
|
19
|
+
* The companion package is operationally simpler than the HTTP-shaped
|
|
20
|
+
* adapters but does NOT survive a process restart mid-stream — the
|
|
21
|
+
* model context lives in the process and is lost on exit. For HITL
|
|
22
|
+
* durable mid-stream resume, one of the HTTP-shaped adapters
|
|
23
|
+
* (`ollamaAdapter`, `llamaCppServerAdapter`, `openAICompatibleAdapter`)
|
|
24
|
+
* is the better choice.
|
|
25
|
+
*
|
|
26
|
+
* @packageDocumentation
|
|
27
|
+
*/
|
|
28
|
+
/** Canonical version constant. Mirrors the `package.json` version. */
|
|
29
|
+
declare const VERSION = "0.5.0";
|
|
30
|
+
//#endregion
|
|
31
|
+
export { LlamaCppNativeCounter, type LlamaCppNativeCounterOptions, type LlamaCppNodeAdapterOptions, type LlamaCppNodeRuntimeOverrides, type LlamaInstance, type LlamaModelInstance, type LlamaSessionInstance, VERSION, llamaCppNodeAdapter };
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAuBA;;;;;;;;;;;;;;;;;cAAa,OAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { llamaCppNodeAdapter } from "./adapter.js";
|
|
2
|
+
import { LlamaCppNativeCounter } from "./counter.js";
|
|
3
|
+
|
|
4
|
+
//#region src/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* @graphorin/provider-llamacpp-node — in-process GGUF execution
|
|
7
|
+
* adapter for the Graphorin framework. The package wraps
|
|
8
|
+
* `node-llama-cpp@^3.5` to load `.gguf` model files directly into the
|
|
9
|
+
* same Node process — no daemon, no port to manage, no GPU contention
|
|
10
|
+
* with other processes.
|
|
11
|
+
*
|
|
12
|
+
* The adapter declares `trust: 'loopback'` permanently because the
|
|
13
|
+
* model lives in the same trust boundary as the host process; the
|
|
14
|
+
* symmetry mirrors `@graphorin/embedder-transformersjs` (in-process
|
|
15
|
+
* embedder; same trust boundary).
|
|
16
|
+
*
|
|
17
|
+
* The companion package is operationally simpler than the HTTP-shaped
|
|
18
|
+
* adapters but does NOT survive a process restart mid-stream — the
|
|
19
|
+
* model context lives in the process and is lost on exit. For HITL
|
|
20
|
+
* durable mid-stream resume, one of the HTTP-shaped adapters
|
|
21
|
+
* (`ollamaAdapter`, `llamaCppServerAdapter`, `openAICompatibleAdapter`)
|
|
22
|
+
* is the better choice.
|
|
23
|
+
*
|
|
24
|
+
* @packageDocumentation
|
|
25
|
+
*/
|
|
26
|
+
/** Canonical version constant. Mirrors the `package.json` version. */
|
|
27
|
+
const VERSION = "0.5.0";
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
export { LlamaCppNativeCounter, VERSION, llamaCppNodeAdapter };
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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.5.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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//#region src/runtime.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Loose structural shapes covering the slice of `node-llama-cpp` we
|
|
4
|
+
* use. Re-declared here to avoid pulling the heavy native peer at
|
|
5
|
+
* type-check time.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* `Llama` engine instance (returned by `getLlama()`).
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
interface LlamaInstance {
|
|
15
|
+
loadModel(args: {
|
|
16
|
+
modelPath: string;
|
|
17
|
+
gpuLayers?: number | 'auto';
|
|
18
|
+
}): Promise<LlamaModelInstance>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Loaded GGUF model.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
interface LlamaModelInstance {
|
|
26
|
+
readonly trainContextSize?: number;
|
|
27
|
+
tokenize(text: string): readonly number[] | Uint32Array | Uint8Array;
|
|
28
|
+
createContext(args?: {
|
|
29
|
+
contextSize?: number;
|
|
30
|
+
}): Promise<{
|
|
31
|
+
getSequence(): {
|
|
32
|
+
dispose?: () => void;
|
|
33
|
+
};
|
|
34
|
+
dispose?: () => void;
|
|
35
|
+
}>;
|
|
36
|
+
dispose?(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Loaded chat session capable of streaming responses.
|
|
40
|
+
*
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
interface LlamaSessionInstance {
|
|
44
|
+
promptStreamingResponse(prompt: string, options?: {
|
|
45
|
+
readonly signal?: AbortSignal;
|
|
46
|
+
readonly maxTokens?: number;
|
|
47
|
+
readonly temperature?: number;
|
|
48
|
+
}): AsyncIterable<string>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Test-only shape for injecting fixture-driven runtime behaviour.
|
|
52
|
+
*
|
|
53
|
+
* @stable
|
|
54
|
+
*/
|
|
55
|
+
interface LlamaCppNodeRuntimeOverrides {
|
|
56
|
+
/** Returns a `LlamaInstance` (the result of `getLlama()`). */
|
|
57
|
+
readonly getLlama?: () => Promise<LlamaInstance>;
|
|
58
|
+
/**
|
|
59
|
+
* Build a streaming chat session against an already-loaded model
|
|
60
|
+
* instance. Used by the adapter to wire `model.tokenize` and
|
|
61
|
+
* `session.promptStreamingResponse` to the per-test fixture.
|
|
62
|
+
*/
|
|
63
|
+
readonly createSession?: (model: LlamaModelInstance, system?: string) => Promise<LlamaSessionInstance>;
|
|
64
|
+
/**
|
|
65
|
+
* Override the `LlamaChatSession` constructor used by the REAL
|
|
66
|
+
* default session factory (PS-3). Tests stub it; production loads it
|
|
67
|
+
* from the `node-llama-cpp` peer.
|
|
68
|
+
*/
|
|
69
|
+
readonly LlamaChatSession?: LlamaChatSessionCtor;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Structural slice of the peer's `LlamaChatSession` class used by the
|
|
73
|
+
* default session factory (PS-3): `prompt(text, { onTextChunk })`
|
|
74
|
+
* resolves with the full response while streaming chunks through the
|
|
75
|
+
* callback.
|
|
76
|
+
*
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
interface LlamaChatSessionPeer {
|
|
80
|
+
prompt(text: string, options?: {
|
|
81
|
+
readonly signal?: AbortSignal;
|
|
82
|
+
readonly maxTokens?: number;
|
|
83
|
+
readonly temperature?: number;
|
|
84
|
+
readonly onTextChunk?: (chunk: string) => void;
|
|
85
|
+
}): Promise<string>;
|
|
86
|
+
}
|
|
87
|
+
/** @internal */
|
|
88
|
+
type LlamaChatSessionCtor = new (args: {
|
|
89
|
+
readonly contextSequence: unknown;
|
|
90
|
+
readonly systemPrompt?: string;
|
|
91
|
+
}) => LlamaChatSessionPeer;
|
|
92
|
+
//#endregion
|
|
93
|
+
export { LlamaCppNodeRuntimeOverrides, LlamaInstance, LlamaModelInstance, LlamaSessionInstance };
|
|
94
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","names":[],"sources":["../src/runtime.ts"],"sourcesContent":[],"mappings":";;AAaA;AASA;;;;;;AAeA;AAgBA;;;AASW,UAjDM,aAAA,CAiDN;EAEI,SAAA,CAAA,IAAA,EAAA;IAAR,SAAA,EAAA,MAAA;IAMuB,SAAA,CAAA,EAAA,MAAA,GAAA,MAAA;EAAoB,CAAA,CAAA,EAxDqB,OAwDrB,CAxD6B,kBAwD7B,CAAA;AAWlD;AAaA;;;;;UAxEiB,kBAAA;;8CAE6B,cAAc;;;MACV;;;;;;cAIpC;;;;;;;UAQG,oBAAA;;sBAIO;;;MAInB;;;;;;;UAQY,4BAAA;;4BAEW,QAAQ;;;;;;mCAOzB,wCAEJ,QAAQ;;;;;;8BAMe;;;;;;;;;;UAWb,oBAAA;;sBAIO;;;;MAKnB;;;KAIO,oBAAA;;;MAGN"}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/runtime.ts
|
|
2
|
+
let cachedLlama = null;
|
|
3
|
+
let cachedChatSessionCtor = null;
|
|
4
|
+
/**
|
|
5
|
+
* Lazily load the `node-llama-cpp` peer and return the `Llama` engine
|
|
6
|
+
* instance. Cached per process.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
async function loadLlamaModule(overrides) {
|
|
11
|
+
if (overrides?.getLlama !== void 0) return overrides.getLlama();
|
|
12
|
+
if (cachedLlama !== null) return cachedLlama;
|
|
13
|
+
let mod;
|
|
14
|
+
try {
|
|
15
|
+
mod = await import("node-llama-cpp");
|
|
16
|
+
} catch (cause) {
|
|
17
|
+
throw new Error("[graphorin/provider-llamacpp-node] missing peer dependency 'node-llama-cpp'. Install it with `pnpm add node-llama-cpp`.", { cause });
|
|
18
|
+
}
|
|
19
|
+
if (typeof mod.getLlama !== "function") throw new Error("[graphorin/provider-llamacpp-node] installed node-llama-cpp does not expose getLlama().");
|
|
20
|
+
const instance = await mod.getLlama();
|
|
21
|
+
cachedLlama = instance;
|
|
22
|
+
return instance;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Lazily resolve the peer's `LlamaChatSession` constructor for the
|
|
26
|
+
* real default session factory (PS-3). Cached per process; the
|
|
27
|
+
* override wins for tests.
|
|
28
|
+
*
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
async function loadLlamaChatSessionCtor(overrides) {
|
|
32
|
+
if (overrides?.LlamaChatSession !== void 0) return overrides.LlamaChatSession;
|
|
33
|
+
if (cachedChatSessionCtor !== null) return cachedChatSessionCtor;
|
|
34
|
+
let mod;
|
|
35
|
+
try {
|
|
36
|
+
mod = await import("node-llama-cpp");
|
|
37
|
+
} catch (cause) {
|
|
38
|
+
throw new Error("[graphorin/provider-llamacpp-node] missing peer dependency 'node-llama-cpp'. Install it with `pnpm add node-llama-cpp`.", { cause });
|
|
39
|
+
}
|
|
40
|
+
if (typeof mod.LlamaChatSession !== "function") throw new Error("[graphorin/provider-llamacpp-node] installed node-llama-cpp does not expose LlamaChatSession.");
|
|
41
|
+
cachedChatSessionCtor = mod.LlamaChatSession;
|
|
42
|
+
return cachedChatSessionCtor;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { loadLlamaChatSessionCtor, loadLlamaModule };
|
|
47
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +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\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":";AAmGA,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
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@graphorin/provider-llamacpp-node",
|
|
3
|
+
"version": "0.5.0",
|
|
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
|
+
"license": "MIT",
|
|
6
|
+
"author": "Oleksiy Stepurenko",
|
|
7
|
+
"homepage": "https://github.com/o-stepper/graphorin/tree/main/packages/provider-llamacpp-node",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/o-stepper/graphorin.git",
|
|
11
|
+
"directory": "packages/provider-llamacpp-node"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/o-stepper/graphorin/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"graphorin",
|
|
18
|
+
"ai",
|
|
19
|
+
"agents",
|
|
20
|
+
"framework",
|
|
21
|
+
"provider",
|
|
22
|
+
"llm",
|
|
23
|
+
"llama-cpp",
|
|
24
|
+
"node-llama-cpp",
|
|
25
|
+
"gguf",
|
|
26
|
+
"in-process",
|
|
27
|
+
"local-first",
|
|
28
|
+
"loopback"
|
|
29
|
+
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=22.0.0"
|
|
33
|
+
},
|
|
34
|
+
"main": "./dist/index.js",
|
|
35
|
+
"module": "./dist/index.js",
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"import": "./dist/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"README.md",
|
|
47
|
+
"CHANGELOG.md",
|
|
48
|
+
"LICENSE"
|
|
49
|
+
],
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@graphorin/core": "0.5.0",
|
|
52
|
+
"@graphorin/provider": "0.5.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"node-llama-cpp": "^3.5.0"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"node-llama-cpp": {
|
|
59
|
+
"optional": false
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"publishConfig": {
|
|
63
|
+
"access": "public",
|
|
64
|
+
"provenance": true
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {},
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "tsdown",
|
|
69
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json",
|
|
70
|
+
"test": "vitest run",
|
|
71
|
+
"lint": "biome check .",
|
|
72
|
+
"clean": "rimraf dist .turbo *.tsbuildinfo"
|
|
73
|
+
}
|
|
74
|
+
}
|