llm-simple-router 0.1.0 → 0.2.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.
Files changed (67) hide show
  1. package/README.md +12 -14
  2. package/dist/admin/groups.js +25 -0
  3. package/dist/admin/providers.d.ts +0 -1
  4. package/dist/admin/providers.js +16 -13
  5. package/dist/admin/proxy-enhancement.d.ts +7 -0
  6. package/dist/admin/proxy-enhancement.js +39 -0
  7. package/dist/admin/router-keys.d.ts +0 -1
  8. package/dist/admin/router-keys.js +17 -8
  9. package/dist/admin/routes.d.ts +0 -3
  10. package/dist/admin/routes.js +9 -4
  11. package/dist/admin/setup.d.ts +7 -0
  12. package/dist/admin/setup.js +44 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +4 -0
  15. package/dist/config.d.ts +1 -4
  16. package/dist/config.js +13 -13
  17. package/dist/db/index.d.ts +5 -2
  18. package/dist/db/index.js +3 -1
  19. package/dist/db/logs.d.ts +5 -2
  20. package/dist/db/logs.js +4 -4
  21. package/dist/db/mappings.d.ts +16 -0
  22. package/dist/db/mappings.js +72 -0
  23. package/dist/db/migrations/014_create_settings.sql +4 -0
  24. package/dist/db/migrations/015_add_original_model.sql +1 -0
  25. package/dist/db/migrations/016_create_session_model_tables.sql +24 -0
  26. package/dist/db/session-states.d.ts +40 -0
  27. package/dist/db/session-states.js +37 -0
  28. package/dist/db/settings.d.ts +4 -0
  29. package/dist/db/settings.js +10 -0
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +53 -13
  32. package/dist/middleware/admin-auth.d.ts +2 -2
  33. package/dist/middleware/admin-auth.js +21 -8
  34. package/dist/middleware/auth.js +46 -1
  35. package/dist/proxy/anthropic.d.ts +0 -1
  36. package/dist/proxy/anthropic.js +2 -2
  37. package/dist/proxy/directive-parser.d.ts +7 -0
  38. package/dist/proxy/directive-parser.js +70 -0
  39. package/dist/proxy/enhancement-handler.d.ts +23 -0
  40. package/dist/proxy/enhancement-handler.js +167 -0
  41. package/dist/proxy/log-helpers.d.ts +41 -0
  42. package/dist/proxy/log-helpers.js +35 -0
  43. package/dist/proxy/mapping-resolver.js +39 -2
  44. package/dist/proxy/model-state.d.ts +28 -0
  45. package/dist/proxy/model-state.js +111 -0
  46. package/dist/proxy/openai.d.ts +0 -1
  47. package/dist/proxy/openai.js +4 -3
  48. package/dist/proxy/proxy-core.d.ts +9 -47
  49. package/dist/proxy/proxy-core.js +215 -344
  50. package/dist/proxy/response-cleaner.d.ts +5 -0
  51. package/dist/proxy/response-cleaner.js +60 -0
  52. package/dist/proxy/strategy/failover.d.ts +1 -1
  53. package/dist/proxy/strategy/failover.js +10 -2
  54. package/dist/proxy/strategy/random.d.ts +1 -1
  55. package/dist/proxy/strategy/random.js +8 -2
  56. package/dist/proxy/strategy/round-robin.d.ts +2 -1
  57. package/dist/proxy/strategy/round-robin.js +13 -2
  58. package/dist/proxy/strategy/targets-rule.d.ts +7 -0
  59. package/dist/proxy/strategy/targets-rule.js +14 -0
  60. package/dist/proxy/strategy/types.d.ts +5 -1
  61. package/dist/proxy/strategy/types.js +3 -0
  62. package/dist/proxy/upstream-call.d.ts +43 -0
  63. package/dist/proxy/upstream-call.js +208 -0
  64. package/dist/utils/password.d.ts +2 -0
  65. package/dist/utils/password.js +14 -0
  66. package/package.json +6 -5
  67. package/.env.example +0 -13
@@ -0,0 +1,60 @@
1
+ const RE_ROUTER_RESPONSE = /<router-response[^>]*>[\s\S]*?<\/router-response>/g;
2
+ const RE_COMMAND = /\[router-command:/;
3
+ /**
4
+ * 清理历史消息中的路由相关内容(命令消息和 router-response 标签)。
5
+ * 只清理历史轮次,跳过最后一条 user 消息(当前轮由 directive-parser 处理)。
6
+ */
7
+ export function cleanRouterResponses(body) {
8
+ const messages = body.messages;
9
+ if (!messages?.length)
10
+ return body;
11
+ const cleaned = JSON.parse(JSON.stringify(body));
12
+ const cleanedMsgs = cleaned.messages;
13
+ // 定位最后一条 user 消息(当前轮),不参与整条过滤
14
+ let lastUserIdx = -1;
15
+ for (let i = cleanedMsgs.length - 1; i >= 0; i--) {
16
+ if (cleanedMsgs[i].role === "user") {
17
+ lastUserIdx = i;
18
+ break;
19
+ }
20
+ }
21
+ const filtered = cleanedMsgs.filter((msg, idx) => {
22
+ // 当前轮的 user 消息保留,由 directive-parser 处理
23
+ if (idx === lastUserIdx)
24
+ return true;
25
+ if (msg.role === "user") {
26
+ const blocks = Array.isArray(msg.content) ? msg.content : [msg.content];
27
+ for (const b of blocks) {
28
+ if (b && typeof b === "object" && "text" in b && typeof b.text === "string") {
29
+ if (RE_COMMAND.test(b.text))
30
+ return false;
31
+ }
32
+ }
33
+ }
34
+ if (msg.role === "assistant") {
35
+ const blocks = Array.isArray(msg.content) ? msg.content : [msg.content];
36
+ const texts = blocks
37
+ .filter((b) => b?.type === "text" && typeof b.text === "string")
38
+ .map((b) => b.text);
39
+ const combined = texts.join("");
40
+ const stripped = combined.replace(RE_ROUTER_RESPONSE, "").trim();
41
+ if (!stripped)
42
+ return false;
43
+ }
44
+ return true;
45
+ });
46
+ // 剥离所有消息中的 <router-response> 标签(包括当前轮)
47
+ for (const msg of filtered) {
48
+ const blocks = Array.isArray(msg.content) ? msg.content : [msg.content];
49
+ for (const b of blocks) {
50
+ if (b && typeof b === "object" && "text" in b && typeof b.text === "string") {
51
+ b.text = b.text
52
+ .replace(RE_ROUTER_RESPONSE, "")
53
+ .replace(/\n{3,}/g, "\n\n")
54
+ .trim();
55
+ }
56
+ }
57
+ }
58
+ cleaned.messages = filtered;
59
+ return cleaned;
60
+ }
@@ -1,4 +1,4 @@
1
1
  import type { MappingStrategy, ResolveContext, Target } from "./types.js";
2
2
  export declare class FailoverStrategy implements MappingStrategy {
3
- select(_rule: unknown, _context: ResolveContext): Target | undefined;
3
+ select(rule: unknown, context: ResolveContext): Target | undefined;
4
4
  }
@@ -1,5 +1,13 @@
1
+ import { isTargetsRule } from "./targets-rule.js";
1
2
  export class FailoverStrategy {
2
- select(_rule, _context) {
3
- throw new Error("Not implemented");
3
+ select(rule, context) {
4
+ if (!isTargetsRule(rule))
5
+ return undefined;
6
+ for (const t of rule.targets) {
7
+ const excluded = context.excludeTargets?.some((e) => e.backend_model === t.backend_model && e.provider_id === t.provider_id);
8
+ if (!excluded)
9
+ return t;
10
+ }
11
+ return undefined;
4
12
  }
5
13
  }
@@ -1,4 +1,4 @@
1
1
  import type { MappingStrategy, ResolveContext, Target } from "./types.js";
2
2
  export declare class RandomStrategy implements MappingStrategy {
3
- select(_rule: unknown, _context: ResolveContext): Target | undefined;
3
+ select(rule: unknown, context: ResolveContext): Target | undefined;
4
4
  }
@@ -1,5 +1,11 @@
1
+ import { isTargetsRule } from "./targets-rule.js";
1
2
  export class RandomStrategy {
2
- select(_rule, _context) {
3
- throw new Error("Not implemented");
3
+ select(rule, context) {
4
+ if (!isTargetsRule(rule))
5
+ return undefined;
6
+ const filtered = rule.targets.filter((t) => !context.excludeTargets?.some((e) => e.backend_model === t.backend_model && e.provider_id === t.provider_id));
7
+ if (filtered.length === 0)
8
+ return undefined;
9
+ return filtered[Math.floor(Math.random() * filtered.length)];
4
10
  }
5
11
  }
@@ -1,4 +1,5 @@
1
1
  import type { MappingStrategy, ResolveContext, Target } from "./types.js";
2
2
  export declare class RoundRobinStrategy implements MappingStrategy {
3
- select(_rule: unknown, _context: ResolveContext): Target | undefined;
3
+ private indexMap;
4
+ select(rule: unknown, context: ResolveContext, clientModel?: string): Target | undefined;
4
5
  }
@@ -1,5 +1,16 @@
1
+ import { isTargetsRule } from "./targets-rule.js";
1
2
  export class RoundRobinStrategy {
2
- select(_rule, _context) {
3
- throw new Error("Not implemented");
3
+ indexMap = new Map();
4
+ select(rule, context, clientModel) {
5
+ if (!isTargetsRule(rule))
6
+ return undefined;
7
+ const key = clientModel ?? JSON.stringify(rule);
8
+ const filtered = rule.targets.filter((t) => !context.excludeTargets?.some((e) => e.backend_model === t.backend_model && e.provider_id === t.provider_id));
9
+ if (filtered.length === 0)
10
+ return undefined;
11
+ const lastIndex = this.indexMap.get(key) ?? -1;
12
+ const nextIndex = (lastIndex + 1) % filtered.length;
13
+ this.indexMap.set(key, nextIndex);
14
+ return filtered[nextIndex];
4
15
  }
5
16
  }
@@ -0,0 +1,7 @@
1
+ import type { Target } from "./types.js";
2
+ interface TargetsRule {
3
+ targets: Target[];
4
+ }
5
+ export declare function isTarget(value: unknown): value is Target;
6
+ export declare function isTargetsRule(value: unknown): value is TargetsRule;
7
+ export {};
@@ -0,0 +1,14 @@
1
+ export function isTarget(value) {
2
+ return (typeof value === "object" &&
3
+ value !== null &&
4
+ "backend_model" in value &&
5
+ typeof value.backend_model === "string" &&
6
+ "provider_id" in value &&
7
+ typeof value.provider_id === "string");
8
+ }
9
+ export function isTargetsRule(value) {
10
+ if (typeof value !== "object" || value === null)
11
+ return false;
12
+ const r = value;
13
+ return Array.isArray(r.targets) && r.targets.every(isTarget);
14
+ }
@@ -1,5 +1,8 @@
1
1
  export declare const STRATEGY_NAMES: {
2
2
  readonly SCHEDULED: "scheduled";
3
+ readonly ROUND_ROBIN: "round-robin";
4
+ readonly RANDOM: "random";
5
+ readonly FAILOVER: "failover";
3
6
  };
4
7
  export interface Target {
5
8
  backend_model: string;
@@ -7,7 +10,8 @@ export interface Target {
7
10
  }
8
11
  export interface ResolveContext {
9
12
  now: Date;
13
+ excludeTargets?: Target[];
10
14
  }
11
15
  export interface MappingStrategy {
12
- select(rule: unknown, context: ResolveContext): Target | undefined;
16
+ select(rule: unknown, context: ResolveContext, clientModel?: string): Target | undefined;
13
17
  }
@@ -1,3 +1,6 @@
1
1
  export const STRATEGY_NAMES = {
2
2
  SCHEDULED: "scheduled",
3
+ ROUND_ROBIN: "round-robin",
4
+ RANDOM: "random",
5
+ FAILOVER: "failover",
3
6
  };
@@ -0,0 +1,43 @@
1
+ import type { FastifyReply } from "fastify";
2
+ import type { RawHeaders } from "./proxy-core.js";
3
+ import type { MetricsResult } from "../metrics/metrics-extractor.js";
4
+ import { SSEMetricsTransform } from "../metrics/sse-metrics-transform.js";
5
+ export interface UpstreamRequestOptions {
6
+ hostname: string;
7
+ port: number;
8
+ path: string;
9
+ method: string;
10
+ headers: Record<string, string>;
11
+ }
12
+ export interface ProxyResult {
13
+ statusCode: number;
14
+ body: string;
15
+ headers: Record<string, string>;
16
+ sentHeaders: Record<string, string>;
17
+ sentBody: string;
18
+ }
19
+ export interface StreamProxyResult {
20
+ statusCode: number;
21
+ responseBody?: string;
22
+ upstreamResponseHeaders?: Record<string, string>;
23
+ sentHeaders?: Record<string, string>;
24
+ metricsResult?: MetricsResult;
25
+ }
26
+ export interface GetProxyResult {
27
+ statusCode: number;
28
+ body: string;
29
+ headers: Record<string, string>;
30
+ }
31
+ /** 根据 URL scheme 选择 http 或 https 模块 */
32
+ export declare function createUpstreamRequest(url: URL, options: UpstreamRequestOptions): import("http").ClientRequest;
33
+ /** 从 URL + headers 构造 Node.js http.request 所需的 options */
34
+ export declare function buildRequestOptions(url: URL, headers: Record<string, string>, method?: string): UpstreamRequestOptions;
35
+ export declare function proxyNonStream(backend: {
36
+ base_url: string;
37
+ }, apiKey: string, body: Record<string, unknown>, clientHeaders: RawHeaders, upstreamPath: string, buildHeaders: (cliHdrs: RawHeaders, key: string, bytes?: number) => Record<string, string>): Promise<ProxyResult>;
38
+ export declare function proxyStream(backend: {
39
+ base_url: string;
40
+ }, apiKey: string, body: Record<string, unknown>, clientHeaders: RawHeaders, reply: FastifyReply, timeoutMs: number, upstreamPath: string, buildHeaders: (cliHdrs: RawHeaders, key: string, bytes?: number) => Record<string, string>, metricsTransform?: SSEMetricsTransform): Promise<StreamProxyResult>;
41
+ export declare function proxyGetRequest(backend: {
42
+ base_url: string;
43
+ }, apiKey: string, clientHeaders: RawHeaders, upstreamPath: string, buildHeaders: (cliHdrs: RawHeaders, key: string) => Record<string, string>): Promise<GetProxyResult>;
@@ -0,0 +1,208 @@
1
+ import { request as httpRequestFn } from "http";
2
+ import { request as httpsRequestFn } from "https";
3
+ import { PassThrough } from "stream";
4
+ // ---------- Constants ----------
5
+ const UPSTREAM_SUCCESS = 200;
6
+ const UPSTREAM_BAD_GATEWAY = 502;
7
+ const HTTPS_DEFAULT_PORT = 443;
8
+ const HTTP_DEFAULT_PORT = 80;
9
+ // ---------- Request utilities ----------
10
+ /** 根据 URL scheme 选择 http 或 https 模块 */
11
+ export function createUpstreamRequest(url, options) {
12
+ return url.protocol === "https:" ? httpsRequestFn(options) : httpRequestFn(options);
13
+ }
14
+ /** 从 URL + headers 构造 Node.js http.request 所需的 options */
15
+ export function buildRequestOptions(url, headers, method = "POST") {
16
+ return {
17
+ hostname: url.hostname,
18
+ port: Number(url.port) || (url.protocol === "https:" ? HTTPS_DEFAULT_PORT : HTTP_DEFAULT_PORT),
19
+ path: url.pathname,
20
+ method,
21
+ headers,
22
+ };
23
+ }
24
+ // ---------- Non-stream proxy ----------
25
+ export function proxyNonStream(backend, apiKey, body, clientHeaders, upstreamPath, buildHeaders) {
26
+ return new Promise((resolve, reject) => {
27
+ const url = new URL(`${backend.base_url}${upstreamPath}`);
28
+ const payload = JSON.stringify(body);
29
+ const upstreamHeaders = buildHeaders(clientHeaders, apiKey, Buffer.byteLength(payload));
30
+ const options = buildRequestOptions(url, upstreamHeaders);
31
+ const req = createUpstreamRequest(url, options);
32
+ req.on("response", (res) => {
33
+ const chunks = [];
34
+ res.on("data", (chunk) => chunks.push(chunk));
35
+ res.on("end", () => {
36
+ resolve({
37
+ statusCode: res.statusCode || UPSTREAM_BAD_GATEWAY,
38
+ body: Buffer.concat(chunks).toString("utf-8"),
39
+ headers: filterHeaders(res.headers),
40
+ sentHeaders: { ...upstreamHeaders },
41
+ sentBody: payload,
42
+ });
43
+ });
44
+ });
45
+ req.on("error", (err) => reject(err));
46
+ req.write(payload);
47
+ req.end();
48
+ });
49
+ }
50
+ // ---------- Stream proxy (SSE) ----------
51
+ export function proxyStream(backend, apiKey, body, clientHeaders, reply, timeoutMs, upstreamPath, buildHeaders, metricsTransform) {
52
+ return new Promise((resolve, reject) => {
53
+ const url = new URL(`${backend.base_url}${upstreamPath}`);
54
+ const payload = JSON.stringify(body);
55
+ const upstreamHeaders = buildHeaders(clientHeaders, apiKey, Buffer.byteLength(payload));
56
+ const options = buildRequestOptions(url, upstreamHeaders);
57
+ const upstreamReq = createUpstreamRequest(url, options);
58
+ upstreamReq.on("response", (upstreamRes) => {
59
+ const statusCode = upstreamRes.statusCode || UPSTREAM_BAD_GATEWAY;
60
+ if (statusCode !== UPSTREAM_SUCCESS) {
61
+ const chunks = [];
62
+ upstreamRes.on("data", (chunk) => chunks.push(chunk));
63
+ upstreamRes.on("end", () => {
64
+ const errorBody = Buffer.concat(chunks).toString("utf-8");
65
+ resolve({
66
+ statusCode,
67
+ responseBody: errorBody,
68
+ upstreamResponseHeaders: filterHeaders(upstreamRes.headers),
69
+ sentHeaders: upstreamHeaders,
70
+ });
71
+ });
72
+ return;
73
+ }
74
+ const sseHeaders = filterHeaders(upstreamRes.headers);
75
+ sseHeaders["Content-Type"] = "text/event-stream";
76
+ sseHeaders["Cache-Control"] = "no-cache";
77
+ sseHeaders["Connection"] = "keep-alive";
78
+ reply.raw.writeHead(statusCode, sseHeaders);
79
+ const passThrough = new PassThrough();
80
+ if (metricsTransform) {
81
+ metricsTransform.pipe(passThrough).pipe(reply.raw);
82
+ }
83
+ else {
84
+ passThrough.pipe(reply.raw);
85
+ }
86
+ const pipeEntry = metricsTransform ?? passThrough;
87
+ const captureChunks = [];
88
+ let idleTimer = null;
89
+ let resolved = false;
90
+ function cleanup() {
91
+ if (idleTimer)
92
+ clearTimeout(idleTimer);
93
+ idleTimer = null;
94
+ if (!passThrough.destroyed)
95
+ passThrough.destroy();
96
+ if (metricsTransform && !metricsTransform.destroyed)
97
+ metricsTransform.destroy();
98
+ if (!upstreamRes.destroyed)
99
+ upstreamRes.destroy();
100
+ }
101
+ function collectMetrics(isComplete) {
102
+ if (!metricsTransform)
103
+ return undefined;
104
+ const result = metricsTransform.getExtractor().getMetrics();
105
+ if (!isComplete) {
106
+ return { ...result, is_complete: 0 };
107
+ }
108
+ return result;
109
+ }
110
+ reply.raw.on("close", () => {
111
+ if (!resolved) {
112
+ cleanup();
113
+ resolve({ statusCode, responseBody: undefined, upstreamResponseHeaders: sseHeaders, sentHeaders: upstreamHeaders, metricsResult: collectMetrics(false) });
114
+ }
115
+ });
116
+ passThrough.on("error", () => {
117
+ cleanup();
118
+ if (!resolved) {
119
+ resolved = true;
120
+ resolve({ statusCode, responseBody: undefined, upstreamResponseHeaders: sseHeaders, sentHeaders: upstreamHeaders, metricsResult: collectMetrics(false) });
121
+ }
122
+ });
123
+ function resetIdleTimer() {
124
+ if (idleTimer)
125
+ clearTimeout(idleTimer);
126
+ idleTimer = setTimeout(() => {
127
+ cleanup();
128
+ if (!resolved) {
129
+ resolved = true;
130
+ resolve({ statusCode, responseBody: undefined, upstreamResponseHeaders: sseHeaders, sentHeaders: upstreamHeaders, metricsResult: collectMetrics(false) });
131
+ }
132
+ }, timeoutMs);
133
+ }
134
+ resetIdleTimer();
135
+ upstreamRes.on("data", (chunk) => {
136
+ if (resolved)
137
+ return;
138
+ resetIdleTimer();
139
+ pipeEntry.write(chunk);
140
+ captureChunks.push(chunk);
141
+ });
142
+ upstreamRes.on("end", () => {
143
+ if (resolved)
144
+ return;
145
+ resolved = true;
146
+ if (idleTimer)
147
+ clearTimeout(idleTimer);
148
+ pipeEntry.end();
149
+ reply.raw.end();
150
+ resolve({
151
+ statusCode,
152
+ responseBody: Buffer.concat(captureChunks).toString("utf-8"),
153
+ upstreamResponseHeaders: sseHeaders,
154
+ sentHeaders: upstreamHeaders,
155
+ metricsResult: collectMetrics(true),
156
+ });
157
+ });
158
+ upstreamRes.on("error", (err) => {
159
+ if (resolved)
160
+ return;
161
+ resolved = true;
162
+ cleanup();
163
+ reject(err);
164
+ });
165
+ });
166
+ upstreamReq.on("error", (err) => reject(err));
167
+ upstreamReq.write(payload);
168
+ upstreamReq.end();
169
+ });
170
+ }
171
+ // ---------- GET proxy ----------
172
+ export function proxyGetRequest(backend, apiKey, clientHeaders, upstreamPath, buildHeaders) {
173
+ return new Promise((resolve, reject) => {
174
+ const url = new URL(`${backend.base_url}${upstreamPath}`);
175
+ const headers = buildHeaders(clientHeaders, apiKey);
176
+ const options = buildRequestOptions(url, headers, "GET");
177
+ const req = createUpstreamRequest(url, options);
178
+ req.on("response", (res) => {
179
+ const chunks = [];
180
+ res.on("data", (chunk) => chunks.push(chunk));
181
+ res.on("end", () => {
182
+ resolve({
183
+ statusCode: res.statusCode || UPSTREAM_BAD_GATEWAY,
184
+ body: Buffer.concat(chunks).toString("utf-8"),
185
+ headers: filterHeaders(res.headers),
186
+ });
187
+ });
188
+ });
189
+ req.on("error", (err) => reject(err));
190
+ req.end();
191
+ });
192
+ }
193
+ // ---------- Shared header filter ----------
194
+ const SKIP_DOWNSTREAM = new Set([
195
+ "content-length",
196
+ "transfer-encoding",
197
+ "connection",
198
+ "keep-alive",
199
+ ]);
200
+ function filterHeaders(raw) {
201
+ const out = {};
202
+ for (const [key, value] of Object.entries(raw)) {
203
+ if (value == null || SKIP_DOWNSTREAM.has(key.toLowerCase()))
204
+ continue;
205
+ out[key] = Array.isArray(value) ? value.join(", ") : value;
206
+ }
207
+ return out;
208
+ }
@@ -0,0 +1,2 @@
1
+ export declare function hashPassword(password: string): string;
2
+ export declare function verifyPassword(password: string, stored: string): boolean;
@@ -0,0 +1,14 @@
1
+ import { randomBytes, scryptSync } from "node:crypto";
2
+ const SCRYPT_KEYLEN = 64;
3
+ export function hashPassword(password) {
4
+ const salt = randomBytes(16).toString("hex");
5
+ const hash = scryptSync(password, salt, SCRYPT_KEYLEN).toString("hex");
6
+ return `${salt}:${hash}`;
7
+ }
8
+ export function verifyPassword(password, stored) {
9
+ const [salt, hash] = stored.split(":");
10
+ if (!salt || !hash)
11
+ return false;
12
+ const derived = scryptSync(password, salt, SCRYPT_KEYLEN).toString("hex");
13
+ return derived === hash;
14
+ }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "llm-simple-router",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "LLM API proxy router with OpenAI/Anthropic support, model mapping, retry strategies, and admin dashboard",
5
5
  "license": "MIT",
6
- "author": "ZzzzSsssWwww",
6
+ "author": "ZZzzswszzZZ",
7
7
  "type": "module",
8
8
  "repository": {
9
9
  "type": "git",
@@ -25,7 +25,7 @@
25
25
  "node": ">=20.0.0"
26
26
  },
27
27
  "bin": {
28
- "llm-simple-router": "./dist/index.js"
28
+ "llm-simple-router": "./dist/cli.js"
29
29
  },
30
30
  "files": [
31
31
  "dist/",
@@ -34,7 +34,7 @@
34
34
  "LICENSE"
35
35
  ],
36
36
  "scripts": {
37
- "dev": "tsx watch src/index.ts",
37
+ "dev": "PORT=9980 tsx watch src/index.ts",
38
38
  "build": "tsc",
39
39
  "build:full": "tsc && node scripts/build.mjs",
40
40
  "prepublishOnly": "node scripts/build.mjs",
@@ -55,7 +55,8 @@
55
55
  "fastify": "^5.3.3",
56
56
  "fastify-plugin": "^5.1.0",
57
57
  "jsonwebtoken": "^9.0.3",
58
- "pino": "^9.6.0"
58
+ "pino": "^9.6.0",
59
+ "pino-pretty": "^13.1.3"
59
60
  },
60
61
  "devDependencies": {
61
62
  "@eslint/js": "^10.0.1",
package/.env.example DELETED
@@ -1,13 +0,0 @@
1
- # 必需配置
2
- ADMIN_PASSWORD=admin123
3
- ENCRYPTION_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
4
- JWT_SECRET=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
5
-
6
- # 可选配置
7
- PORT=3000
8
- DB_PATH=./data/router.db
9
- LOG_LEVEL=info
10
- TZ=Asia/Shanghai
11
- STREAM_TIMEOUT_MS=3000000
12
- RETRY_MAX_ATTEMPTS=3
13
- RETRY_BASE_DELAY_MS=1000