@yzj01/llm-router 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.
@@ -0,0 +1,222 @@
1
+ type Tier = "SIMPLE" | "MEDIUM" | "COMPLEX" | "REASONING";
2
+ interface ProxyConfig {
3
+ port: number;
4
+ upstreamUrl: string;
5
+ apiKey?: string;
6
+ headers?: Record<string, string>;
7
+ trace?: "off" | "summary" | "debug";
8
+ }
9
+ interface PhysicalModel {
10
+ id: string;
11
+ name: string;
12
+ inputPrice: number;
13
+ outputPrice: number;
14
+ contextWindow: number;
15
+ maxOutput: number;
16
+ reasoning: boolean;
17
+ toolCalling: boolean;
18
+ }
19
+ type PublicModelMetadata = {
20
+ name: string;
21
+ reasoning: boolean;
22
+ contextWindow: number;
23
+ maxTokens: number;
24
+ cost: {
25
+ input: number;
26
+ output: number;
27
+ cacheRead: number;
28
+ cacheWrite: number;
29
+ };
30
+ };
31
+ interface PublicModelRouter {
32
+ kind: "router";
33
+ metadata: PublicModelMetadata;
34
+ }
35
+ interface PublicModelAlias {
36
+ kind: "alias";
37
+ candidates: string[];
38
+ selection?: "cheapest" | "first";
39
+ metadata?: PublicModelMetadata;
40
+ }
41
+ type PublicModelConfig = PublicModelRouter | PublicModelAlias;
42
+ interface TierEntry {
43
+ publicModel: string;
44
+ fallback?: string[];
45
+ }
46
+ interface RoutingTierBoundaries {
47
+ simpleMedium: number;
48
+ mediumComplex: number;
49
+ complexReasoning: number;
50
+ }
51
+ interface RoutingSpec {
52
+ tiers: Record<Tier, TierEntry>;
53
+ structuredOutputMinTier?: Tier;
54
+ ambiguousDefaultTier?: Tier;
55
+ tierBoundaries?: RoutingTierBoundaries;
56
+ confidenceThreshold?: number;
57
+ }
58
+ interface RawConfig {
59
+ version: number;
60
+ proxy: ProxyConfig;
61
+ models: PhysicalModel[];
62
+ publicModels: Record<string, PublicModelConfig>;
63
+ routing: RoutingSpec;
64
+ }
65
+ type ConfigSource = {
66
+ kind: "inline";
67
+ config: RawConfig;
68
+ } | {
69
+ kind: "file";
70
+ path: string;
71
+ };
72
+
73
+ type TraceMode = "off" | "summary" | "debug";
74
+ type TraceReason = "first-pass" | "user" | "reasoning" | "error";
75
+ type TraceSessionAction = "none" | "set" | "reuse";
76
+ type TraceAttempt = {
77
+ model: string;
78
+ status: "success" | "error";
79
+ error?: string;
80
+ };
81
+ type TraceSummaryInput = {
82
+ requestedModel: string;
83
+ routedModel: string;
84
+ actualModel: string;
85
+ tier: Tier;
86
+ profile: string;
87
+ reason: TraceReason;
88
+ routed: boolean;
89
+ explicit: boolean;
90
+ fallback: boolean;
91
+ };
92
+ type RouteTraceLog = TraceSummaryInput & {
93
+ trace: string;
94
+ method?: string;
95
+ confidence?: number;
96
+ score?: number;
97
+ agenticScore?: number;
98
+ attempts: TraceAttempt[];
99
+ sessionAction: TraceSessionAction;
100
+ promptPreview?: string;
101
+ };
102
+ type TraceWriter = (message: string) => void;
103
+ type TraceLogger = {
104
+ debug?: TraceWriter;
105
+ info?: TraceWriter;
106
+ };
107
+ declare function resolveTraceWriter(logger?: TraceLogger): TraceWriter;
108
+ declare function normalizeTraceMode(mode: unknown): TraceMode;
109
+ declare function getPromptPreview(prompt: string): string;
110
+ declare function buildTraceSummary(input: TraceSummaryInput): string;
111
+ declare function emitRouteTrace(mode: TraceMode, detail: RouteTraceLog, writer?: TraceWriter): void;
112
+
113
+ /**
114
+ * 自动路由请求的 session pinning 状态。
115
+ *
116
+ * 这里缓存的是最近一次稳定命中的 alias / physical model 组合,用来减少同一
117
+ * 会话内的 tier 抖动。
118
+ */
119
+ type SessionEntry = {
120
+ sessionId: string;
121
+ physicalModelId: string;
122
+ routedPublicModel: string;
123
+ pinnedTier: Tier;
124
+ createdAt: number;
125
+ updatedAt: number;
126
+ expiresAt: number;
127
+ inputTokens: number;
128
+ outputTokens: number;
129
+ costEstimate: number;
130
+ };
131
+ type SessionConfig = {
132
+ enabled: boolean;
133
+ ttlMs: number;
134
+ cleanupIntervalMs: number;
135
+ };
136
+ declare const DEFAULT_SESSION_CONFIG: SessionConfig;
137
+ type SessionStats = {
138
+ enabled: boolean;
139
+ size: number;
140
+ totalInputTokens: number;
141
+ totalOutputTokens: number;
142
+ totalCostEstimate: number;
143
+ };
144
+ /**
145
+ * 仅服务于 `auto` 路由的轻量内存 session store。
146
+ *
147
+ * 它不是通用会话数据库;这里只关心 pinning、TTL 和少量成本统计。
148
+ */
149
+ declare class SessionStore {
150
+ private readonly config;
151
+ private readonly sessions;
152
+ private readonly cleanupTimer?;
153
+ constructor(config?: Partial<SessionConfig>);
154
+ /**
155
+ * 读取一个未过期 session;如果已经过期,会顺手删除并返回 undefined。
156
+ */
157
+ getSession(sessionId: string | undefined): SessionEntry | undefined;
158
+ /**
159
+ * 创建或更新某个 session 的 pinning 结果,同时保留历史用量累计值。
160
+ */
161
+ setSession(sessionId: string | undefined, input: {
162
+ physicalModelId: string;
163
+ routedPublicModel: string;
164
+ pinnedTier: Tier;
165
+ }): SessionEntry | undefined;
166
+ /**
167
+ * 只刷新 TTL,不改动当前 alias / physical model 选择结果。
168
+ */
169
+ touchSession(sessionId: string | undefined): boolean;
170
+ clearSession(sessionId: string | undefined): boolean;
171
+ clearAll(): void;
172
+ /**
173
+ * 返回清理过期项后的聚合统计。
174
+ */
175
+ getStats(): SessionStats;
176
+ /**
177
+ * 把一次上游调用的 token / cost 增量累计到 session 上。
178
+ */
179
+ recordUsage(sessionId: string | undefined, usage: {
180
+ inputTokens?: number;
181
+ outputTokens?: number;
182
+ costEstimate?: number;
183
+ }): void;
184
+ /**
185
+ * 停止后台清理定时器。
186
+ */
187
+ close(): void;
188
+ private cleanupExpired;
189
+ private isExpired;
190
+ }
191
+ /**
192
+ * 把请求文本和工具集合归一化后压缩成稳定短哈希,作为隐式 session key。
193
+ */
194
+ declare function hashRequestContent(content: string, toolNames?: string[]): string;
195
+ /**
196
+ * 优先读取显式 `x-session-id`,否则退化为首条 user 消息内容哈希。
197
+ */
198
+ declare function deriveSessionId(headers: Record<string, string | string[] | undefined>, messages: unknown[]): string | undefined;
199
+
200
+ declare const VERSION = "1.0.0";
201
+ /**
202
+ * 启动本地 HTTP proxy 所需的全部输入。
203
+ */
204
+ type ProxyOptions = {
205
+ config: RawConfig;
206
+ traceLogger?: TraceLogger;
207
+ session?: Partial<SessionConfig>;
208
+ };
209
+ /**
210
+ * 已启动 proxy 的可关闭句柄。
211
+ */
212
+ type ProxyHandle = {
213
+ port: number;
214
+ baseUrl: string;
215
+ close: () => Promise<void>;
216
+ };
217
+ /**
218
+ * 启动本地 Router HTTP 服务。
219
+ */
220
+ declare function startProxy(options: ProxyOptions): Promise<ProxyHandle>;
221
+
222
+ export { type ConfigSource as C, DEFAULT_SESSION_CONFIG as D, type ProxyOptions as P, type RawConfig as R, type SessionConfig as S, type Tier as T, VERSION as V, type ProxyHandle as a, type PhysicalModel as b, type TraceMode as c, type TraceLogger as d, type PublicModelConfig as e, type PublicModelMetadata as f, type RouteTraceLog as g, type SessionEntry as h, type SessionStats as i, SessionStore as j, type TierEntry as k, type TraceAttempt as l, type TraceReason as m, type TraceSessionAction as n, type TraceSummaryInput as o, type TraceWriter as p, buildTraceSummary as q, deriveSessionId as r, emitRouteTrace as s, getPromptPreview as t, hashRequestContent as u, normalizeTraceMode as v, resolveTraceWriter as w, startProxy as x };
@@ -0,0 +1,37 @@
1
+ {
2
+ "id": "llm-router",
3
+ "name": "LLM Router",
4
+ "description": "LLM Router local routing proxy for OpenClaw",
5
+ "version": "1.0.0",
6
+ "main": "./dist/index.js",
7
+ "activation": {
8
+ "onStartup": true
9
+ },
10
+ "configSchema": {
11
+ "type": "object",
12
+ "description": "Provide either llm-router config or llm-router configPath. port, upstreamUrl, and trace override config.proxy.* at runtime.",
13
+ "properties": {
14
+ "config": {
15
+ "type": "object",
16
+ "description": "Inline RawConfig passed directly to llm-router."
17
+ },
18
+ "configPath": {
19
+ "type": "string",
20
+ "description": "Path to a RawConfig JSON file for llm-router."
21
+ },
22
+ "port": {
23
+ "type": "number",
24
+ "description": "Runtime override config.proxy.port. Optional; if omitted, use the value from pluginConfig.config / configPath."
25
+ },
26
+ "upstreamUrl": {
27
+ "type": "string",
28
+ "description": "Runtime override config.proxy.upstreamUrl. Optional; if omitted, use the value from pluginConfig.config / configPath."
29
+ },
30
+ "trace": {
31
+ "type": "string",
32
+ "enum": ["off", "summary", "debug"],
33
+ "description": "Runtime override config.proxy.trace."
34
+ }
35
+ }
36
+ }
37
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@yzj01/llm-router",
3
+ "version": "1.0.0",
4
+ "description": "LLM Router local routing proxy for OpenAI-compatible Chat Completions APIs.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "llm-router": "dist/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md",
20
+ "openclaw.plugin.json"
21
+ ],
22
+ "openclaw": {
23
+ "extensions": [
24
+ "./dist/index.js"
25
+ ]
26
+ },
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "dev": "tsup --watch",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "typecheck": "tsc --noEmit",
33
+ "lint": "eslint src/ test/ scripts/ --no-error-on-unmatched-pattern",
34
+ "format": "prettier --write .",
35
+ "format:check": "prettier --check .",
36
+ "bench": "npx tsx scripts/bench.ts"
37
+ },
38
+ "keywords": [
39
+ "deepseek",
40
+ "llm-router",
41
+ "local-proxy",
42
+ "openai-compatible",
43
+ "model-routing"
44
+ ],
45
+ "license": "MIT",
46
+ "devDependencies": {
47
+ "@eslint/js": "^10.0.1",
48
+ "@types/node": "^22.10.0",
49
+ "eslint": "^10.2.0",
50
+ "prettier": "^3.8.1",
51
+ "tsup": "^8.0.0",
52
+ "typescript": "^5.7.0",
53
+ "typescript-eslint": "^8.58.1",
54
+ "vitest": "^4.1.3"
55
+ },
56
+ "engines": {
57
+ "node": ">=20"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public",
61
+ "registry": "https://registry.npmjs.org/"
62
+ }
63
+ }