@zenithfoundry/slm-gate 1.2.1
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/.env.example +669 -0
- package/LICENSE +21 -0
- package/README.md +317 -0
- package/configs/antigravity/.env.16gb.example +674 -0
- package/configs/antigravity/.env.24gb.example +674 -0
- package/configs/antigravity/.env.32gb.example +674 -0
- package/configs/antigravity/README.md +109 -0
- package/configs/claude-code/.env.16gb.example +674 -0
- package/configs/claude-code/.env.24gb.example +674 -0
- package/configs/claude-code/.env.32gb.example +674 -0
- package/configs/claude-code/README.md +52 -0
- package/configs/claude-desktop/.env.16gb.example +674 -0
- package/configs/claude-desktop/.env.24gb.example +674 -0
- package/configs/claude-desktop/.env.32gb.example +674 -0
- package/configs/claude-desktop/README.md +37 -0
- package/configs/cline-continue-opencode/.env.16gb.example +674 -0
- package/configs/cline-continue-opencode/.env.24gb.example +674 -0
- package/configs/cline-continue-opencode/.env.32gb.example +674 -0
- package/configs/cline-continue-opencode/README.md +34 -0
- package/configs/cursor/.env.16gb.example +674 -0
- package/configs/cursor/.env.24gb.example +674 -0
- package/configs/cursor/.env.32gb.example +674 -0
- package/configs/cursor/README.md +26 -0
- package/configs/generic-http/.env.16gb.example +674 -0
- package/configs/generic-http/.env.24gb.example +674 -0
- package/configs/generic-http/.env.32gb.example +674 -0
- package/configs/generic-http/README.md +20 -0
- package/configs/generic-stdio/.env.16gb.example +674 -0
- package/configs/generic-stdio/.env.24gb.example +674 -0
- package/configs/generic-stdio/.env.32gb.example +674 -0
- package/configs/generic-stdio/README.md +24 -0
- package/configs/preserve/README.md +26 -0
- package/configs/preserve/tls.json +61 -0
- package/dist/adapters/tech-lead-stack.js +38 -0
- package/dist/cache/index.js +173 -0
- package/dist/cli.js +256 -0
- package/dist/config.js +255 -0
- package/dist/dashboard/data.js +149 -0
- package/dist/dashboard/export.js +42 -0
- package/dist/dashboard/serve.js +63 -0
- package/dist/doctor.js +338 -0
- package/dist/hardware.js +126 -0
- package/dist/home-dir.js +39 -0
- package/dist/ledger/flush-lifecycle.js +50 -0
- package/dist/ledger/index.js +946 -0
- package/dist/ledger/report.js +69 -0
- package/dist/ledger/setup-dashboard.js +456 -0
- package/dist/ledger/smoke.js +37 -0
- package/dist/ledger/sync-config.js +177 -0
- package/dist/ledger/sync.js +307 -0
- package/dist/ledger/verify.js +185 -0
- package/dist/ledger/wipe-langfuse.js +130 -0
- package/dist/llm-gate/distill.js +239 -0
- package/dist/llm-gate/formats/anthropic.js +185 -0
- package/dist/llm-gate/formats/chat-completions.js +103 -0
- package/dist/llm-gate/formats/contract.js +29 -0
- package/dist/llm-gate/formats/gemini.js +84 -0
- package/dist/llm-gate/formats/internal.js +1 -0
- package/dist/llm-gate/formats/openai.js +77 -0
- package/dist/llm-gate/formats/responses.js +146 -0
- package/dist/llm-gate/forward.js +150 -0
- package/dist/llm-gate/index.js +40 -0
- package/dist/llm-gate/local-first.js +217 -0
- package/dist/llm-gate/pipeline.js +267 -0
- package/dist/llm-gate/server.js +289 -0
- package/dist/mcp-gate/ground.js +64 -0
- package/dist/mcp-gate/index.js +57 -0
- package/dist/mcp-gate/pipeline.js +252 -0
- package/dist/mcp-gate/server.js +302 -0
- package/dist/mcp-gate/tool-names.js +57 -0
- package/dist/models/check.js +26 -0
- package/dist/models/footprint.js +137 -0
- package/dist/models/helpers.js +91 -0
- package/dist/models/index.js +5 -0
- package/dist/models/reasoning.js +91 -0
- package/dist/models/roles.js +9 -0
- package/dist/models/slm.js +243 -0
- package/dist/models/types.js +1 -0
- package/dist/pricing/index.js +115 -0
- package/dist/pricing/plans.js +54 -0
- package/dist/pricing/providers.js +172 -0
- package/dist/resolver/index.js +277 -0
- package/dist/resolver/types.js +1 -0
- package/dist/setup/claim.js +41 -0
- package/dist/setup/gate-command.js +41 -0
- package/dist/setup/init.js +92 -0
- package/dist/setup/local-models.js +123 -0
- package/dist/setup/model-gate.js +220 -0
- package/dist/setup/notify.js +45 -0
- package/dist/setup/ollama-install.js +53 -0
- package/dist/setup/parent-watch.js +84 -0
- package/dist/setup/required-models.js +20 -0
- package/dist/setup/startup.js +132 -0
- package/dist/setup/tool-settings.js +101 -0
- package/dist/utils/backoff.js +47 -0
- package/dist/utils/compression.js +145 -0
- package/dist/utils/constants.js +22 -0
- package/dist/utils/duration.js +43 -0
- package/dist/utils/elision.js +556 -0
- package/dist/utils/embedding.js +32 -0
- package/dist/utils/entry-point.js +23 -0
- package/dist/utils/local-only.js +82 -0
- package/dist/utils/preserve-patterns.js +115 -0
- package/dist/utils/safety.js +30 -0
- package/dist/verifier/index.js +67 -0
- package/package.json +121 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview "Only programs on this computer": the one rule both slm-gate HTTP servers apply (the model
|
|
3
|
+
* gate on LLM_GATE_PORT and the MCP server's HTTP mode on MCP_GATE_PORT). Kept in one place because it is a
|
|
4
|
+
* security rule and two copies could drift.
|
|
5
|
+
*
|
|
6
|
+
* Two parts, because binding alone is not enough:
|
|
7
|
+
* - Listen on the loopback addresses only, so other machines on the network cannot connect.
|
|
8
|
+
* - Refuse requests addressed to another name (Host) or sent by a web page from another site (Origin).
|
|
9
|
+
* A page open in the browser runs on this computer too: through DNS rebinding it can reach 127.0.0.1
|
|
10
|
+
* under its own domain name, and any page can send cross-site requests to localhost.
|
|
11
|
+
*/
|
|
12
|
+
import http from 'node:http';
|
|
13
|
+
/** Names for this computer. Other spellings (`localhost.`, `127.1`, …) are refused: fail closed. */
|
|
14
|
+
const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1']);
|
|
15
|
+
/** A Host header's name without the port or IPv6 brackets, e.g. `[::1]:8787` → `::1`. */
|
|
16
|
+
function hostnameOf(host) {
|
|
17
|
+
const value = host.trim().toLowerCase();
|
|
18
|
+
if (value.startsWith('['))
|
|
19
|
+
return value.slice(1, value.indexOf(']'));
|
|
20
|
+
const colon = value.lastIndexOf(':');
|
|
21
|
+
return colon === -1 ? value : value.slice(0, colon);
|
|
22
|
+
}
|
|
23
|
+
function isLocalOrigin(origin) {
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL(origin);
|
|
26
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, '');
|
|
27
|
+
return (url.protocol === 'http:' || url.protocol === 'https:') && LOCAL_HOSTNAMES.has(hostname);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false; // includes the literal `null` sent by sandboxed pages and file:// documents
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* True for a request addressed to this computer by name, and either sent by a program (no Origin) or by a
|
|
35
|
+
* web page served from this computer. Coding tools' model clients send no Origin.
|
|
36
|
+
*/
|
|
37
|
+
export function isLocalRequest(headers) {
|
|
38
|
+
if (!headers.host || !LOCAL_HOSTNAMES.has(hostnameOf(headers.host)))
|
|
39
|
+
return false;
|
|
40
|
+
return headers.origin === undefined || isLocalOrigin(headers.origin);
|
|
41
|
+
}
|
|
42
|
+
function listenOn(params) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
params.server.once('error', reject);
|
|
45
|
+
params.server.listen({ host: params.host, port: params.port, ipv6Only: params.host === '::1' }, () => {
|
|
46
|
+
params.server.off('error', reject);
|
|
47
|
+
resolve();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Serves `handler` on 127.0.0.1 and ::1, so `localhost` works whichever address a client's resolver picks
|
|
53
|
+
* first (macOS answers ::1 first, and not every client falls back).
|
|
54
|
+
*
|
|
55
|
+
* @param params.port The port (0 picks a free one, the same for both addresses)
|
|
56
|
+
* @param params.onListening Called once both addresses listen
|
|
57
|
+
* @returns The listening servers (to close them)
|
|
58
|
+
* @throws When 127.0.0.1 cannot be used (e.g. the port is taken), or ::1 fails for any reason other than
|
|
59
|
+
* IPv6 being unavailable; nothing is left listening then
|
|
60
|
+
*/
|
|
61
|
+
export async function listenOnThisComputer(params) {
|
|
62
|
+
const ipv4 = http.createServer(params.handler);
|
|
63
|
+
await listenOn({ server: ipv4, host: '127.0.0.1', port: params.port });
|
|
64
|
+
const port = ipv4.address().port;
|
|
65
|
+
const ipv6 = http.createServer(params.handler);
|
|
66
|
+
try {
|
|
67
|
+
await listenOn({ server: ipv6, host: '::1', port });
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
const code = err.code;
|
|
71
|
+
if (code !== 'EADDRNOTAVAIL' && code !== 'EAFNOSUPPORT') {
|
|
72
|
+
// E.g. another program holds [::1]:port: `localhost` clients would reach it, not us.
|
|
73
|
+
await new Promise(resolve => ipv4.close(resolve));
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
console.error(`[slm-gate] IPv6 is not available on this computer; listening on 127.0.0.1:${port} only.`);
|
|
77
|
+
params.onListening?.();
|
|
78
|
+
return [ipv4];
|
|
79
|
+
}
|
|
80
|
+
params.onListening?.();
|
|
81
|
+
return [ipv4, ipv6];
|
|
82
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import { CONFIG } from '../config.js';
|
|
3
|
+
/**
|
|
4
|
+
* A rigid set of baseline RegExp patterns used to identify lines that MUST NEVER be dropped
|
|
5
|
+
* during local offline Small Language Model (SLM) contextual compression.
|
|
6
|
+
*
|
|
7
|
+
* The system ensures that any line matching these patterns is explicitly preserved and shielded
|
|
8
|
+
* via placeholder substitution before handing the text to the SLM. This guarantees that
|
|
9
|
+
* critical structural integrity, hard requirements, and specific directives survive the
|
|
10
|
+
* lossy distillation process.
|
|
11
|
+
*/
|
|
12
|
+
export const BUILTIN_PATTERNS = [
|
|
13
|
+
// requirement keywords
|
|
14
|
+
/.*(?:MUST|MUST NOT|SHALL|REQUIRED|SHOULD NOT|NEVER|ALWAYS|DO NOT|MANDATORY|PROHIBITED|IMPORTANT|CRITICAL|WARNING|CAUTION).*/i,
|
|
15
|
+
// markdown headings
|
|
16
|
+
/^#{1,6}\s/,
|
|
17
|
+
// numbered steps
|
|
18
|
+
/^\s*\d+[.)]\s/,
|
|
19
|
+
// checklist items
|
|
20
|
+
/^\s*[-*]\s+\[[ xX]\]/,
|
|
21
|
+
// fenced code blocks / inline code lines (contain backticks)
|
|
22
|
+
/`/,
|
|
23
|
+
// URLs
|
|
24
|
+
/https?:\/\//,
|
|
25
|
+
// path/KEY[:=]value config lines
|
|
26
|
+
/^[A-Za-z0-9_.\/-]+[:=]/,
|
|
27
|
+
// output/format directives
|
|
28
|
+
/(?:format:|respond with|output must|Answer:)/i,
|
|
29
|
+
// hard quantities
|
|
30
|
+
/(?:no more than \d|at most|exactly \d|maximum|minimum)/i,
|
|
31
|
+
// YAML frontmatter block markers
|
|
32
|
+
/^---/,
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Attempts to compile a regular expression string into an executable RegExp object.
|
|
36
|
+
* It first attempts to use `re2` (a fast, linear-time regex engine safe against ReDoS attacks)
|
|
37
|
+
* if installed. If `re2` is missing or rejects the pattern (e.g., due to unsupported features
|
|
38
|
+
* like complex lookaheads), it gracefully falls back to the native V8 RegExp engine.
|
|
39
|
+
*
|
|
40
|
+
* @param pattern - The raw regular expression string to compile
|
|
41
|
+
* @returns The compiled RegExp instance, or null if the pattern is fatally invalid
|
|
42
|
+
*/
|
|
43
|
+
async function compilePattern(pattern) {
|
|
44
|
+
let Re2;
|
|
45
|
+
try {
|
|
46
|
+
// @ts-ignore - optional dependency
|
|
47
|
+
Re2 = (await import('re2')).default;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// re2 not installed, will fallback
|
|
51
|
+
}
|
|
52
|
+
if (Re2) {
|
|
53
|
+
try {
|
|
54
|
+
return new Re2(pattern);
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
// Re2 threw (e.g. unsupported syntax like lookaheads), fallback to native RegExp
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
return new RegExp(pattern);
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
console.error(`[distill] Warning: Skipping invalid regex pattern: ${pattern}`);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Constructs the aggregate array of preservation Regex patterns for the current session.
|
|
70
|
+
* Depending on the configuration (`DISTILL_PRESERVE_MODE`), it will either:
|
|
71
|
+
* - 'extend': Combine `BUILTIN_PATTERNS`, user-defined JSON patterns, and adapter-specific patterns.
|
|
72
|
+
* - 'replace': Ignore `BUILTIN_PATTERNS` entirely, relying only on user/adapter specifications.
|
|
73
|
+
*
|
|
74
|
+
* @returns A promise resolving to the final array of compiled RegExp patterns
|
|
75
|
+
*/
|
|
76
|
+
export async function buildPreserveList() {
|
|
77
|
+
let patterns = [];
|
|
78
|
+
const mode = CONFIG.DISTILL_PRESERVE_MODE || 'extend';
|
|
79
|
+
if (mode === 'extend') {
|
|
80
|
+
patterns = [...BUILTIN_PATTERNS];
|
|
81
|
+
}
|
|
82
|
+
// Load custom preservation patterns defined in an external JSON file by the user
|
|
83
|
+
if (CONFIG.DISTILL_PRESERVE_PATH) {
|
|
84
|
+
try {
|
|
85
|
+
const data = await fs.readFile(CONFIG.DISTILL_PRESERVE_PATH, 'utf-8');
|
|
86
|
+
const parsed = JSON.parse(data);
|
|
87
|
+
if (Array.isArray(parsed.patterns)) {
|
|
88
|
+
for (const p of parsed.patterns) {
|
|
89
|
+
const compiled = await compilePattern(p);
|
|
90
|
+
if (compiled) {
|
|
91
|
+
patterns.push(compiled);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
console.error(`[distill] Warning: Failed to read or parse user preserve patterns from ${CONFIG.DISTILL_PRESERVE_PATH}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Optionally load tech-lead-stack patterns if running in decoupled integration mode
|
|
101
|
+
if (CONFIG.TLS_ADAPTER) {
|
|
102
|
+
try {
|
|
103
|
+
// Guarded dynamic import
|
|
104
|
+
// @ts-ignore - Decoupling: adapter may not exist in pure MCP-gate configurations
|
|
105
|
+
const adapter = await import('../adapters/tech-lead-stack.js');
|
|
106
|
+
if (adapter.tlsPreservePatterns) {
|
|
107
|
+
patterns.push(...adapter.tlsPreservePatterns);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
console.error(`[distill] Warning: Failed to load TLS adapter patterns`, e);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return patterns;
|
|
115
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { TOOL_RESULT_PREFIXES, CURSOR_TOOL_MARKERS } from './constants.js';
|
|
2
|
+
/**
|
|
3
|
+
* Heuristic to detect if the *last* message in the conversation actually represents
|
|
4
|
+
* a tool response or a file output, rather than the user's own turn.
|
|
5
|
+
*
|
|
6
|
+
* If it is a tool output, it's unsafe to locally defer since we shouldn't execute
|
|
7
|
+
* commands derived directly from data.
|
|
8
|
+
*/
|
|
9
|
+
export function isLatestInstructionFromTool(messages) {
|
|
10
|
+
if (messages.length === 0)
|
|
11
|
+
return false;
|
|
12
|
+
const last = messages[messages.length - 1];
|
|
13
|
+
if (last.role === 'tool') {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
// Some clients encode tool results as 'user' or 'assistant' messages.
|
|
17
|
+
const content = last.content.trim();
|
|
18
|
+
if (TOOL_RESULT_PREFIXES.some(prefix => content.startsWith(prefix))) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (CURSOR_TOOL_MARKERS.some(marker => content.includes(marker))) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
// A very common pattern: JSON objects embedded directly at the start that look like tool returns
|
|
25
|
+
if (content.startsWith('{"') && content.includes('}') && (content.includes('result') || content.includes('output'))) {
|
|
26
|
+
// Basic heuristic for raw JSON return
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Verifier Module for small-language-model-gate.
|
|
3
|
+
*
|
|
4
|
+
* This module is responsible for analyzing the output of the local Small Language Model (SLM)
|
|
5
|
+
* and deciding whether the response is acceptable or if the request must be escalated to the
|
|
6
|
+
* downstream cloud LLM. It acts as a safety and quality gate.
|
|
7
|
+
*
|
|
8
|
+
* It is primarily used by the `llm-gate` routing layer to evaluate the confidence and
|
|
9
|
+
* structural validity of local inferences before deferring to them.
|
|
10
|
+
*
|
|
11
|
+
* The verification strictness is monotonic (levels 1-5), meaning each level is a superset
|
|
12
|
+
* of the one below it. As the strictness level increases, the likelihood of escalation also increases.
|
|
13
|
+
*
|
|
14
|
+
* Strictness Levels:
|
|
15
|
+
* - Level 1: Escalates if the answer is completely empty.
|
|
16
|
+
* - Level 2: Escalates if the answer fails a provided regex format constraint.
|
|
17
|
+
* - Level 3: Escalates if the answer contains hedging language (e.g., "I'm not sure").
|
|
18
|
+
* - Level 4: Escalates if multiple generated samples disagree with each other.
|
|
19
|
+
* - Level 5: Always escalates to the cloud model (bypasses local SLM entirely).
|
|
20
|
+
*/
|
|
21
|
+
import { checkAgreement } from '../models/reasoning.js';
|
|
22
|
+
export const HEDGING_LEXICON = [
|
|
23
|
+
"i'm not sure",
|
|
24
|
+
"i am not sure",
|
|
25
|
+
"i think",
|
|
26
|
+
"possibly",
|
|
27
|
+
"cannot determine",
|
|
28
|
+
"unclear",
|
|
29
|
+
"it's unclear"
|
|
30
|
+
];
|
|
31
|
+
const HEDGING_REGEX = new RegExp(`\\b(${HEDGING_LEXICON.join('|')})\\b`, 'i');
|
|
32
|
+
export function verify(answer, samples, constraints, level) {
|
|
33
|
+
const flags = [];
|
|
34
|
+
// Level 1: empty or unparseable
|
|
35
|
+
if (level >= 1) {
|
|
36
|
+
if (answer.trim() === '') {
|
|
37
|
+
flags.push('empty');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Level 2: fails format constraint
|
|
41
|
+
if (level >= 2) {
|
|
42
|
+
if (constraints.formatRe && !constraints.formatRe.test(answer)) {
|
|
43
|
+
flags.push('format_mismatch');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Level 3: hedging language
|
|
47
|
+
if (level >= 3) {
|
|
48
|
+
if (HEDGING_REGEX.test(answer)) {
|
|
49
|
+
flags.push('hedging');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Level 4: samples disagree
|
|
53
|
+
if (level >= 4) {
|
|
54
|
+
if (samples.length > 0 && checkAgreement(samples) === null) {
|
|
55
|
+
flags.push('disagreement');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Level 5: always escalate
|
|
59
|
+
if (level >= 5) {
|
|
60
|
+
flags.push('always_escalate');
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
escalate: flags.length > 0,
|
|
64
|
+
level,
|
|
65
|
+
flags
|
|
66
|
+
};
|
|
67
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zenithfoundry/slm-gate",
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A decoupled local-SLM pre-processing and routing layer for MCP and cloud LLMs. Intercepts, distills, and routes prompts locally with Ollama to cut token costs and protect monthly quota.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "bronz3beard",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/zenithfoundry/slm-gate.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/zenithfoundry/slm-gate/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/zenithfoundry/slm-gate#readme",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ai",
|
|
18
|
+
"slm",
|
|
19
|
+
"small-language-model",
|
|
20
|
+
"mcp",
|
|
21
|
+
"mcp-server",
|
|
22
|
+
"model-context-protocol",
|
|
23
|
+
"local-ai",
|
|
24
|
+
"ollama",
|
|
25
|
+
"qwen",
|
|
26
|
+
"prompt-compression",
|
|
27
|
+
"llm-routing",
|
|
28
|
+
"token-optimization",
|
|
29
|
+
"cost-reduction",
|
|
30
|
+
"agentic-workflows",
|
|
31
|
+
"ai-agents",
|
|
32
|
+
"antigravity",
|
|
33
|
+
"cursor",
|
|
34
|
+
"claude-code"
|
|
35
|
+
],
|
|
36
|
+
"bin": {
|
|
37
|
+
"slm-gate": "./dist/cli.js"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"!dist/**/*.test.js",
|
|
42
|
+
"configs",
|
|
43
|
+
".env.example"
|
|
44
|
+
],
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=22",
|
|
47
|
+
"pnpm": ">=10"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@langfuse/client": "^5.11.1",
|
|
51
|
+
"@langfuse/otel": "^5.11.1",
|
|
52
|
+
"@langfuse/tracing": "^5.11.1",
|
|
53
|
+
"@modelcontextprotocol/sdk": "^1.30.1",
|
|
54
|
+
"@opentelemetry/sdk-node": "^0.222.0",
|
|
55
|
+
"better-sqlite3": "^13.0.3",
|
|
56
|
+
"dotenv": "^16.4.5",
|
|
57
|
+
"markdown-it": "^15.0.2",
|
|
58
|
+
"ollama": "^0.6.3",
|
|
59
|
+
"zod": "^3.23.8",
|
|
60
|
+
"zod-to-json-schema": "^3.23.1"
|
|
61
|
+
},
|
|
62
|
+
"optionalDependencies": {
|
|
63
|
+
"re2": "^1.21.4"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@jest/globals": "^30.5.2",
|
|
67
|
+
"@types/better-sqlite3": "^7.6.11",
|
|
68
|
+
"@types/jest": "^29.5.12",
|
|
69
|
+
"@types/markdown-it": "^14.2.0",
|
|
70
|
+
"@types/node": "^20.14.9",
|
|
71
|
+
"fast-check": "^4.10.2",
|
|
72
|
+
"jest": "^29.7.0",
|
|
73
|
+
"ts-jest": "^29.4.13",
|
|
74
|
+
"tsx": "^4.23.15",
|
|
75
|
+
"typescript": "^5.5.3"
|
|
76
|
+
},
|
|
77
|
+
"publishConfig": {
|
|
78
|
+
"access": "public"
|
|
79
|
+
},
|
|
80
|
+
"scripts": {
|
|
81
|
+
"slm-gate": "tsx src/cli.ts",
|
|
82
|
+
"build": "tsc",
|
|
83
|
+
"dev": "tsx src/cli.ts",
|
|
84
|
+
"config": "node --import tsx src/config.ts",
|
|
85
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests",
|
|
86
|
+
"test:summary": "NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests --reporters=\"summary\"",
|
|
87
|
+
"test:decoupling": "bash -c 'trap \"mv .src_adapters_tmp src/adapters 2>/dev/null\" EXIT; mv src/adapters .src_adapters_tmp; pnpm run build && pnpm run test'",
|
|
88
|
+
"models:check": "node --import tsx src/models/check.ts",
|
|
89
|
+
"ledger:smoke": "tsx src/ledger/smoke.ts",
|
|
90
|
+
"llm-gate:serve": "tsx src/llm-gate/index.ts",
|
|
91
|
+
"mcp-gate:stdio": "tsx src/mcp-gate/index.ts",
|
|
92
|
+
"mcp-gate:http": "tsx src/mcp-gate/index.ts",
|
|
93
|
+
"e2e:standalone-mcp": "tsx scripts/e2e-standalone-mcp.ts",
|
|
94
|
+
"e2e:llm-gate-routing": "tsx scripts/e2e-llm-gate-routing.ts",
|
|
95
|
+
"e2e:distill-fallback": "tsx scripts/e2e-distill-fallback.ts",
|
|
96
|
+
"e2e:resolver-ambiguities": "tsx scripts/e2e-resolver-ambiguities.ts",
|
|
97
|
+
"smoke:e2e": "tsx scripts/smoke-e2e.ts",
|
|
98
|
+
"e2e:tls-downstream": "tsx scripts/e2e-tls-downstream.ts",
|
|
99
|
+
"e2e:npm-install": "tsx scripts/e2e-npm-install.ts",
|
|
100
|
+
"test:e2e": "pnpm run build && pnpm run e2e:standalone-mcp && pnpm run e2e:llm-gate-routing && pnpm run e2e:distill-fallback && pnpm run e2e:resolver-ambiguities && pnpm run smoke:e2e",
|
|
101
|
+
"e2e": "pnpm run test:e2e",
|
|
102
|
+
"bench": "tsx harness/run.ts",
|
|
103
|
+
"bench:reset": "rm -rf harness/.cache && pnpm run bench",
|
|
104
|
+
"metrics": "tsx harness/metrics.ts",
|
|
105
|
+
"migrate:provider": "tsx scripts/migrations/001-add-provider.ts",
|
|
106
|
+
"ledger:sync": "tsx src/ledger/sync.ts",
|
|
107
|
+
"ledger:verify": "tsx src/ledger/verify.ts",
|
|
108
|
+
"ledger:report": "tsx src/ledger/report.ts",
|
|
109
|
+
"dashboard": "tsx src/dashboard/serve.ts",
|
|
110
|
+
"dashboard:export": "tsx src/dashboard/export.ts",
|
|
111
|
+
"ledger:reset": "rm -f output/ledger.sqlite output/ledger.sqlite-wal output/ledger.sqlite-shm output/deferral_curve.svg output/leaderboard.md",
|
|
112
|
+
"ledger:wipe-langfuse": "tsx src/ledger/wipe-langfuse.ts",
|
|
113
|
+
"langfuse:wipe": "tsx src/ledger/wipe-langfuse.ts",
|
|
114
|
+
"langfuse:setup-dashboard": "tsx src/ledger/setup-dashboard.ts",
|
|
115
|
+
"elision:stats": "node scripts/elision-clean.mjs --stats",
|
|
116
|
+
"elision:clean": "node scripts/elision-clean.mjs --interactive",
|
|
117
|
+
"elision:clean:dry": "node scripts/elision-clean.mjs --older-than 180d --dry-run",
|
|
118
|
+
"elision:clean:old": "node scripts/elision-clean.mjs --older-than 180d",
|
|
119
|
+
"elision:clean:all": "node scripts/elision-clean.mjs --all"
|
|
120
|
+
}
|
|
121
|
+
}
|