myapikey 0.19.1 → 0.19.3
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/README.md +79 -256
- package/README.zh-CN.md +80 -257
- package/package.json +1 -1
- package/packages/core/src/server/tokens.ts +32 -5
- package/packages/core/src/shared/types.ts +7 -2
package/README.md
CHANGED
|
@@ -2,311 +2,134 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/myapikey)
|
|
4
4
|
[](./LICENSE)
|
|
5
|
-
[](#up-and-running-in-60-seconds)
|
|
6
6
|
[](#development)
|
|
7
7
|
|
|
8
8
|
English | [简体中文](README.zh-CN.md)
|
|
9
9
|
|
|
10
|
-
**One address, one key,
|
|
10
|
+
**One address, one key, every AI tool you use.** A tiny self-hosted LLM gateway for your home server: keep your provider keys in one place, point every tool at the same URL with the same key, and fail over automatically when a backend breaks.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
## Sound familiar?
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
- You use **Claude Code, a few OpenAI-compatible CLIs, editor plugins, scripts** — and each one needs its own base URL + API key pasted in.
|
|
15
|
+
- Your **real keys** (OpenAI, Anthropic, OpenRouter, a local Ollama…) are scattered across a dozen config files. Rotating one means remembering everywhere it's pasted.
|
|
16
|
+
- Your **main provider 429s or goes down** mid-task and the tool just dies. Switching to a backup means re-editing configs.
|
|
17
|
+
- You tried **one-api / new-api-style gateways**, which *translate* between formats — and drop fields, mangle streams, or hold new upstream parameters hostage until someone updates the gateway.
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
MyAPIKey is built to end exactly this.
|
|
17
20
|
|
|
18
|
-
##
|
|
21
|
+
## What you get
|
|
19
22
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
- [API surface](#api-surface)
|
|
29
|
-
- [Storage](#storage)
|
|
30
|
-
- [Development](#development)
|
|
31
|
-
- [Notes and non-goals](#notes-and-non-goals)
|
|
23
|
+
- **One address, one key.** Every tool points at `http://<your-server>:7800` with a single `sk-myapikey-…` key. Real provider keys live only inside the gateway — rotate one, swap a backend, and no tool config ever changes again.
|
|
24
|
+
- **Automatic failover + circuit breaker.** Give a model an ordered chain of backends. On `429` / `5xx` / timeout the next one takes over; a source that keeps failing cools down (30 s → 5 min) so later calls skip it. Your agent never notices.
|
|
25
|
+
- **Pure passthrough, zero translation.** OpenAI-format calls reach OpenAI-speaking backends, Anthropic-format calls reach Anthropic-speaking backends — bodies forwarded byte-for-byte, streaming straight through. New upstream fields and parameters work without the gateway "supporting" them first.
|
|
26
|
+
- **Rate limiting that queues instead of 429ing.** Cap each backend's rpm, or give a model an even per-minute pace; excess calls wait in the gateway instead of coming straight back as `429`.
|
|
27
|
+
- **Model name mapping.** Expose a friendly name to your tools (`claude-sonnet-4`); each backend maps it to its own real id (`claude-sonnet-4-20250514` here, something else there).
|
|
28
|
+
- **A default thinking level per slot.** Pin a reasoning effort / token budget on a route; it overrides whatever the request carried — handy for taming Claude Code.
|
|
29
|
+
- **See everything.** Web UI (English / 简体中文): live call log, success rate, p50/p95 latency, per-model / per-backend / per-day stats. The CLI can do all of it too.
|
|
30
|
+
- **Featherweight.** No database — one `data.json` plus a `logs.jsonl`. Runs with `npx`. Built for LAN / home-server use.
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
## Up and running in 60 seconds
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
If any of these sound familiar, MyAPIKey is for you:
|
|
38
|
-
|
|
39
|
-
- You juggle **several API keys** across OpenAI, Anthropic, OpenRouter, a local model, etc., and you're tired of pasting a different one into every tool.
|
|
40
|
-
- You want **one stable address** for your agents, even when the provider behind it changes.
|
|
41
|
-
- You want **automatic fallback** — if your primary backend rate-limits or goes down, the next one takes over.
|
|
42
|
-
- You want to **rename or swap a model** without editing every tool's config.
|
|
43
|
-
- You'd like a **log of every call** (latency, status, which backend served it).
|
|
44
|
-
|
|
45
|
-
MyAPIKey gives you all of that. You keep your real provider keys inside the gateway; your tools only ever know `http://<host>:7800` and one `sk-myapikey-…` key.
|
|
46
|
-
|
|
47
|
-
**The one thing to know up front:** the gateway **forwards in your tool's native format and never translates**. A call to the OpenAI endpoint reaches an OpenAI-speaking backend; a call to the Anthropic endpoint reaches an Anthropic-speaking backend. This keeps your requests and responses lossless. (See [How routing works](#how-routing-works) once you're set up.)
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## Quick start
|
|
52
|
-
|
|
53
|
-
Requires **Node.js 18+**. No install needed — run it straight from npm:
|
|
34
|
+
Requires Node.js 18+.
|
|
54
35
|
|
|
55
36
|
```bash
|
|
56
37
|
npx myapikey serve # → http://localhost:7800
|
|
57
38
|
```
|
|
58
39
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
npm install -g myapikey
|
|
63
|
-
myapikey serve
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
On first run it prints generated credentials **and saves them to a file** so you can't lose them:
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
First run — here are your credentials (save them):
|
|
70
|
-
username : <random> (web login)
|
|
71
|
-
password : <random> (web login)
|
|
72
|
-
api key : sk-myapikey-… (put this in the tool's "api key" field)
|
|
73
|
-
↳ also written to ~/.myapikey/credentials.txt
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Open `http://localhost:7800` in a browser and sign in with the **username / password** to reach the web UI. The **API key** is separate — that's what your AI tools use later.
|
|
77
|
-
|
|
78
|
-
> Forgot the credentials? `cat ~/.myapikey/credentials.txt` (regenerated on every start). Override the data location with `--data-dir <path>` or the `MYAPIKEY_DATA_DIR` env var.
|
|
40
|
+
First run prints your credentials and also saves them to `~/.myapikey/credentials.txt`:
|
|
79
41
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
## Add your first backend
|
|
85
|
-
|
|
86
|
-
A **backend** (provider) is one upstream API: OpenAI, Anthropic, OpenRouter, a local Ollama, a vendor like Volcengine Ark, etc. You add it once with its real key, then enable models through it.
|
|
87
|
-
|
|
88
|
-
### Easiest: the web UI
|
|
89
|
-
|
|
90
|
-
1. Open the **Models** tab → **Add backend**.
|
|
91
|
-
2. Enter a name, the backend's base URL(s), and its API key.
|
|
92
|
-
3. Pick the wire format(s) it speaks: `openai`, `anthropic` (or both).
|
|
93
|
-
4. Save, then click **discover** to pull its model list.
|
|
94
|
-
5. **Enable** the models you want, per format slot.
|
|
95
|
-
|
|
96
|
-
### Or, the CLI
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
# An OpenAI-compatible backend. The OpenAI base includes the version segment
|
|
100
|
-
# (e.g. /v1, or a vendor's own like Ark's /api/v3).
|
|
101
|
-
myapikey provider add openai-direct \
|
|
102
|
-
--base-url-openai https://api.openai.com/v1 \
|
|
103
|
-
--key sk-... \
|
|
104
|
-
--formats openai
|
|
105
|
-
|
|
106
|
-
# An Anthropic backend. The Anthropic base EXCLUDES /v1
|
|
107
|
-
# (https://api.anthropic.com, or Ark's /api/plan).
|
|
108
|
-
myapikey provider add anthropic-direct \
|
|
109
|
-
--base-url-anthropic https://api.anthropic.com \
|
|
110
|
-
--key sk-ant-... \
|
|
111
|
-
--formats anthropic
|
|
112
|
-
|
|
113
|
-
# See what each offers, then enable a model on a slot:
|
|
114
|
-
myapikey provider models openai-direct
|
|
115
|
-
myapikey model enable gpt-4o-mini --format openai --via openai-direct
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
That model is now reachable through the gateway. To add a **fallback** for the same model (used when the first one fails), add another backend and chain it:
|
|
42
|
+
1. Open `http://localhost:7800` and sign in with the printed **username / password** (web login).
|
|
43
|
+
2. **Models** tab → **Add backend**: paste the backend's base URL + its real API key, tick the formats it speaks (`openai` / `anthropic`), then **discover** → **enable** the models you want.
|
|
44
|
+
3. Point your tools at the gateway — every tool's "API key" field gets the **gateway key**, not your web password:
|
|
119
45
|
|
|
120
46
|
```bash
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
myapikey model list # see the routing table
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
> `--format` selects the routing **slot**: `openai` (for `/openai/v1/chat/completions`), `anthropic` (for `/anthropic/v1/messages`), or `responses` (for `/openai/v1/responses`). The `responses` slot only accepts backends you've marked **supportsResponses** — set that toggle in the web UI (the CLI doesn't expose it yet).
|
|
128
|
-
|
|
129
|
-
---
|
|
130
|
-
|
|
131
|
-
## Connect a tool
|
|
132
|
-
|
|
133
|
-
Once a model is enabled, point any agent at the gateway. Get your connection info:
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
myapikey whoami # prints base url + api key + ready-to-paste env lines
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
For an **OpenAI-compatible** tool (covers `/chat/completions` *and* `/responses`):
|
|
47
|
+
# Claude Code / anything Anthropic
|
|
48
|
+
export ANTHROPIC_BASE_URL=http://localhost:7800/anthropic
|
|
49
|
+
export ANTHROPIC_API_KEY=sk-myapikey-…
|
|
140
50
|
|
|
141
|
-
|
|
51
|
+
# OpenAI-compatible tools (Codex, CLIs, plugins)
|
|
142
52
|
export OPENAI_BASE_URL=http://localhost:7800/openai/v1
|
|
143
|
-
export OPENAI_API_KEY
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
For **Anthropic / Claude Code**:
|
|
147
|
-
|
|
148
|
-
```bash
|
|
149
|
-
export ANTHROPIC_BASE_URL=http://localhost:7800/anthropic
|
|
150
|
-
export ANTHROPIC_API_KEY=<gateway api key>
|
|
53
|
+
export OPENAI_API_KEY=sk-myapikey-…
|
|
151
54
|
```
|
|
152
55
|
|
|
153
|
-
>
|
|
56
|
+
> Why `…/openai/v1` but `…/anthropic`? Each SDK appends its own paths (OpenAI adds `/chat/completions`, Anthropic adds `/v1/messages`), so the gateway follows each ecosystem's own convention. Each surface also has its own `/models`, listing only models enabled for that format. `myapikey whoami` prints all of this ready to paste, anytime.
|
|
154
57
|
|
|
155
|
-
|
|
58
|
+
4. Smoke test without any tool:
|
|
156
59
|
|
|
157
60
|
```bash
|
|
158
61
|
myapikey call gpt-4o-mini "Say hello in one sentence."
|
|
159
62
|
```
|
|
160
63
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
**Raw HTTP** (no SDK) — hit either surface directly with the gateway API key as a `Bearer` token:
|
|
164
|
-
|
|
165
|
-
```bash
|
|
166
|
-
# OpenAI family
|
|
167
|
-
curl http://localhost:7800/openai/v1/chat/completions \
|
|
168
|
-
-H "Authorization: Bearer <gateway api key>" -H "Content-Type: application/json" \
|
|
169
|
-
-d '{"model":"<model>","messages":[{"role":"user","content":"hi"}]}'
|
|
170
|
-
|
|
171
|
-
# Anthropic family
|
|
172
|
-
curl http://localhost:7800/anthropic/v1/messages \
|
|
173
|
-
-H "Authorization: Bearer <gateway api key>" -H "Content-Type: application/json" \
|
|
174
|
-
-d '{"model":"<model>","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}'
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
Whichever endpoint you point a tool at, the gateway forwards in that format and never translates — so make sure each model you call is backed by at least one source on the matching slot ([see below](#how-routing-works)).
|
|
178
|
-
|
|
179
|
-
---
|
|
64
|
+
Prefer the terminal? `provider add` → `provider models` → `model enable` does the same job — cheat sheet below.
|
|
180
65
|
|
|
181
|
-
## How routing works
|
|
66
|
+
## How routing works (the whole story)
|
|
182
67
|
|
|
183
|
-
The gateway
|
|
68
|
+
- The gateway has two entrances: `/openai/v1/*` (chat completions, responses, models) and `/anthropic/v1/*` (messages, models). A request is forwarded in the entrance's format, **never translated**.
|
|
69
|
+
- Each model has an independent, ordered backend chain **per format**. Within a chain, `429` / `5xx` / timeouts fail over to the next backend; other `4xx` errors come back as-is (they're the caller's fault, not the backend's). No failover once streaming has started. If no backend serves that model on that format, you get a `404` — a clear failure beats a silent translation.
|
|
70
|
+
- Anything that speaks either format works: OpenAI, Anthropic, OpenRouter, Ollama / vLLM, Volcengine Ark, your company's internal gateway…
|
|
184
71
|
|
|
185
|
-
|
|
72
|
+
## Why no translation?
|
|
186
73
|
|
|
187
|
-
|
|
74
|
+
one-api / new-api convert OpenAI ↔ Anthropic shapes. Convenient, until a field gets dropped, a stream gets mangled, or a new upstream parameter needs a gateway update before you can send it. OpenRouter is hosted and takes a cut per call. MyAPIKey's answer is to not have a translation layer at all:
|
|
188
75
|
|
|
189
|
-
|
|
76
|
+
| | MyAPIKey | one-api / new-api | OpenRouter |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| Deploy | self-hosted, one `npx` | self-hosted, heavier | hosted service |
|
|
79
|
+
| Cost | free | free | markup per call |
|
|
80
|
+
| Formats | passthrough, byte-for-byte | translated | translated |
|
|
81
|
+
| New upstream params | work immediately | wait for gateway support | wait for platform support |
|
|
82
|
+
| Provider keys | on your machine | on your machine | on their platform |
|
|
190
83
|
|
|
191
|
-
|
|
84
|
+
The trade-off, stated plainly: an Anthropic-format backend cannot be called through the OpenAI entrance. If what you want is format conversion, this isn't your tool (yet).
|
|
192
85
|
|
|
193
|
-
|
|
86
|
+
## Managing it
|
|
194
87
|
|
|
195
|
-
|
|
88
|
+
**Web UI** — `http://localhost:7800`, English + 简体中文. Five tabs: **Connect** (copy-paste-ready env lines), **Models** (backends, discovery, per-format enable, drag-to-order chains, name mapping, test call), **Logs** (live timeline), **Stats** (counts, success rate, p50/p95 by model / backend / day), **Settings** (rotate API key, change password, circuit-breaker state, storage paths).
|
|
196
89
|
|
|
197
|
-
|
|
90
|
+
**CLI cheat sheet** (same admin API as the web UI — the two never drift apart):
|
|
198
91
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
If no mapping is set for a source, the public model name is sent unchanged.
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
## Web UI
|
|
216
|
-
|
|
217
|
-
Visit `http://localhost:7800`, sign in with your username/password. Ships in **English and Chinese** (toggle in the top bar). Five tabs:
|
|
218
|
-
|
|
219
|
-
- **Connect** — copy-paste base URL / API key / login, plus ready-to-use SDK env lines.
|
|
220
|
-
- **Models** — full CRUD: add sources, discover & enable models per slot, drag-order priority, toggle each slot, set [model name mapping](#model-name-mapping), and run an end-to-end **test** call against any model + slot.
|
|
221
|
-
- **Logs** — live recent-calls timeline (latency, status, source, format), with circuit-breaker cooldown events shown alongside the failures that triggered them.
|
|
222
|
-
- **Stats** — aggregate call counts / success rate / latency (p50, p95) by model, source, format, and day, over a selectable range (24h / 7d / 30d / 90d / all).
|
|
223
|
-
- **Settings** — rotate the API key, change the account password, view/reset circuit-breaker state, and see where `data.json` + `logs.jsonl` live.
|
|
92
|
+
| I want to… | Command |
|
|
93
|
+
|---|---|
|
|
94
|
+
| run the gateway | `myapikey serve [--port 7800] [--data-dir <dir>]` |
|
|
95
|
+
| print what to paste into tools | `myapikey whoami` |
|
|
96
|
+
| add a backend | `myapikey provider add <name> --base-url-openai https://api.openai.com/v1 --key sk-… --formats openai` |
|
|
97
|
+
| see a backend's models | `myapikey provider models <name>` |
|
|
98
|
+
| enable a model | `myapikey model enable <model> --format openai --via <backend>` |
|
|
99
|
+
| add a fallback + set order | `myapikey model add-provider <model> <backend> --format openai` · `myapikey model prioritize <model> <primary> <backup> --format openai` |
|
|
100
|
+
| see the routing table | `myapikey model list` |
|
|
101
|
+
| pace a model | `myapikey model pace <model> <rpm\|0>` |
|
|
102
|
+
| pin a slot's thinking level | `myapikey model thinking <model> <index> [value] --format <fmt>` |
|
|
103
|
+
| quick test | `myapikey call <model> "hi"` |
|
|
224
104
|
|
|
225
|
-
|
|
105
|
+
> OpenAI base URLs **include** the version segment (`/v1`, Ark's `/api/v3`); Anthropic base URLs **exclude** it (`https://api.anthropic.com`). The `responses` format only accepts backends you've marked `supportsResponses` (web UI toggle).
|
|
106
|
+
> CLI global flags: `-u/--url`, `--user`, `--pass`, `--api-key`, or env `MYAPIKEY_URL` / `MYAPIKEY_USER` / `MYAPIKEY_PASS` / `MYAPIKEY_API_KEY`.
|
|
226
107
|
|
|
227
|
-
|
|
108
|
+
## Where your data lives
|
|
228
109
|
|
|
229
|
-
|
|
110
|
+
One directory (default `~/.myapikey`, override with `--data-dir` or `MYAPIKEY_DATA_DIR`):
|
|
230
111
|
|
|
231
|
-
|
|
|
112
|
+
| File | What |
|
|
232
113
|
|---|---|
|
|
233
|
-
| `
|
|
234
|
-
| `
|
|
235
|
-
| `
|
|
236
|
-
| `
|
|
237
|
-
| `provider models <ref>` | discover models offered by a backend |
|
|
238
|
-
| `provider remove <ref>` | remove a backend (id or name) |
|
|
239
|
-
| `model enable <name> --format <fmt> [--via <ref>]` | enable a model on a slot (openai / anthropic / responses) |
|
|
240
|
-
| `model disable <name> --format <fmt>` | disable one slot (keeps config) |
|
|
241
|
-
| `model list` | show the per-slot routing table |
|
|
242
|
-
| `model add-provider <name> <ref> --format <fmt>` | add a fallback source for a slot |
|
|
243
|
-
| `model remove-provider <name> <ref> --format <fmt>` | remove a source from a slot |
|
|
244
|
-
| `model prioritize <name> <ref>... --format <fmt>` | set source order for a slot (left = primary) |
|
|
245
|
-
| `model remove <name>` | remove a model entirely (all slots) |
|
|
246
|
-
| `call <model> [prompt...]` | quick test through the gateway (OpenAI path) |
|
|
247
|
-
|
|
248
|
-
`<ref>` is a provider id or name. Global flags: `-u/--url`, `--user`, `--pass`, `--api-key` (or `MYAPIKEY_URL` / `MYAPIKEY_USER` / `MYAPIKEY_PASS` / `MYAPIKEY_API_KEY` env vars).
|
|
249
|
-
|
|
250
|
-
---
|
|
251
|
-
|
|
252
|
-
## API surface
|
|
253
|
-
|
|
254
|
-
**Agent-facing (two surfaces, one API key — `Authorization: Bearer` or `x-api-key`):**
|
|
255
|
-
|
|
256
|
-
- `POST /openai/v1/chat/completions` — OpenAI-format proxy
|
|
257
|
-
- `POST /openai/v1/responses` — OpenAI Responses API (only sources marked `supportsResponses`)
|
|
258
|
-
- `GET /openai/v1/models` — models enabled on the **openai** slot, OpenAI list shape (public — no key needed)
|
|
259
|
-
- `POST /anthropic/v1/messages` — Anthropic-format proxy
|
|
260
|
-
- `GET /anthropic/v1/models` — models enabled on the **anthropic** slot, Anthropic list shape (public — no key needed)
|
|
261
|
-
- `GET /health` — public liveness check
|
|
262
|
-
|
|
263
|
-
**Admin (`/admin`, account password — HTTP Basic):**
|
|
264
|
-
|
|
265
|
-
- `/admin/account` `GET` / `PUT` — read or change username/password (either field optional)
|
|
266
|
-
- `/admin/api-key` `GET`, `/admin/api-key/rotate` `POST` — the key agents use
|
|
267
|
-
- `/admin/connection` `GET` — detected LAN IP (for pointing other machines at the gateway)
|
|
268
|
-
- `/admin/providers`, `/admin/providers/:id` `POST`/`PUT`/`DELETE`, `/admin/providers/:id/discover` `POST` — backend CRUD + model discovery
|
|
269
|
-
- `/admin/models`, `/admin/models/:name/{providers,priority,map,disable,test}` — routing-table CRUD, [per-source name mapping](#model-name-mapping), and end-to-end test
|
|
270
|
-
- `/admin/logs` `GET` — recent calls (tail of the on-disk log)
|
|
271
|
-
- `/admin/stats?range=24h|7d|30d|90d|all` `GET` — aggregate stats over retained history
|
|
272
|
-
- `/admin/storage` `GET` — where `data.json` + `logs.jsonl` live
|
|
273
|
-
- `/admin/circuit` `GET`, `/admin/circuit/:id/reset` `POST` — circuit-breaker snapshot + manual reset
|
|
274
|
-
|
|
275
|
-
---
|
|
276
|
-
|
|
277
|
-
## Storage
|
|
278
|
-
|
|
279
|
-
Everything lives in one data directory (default `~/.myapikey`):
|
|
280
|
-
|
|
281
|
-
| File | Contents |
|
|
282
|
-
|---|---|
|
|
283
|
-
| `data.json` | all config — providers, the per-model routing table, account, API key |
|
|
284
|
-
| `logs.jsonl` | call history, one JSON object per line (retained ~90 days / 1 M lines) |
|
|
285
|
-
| `credentials.txt` | human-readable web login + API key, regenerated on every startup |
|
|
286
|
-
| `client.json` | CLI client profile (base url + login + API key) |
|
|
287
|
-
|
|
288
|
-
Override the directory with `--data-dir <path>` or `MYAPIKEY_DATA_DIR`. Call logs are never held in memory: the Logs timeline tail-reads the newest ~200 entries, and stats stream over the full retained file on demand.
|
|
289
|
-
|
|
290
|
-
---
|
|
114
|
+
| `data.json` | all config: backends, routing table, account, API key |
|
|
115
|
+
| `logs.jsonl` | call history (kept ~90 days / 1 M lines) |
|
|
116
|
+
| `credentials.txt` | your login + API key in plain text, rewritten every start |
|
|
117
|
+
| `client.json` | the CLI's saved connection profile |
|
|
291
118
|
|
|
292
119
|
## Development
|
|
293
120
|
|
|
294
121
|
```bash
|
|
295
122
|
npm install
|
|
296
|
-
npm run build:web # build the Vue UI into packages/web/dist
|
|
297
|
-
npm
|
|
298
|
-
npm run dev
|
|
299
|
-
npm
|
|
300
|
-
npm run
|
|
123
|
+
npm run build:web # build the Vue UI into packages/web/dist
|
|
124
|
+
npm run dev # gateway with watch reload
|
|
125
|
+
npm run dev:web # vite dev server (proxies API calls to :7800)
|
|
126
|
+
npm test # vitest unit + integration
|
|
127
|
+
npm run test:e2e # playwright against a real gateway process
|
|
128
|
+
npm run typecheck # tsc + vue-tsc
|
|
301
129
|
```
|
|
302
130
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
---
|
|
306
|
-
|
|
307
|
-
## Notes and non-goals
|
|
131
|
+
## Honest limits
|
|
308
132
|
|
|
309
|
-
- **No OpenAI↔Anthropic translation.**
|
|
310
|
-
- **
|
|
311
|
-
- **
|
|
312
|
-
- **The login password and the API key are separate.** The API key is what agents use; rotate it (`POST /admin/api-key/rotate` or the web UI) if a tool's config leaks. Don't hand either to untrusted code.
|
|
133
|
+
- **No OpenAI↔Anthropic translation.** Wrong entrance for a model → `404`.
|
|
134
|
+
- **Single user, no TLS.** Meant for your own network; put a reverse proxy in front if you expose it.
|
|
135
|
+
- **Web password ≠ API key.** The password administers the UI/CLI; agents use the `sk-myapikey-…` key. If a tool config leaks, rotate the key (Settings, or `POST /admin/api-key/rotate`) and nothing else changes.
|
package/README.zh-CN.md
CHANGED
|
@@ -2,311 +2,134 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/myapikey)
|
|
4
4
|
[](./LICENSE)
|
|
5
|
-
[](
|
|
5
|
+
[](#60-秒跑起来)
|
|
6
|
+
[](#开发)
|
|
7
7
|
|
|
8
8
|
[English](README.md) | 简体中文
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
**一个地址、一把 key,接上你所有的 AI 工具。** 一个跑在自己家用服务器上的轻量 LLM 网关:供应商的 key 收进网关统一保管,所有工具只认同一个地址 + 同一把 key,后端挂了自动切换。
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
## 是不是在说你
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
- 你用着 **Claude Code、好几个 OpenAI 兼容 CLI、编辑器插件、脚本**——每个都得单独填一遍 base URL + API key。
|
|
15
|
+
- 你的**真实 key**(OpenAI、Anthropic、OpenRouter、本地 Ollama……)散落在十几个配置文件里。想换一把 key?先想清楚它粘在了哪儿。
|
|
16
|
+
- **主力供应商一限流或宕机**,活儿干到一半就断。切备用?又得改一轮配置。
|
|
17
|
+
- 你试过 **one-api / new-api 这类网关**,它们在格式之间做*翻译*——然后你遇到丢字段、流式错乱、新参数要等网关适配才能用。
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
MyAPIKey 就是为治这些而生的。
|
|
17
20
|
|
|
18
|
-
##
|
|
21
|
+
## 你能得到什么
|
|
19
22
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
- [接口一览](#接口一览)
|
|
29
|
-
- [存储说明](#存储说明)
|
|
30
|
-
- [开发指南](#开发指南)
|
|
31
|
-
- [说明与非目标](#说明与非目标)
|
|
23
|
+
- **一个地址 + 一把 key。** 所有工具都指向 `http://你的机器:7800`,只用一把 `sk-myapikey-…`。真实供应商 key 只存在网关里——轮换、换后端,都不用再碰任何工具的配置。
|
|
24
|
+
- **自动故障转移 + 熔断。** 给同一个模型配一条有序的后端链:429 / 5xx / 超时自动切下一条;连续失败的后端进入冷却(30 秒 → 5 分钟),后续请求先跳过它。你的 agent 毫无感知。
|
|
25
|
+
- **纯透传,零翻译。** OpenAI 格式的调用只发给说 OpenAI 格式的后端,Anthropic 同理——请求/响应逐字节转发,流式直通。上游新出的字段、新参数,不需要网关"适配"就能用。
|
|
26
|
+
- **限流靠排队,不甩 429。** 给每个后端设 rpm 上限,或给模型设匀速节奏;超出的调用先在网关排队,而不是直接把 429 甩回工具。
|
|
27
|
+
- **模型名映射。** 对工具暴露一个好记的名字(`claude-sonnet-4`);每个后端各自映射到真实 id(这里发 `claude-sonnet-4-20250514`,那边发别的)。
|
|
28
|
+
- **每个槽位可固定默认 thinking 档位。** 在路由上钉死一个推理档位 / 预算,覆盖请求里带的设置——想管住 Claude Code 的思考行为,网关说了算。
|
|
29
|
+
- **全程看得见。** Web 界面(中/英):实时调用日志、成功率、p50/p95 延迟,按模型 / 来源 / 日期的统计;CLI 功能完全对等。
|
|
30
|
+
- **轻得没负担。** 无数据库——一个 `data.json` 加一个 `logs.jsonl`;`npx` 一行起跑;为局域网 / 家用服务器设计。
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
## 60 秒跑起来
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
如果下面几条说到你心坎里,那 MyAPIKey 就适合你:
|
|
38
|
-
|
|
39
|
-
- 你在 OpenAI、Anthropic、OpenRouter、本地模型之间来回切换**好几把 API Key**,受够了每个工具都要粘一份不同的密钥。
|
|
40
|
-
- 你想让 agent 始终指向**同一个稳定地址**,哪怕背后的供应商变了。
|
|
41
|
-
- 你想要**自动故障转移**——主后端被限流或掉线时,下一个顶上。
|
|
42
|
-
- 你想**在不改动任何工具配置**的前提下,重命名或替换某个模型。
|
|
43
|
-
- 你想要**每一次调用的记录**(延迟、状态、由哪个后端服务)。
|
|
44
|
-
|
|
45
|
-
MyAPIKey 一次性给你这些。你把真实的供应商密钥留在网关内部;你的工具只认识 `http://<主机>:7800` 和一把 `sk-myapikey-…` 密钥。
|
|
46
|
-
|
|
47
|
-
**先知道一件事:网关按你工具的原生格式转发,绝不翻译。** 调用 OpenAI 端点的请求只会到达支持 OpenAI 格式的后端;调用 Anthropic 端点的请求只会到达支持 Anthropic 格式的后端。这保证了请求与响应零损耗。(配好之后,可看 [路由是怎么工作的](#路由是怎么工作的)。)
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## 快速开始
|
|
52
|
-
|
|
53
|
-
需要 **Node.js 18+**。无需安装,直接用 npm 跑:
|
|
34
|
+
需要 Node.js 18+。
|
|
54
35
|
|
|
55
36
|
```bash
|
|
56
37
|
npx myapikey serve # → http://localhost:7800
|
|
57
38
|
```
|
|
58
39
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
npm install -g myapikey
|
|
63
|
-
myapikey serve
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
首次运行会打印生成的凭据,**并同时写入文件**,这样你就不会弄丢:
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
First run — here are your credentials (save them):
|
|
70
|
-
username : <随机> (网页登录用)
|
|
71
|
-
password : <随机> (网页登录用)
|
|
72
|
-
api key : sk-myapikey-… (填进工具的 "api key" 字段)
|
|
73
|
-
↳ also written to ~/.myapikey/credentials.txt
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
在浏览器打开 `http://localhost:7800`,用**用户名 / 密码**登录进入网页界面。**API Key** 是分开的另一把——那是你的 AI 工具稍后用的。
|
|
77
|
-
|
|
78
|
-
> 忘了凭据?`cat ~/.myapikey/credentials.txt`(每次启动都会重新生成)。用 `--data-dir <路径>` 或环境变量 `MYAPIKEY_DATA_DIR` 可改数据存放位置。
|
|
79
|
-
|
|
80
|
-
网关已经跑起来了。下一步,给它一个可以转发的后端。
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## 添加第一个后端
|
|
85
|
-
|
|
86
|
-
一个**后端**(provider / 来源)就是一个上游 API:OpenAI、Anthropic、OpenRouter、本地 Ollama、火山引擎 Ark 之类的厂商等。你只需用它的真实密钥加一次,然后通过它启用模型。
|
|
87
|
-
|
|
88
|
-
### 最简单:网页界面
|
|
89
|
-
|
|
90
|
-
1. 打开**模型**标签 → **添加模型来源**。
|
|
91
|
-
2. 填名称、后端的 Base URL、它的 API Key。
|
|
92
|
-
3. 勾选它支持的线材格式:`openai`、`anthropic`(或两者都勾)。
|
|
93
|
-
4. 保存,然后点**刷新模型**拉取它的模型列表。
|
|
94
|
-
5. 在对应的格式槽位上**启用**你想要的模型。
|
|
95
|
-
|
|
96
|
-
### 或者:命令行
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
# 一个 OpenAI 兼容后端。OpenAI 的 Base URL 含版本段
|
|
100
|
-
# (例如 /v1,或厂商自己的,如火山 Ark 的 /api/v3)。
|
|
101
|
-
myapikey provider add openai-direct \
|
|
102
|
-
--base-url-openai https://api.openai.com/v1 \
|
|
103
|
-
--key sk-... \
|
|
104
|
-
--formats openai
|
|
105
|
-
|
|
106
|
-
# 一个 Anthropic 后端。Anthropic 的 Base URL 不含 /v1
|
|
107
|
-
# (https://api.anthropic.com,或火山 Ark 的 /api/plan)。
|
|
108
|
-
myapikey provider add anthropic-direct \
|
|
109
|
-
--base-url-anthropic https://api.anthropic.com \
|
|
110
|
-
--key sk-ant-... \
|
|
111
|
-
--formats anthropic
|
|
112
|
-
|
|
113
|
-
# 看看各自提供哪些模型,然后在某个槽位上启用一个:
|
|
114
|
-
myapikey provider models openai-direct
|
|
115
|
-
myapikey model enable gpt-4o-mini --format openai --via openai-direct
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
这个模型现在就能通过网关访问了。要为同一个模型加**备用来源**(在第一个失败时顶上),再加一个后端并把它串进链里:
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
myapikey provider add backup --base-url-openai https://api.openrouter.ai/api/v1 --key sk-or-... --formats openai
|
|
122
|
-
myapikey model add-provider gpt-4o-mini backup --format openai
|
|
123
|
-
myapikey model prioritize gpt-4o-mini openai-direct backup --format openai # 左边 = 主
|
|
124
|
-
myapikey model list # 查看路由表
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
> `--format` 选择路由**槽位**:`openai`(对应 `/openai/v1/chat/completions`)、`anthropic`(对应 `/anthropic/v1/messages`)、或 `responses`(对应 `/openai/v1/responses`)。`responses` 槽位只接受你标记了 **supportsResponses** 的后端——在网页界面里勾选那个开关(CLI 暂未暴露它)。
|
|
128
|
-
|
|
129
|
-
---
|
|
130
|
-
|
|
131
|
-
## 连接你的工具
|
|
132
|
-
|
|
133
|
-
一旦启用某个模型,就可以把任意 agent 指向网关。先取出连接信息:
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
myapikey whoami # 打印 base url + api key + 可直接粘贴的环境变量
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
**兼容 OpenAI** 的工具(覆盖 `/chat/completions` *和* `/responses`):
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
export OPENAI_BASE_URL=http://localhost:7800/openai/v1
|
|
143
|
-
export OPENAI_API_KEY=<网关 API Key>
|
|
144
|
-
```
|
|
40
|
+
首次启动会打印凭据,并同时存到 `~/.myapikey/credentials.txt`:
|
|
145
41
|
|
|
146
|
-
|
|
42
|
+
1. 浏览器打开 `http://localhost:7800`,用打印出来的**用户名 / 密码**登录(网页管理用)。
|
|
43
|
+
2. **模型**页 → **添加后端**:填后端的 base URL + 它的真实 key,勾选它说的格式(`openai` / `anthropic`),然后**刷新模型** → 在对应格式上**启用**你想要的。
|
|
44
|
+
3. 把工具指过来——所有工具里的 "API key" 字段都填**网关 key**,不是网页登录密码:
|
|
147
45
|
|
|
148
46
|
```bash
|
|
47
|
+
# Claude Code / Anthropic 系
|
|
149
48
|
export ANTHROPIC_BASE_URL=http://localhost:7800/anthropic
|
|
150
|
-
export ANTHROPIC_API_KEY
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
> 不管某个工具把它叫什么 "API key" 字段,都填**网关 API Key**——*不是*你的网页登录密码。登录密码只用于网页界面和 CLI 管理命令。
|
|
49
|
+
export ANTHROPIC_API_KEY=sk-myapikey-…
|
|
154
50
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
myapikey call gpt-4o-mini "用一句话打个招呼。"
|
|
51
|
+
# OpenAI 兼容工具(Codex、各种 CLI / 插件)
|
|
52
|
+
export OPENAI_BASE_URL=http://localhost:7800/openai/v1
|
|
53
|
+
export OPENAI_API_KEY=sk-myapikey-…
|
|
159
54
|
```
|
|
160
55
|
|
|
161
|
-
|
|
56
|
+
> 为什么一个是 `…/openai/v1` 一个是 `…/anthropic`?各生态 SDK 自己补路径(OpenAI 补 `/chat/completions`,Anthropic 补 `/v1/messages`),网关按各自的约定来。两边各有独立的 `/models`,只列出各自格式启用的模型。`myapikey whoami` 随时打印这些可直接粘贴的配置。
|
|
162
57
|
|
|
163
|
-
|
|
58
|
+
4. 不开工具也能冒烟测试:
|
|
164
59
|
|
|
165
60
|
```bash
|
|
166
|
-
|
|
167
|
-
curl http://localhost:7800/openai/v1/chat/completions \
|
|
168
|
-
-H "Authorization: Bearer <网关 API Key>" -H "Content-Type: application/json" \
|
|
169
|
-
-d '{"model":"<模型名>","messages":[{"role":"user","content":"hi"}]}'
|
|
170
|
-
|
|
171
|
-
# Anthropic 系
|
|
172
|
-
curl http://localhost:7800/anthropic/v1/messages \
|
|
173
|
-
-H "Authorization: Bearer <网关 API Key>" -H "Content-Type: application/json" \
|
|
174
|
-
-d '{"model":"<模型名>","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}'
|
|
61
|
+
myapikey call gpt-4o-mini "用一句话介绍你自己"
|
|
175
62
|
```
|
|
176
63
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
---
|
|
64
|
+
喜欢全程命令行?`provider add` → `provider models` → `model enable` 干的是同一件事,速查表在下面。
|
|
180
65
|
|
|
181
|
-
##
|
|
66
|
+
## 路由是怎么回事(就这么多)
|
|
182
67
|
|
|
183
|
-
|
|
68
|
+
- 网关开两个入口:`/openai/v1/*`(chat completions、responses、models)和 `/anthropic/v1/*`(messages、models)。请求从哪个口进,就按哪个口的格式转发,**绝不翻译**。
|
|
69
|
+
- 每个模型在每个格式上有一条独立、有序的后端链。链内按序故障转移:429 / 5xx / 超时切下一条;其他 4xx 原样返回(那是调用方的错);开始流式后不再切换。该格式下没有任何后端能服务这个模型,就返回 `404`——宁可失败得明明白白,也不悄悄翻译。
|
|
70
|
+
- 只要"会说"这两种格式的都能接:OpenAI、Anthropic、OpenRouter、Ollama / vLLM、火山 Ark、公司内部网关……
|
|
184
71
|
|
|
185
|
-
|
|
72
|
+
## 为什么不做翻译?
|
|
186
73
|
|
|
187
|
-
|
|
74
|
+
one-api / new-api 在 OpenAI ↔ Anthropic 之间做格式转换,方便是方便,但字段会丢、流式会乱,上游出新参数还得等网关更新;OpenRouter 是托管服务,每次调用抽成。MyAPIKey 的答案是干脆没有翻译层:
|
|
188
75
|
|
|
189
|
-
|
|
76
|
+
| | MyAPIKey | one-api / new-api | OpenRouter |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| 部署 | 自托管,`npx` 一行 | 自托管,较重 | 托管服务 |
|
|
79
|
+
| 费用 | 免费 | 免费 | 按次加价 |
|
|
80
|
+
| 格式 | 逐字节透传 | 翻译 | 翻译 |
|
|
81
|
+
| 上游新参数 | 立即可用 | 等网关适配 | 等平台适配 |
|
|
82
|
+
| 供应商 key | 在你自己机器上 | 在你自己机器上 | 在平台手里 |
|
|
190
83
|
|
|
191
|
-
|
|
84
|
+
代价也直说:Anthropic 格式的后端没法从 OpenAI 入口调。如果你要的就是格式互转,这个工具暂时不适合你。
|
|
192
85
|
|
|
193
|
-
|
|
86
|
+
## 怎么管
|
|
194
87
|
|
|
195
|
-
|
|
88
|
+
**Web 界面** — `http://localhost:7800`,中英双语。五个页:**使用方式**(可直接粘贴的连接配置)、**模型**(后端、发现、按格式启用、拖拽排序、名称映射、测试调用)、**最近调用**(实时时间线)、**统计**(调用数 / 成功率 / p50 / p95,按模型 / 来源 / 日期)、**设置**(轮换 key、改密码、熔断状态、存储位置)。
|
|
196
89
|
|
|
197
|
-
|
|
90
|
+
**CLI 速查**(和网页同一套 admin API,两边永远一致):
|
|
198
91
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
若某来源未设映射,则原样发送公开模型名。
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
## 网页界面
|
|
216
|
-
|
|
217
|
-
浏览器打开 `http://localhost:7800`,用用户名 / 密码登录。内置**中英文**,顶栏可切换。五个标签:
|
|
218
|
-
|
|
219
|
-
- **使用方式** — 可直接复制粘贴的 Base URL / API Key / 登录信息,以及现成的 SDK 环境变量。
|
|
220
|
-
- **模型** — 完整增删改查:添加来源、按槽位发现并启用模型、拖拽排序优先级、切换每个槽位、设置[模型名映射](#模型名映射),还能对任意「模型 + 槽位」跑一次端到端**测试**。
|
|
221
|
-
- **最近调用** — 实时的近期调用时间线(延迟、状态、来源、格式),熔断冷却事件会和触发它的失败记录并排显示。
|
|
222
|
-
- **统计** — 按模型 / 来源 / 格式 / 日期汇总调用次数、成功率、延迟(p50、p95),可选时间范围(24 小时 / 7 天 / 30 天 / 90 天 / 全部)。
|
|
223
|
-
- **设置** — 轮换 API Key、修改账号密码、查看 / 重置熔断状态、查看 `data.json` 和 `logs.jsonl` 的位置。
|
|
224
|
-
|
|
225
|
-
它和 CLI 用的是同一套 admin API——想用哪种配置都行。
|
|
92
|
+
| 我想…… | 命令 |
|
|
93
|
+
|---|---|
|
|
94
|
+
| 起网关 | `myapikey serve [--port 7800] [--data-dir <目录>]` |
|
|
95
|
+
| 打印要粘给工具的配置 | `myapikey whoami` |
|
|
96
|
+
| 加后端 | `myapikey provider add <名> --base-url-openai https://api.openai.com/v1 --key sk-… --formats openai` |
|
|
97
|
+
| 看后端有哪些模型 | `myapikey provider models <名>` |
|
|
98
|
+
| 启用模型 | `myapikey model enable <模型> --format openai --via <后端>` |
|
|
99
|
+
| 加备用 + 排序 | `myapikey model add-provider <模型> <后端> --format openai` · `myapikey model prioritize <模型> <主> <备> --format openai` |
|
|
100
|
+
| 看路由表 | `myapikey model list` |
|
|
101
|
+
| 给模型设匀速限速 | `myapikey model pace <模型> <rpm\|0>` |
|
|
102
|
+
| 钉死槽位 thinking 档位 | `myapikey model thinking <模型> <链上位次> [档位] --format <格式>` |
|
|
103
|
+
| 快速试一把 | `myapikey call <模型> "你好"` |
|
|
226
104
|
|
|
227
|
-
|
|
105
|
+
> OpenAI 的 base URL **含**版本段(`/v1`、火山 Ark 的 `/api/v3`);Anthropic 的 base URL **不含**(`https://api.anthropic.com`)。`responses` 格式只接受你标记了 `supportsResponses` 的后端(网页里勾选)。
|
|
106
|
+
> CLI 全局参数:`-u/--url`、`--user`、`--pass`、`--api-key`,或环境变量 `MYAPIKEY_URL` / `MYAPIKEY_USER` / `MYAPIKEY_PASS` / `MYAPIKEY_API_KEY`。
|
|
228
107
|
|
|
229
|
-
##
|
|
108
|
+
## 数据都在哪
|
|
230
109
|
|
|
231
|
-
|
|
232
|
-
|---|---|
|
|
233
|
-
| `serve [--port 7800] [--data-dir <目录>] [--web-dir <路径>]` | 运行网关 |
|
|
234
|
-
| `whoami` | 打印给 agent 用的连接信息(base url、api key、登录信息、示例环境变量) |
|
|
235
|
-
| `provider add <名称> --base-url-openai URL [--base-url-anthropic URL] --key KEY --formats openai,anthropic` | 添加后端(`supportsResponses` 在网页界面设置) |
|
|
236
|
-
| `provider list` | 列出后端 |
|
|
237
|
-
| `provider models <引用>` | 发现某个后端提供的模型 |
|
|
238
|
-
| `provider remove <引用>` | 移除后端(用 id 或名称) |
|
|
239
|
-
| `model enable <名称> --format <格式> [--via <引用>]` | 在某个槽位启用模型(openai / anthropic / responses) |
|
|
240
|
-
| `model disable <名称> --format <格式>` | 禁用某个槽位(保留配置) |
|
|
241
|
-
| `model list` | 查看按槽位的路由表 |
|
|
242
|
-
| `model add-provider <名称> <引用> --format <格式>` | 为某个槽位添加备用来源 |
|
|
243
|
-
| `model remove-provider <名称> <引用> --format <格式>` | 从某个槽位移除来源 |
|
|
244
|
-
| `model prioritize <名称> <引用>... --format <格式>` | 设置某个槽位的来源顺序(左 = 主) |
|
|
245
|
-
| `model remove <名称>` | 完全移除一个模型(所有槽位) |
|
|
246
|
-
| `call <模型> [提示词...]` | 通过网关快速测试(走 OpenAI 路径) |
|
|
247
|
-
|
|
248
|
-
`<引用>` 是来源的 id 或名称。全局参数:`-u/--url`、`--user`、`--pass`、`--api-key`(或 `MYAPIKEY_URL` / `MYAPIKEY_USER` / `MYAPIKEY_PASS` / `MYAPIKEY_API_KEY` 环境变量)。
|
|
249
|
-
|
|
250
|
-
---
|
|
251
|
-
|
|
252
|
-
## 接口一览
|
|
253
|
-
|
|
254
|
-
**面向 agent(两个面,共用一把 API Key —— `Authorization: Bearer` 或 `x-api-key`):**
|
|
255
|
-
|
|
256
|
-
- `POST /openai/v1/chat/completions` — OpenAI 格式代理
|
|
257
|
-
- `POST /openai/v1/responses` — OpenAI Responses API(仅限标记了 `supportsResponses` 的来源)
|
|
258
|
-
- `GET /openai/v1/models` — 启用在 **openai** 槽位的模型,OpenAI 列表格式(公开——无需 Key)
|
|
259
|
-
- `POST /anthropic/v1/messages` — Anthropic 格式代理
|
|
260
|
-
- `GET /anthropic/v1/models` — 启用在 **anthropic** 槽位的模型,Anthropic 列表格式(公开——无需 Key)
|
|
261
|
-
- `GET /health` — 公开存活检查
|
|
262
|
-
|
|
263
|
-
**管理(`/admin`,账号密码 —— HTTP Basic):**
|
|
264
|
-
|
|
265
|
-
- `/admin/account` `GET` / `PUT` — 读取或修改用户名 / 密码(任一字段可选)
|
|
266
|
-
- `/admin/api-key` `GET`、`/admin/api-key/rotate` `POST` — agent 用的那把密钥
|
|
267
|
-
- `/admin/connection` `GET` — 探测到的局域网 IP(用于把其他机器指向网关)
|
|
268
|
-
- `/admin/providers`、`/admin/providers/:id` `POST`/`PUT`/`DELETE`、`/admin/providers/:id/discover` `POST` — 后端增删改查 + 模型发现
|
|
269
|
-
- `/admin/models`、`/admin/models/:name/{providers,priority,map,disable,test}` — 路由表增删改查、[按来源的模型名映射](#模型名映射)、端到端测试
|
|
270
|
-
- `/admin/logs` `GET` — 近期调用(落盘日志的尾部)
|
|
271
|
-
- `/admin/stats?range=24h|7d|30d|90d|all` `GET` — 基于保留历史的聚合统计
|
|
272
|
-
- `/admin/storage` `GET` — 查看 `data.json` 和 `logs.jsonl` 的位置
|
|
273
|
-
- `/admin/circuit` `GET`、`/admin/circuit/:id/reset` `POST` — 熔断快照 + 手动重置
|
|
274
|
-
|
|
275
|
-
---
|
|
276
|
-
|
|
277
|
-
## 存储说明
|
|
278
|
-
|
|
279
|
-
所有东西都放在一个数据目录里(默认 `~/.myapikey`):
|
|
110
|
+
一个目录(默认 `~/.myapikey`,可用 `--data-dir` 或 `MYAPIKEY_DATA_DIR` 改):
|
|
280
111
|
|
|
281
112
|
| 文件 | 内容 |
|
|
282
113
|
|---|---|
|
|
283
|
-
| `data.json` |
|
|
284
|
-
| `logs.jsonl` |
|
|
285
|
-
| `credentials.txt` |
|
|
286
|
-
| `client.json` | CLI
|
|
114
|
+
| `data.json` | 全部配置:后端、路由表、账号、API key |
|
|
115
|
+
| `logs.jsonl` | 调用历史(保留约 90 天 / 100 万行) |
|
|
116
|
+
| `credentials.txt` | 明文的登录信息 + API key,每次启动重写 |
|
|
117
|
+
| `client.json` | CLI 自己存的连接配置 |
|
|
287
118
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
---
|
|
291
|
-
|
|
292
|
-
## 开发指南
|
|
119
|
+
## 开发
|
|
293
120
|
|
|
294
121
|
```bash
|
|
295
122
|
npm install
|
|
296
|
-
npm run build:web # 把 Vue 界面构建到 packages/web/dist
|
|
297
|
-
npm start # tsx ... serve(网关在 :7800)
|
|
123
|
+
npm run build:web # 把 Vue 界面构建到 packages/web/dist
|
|
298
124
|
npm run dev # 网关,带 watch 热重载
|
|
299
|
-
npm run dev:web # vite 开发服务器(
|
|
300
|
-
npm
|
|
125
|
+
npm run dev:web # vite 开发服务器(API 代理到 :7800)
|
|
126
|
+
npm test # vitest 单测 + 集成
|
|
127
|
+
npm run test:e2e # playwright 打真实网关进程
|
|
128
|
+
npm run typecheck # tsc + vue-tsc
|
|
301
129
|
```
|
|
302
130
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
---
|
|
306
|
-
|
|
307
|
-
## 说明与非目标
|
|
131
|
+
## 丑话说在前面
|
|
308
132
|
|
|
309
|
-
- **不做 OpenAI ↔ Anthropic 翻译。**
|
|
310
|
-
-
|
|
311
|
-
-
|
|
312
|
-
- **登录密码和 API Key 是分开的两把。** API Key 是 agent 用的;若某个工具的配置泄露了,就轮换它(`POST /admin/api-key/rotate` 或网页界面)。两把都不要交给不可信的代码路径。
|
|
133
|
+
- **不做 OpenAI ↔ Anthropic 翻译。** 入口和模型格式对不上 → `404`。
|
|
134
|
+
- **单用户、无 TLS。** 给你自己的内网用的;要暴露到外网请自己套反代。
|
|
135
|
+
- **网页密码 ≠ API key。** 密码只管 UI/CLI,agent 只认 `sk-myapikey-…`。工具配置泄露了,去设置页轮换 key,其他什么都不用动。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myapikey",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Personal LLM API gateway & proxy — one address + one API key for all your models. Forwards OpenAI & Anthropic calls to your backends with failover and a circuit breaker. Pure passthrough, no format translation. Self-hosted (CLI + web UI).",
|
|
6
6
|
"keywords": [
|
|
@@ -70,10 +70,19 @@ type UsageFields = Partial<Pick<Usage, "input" | "output" | "cacheRead" | "cache
|
|
|
70
70
|
* `message_delta.usage.output_tokens` (output); a non-streaming Message
|
|
71
71
|
* carries `usage` directly (both).
|
|
72
72
|
* - openai chat: only the final chunk (with stream_options.include_usage) has
|
|
73
|
-
* `usage` (prompt/completion tokens
|
|
73
|
+
* `usage` (prompt/completion tokens + `prompt_tokens_details.cached_tokens`
|
|
74
|
+
* / DeepSeek's `prompt_cache_hit_tokens` for cache hits); the non-streaming
|
|
75
|
+
* body has it too.
|
|
74
76
|
* - responses: `response.usage` on response.completed/in-progress, or top-level
|
|
75
77
|
* `usage` on a non-streaming Response. Accepts both the `*_tokens` and bare
|
|
76
|
-
* `input`/`output` spellings the API has used over time
|
|
78
|
+
* `input`/`output` spellings the API has used over time, plus
|
|
79
|
+
* `input_tokens_details.cached_tokens` for cache hits.
|
|
80
|
+
*
|
|
81
|
+
* Cache-hit fields (`cacheRead` etc.) are captured on every wire that reports
|
|
82
|
+
* them. On the OpenAI-family wires the reported `input`/`prompt_tokens`
|
|
83
|
+
* INCLUDES the cached subset, so it is subtracted here — `Usage.input` means
|
|
84
|
+
* UNCACHED prompt tokens on every wire (Anthropic's native semantics), which
|
|
85
|
+
* is what the store's cache-hit-rate formula assumes. */
|
|
77
86
|
function extractUsage(obj: any, key: RouteKey): UsageFields | null {
|
|
78
87
|
if (!obj || typeof obj !== "object") return null;
|
|
79
88
|
|
|
@@ -94,13 +103,31 @@ function extractUsage(obj: any, key: RouteKey): UsageFields | null {
|
|
|
94
103
|
if (u) {
|
|
95
104
|
const input = num(u.input_tokens) ?? num(u.input);
|
|
96
105
|
const output = num(u.output_tokens) ?? num(u.output);
|
|
97
|
-
|
|
106
|
+
// OpenAI Responses reports cache hits inside `input_tokens_details` — a
|
|
107
|
+
// SUBSET of input_tokens (like chat/completions, unlike Anthropic where
|
|
108
|
+
// input_tokens excludes cache), so subtract to keep Usage.input = uncached.
|
|
109
|
+
const details = u.input_tokens_details as { cached_tokens?: unknown } | undefined;
|
|
110
|
+
const cached = num(details?.cached_tokens) ?? 0;
|
|
111
|
+
if (typeof input === "number" || typeof output === "number") {
|
|
112
|
+
return { input: input === undefined ? undefined : input - cached, output, cacheRead: cached > 0 ? cached : undefined };
|
|
113
|
+
}
|
|
98
114
|
}
|
|
99
115
|
return null;
|
|
100
116
|
}
|
|
101
|
-
// openai /chat/completions
|
|
117
|
+
// openai /chat/completions (also its OpenAI-compatible clones)
|
|
102
118
|
if (obj.usage) {
|
|
103
|
-
|
|
119
|
+
const input = num(obj.usage.prompt_tokens);
|
|
120
|
+
const output = num(obj.usage.completion_tokens);
|
|
121
|
+
// Prompt-cache hits on this wire: `prompt_tokens_details.cached_tokens`
|
|
122
|
+
// (OpenAI, Ark, Qwen, GLM, …) or DeepSeek's `prompt_cache_hit_tokens`.
|
|
123
|
+
// Both spell the cached tokens as a SUBSET of prompt_tokens (cached +
|
|
124
|
+
// uncached = prompt), so — unlike Anthropic's separate counters — the hit
|
|
125
|
+
// part is subtracted from input. That keeps Usage.input = UNCACHED prompt
|
|
126
|
+
// tokens on every wire, which is what the stats hit-rate formula
|
|
127
|
+
// (cacheRead / (input + cacheRead + cacheCreation)) assumes.
|
|
128
|
+
const details = obj.usage.prompt_tokens_details as { cached_tokens?: unknown } | undefined;
|
|
129
|
+
const cached = num(details?.cached_tokens) ?? num(obj.usage.prompt_cache_hit_tokens) ?? 0;
|
|
130
|
+
return { input: input === undefined ? undefined : input - cached, output, cacheRead: cached > 0 ? cached : undefined };
|
|
104
131
|
}
|
|
105
132
|
return null;
|
|
106
133
|
}
|
|
@@ -118,8 +118,13 @@ export interface GateConfig {
|
|
|
118
118
|
* streams where the upstream omits usage (most agents don't set
|
|
119
119
|
* stream_options.include_usage), `estimated` is set and input/output come from
|
|
120
120
|
* a local tokenizer approximation (gpt-tokenizer, o200k_base) instead — the UI
|
|
121
|
-
* renders those with a ≈ marker. cacheRead/cacheCreation (prompt-
|
|
122
|
-
*
|
|
121
|
+
* renders those with a ≈ marker. cacheRead/cacheCreation (prompt-cache hit /
|
|
122
|
+
* write tokens) are surfaced separately from `input`, which always means
|
|
123
|
+
* UNCACHED prompt tokens: Anthropic reports them that way natively, while on
|
|
124
|
+
* the OpenAI-family wires the cached subset (chat `prompt_tokens_details.
|
|
125
|
+
* cached_tokens`, DeepSeek `prompt_cache_hit_tokens`, /responses
|
|
126
|
+
* `input_tokens_details.cached_tokens`) arrives INCLUDED in prompt_tokens /
|
|
127
|
+
* input_tokens and is subtracted at capture time to match. */
|
|
123
128
|
export interface Usage {
|
|
124
129
|
input: number;
|
|
125
130
|
output: number;
|