1. Start the proxy
npx @kushalicious/jevcache@latest init # writes OPENAI_BASE_URL into ./.env
npx @kushalicious/jevcache@latest start # serves http://127.0.0.1:8080/v1Node.js 22.5 or later, or Docker (ghcr.io/kushals256/jevcache:latest). The default upstream is OpenRouter, so the examples use OpenRouter model IDs such as openai/gpt-4o-mini.
OpenAI Node SDK
import OpenAI from "openai"
const client = new OpenAI({
apiKey: process.env.OPENROUTER_API_KEY,
baseURL: "http://127.0.0.1:8080/v1", // was https://openrouter.ai/api/v1
})
const r = await client.chat.completions.create({
model: "openai/gpt-4o-mini",
messages: [{ role: "user", content: "Explain quicksort simply" }],
})
console.log(r.choices[0].message.content)OpenAI Python SDK
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["OPENROUTER_API_KEY"],
base_url="http://127.0.0.1:8080/v1",
)
r = client.chat.completions.create(
model="openai/gpt-4o-mini",
messages=[{"role": "user", "content": "Explain quicksort simply"}],
)
print(r.choices[0].message.content)Both SDKs also read OPENAI_BASE_URL from the environment, which is what init writes.
Vercel AI SDK
import { createOpenAI } from "@ai-sdk/openai"
import { generateText } from "ai"
const openai = createOpenAI({
baseURL: "http://127.0.0.1:8080/v1",
apiKey: process.env.OPENROUTER_API_KEY,
})
const { text } = await generateText({
model: openai.chat("openai/gpt-4o-mini"), // .chat = Chat Completions
prompt: "Explain quicksort simply",
})LangChain
import { ChatOpenAI } from "@langchain/openai"
const llm = new ChatOpenAI({
model: "openai/gpt-4o-mini",
apiKey: process.env.OPENROUTER_API_KEY,
configuration: { baseURL: "http://127.0.0.1:8080/v1" },
})import os
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="openai/gpt-4o-mini",
api_key=os.environ["OPENROUTER_API_KEY"],
base_url="http://127.0.0.1:8080/v1",
)curl
curl -si http://127.0.0.1:8080/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"openai/gpt-4o-mini","messages":[{"role":"user","content":"Explain mutexes simply"}]}' \
| grep -i x-jevcacheLocal models: Ollama, vLLM, llama.cpp
Set the upstream to any OpenAI-compatible server. Run the judge locally too (ADJUDICATOR=kev or laya) and nothing leaves your machine.
UPSTREAM_BASE_URL=http://127.0.0.1:11434/v1 # Ollama
# UPSTREAM_BASE_URL=http://127.0.0.1:8000/v1 # vLLM (Laya also defaults to :8000)
# llama.cpp's server also defaults to :8080, so move one of them:
# PORT=8090Coding agents
In Claude Code, install the plugin and run /morrowcache:setup in your project. It inspects how your app calls a model, starts the proxy, changes the base URL with the smallest edit and runs jevcache verify to show a MISS then a HIT. /morrowcache:stats explains the numbers.
/plugin marketplace add kushals256/jevcache
/plugin install morrowcache@morrowcacheIn Cursor, Codex or any other coding agent, paste the agent setup prompt. The agent installs the proxy, writes .env, wires the base URL in your app and proves a MISS then a HIT. It caches your app’s model calls, not the agent’s own.
FAQ
- Why does the Vercel AI SDK return a 501 from MorrowCache?
- Recent AI SDK versions call the OpenAI Responses API by default. MorrowCache only serves Chat Completions, so create the model with openai.chat('model-id') instead of openai('model-id').
- Which API key does my client send?
- Your client's Authorization header is forwarded to the upstream. If the client sends none, MorrowCache uses UPSTREAM_API_KEY. The default upstream is OpenRouter.
- Can the upstream be a local model?
- Yes. Set UPSTREAM_BASE_URL to any OpenAI-compatible server, such as Ollama, vLLM or llama.cpp's server, and MorrowCache caches its answers the same way.