{"id":50438224,"url":"https://github.com/saanvijay/llm-nexus","last_synced_at":"2026-05-31T18:02:07.950Z","repository":{"id":349275794,"uuid":"1196621614","full_name":"saanvijay/LLM-nexus","owner":"saanvijay","description":"A transparent HTTPS proxy between any AI client and upstream LLMs — redacts PII, compresses prompts, caches semantically similar responses, and surfaces everything in a real-time dashboard. Configurable via a single JSON file with upstream proxy chaining and MCP server integration.","archived":false,"fork":false,"pushed_at":"2026-04-18T15:44:21.000Z","size":163,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-18T17:31:06.154Z","etag":null,"topics":["guardrails","large-language-models","model-context-protocol","pii-redaction","prompt-compression","proxy-chain","proxy-server","token"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/saanvijay.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-30T21:58:23.000Z","updated_at":"2026-04-18T15:44:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/saanvijay/LLM-nexus","commit_stats":null,"previous_names":["saanvijay/llm-nexus"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/saanvijay/LLM-nexus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saanvijay%2FLLM-nexus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saanvijay%2FLLM-nexus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saanvijay%2FLLM-nexus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saanvijay%2FLLM-nexus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/saanvijay","download_url":"https://codeload.github.com/saanvijay/LLM-nexus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saanvijay%2FLLM-nexus/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33742187,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["guardrails","large-language-models","model-context-protocol","pii-redaction","prompt-compression","proxy-chain","proxy-server","token"],"created_at":"2026-05-31T18:02:03.966Z","updated_at":"2026-05-31T18:02:07.941Z","avatar_url":"https://github.com/saanvijay.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LLM-nexus\n\n[![CI](https://github.com/saanvijay/LLM-nexus/actions/workflows/ci.yml/badge.svg)](https://github.com/saanvijay/LLM-nexus/actions/workflows/ci.yml)\n[![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen?logo=node.js)](https://nodejs.org)\n[![License](https://img.shields.io/github/license/saanvijay/LLM-nexus)](https://github.com/saanvijay/LLM-nexus/blob/main/LICENSE)\n[![Commit Activity](https://img.shields.io/github/commit-activity/m/saanvijay/LLM-nexus)](https://github.com/saanvijay/LLM-nexus/commits/main)\n[![Last Commit](https://img.shields.io/github/last-commit/saanvijay/LLM-nexus)](https://github.com/saanvijay/LLM-nexus/commits/main)\n[![Issues](https://img.shields.io/github/issues/saanvijay/LLM-nexus)](https://github.com/saanvijay/LLM-nexus/issues)\n\n**LLM-Nexus** is a lightweight MITM proxy that sits between any AI client and the upstream LLM. It provides:\n\n- **Observability** — intercepts every request, logs prompts and completions with accurate BPE token counts, and streams everything to a real-time dashboard\n- **Cost reduction** — compresses verbose prompts to strip filler tokens, then serves repeated or semantically similar prompts from an in-memory cache, skipping the upstream LLM call entirely\n- **Privacy guardrails** — redacts PII (emails, API keys, SSNs, credit cards, and more) from every request before it is logged, cached, or forwarded\n- **Agent integration** — exposes an MCP server so any MCP-compatible AI agent (Claude Desktop, custom agents) can query logs, stats, and cache as tools\n\n---\n\n## Project Structure\n\n```\nconfig/\n├── config.json               # Proxy settings (port, logLevel, redactPII, etc.)\n└── pii.config.json           # PII redaction rules — add or disable rules here\n\nbackend/\n├── proxy/\n│   ├── server.js             # Entry point — proxy + dashboard startup\n│   ├── handler.js            # Request / response forwarding logic\n│   └── certManager.js        # CA + per-host TLS cert generation \u0026 cache\n├── dashboard/\n│   ├── server.js             # HTTP server for dashboard UI + REST API (port 3001)\n│   └── store.js              # In-memory log store with SSE broadcast\n├── mcp/\n│   └── server.js             # MCP stdio server — AI agent tool integration\n└── utils/\n    ├── logger.js             # Prompt/response extraction and log formatting\n    ├── cache.js              # In-memory prompt cache (exact + similarity matching)\n    ├── tokenizer.js          # Real BPE token counting via tiktoken\n    ├── simpleOps.js          # Simple file-op detection and interception\n    └── redactor.js           # PII guardrail — redacts sensitive data before forwarding\n\nfrontend/\n└── index.html                # Observability dashboard (single-file, no build step)\n```\n\n## Request pipeline\n\n![LLM-Nexus Request Pipeline](assets/flow-diagram.svg)\n\n\u003e Source: [assets/flow-diagram.excalidraw](assets/flow-diagram.excalidraw) — open in [Excalidraw](https://excalidraw.com) to edit.\n\n| Step | Action |\n|---|---|\n| 1 | **Simple-op check** — intercept immediately, return manual instruction *(if `saveToken: true`)* |\n| 2 | **Exact cache hit** — replay stored response, skip LLM |\n| 3 | **Similar cache hit** — replay best matching cached response, skip LLM |\n| 4 | **Upstream LLM call** — forward request, cache response, push to dashboard store |\n\n---\n\n## Setup\n\n**1. Install dependencies**\n\n```bash\ncd backend \u0026\u0026 npm install\n```\n\n**2. Start the proxy**\n\n```bash\nnode proxy/server.js\n```\n\nThis starts two servers simultaneously:\n- **Proxy** on `http://localhost:3000` — intercepts all LLM traffic\n- **Dashboard** on `http://localhost:3001` — observability UI + REST API\n\nOn first run a self-signed CA certificate is generated and saved to `backend/certs/`. The startup output prints the exact command to trust it.\n\n**3. Trust the CA cert (macOS, run once)**\n\n```bash\nsudo security add-trusted-cert -d -r trustRoot \\\n  -k /Library/Keychains/System.keychain \\\n  backend/certs/ca.crt\n```\n\n**4. Tell Node.js about the CA cert**\n\nAdd to `~/.zprofile` (not `~/.zshrc` — GUI apps like VS Code don't read `~/.zshrc`):\n\n```bash\nexport NODE_EXTRA_CA_CERTS=\"/Users/\u003cyour-username\u003e/LLM-nexus/backend/certs/ca.crt\"\n```\n\nApply immediately:\n\n```bash\nlaunchctl setenv NODE_EXTRA_CA_CERTS \"/Users/\u003cyour-username\u003e/LLM-nexus/backend/certs/ca.crt\"\n```\n\n**5. Export proxy env vars**\n\nAdd to `~/.zprofile`:\n\n```bash\nexport HTTP_PROXY=http://localhost:3000\nexport HTTPS_PROXY=http://localhost:3000\n```\n\n\u003e `HTTPS_PROXY` is required for LLM APIs — all Anthropic and OpenAI traffic is HTTPS. `HTTP_PROXY` alone will not intercept it.\n\nApply immediately:\n\n```bash\nlaunchctl setenv HTTP_PROXY \"http://localhost:3000\"\nlaunchctl setenv HTTPS_PROXY \"http://localhost:3000\"\nsource ~/.zprofile\n```\n\n**6. VS Code setting** (catches anything Electron still rejects)\n\nAdd to VS Code `settings.json`:\n\n```json\n\"http.proxyStrictSSL\": false\n```\n\n**7. Restart VS Code** (Cmd+Q — not just close the window) so Copilot picks up all changes.\n\n---\n\n## Using with CLI tools (Claude CLI, curl, etc.)\n\nGUI apps like VS Code pick up env vars from `launchctl`. CLI tools launched from a terminal only see what is exported in that shell session.\n\nBefore launching any CLI tool you want to intercept, export all three vars in the same terminal:\n\n```bash\nexport HTTP_PROXY=http://localhost:3000\nexport HTTPS_PROXY=http://localhost:3000\nexport NODE_EXTRA_CA_CERTS=\"/Users/\u003cyour-username\u003e/LLM-nexus/backend/certs/ca.crt\"\nclaude   # or any other CLI\n```\n\nIf you see **\"SSL certificate verification failed\"** with a CLI tool, the most common cause is that `NODE_EXTRA_CA_CERTS` is not set (or points to a stale path). Verify with:\n\n```bash\necho $NODE_EXTRA_CA_CERTS\n```\n\nIt must point to `backend/certs/ca.crt` inside this repo. If the path is wrong or empty, set it in the current shell before retrying.\n\n---\n\n## Observability Dashboard\n\nOpen `http://localhost:3001` in any browser after starting the proxy.\n\n### Stats bar\n\n| Metric | Description |\n|---|---|\n| Total Calls | All intercepted requests |\n| Total Tokens | Cumulative tokens across all LLM calls |\n| Cache Hits | Exact + similarity hits served from cache |\n| Avg Latency | Mean round-trip time for upstream LLM calls |\n\n### Filter tabs\n\n- **All** — every intercepted event\n- **LLM Calls** — upstream completions with full prompt/response detail\n- **Cache Hits** — requests served from cache, including similarity score\n- **Simple Ops** — file operations intercepted before reaching the LLM\n\n### Detail panel\n\nClicking any entry in the list opens a detail panel showing:\n- Model name, HTTP status, latency\n- Token breakdown cards — System / Input / Output / Total\n- Full system prompt, user input, and LLM output with syntax-highlighted sections\n\n### Live feed\n\nThe dashboard connects to the proxy via Server-Sent Events and updates in real time without polling or page refresh. The green dot in the header indicates an active SSE connection.\n\n---\n\n## REST API\n\nThe dashboard server exposes a REST API on port 3001 that any HTTP client or agent can call.\n\n| Method | Path | Description |\n|---|---|---|\n| `GET` | `/api/logs` | All stored log entries (newest first) |\n| `GET` | `/api/logs?type=llm` | Filter by type: `llm`, `cache_hit`, `simple_op` |\n| `GET` | `/api/logs?query=async` | Full-text search across all log fields |\n| `GET` | `/api/logs?limit=20` | Limit result count (max 200) |\n| `GET` | `/api/stats` | Aggregate statistics (calls, tokens, cache hits, latency) |\n| `GET` | `/api/cache` | Cache entry count, similarity threshold, key previews |\n| `DELETE` | `/api/cache` | Clear the entire prompt cache |\n| `GET` | `/api/config` | Current proxy configuration |\n| `GET` | `/api/stream` | SSE live feed of new log entries |\n\nParameters can be combined: `/api/logs?type=llm\u0026query=async\u0026limit=10`\n\n---\n\n## MCP Server (AI Agent Integration)\n\nThe MCP (Model Context Protocol) server lets any MCP-compatible AI agent — Claude Desktop, custom agents, or agent frameworks — call this proxy's functions as tools.\n\n### Start the MCP server\n\n```bash\nnode backend/mcp/server.js\n```\n\nThe MCP server communicates over **stdio** (standard MCP convention) and talks to the dashboard REST API on `localhost:3001`. The proxy must be running first.\n\n### Available tools\n\n| Tool | Description |\n|---|---|\n| `get_logs` | Retrieve already-processed logs — filterable by `type`, `query`, `limit` |\n| `get_stats` | Aggregate stats over processed requests: calls, tokens, cache hits, avg latency |\n| `get_cache_info` | Cache entry count, similarity threshold, key previews |\n| `search_logs` | Full-text search across already-processed log entries |\n\n### Connect to Claude Desktop\n\nAdd to `~/.claude/claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"llm-nexus\": {\n      \"command\": \"node\",\n      \"args\": [\"/Users/\u003cyour-username\u003e/LLM-nexus/backend/mcp/server.js\"]\n    }\n  }\n}\n```\n\nRestart Claude Desktop. The tools will appear automatically under the llm-nexus server.\n\n### Connect to any MCP-compatible agent\n\nAny agent that supports the Model Context Protocol can connect by launching the server as a subprocess and communicating via stdin/stdout. The server name is `llm-nexus`, version `1.0.0`.\n\n---\n\n## Features\n\n### Token counting\n\nEvery intercepted request is tokenised with [tiktoken](https://github.com/openai/tiktoken) — the same BPE tokeniser used by OpenAI models. Token counts are computed locally from the actual prompt and response text.\n\nThe model is read from the request body and the correct encoding is selected automatically:\n\n| Model prefix | Encoding |\n|---|---|\n| `gpt-4o` | `o200k_base` |\n| `gpt-4`, `gpt-3.5` | `cl100k_base` |\n| `text-davinci` | `p50k_base` |\n| Unknown | `cl100k_base` (fallback) |\n\n### Prompt cache\n\nIdentical and similar prompts are served from an in-memory cache, skipping the upstream LLM call entirely.\n\n**Exact match** — the full prompt text is the cache key. Same prompt → instant replay.\n\n**Similarity match** — prompts are tokenised into word sets and compared with Jaccard similarity. Any prompt scoring ≥ 75% against a cached entry is a hit. The threshold is configurable via `SIMILARITY_THRESHOLD` in [backend/utils/cache.js](backend/utils/cache.js).\n\n### Prompt compression\n\nBefore a request is forwarded to the upstream LLM (or stored in the cache), the proxy runs the prompt through a multi-pass compressor that reduces token count while preserving meaning. Enabled by default via `compressPrompts: true` in `config.json`.\n\nThe compressor is implemented in [backend/utils/compressor.js](backend/utils/compressor.js) and applies 13 rules in order:\n\n| Pass | Rules | Example |\n|---|---|---|\n| Whitespace | Trailing spaces, 3+ blank lines, multiple spaces | `\"too   many  spaces\"` → `\"too many spaces\"` |\n| Punctuation | Repeated `!!!`, `???`, `....` | `\"really???\"` → `\"really?\"` |\n| AI filler | Self-introductions, hollow openers | `\"Certainly! I'd be happy to help.\"` → `\"\"` |\n| Verbose connectives | Long phrases → short equivalents | `\"In order to\"` → `\"To\"`, `\"Due to the fact that\"` → `\"Because\"` |\n| Verbose instructions | Wordy imperatives | `\"Please make sure that you\"` → `\"Ensure\"` |\n| Redundant qualifiers | Words that add length without precision | `\"very unique\"` → `\"unique\"`, `\"basically\"` → removed |\n| Deduplication | Identical adjacent sentences removed | Second copy of a repeated instruction dropped |\n\n**Token savings** are computed with tiktoken (same BPE tokeniser used for input/output counts) and logged on every call:\n\n```\n[COMPRESS] 24 tokens saved (28% reduction: 87 → 63)\n```\n\nThe dashboard detail panel shows a **Compression card** with a before/after bar chart for each LLM call, and the stats bar tracks cumulative **Tokens Saved** across the session.\n\nTo disable, set in `config.json`:\n\n```json\n\"compressPrompts\": false\n```\n\n### Simple-op interception\n\nPrompts describing trivial file-system operations are intercepted before reaching the LLM. Enabled only when `saveToken: true` is set in config (default: `false`).\n\n| Prompt contains | Operation |\n|---|---|\n| `create/add/make a file` | Create / Add File |\n| `delete/remove a file` | Delete / Remove File |\n| `move file/directory` | Move File / Directory |\n| `rename file/directory` | Rename File / Directory |\n| `copy file/directory` | Copy File / Directory |\n| `add a comment` | Add Comment |\n\n### Log levels\n\nSet `logLevel` in `config.json` or via the `LOG_LEVEL` environment variable.\n\n| Level | Behaviour |\n|---|---|\n| `INFO` (default) | Only LLM calls — prompts, responses, cache hits, simple ops |\n| `DEBUG` | Everything including raw HTTP traffic, telemetry, REST requests |\n\n### PII Redaction\n\nWhen `redactPII: true` is set in `config.json` (default), the proxy scrubs Personally Identifiable Information from every request **before** it is logged, cached, or forwarded upstream. The original value is never stored anywhere.\n\nRules are defined in [config/pii.config.json](config/pii.config.json). Each rule has:\n\n| Field | Description |\n|---|---|\n| `name` | Canonical placeholder label, e.g. `EMAIL` → replaced with `[EMAIL]` |\n| `aliases` | Alternative names to reference this rule (e.g. `emailAddress`, `email_address`, `mail`) |\n| `description` | Human-readable explanation of what the rule detects |\n| `pattern` | JSON-escaped regex pattern string |\n| `flags` | Regex flags — `g`, `gi`, etc. |\n| `enabled` | Set to `false` to skip a rule without deleting it |\n\n#### Built-in rules\n\n| Name | Aliases | Detects |\n|---|---|---|\n| `API_KEY` | `apiKey`, `api_key`, `token`, `secret` | OpenAI `sk-...`, Anthropic `sk-ant-...`, GitHub `ghp_/gho_/ghs_`, Bearer tokens |\n| `CREDIT_CARD` | `creditCard`, `credit_card`, `cardNumber`, `card_number` | Visa, Mastercard, Amex, Discover 16-digit numbers |\n| `BANK_ACCOUNT` | `bankAccount`, `bank_account`, `accountNumber`, `routingNumber` | Account/routing numbers preceded by a label |\n| `SSN` | `ssn`, `socialSecurity`, `social_security`, `taxId`, `tax_id` | US Social Security Number (NNN-NN-NNNN) |\n| `PASSPORT` | `passport`, `passportNumber`, `passport_number` | 1-2 uppercase letters + 6-9 digits |\n| `EMAIL` | `email`, `emailAddress`, `email_address`, `mail` | Email addresses |\n| `PHONE` | `phone`, `phoneNumber`, `phone_number`, `mobile`, `cell` | US and international phone numbers |\n| `IP_ADDRESS` | `ipAddress`, `ip_address`, `ip`, `ipv4` | Public IPv4 (private ranges excluded) |\n| `DATE_OF_BIRTH` | `dateOfBirth`, `date_of_birth`, `dob`, `birthday`, `birthDate` | DOB when labelled with `dob:`, `born on`, `birthday`, etc. |\n\n#### Adding a custom rule\n\nAppend an entry to `pii.config.json`:\n\n```json\n{\n  \"name\": \"EMPLOYEE_ID\",\n  \"aliases\": [\"employeeId\", \"employee_id\", \"empId\"],\n  \"description\": \"Internal employee ID format EMP-XXXXXX\",\n  \"pattern\": \"\\\\bEMP-\\\\d{6}\\\\b\",\n  \"flags\": \"gi\",\n  \"enabled\": true\n}\n```\n\nRestart the proxy for changes to take effect. No code changes required.\n\n---\n\n## Configuration\n\n### config.json\n\nEdit [config/config.json](config/config.json) for proxy-level settings:\n\n| Key | Default | Env override | Description |\n|---|---|---|---|\n| `port` | `3000` | `PORT` | Proxy listen port |\n| `host` | `localhost` | `HOST` | Proxy bind address |\n| `requestTimeout` | `30000` | `REQUEST_TIMEOUT` | Upstream timeout (ms) |\n| `logLevel` | `\"INFO\"` | `LOG_LEVEL` | `INFO` or `DEBUG` |\n| `saveToken` | `false` | — | Enable simple-op interception |\n| `redactPII` | `true` | — | Enable PII redaction guardrail |\n| `compressPrompts` | `true` | — | Enable prompt compression before forwarding |\n| `upstreamProxy.host` | `null` | — | Hostname of the upstream (chained) proxy |\n| `upstreamProxy.port` | `null` | — | Port of the upstream proxy |\n| `upstreamProxy.auth` | `null` | — | Basic-auth credentials as `\"user:password\"`, or `null` |\n| `defaultPorts.http` | `80` | — | Default HTTP port |\n| `defaultPorts.https` | `443` | — | Default HTTPS port |\n\nDashboard port can be changed via the `DASHBOARD_PORT` environment variable (default `3001`).\n\n### pii.config.json\n\nEdit [config/pii.config.json](config/pii.config.json) to manage PII redaction rules. See the [PII Redaction](#pii-redaction) section for the full rule schema and built-in rule list.\n\n---\n\n## Testing\n\nAll tests live in [backend/tests/](backend/tests/) and are fully standalone — no proxy process needs to be running.\n\n| Test file | What it covers | Tests |\n|---|---|---|\n| [test-proxy-chain.js](backend/tests/test-proxy-chain.js) | Upstream proxy chain (CONNECT tunnel + TLS) | 2 |\n| [test-cache.js](backend/tests/test-cache.js) | In-memory prompt cache | 41 |\n| [test-redactor.js](backend/tests/test-redactor.js) | PII redaction — all 9 rules | 78 |\n| [test-compressor.js](backend/tests/test-compressor.js) | Prompt compression — all 13 rules | 62 |\n\nRun all:\n\n```bash\nnode backend/tests/test-proxy-chain.js\nnode backend/tests/test-cache.js\nnode backend/tests/test-redactor.js\nnode backend/tests/test-compressor.js\n```\n\n---\n\n### Proxy chain — `test-proxy-chain.js`\n\nVerifies the full upstream proxy-chain path without touching production config:\n\n1. Spins up a local mini CONNECT proxy on a random port\n2. Calls `openTunnel()` directly (same code path as `handler.js`)\n3. TLS-wraps the raw socket and fires a real HTTPS GET to `httpbin.org/get`\n\n```\n[mini-proxy] listening on 127.0.0.1:\u003cport\u003e\n\nTest 1: openTunnel() via mini-proxy → httpbin.org:443\n  [mini-proxy] CONNECT httpbin.org:443\nTest 2: TLS wrap + HTTPS GET https://httpbin.org/get\n  status: 200\n\n✓ Test 1 PASSED — mini-proxy received CONNECT tunnel request\n✓ Test 2 PASSED — TLS + HTTPS request succeeded through chain\n\n✅ Proxy chain is working.\n```\n\n\u003e `upstreamProxy` in `config.json` does not need to be enabled.\n\n---\n\n### Prompt cache — `test-cache.js`\n\n41 assertions across 7 sections:\n\n| Section | What is tested |\n|---|---|\n| `getCacheKey` | All four prompt fields (`prompt`, `messages`, `inputs`, `input`), wrong content-type, malformed JSON, field priority |\n| `set / get / size / clear / keys` | Basic CRUD, null on miss, key listing |\n| Overwrite | Re-inserting same key replaces value and refreshes insertion order |\n| `findSimilar` — strings | Exact score 1.0, near-identical hit (Jaccard ≥ 0.75), unrelated miss, empty cache |\n| `findSimilar` — messages | OpenAI `messages[]` format flattened correctly for similarity |\n| Best match selection | Returns highest-scoring entry when multiple candidates qualify |\n| `MAX_SIZE` eviction | Cache stays ≤ 500 entries; oldest entry evicted; re-insert does not exceed limit |\n\n---\n\n### PII redactor — `test-redactor.js`\n\n78 assertions covering all 9 built-in rules plus buffer-level behaviour:\n\n| Section | What is tested |\n|---|---|\n| Rule loading | All rules compiled, `getRuleByName` by canonical name / alias / case-insensitive |\n| `EMAIL` | Two matches in one string aggregated into one `found` entry with `count=2`; no false positives |\n| `PHONE` | US formats matched; short numbers ignored |\n| `SSN` | Valid format matched; `000-xx` and `9xx-xx` invalid prefixes excluded |\n| `CREDIT_CARD` | Visa, Mastercard, Discover formats |\n| `API_KEY` | OpenAI `sk-`, Anthropic `sk-ant-`, GitHub `ghp_` |\n| `BANK_ACCOUNT` | `account:` and `routing #` label variants |\n| `PASSPORT` | 1–2 uppercase letters + 6–9 digits |\n| `IP_ADDRESS` | Public IPs matched; private ranges (`10.x`, `192.168.x`, `127.x`) excluded |\n| `DATE_OF_BIRTH` | Four label variants matched; unlabelled dates not redacted |\n| Multi-type | EMAIL + SSN + PHONE in one string, all redacted independently |\n| `redactBuffer` no-ops | `null`, empty, non-JSON content-type, no-PII → original buffer reference returned |\n| `redactBuffer` messages | String content, OpenAI block-content array, image blocks untouched |\n| `redactBuffer` prompt | Plain `prompt` field redacted; `messages` + `prompt` both present |\n| Idempotency | Double-redacting a placeholder does not double-wrap it |\n\n---\n\n### Prompt compressor — `test-compressor.js`\n\n62 assertions covering all 13 rules and both `compressString` / `compressBuffer` entry points:\n\n| Section | What is tested |\n|---|---|\n| Rule inventory | All 13 rules present, every rule has `name`, `fn`, and `enabled` |\n| Whitespace | Trailing spaces, 3+ blank lines collapsed, multiple spaces normalised |\n| Punctuation | `!!` / `!!!` → `!`, `??` → `?`, `....` → `...` |\n| AI preamble | `\"As an AI language model,\"` and `\"As a large language model,\"` removed |\n| Filler openers | `\"Certainly!\"`, `\"Of course!\"`, `\"I'd be happy to help\"`, `\"I hope this helps\"`, `\"Feel free to ask\"` removed |\n| Verbose connectives | 13 phrase substitutions (`\"In order to\"` → `\"To\"`, `\"Due to the fact that\"` → `\"Because\"`, etc.) |\n| Verbose instructions | `\"Please make sure that you\"` / `\"Make sure to\"` / `\"You must ensure that\"` → `\"Ensure\"` |\n| Redundant qualifiers | `\"very unique\"` → `\"unique\"`, `\"absolutely certain\"` → `\"certain\"`, `\"basically\"` / `\"literally\"` removed |\n| Sentence deduplication | Duplicate adjacent sentence removed; unique sentences all retained |\n| Idempotency | Compressing an already-compressed string produces the same result |\n| `compressBuffer` no-ops | `null`, empty, non-JSON content-type, nothing-to-compress → original buffer reference returned |\n| `compressBuffer` messages | String content and OpenAI block-content arrays compressed; image blocks untouched |\n| `compressBuffer` prompt | Plain `prompt` field compressed |\n| Token savings | Verbose system prompt achieves ≥ 10 tokens / ≥ 10% reduction |\n\n---\n\n## Troubleshooting\n\n### Certificate signature failure\n\nIf you see `certificate signature failure`, the CA cert in the keychain no longer matches the key on disk:\n\n```bash\n# 1. Remove old certs\nrm backend/certs/ca.crt backend/certs/ca.key\n\n# 2. Remove old trusted cert from keychain\nsudo security delete-certificate -c \"LLM-Nexus Proxy CA\" /Library/Keychains/System.keychain\n\n# 3. Restart the server — new CA is generated automatically\nnode proxy/server.js\n\n# 4. Trust the new CA\nsudo security add-trusted-cert -d -r trustRoot \\\n  -k /Library/Keychains/System.keychain \\\n  backend/certs/ca.crt\n\n# 5. Re-apply env vars and fully restart VS Code\nlaunchctl setenv NODE_EXTRA_CA_CERTS \"/Users/\u003cyour-username\u003e/LLM-nexus/backend/certs/ca.crt\"\n```\n\n### Error reference\n\n| Error | Fetcher | Fix |\n|---|---|---|\n| `ERR_CERT_AUTHORITY_INVALID` | `electron-fetch` | macOS keychain trust (step 3) |\n| `fetch failed` | `node-fetch` | `NODE_EXTRA_CA_CERTS` in `~/.zprofile` + `launchctl` (step 4) |\n| `unable to verify first certificate` | `node-http` | `NODE_EXTRA_CA_CERTS` in `~/.zprofile` + `launchctl` (step 4) |\n| `certificate signature failure` | `node-http` | Regenerate certs (see above) |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaanvijay%2Fllm-nexus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaanvijay%2Fllm-nexus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaanvijay%2Fllm-nexus/lists"}