cachegate 1.0.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/.dockerignore ADDED
@@ -0,0 +1,11 @@
1
+ node_modules
2
+ npm-debug.log
3
+ .env
4
+ .env.local
5
+ .git
6
+ .gitignore
7
+ test/
8
+ .github/
9
+ data/
10
+ *.md
11
+ !README.md
package/.env.example ADDED
@@ -0,0 +1,112 @@
1
+ # cachegate environment template
2
+ # Copy the values you need into your real .env file.
3
+ # NEVER commit .env to Git.
4
+
5
+ PORT=4000
6
+
7
+ # Required: protects the router from unauthorized use. The server
8
+ # refuses to start without this UNLESS ALLOW_INSECURE_LOCAL_DEV=true
9
+ # (below) is explicitly set - a missing key never silently means "no
10
+ # auth enforced."
11
+ # Generate with: openssl rand -hex 32
12
+ MODEL_ROUTER_INTERNAL_KEY=your-random-internal-key
13
+
14
+ # Optional: skip the MODEL_ROUTER_INTERNAL_KEY requirement above, for a
15
+ # throwaway LOCAL instance only. Never set this on anything reachable
16
+ # from outside your own machine.
17
+ # ALLOW_INSECURE_LOCAL_DEV=true
18
+
19
+ # Required (at least one provider key - Anthropic, OpenAI, or both).
20
+ # The model itself is named PER REQUEST in the API call's own "model"
21
+ # field (see README's "Usage" section), not configured here - there is
22
+ # no ANTHROPIC_MODEL/OPENAI_MODEL env var to set.
23
+ ANTHROPIC_API_KEY=sk-ant-api03-...
24
+
25
+ # Also required for the semantic cache's embeddings, regardless of
26
+ # which provider actually serves chat requests - it's the only
27
+ # embedding backend implemented (see semanticCache.js / README).
28
+ # OPENAI_API_KEY=sk-proj-...
29
+
30
+ # Optional: which OpenAI embedding model the semantic cache uses.
31
+ # Only matters if OPENAI_API_KEY is set. Default is a good balance of
32
+ # cost and quality; change only if you know you want a different one.
33
+ # EMBEDDING_MODEL=text-embedding-3-small
34
+
35
+ # Optional: Redis for response caching (strongly recommended for cost savings)
36
+ # Local development:
37
+ # REDIS_URL=redis://localhost:6379
38
+ # Render Key Value:
39
+ # REDIS_URL=rediss://default:PASSWORD@HOST:PORT
40
+
41
+ # Optional: persistent metrics storage (see metrics.js's own comment).
42
+ # Unset (the default) keeps metrics as local JSONL files, which don't
43
+ # survive a restart/redeploy on most hosts - fine for a standalone
44
+ # deployment with no database of its own. Set this to a real Postgres
45
+ # connection string to persist metrics there instead.
46
+ # DATABASE_URL=postgres://user:pass@host:5432/dbname
47
+ #
48
+ # MEMOCODE_ROUTER_DATABASE_URL does the exact same thing and takes
49
+ # priority over DATABASE_URL if both are set - use this instead when
50
+ # embedding cachegate inside an app that already has its own
51
+ # DATABASE_URL pointed at a different database (so the two never
52
+ # collide), or just to keep this router's own connection string
53
+ # explicitly distinct from whatever else reads DATABASE_URL in your stack.
54
+ # MEMOCODE_ROUTER_DATABASE_URL=postgres://user:pass@host:5432/dbname
55
+
56
+ # Optional: where JSONL metrics files live, if Postgres isn't
57
+ # configured. Despite the name, this now names a DIRECTORY (kept for
58
+ # backward compatibility with older configs that pointed it at a
59
+ # single file - see metrics.js's own comment). Defaults to ./data
60
+ # next to this file.
61
+ # METRICS_LOG_PATH=./data
62
+
63
+ # Optional: how long metrics history is kept before pruneOlderThan()'s
64
+ # scheduled daily job deletes it. Default 90 days deliberately matches
65
+ # /dashboard/data's own longest supported range - pruning any sooner
66
+ # would make its "Last 90 days" option quietly lie.
67
+ # METRICS_RETENTION_DAYS=90
68
+
69
+ # Optional: semantic cache tuning (all have sane defaults - see README's
70
+ # "Two kinds of cache hit" section before changing these).
71
+ # SEMANTIC_CACHE_ENABLED=false # hard off-switch, even with a key configured
72
+ # SEMANTIC_CACHE_THRESHOLD=0.93 # cosine similarity floor for a match
73
+ # SEMANTIC_CACHE_MAX_CANDIDATES=200 # per-model list cap (brute-force scan size)
74
+ # SEMANTIC_CACHE_TTL_SECONDS=3600
75
+
76
+ # Optional: override the default "router:" virtual-model tiers
77
+ # (router.js's own DEFAULT_TIERS) with your own JSON, e.g. to add a
78
+ # model, swap a provider, or define a new tier name entirely. Must be
79
+ # valid JSON matching the shape:
80
+ # {"router:fast-cheap":[{"provider":"openai","model":"gpt-4o-mini"}]}
81
+ # Invalid JSON logs a warning and falls back to the built-in defaults
82
+ # rather than crashing.
83
+ # ROUTER_TIERS_JSON={"router:fast-cheap":[...]}
84
+
85
+ # Optional: routing strategy for "router:" virtual models (router.js).
86
+ # See README's "Where this leaves things" section for what each one
87
+ # actually does before changing this - there's no blended cost/latency
88
+ # score, only these three explicit options.
89
+ # ROUTER_STRATEGY=cost # default: cheapest healthy candidate
90
+ # ROUTER_STRATEGY=latency # fastest healthy candidate, cost only as a tiebreaker
91
+ # ROUTER_STRATEGY=latency-guarded-cost # cheapest healthy candidate, excluding any that's too much slower than the fastest known one
92
+ # ROUTER_LATENCY_GUARD_MULTIPLIER=3 # only used by latency-guarded-cost
93
+
94
+ # Optional: rate limiting on /v1/* (server.js). Default 300 requests
95
+ # per 60 seconds - shared across EVERY caller of this router combined,
96
+ # not per end user if you're fronting it with your own per-user auth.
97
+ # See server.js's own comment above the rate limiter for the reasoning.
98
+ # RATE_LIMIT_MAX=300
99
+ # RATE_LIMIT_WINDOW_MS=60000
100
+
101
+ # Optional: separate, more generous rate limit for the read-only
102
+ # aggregate endpoints (/stats, /dashboard/data) - lower stakes than
103
+ # /v1 (no provider spend on the line) but still real server work.
104
+ # Shares RATE_LIMIT_WINDOW_MS above.
105
+ # READ_RATE_LIMIT_MAX=120
106
+
107
+ # Optional: max JSON body size accepted on /v1/* (server.js). Default
108
+ # 2mb comfortably covers even a very long text conversation - this
109
+ # router doesn't support image/multimodal content, so there's no
110
+ # legitimate reason for a much larger payload. Raise only if you have
111
+ # a specific reason to expect longer request bodies.
112
+ # JSON_BODY_LIMIT=2mb
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: Bug report
3
+ about: Something isn't working the way it should
4
+ title: ''
5
+ labels: bug
6
+ assignees: ''
7
+ ---
8
+
9
+ **What happened**
10
+ A clear description of the actual behavior.
11
+
12
+ **What you expected instead**
13
+
14
+ **Steps to reproduce**
15
+ 1.
16
+ 2.
17
+ 3.
18
+
19
+ **Environment**
20
+ - cachegate version:
21
+ - Node version:
22
+ - Deployment: standalone / embedded in another app / Docker
23
+ - Redis: version, and whether it's configured at all (some features
24
+ degrade cleanly without it — see README's "Features")
25
+
26
+ **Relevant config**
27
+ Which env vars are set (names only — **never paste real API keys, the
28
+ internal key, or a database URL here**): e.g. `ROUTER_STRATEGY=cost`,
29
+ `SEMANTIC_CACHE_ENABLED=true`, `DATABASE_URL` set (Postgres) vs. unset
30
+ (JSONL).
31
+
32
+ **Logs**
33
+ Relevant server console output, with any secret values redacted.
@@ -0,0 +1,8 @@
1
+ # NOTE (remove before step 15/16 ships): the URL below has a placeholder
2
+ # "OWNER" - fill in the real GitHub org/username once the public repo
3
+ # actually exists.
4
+ blank_issues_enabled: true
5
+ contact_links:
6
+ - name: Security vulnerability
7
+ url: https://github.com/OWNER/cachegate/security/advisories/new
8
+ about: Do not open a public issue for a vulnerability - use private vulnerability reporting instead. See SECURITY.md.
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest something this project should do
4
+ title: ''
5
+ labels: enhancement
6
+ assignees: ''
7
+ ---
8
+
9
+ **Before filing — is this in scope?**
10
+ Read `README.md`'s "What this is NOT" section first. Login, billing,
11
+ multi-tenant key custody, or a hosted-service feature will be closed as
12
+ out of scope regardless of how it's proposed — that functionality
13
+ belongs to a separate closed product built on this engine, not to this
14
+ engine. Everything else (routing, caching, provider support, the
15
+ dashboard, operational tooling) is fair game.
16
+
17
+ **What problem does this solve?**
18
+ Describe the actual pain, not just the feature - "I need X because Y
19
+ currently forces me to Z."
20
+
21
+ **What would the ideal solution look like?**
22
+ A sketch is fine - an API shape, a new env var, a CLI flag.
23
+
24
+ **Alternatives considered**
25
+ What are you doing today without this? Is there a workaround?
26
+
27
+ **Additional context**
28
+ Anything else - a link to how a similar tool does this, a real
29
+ production scenario this came up in.
@@ -0,0 +1,25 @@
1
+ ## What changed and why
2
+
3
+ <!-- The problem this solves, not just the diff. If it fixes a bug,
4
+ say how you found and verified it. -->
5
+
6
+ ## Scope check
7
+
8
+ - [ ] I've read `README.md`'s "What this is NOT" section and confirmed
9
+ this doesn't add login, billing, multi-tenant key custody, or a
10
+ hosted-service feature.
11
+
12
+ ## Testing
13
+
14
+ <!-- What you ran, and what it proved. "npm test passes" is fine for a
15
+ small change; describe manual verification for anything that couldn't
16
+ be covered by the existing suite (e.g. a real provider call). -->
17
+
18
+ - [ ] `npm test` passes locally
19
+ - [ ] Added/updated tests for the behavior this changes, or explained
20
+ why that's not practical (e.g. it needs a live provider call)
21
+
22
+ ## Anything reviewers should look at closely
23
+
24
+ <!-- Optional - a tricky edge case, a tradeoff you're not fully sure
25
+ about, a place you deviated from an existing pattern in the codebase. -->
@@ -0,0 +1,63 @@
1
+ name: Tests
2
+
3
+ # Runs on every push and PR - unlike this monorepo's own root
4
+ # .github/workflows/e2e.yml (currently workflow_dispatch-only), this
5
+ # is a public OSS repo once extracted: a green check on every PR IS
6
+ # the trust signal step 8 of OPEN_SOURCE_ROADMAP.md exists for, so
7
+ # automatic triggers are the whole point here, not something to dial
8
+ # back to manual.
9
+ on:
10
+ push:
11
+ branches: [main]
12
+ pull_request:
13
+
14
+ jobs:
15
+ test:
16
+ runs-on: ubuntu-latest
17
+
18
+ # metrics-postgres.test.js needs a REAL reachable Postgres - it
19
+ # fails loudly (ECONNREFUSED) rather than skipping if one isn't
20
+ # running, by design (see that file's own header comment). Values
21
+ # here match its own hardcoded fallback connection string exactly
22
+ # (MEMOCODE_ROUTER_TEST_DATABASE_URL's default), so no extra env
23
+ # wiring is needed below.
24
+ services:
25
+ postgres:
26
+ image: postgres:16
27
+ env:
28
+ POSTGRES_USER: postgres
29
+ POSTGRES_PASSWORD: dryrun
30
+ POSTGRES_DB: router_metrics_dryrun
31
+ ports:
32
+ - 5432:5432
33
+ options: >-
34
+ --health-cmd pg_isready
35
+ --health-interval 10s
36
+ --health-timeout 5s
37
+ --health-retries 5
38
+
39
+ strategy:
40
+ # Multiple Node versions, not just one: package.json declares
41
+ # "engines": {"node": ">=18.0.0"} - running the suite against all
42
+ # three actually backs that claim instead of just asserting it.
43
+ matrix:
44
+ node-version: ['18.x', '20.x', '22.x']
45
+
46
+ steps:
47
+ - uses: actions/checkout@v4
48
+
49
+ - uses: actions/setup-node@v4
50
+ with:
51
+ node-version: ${{ matrix.node-version }}
52
+
53
+ # semanticCache.test.js spawns its own throwaway redis-server
54
+ # process directly (a real instance, not a mock) - ubuntu-latest
55
+ # doesn't ship the binary, so it has to be installed explicitly.
56
+ # This is NOT the same thing as the Postgres service above: that
57
+ # test connects to an already-running external service, this one
58
+ # starts and stops its own process on a random port.
59
+ - name: Install redis-server
60
+ run: sudo apt-get update && sudo apt-get install -y redis-server
61
+
62
+ - run: npm ci
63
+ - run: npm test
@@ -0,0 +1,66 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in
6
+ our community a harassment-free experience for everyone, regardless of
7
+ age, body size, visible or invisible disability, ethnicity, sex
8
+ characteristics, gender identity and expression, level of experience,
9
+ education, socio-economic status, nationality, personal appearance,
10
+ race, religion, or sexual identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open,
13
+ welcoming, diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment:
18
+
19
+ * Demonstrating empathy and kindness toward other people
20
+ * Being respectful of differing opinions, viewpoints, and experiences
21
+ * Giving and gracefully accepting constructive feedback
22
+ * Accepting responsibility and apologizing to those affected by our
23
+ mistakes, and learning from the experience
24
+ * Focusing on what is best not just for us as individuals, but for the
25
+ overall community
26
+
27
+ Examples of unacceptable behavior:
28
+
29
+ * The use of sexualized language or imagery, and sexual attention or
30
+ advances of any kind
31
+ * Trolling, insulting or derogatory comments, and personal or political
32
+ attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email
35
+ address, without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Project maintainers are responsible for clarifying and enforcing our
42
+ standards of acceptable behavior and will take appropriate and fair
43
+ corrective action in response to any behavior that they deem
44
+ inappropriate, threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies within all community spaces (issues, pull
49
+ requests, discussions), and also applies when an individual is
50
+ officially representing the community in public spaces.
51
+
52
+ ## Enforcement
53
+
54
+ Instances of abusive, harassing, or otherwise unacceptable behavior may
55
+ be reported to the maintainers via the contact method listed in
56
+ `SECURITY.md`. All complaints will be reviewed and investigated
57
+ promptly and fairly.
58
+
59
+ ## Attribution
60
+
61
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
62
+ version 2.1, available at
63
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
64
+
65
+ [homepage]: https://www.contributor-covenant.org
66
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
@@ -0,0 +1,94 @@
1
+ # Contributing to cachegate
2
+
3
+ Thanks for considering it. This is a small, deliberately focused
4
+ project — read the scope note below before writing code, it'll save
5
+ you a round-trip.
6
+
7
+ ## Scope — read this first
8
+
9
+ `README.md`'s "What this is NOT" section is the actual contract, not
10
+ just a marketing caveat: **no login, no billing, no multi-tenant key
11
+ custody, no hosted service** live in this repository, and a PR adding
12
+ any of those will be closed regardless of how well it's built — that
13
+ functionality belongs to a separate, closed product built on top of
14
+ this engine, not this engine itself. This isn't a licensing
15
+ restriction (MIT permits building any of that — see `LICENSE`); it's
16
+ that this repository specifically isn't going to grow into a hosted
17
+ competitor to its own paid product, so a PR heading that direction
18
+ gets closed here regardless of quality, not merged and then diverged
19
+ from later.
20
+
21
+ Everything else — routing strategies, cache behavior, provider
22
+ support, the dashboard, bug fixes — is fair game.
23
+
24
+ ## Running it locally
25
+
26
+ ```bash
27
+ git clone <this-repo-url>
28
+ cd cachegate
29
+ npm install
30
+ cp .env.example .env # then fill in a real key for at least one provider
31
+ npm start
32
+ ```
33
+
34
+ ## Running the tests
35
+
36
+ ```bash
37
+ npm test
38
+ ```
39
+
40
+ This runs Node's built-in test runner (`node --test`) across every file
41
+ in `test/`. A few things worth knowing before you add to it:
42
+
43
+ - **No test here calls a real provider API.** That needs live keys and
44
+ real spend, which isn't reasonable to require of a contributor or a
45
+ CI run. Provider-facing logic (`providers/anthropic.js`,
46
+ `providers/openai.js`) is tested by factoring the pure,
47
+ non-network parts (see `applyStreamEvent`/`applyStreamChunk`) out
48
+ into directly-testable functions with canned input — follow that
49
+ pattern for new provider logic rather than trying to mock the HTTP
50
+ layer.
51
+ - **Tests need to be genuinely isolated.** Several existing tests reset
52
+ `METRICS_LOG_PATH` to a fresh temp file per test (see
53
+ `router.test.js`'s `freshModules` helper) specifically because
54
+ `metrics.js` reads its config once at require time. If your test
55
+ writes metrics, don't assume a clean slate — either isolate it the
56
+ same way, or place it deliberately last in its file if it needs to
57
+ run after everything else that depends on a clean state (see
58
+ `server.test.js`'s own comment on this for a worked example).
59
+ - All tests must pass before a PR is reviewed. If a test is failing for
60
+ a reason unrelated to your change, say so explicitly in the PR rather
61
+ than silently working around it — that's a real bug worth its own
62
+ issue.
63
+
64
+ ## Opening a PR
65
+
66
+ - Explain the *why*, not just the *what* — a one-line "fixes X" is
67
+ fine for a trivial fix, but anything behavioral should say what
68
+ problem it solves and how you verified the fix, the same standard
69
+ this codebase's own commit history holds itself to.
70
+ - If you're touching `router.js`'s scoring logic, `failover.js`'s
71
+ retry logic, or anything else with existing inline documentation
72
+ explaining a past decision (search the file for "why" before
73
+ changing something that looks arbitrary) — it's very likely not
74
+ arbitrary. If you disagree with the reasoning, say so in the PR;
75
+ don't just silently remove it.
76
+ - Small, focused PRs review faster than large ones bundling several
77
+ unrelated changes.
78
+
79
+ **How merges actually work here, stated plainly:** this repository is
80
+ mirrored out from a private internal monorepo where day-to-day
81
+ development happens, rather than the other way around. Your PR gets
82
+ reviewed and, once approved, merged here on GitHub like normal — but
83
+ it's also manually reapplied on the internal side afterward, through
84
+ that project's own review process, rather than auto-syncing. In
85
+ practice this means a short delay between "merged here" and "in the
86
+ next internal release," not a rejection — you'll see it land in a
87
+ tagged release once that happens.
88
+
89
+ ## Reporting a bug vs. reporting a security issue
90
+
91
+ Regular bugs: open a GitHub issue. Security vulnerabilities (anything
92
+ that could let a request bypass auth, leak another deployment's data,
93
+ or exhaust resources in a way rate limiting doesn't already cover): see
94
+ `SECURITY.md` instead — please don't file those as public issues.
package/Dockerfile ADDED
@@ -0,0 +1,16 @@
1
+ FROM node:20-slim
2
+ WORKDIR /app
3
+ COPY package*.json ./
4
+ RUN npm ci --omit=dev
5
+ COPY . .
6
+ # The official Node images already ship a non-root "node" user (uid 1000) -
7
+ # use it instead of running as root, standard practice for a public image.
8
+ RUN chown -R node:node /app
9
+ USER node
10
+ EXPOSE 4000
11
+ # No curl/wget in the slim base image - Node's own http module does the
12
+ # check instead. Uses the real GET /health endpoint (public, no auth,
13
+ # exists specifically for this).
14
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
15
+ CMD node -e "require('http').get('http://localhost:'+(process.env.PORT||4000)+'/health',(r)=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))"
16
+ CMD ["node", "server.js"]
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MemoCode
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.