llm-simple-router 0.5.1 → 0.5.4
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/config/recommended-providers.json +291 -0
- package/config/recommended-retry-rules.json +10 -0
- package/dist/admin/api-response.d.ts +26 -0
- package/dist/admin/api-response.js +44 -0
- package/dist/admin/constants.d.ts +0 -2
- package/dist/admin/constants.js +0 -3
- package/dist/admin/groups.js +44 -5
- package/dist/admin/logs.js +3 -2
- package/dist/admin/mappings.js +7 -6
- package/dist/admin/metrics.js +23 -5
- package/dist/admin/monitor.js +4 -1
- package/dist/admin/providers.js +201 -26
- package/dist/admin/proxy-enhancement.js +19 -14
- package/dist/admin/recommended.js +1 -9
- package/dist/admin/retry-rules.js +8 -4
- package/dist/admin/router-keys.js +5 -1
- package/dist/admin/settings-import-export.js +3 -2
- package/dist/admin/settings.js +7 -5
- package/dist/admin/setup.js +3 -2
- package/dist/admin/stats.js +20 -3
- package/dist/admin/upgrade.js +8 -7
- package/dist/admin/usage.js +12 -24
- package/dist/config/model-context.d.ts +10 -0
- package/dist/config/model-context.js +105 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +11 -0
- package/dist/db/index.d.ts +6 -4
- package/dist/db/index.js +4 -3
- package/dist/db/logs.d.ts +4 -0
- package/dist/db/logs.js +7 -3
- package/dist/db/mappings.d.ts +2 -1
- package/dist/db/mappings.js +7 -10
- package/dist/db/metrics.js +3 -4
- package/dist/db/migrations/023_create_provider_model_info.sql +8 -0
- package/dist/db/migrations/024_add_mapping_groups_is_active.sql +1 -0
- package/dist/db/migrations/025_add_client_status_code.sql +3 -0
- package/dist/db/model-info.d.ts +14 -0
- package/dist/db/model-info.js +27 -0
- package/dist/db/providers.d.ts +9 -0
- package/dist/db/providers.js +6 -0
- package/dist/db/retry-rules.d.ts +1 -0
- package/dist/db/retry-rules.js +3 -0
- package/dist/db/stats.d.ts +1 -2
- package/dist/db/stats.js +7 -11
- package/dist/index.js +64 -34
- package/dist/metrics/metrics-extractor.js +1 -1
- package/dist/metrics/sse-parser.js +2 -0
- package/dist/middleware/admin-auth.js +6 -5
- package/dist/middleware/auth.js +2 -11
- package/dist/monitor/request-tracker.d.ts +3 -0
- package/dist/monitor/request-tracker.js +27 -45
- package/dist/monitor/runtime-collector.js +1 -1
- package/dist/monitor/stream-content-accumulator.d.ts +14 -0
- package/dist/monitor/stream-content-accumulator.js +58 -0
- package/dist/proxy/anthropic.d.ts +2 -1
- package/dist/proxy/anthropic.js +16 -3
- package/dist/proxy/enhancement/directive-parser.d.ts +24 -0
- package/dist/proxy/enhancement/directive-parser.js +141 -0
- package/dist/proxy/enhancement/enhancement-handler.js +419 -0
- package/dist/proxy/enhancement/index.d.ts +3 -0
- package/dist/proxy/enhancement/index.js +3 -0
- package/dist/proxy/{response-cleaner.js → enhancement/response-cleaner.js} +14 -0
- package/dist/proxy/enhancement-config.d.ts +6 -0
- package/dist/proxy/enhancement-config.js +19 -0
- package/dist/proxy/log-helpers.d.ts +1 -1
- package/dist/proxy/mapping-resolver.js +4 -4
- package/dist/proxy/openai.d.ts +2 -1
- package/dist/proxy/openai.js +44 -7
- package/dist/proxy/orchestrator.d.ts +0 -1
- package/dist/proxy/orchestrator.js +1 -3
- package/dist/proxy/overflow.d.ts +18 -0
- package/dist/proxy/overflow.js +128 -0
- package/dist/proxy/patch/deepseek/index.d.ts +6 -0
- package/dist/proxy/patch/deepseek/index.js +11 -0
- package/dist/proxy/patch/deepseek/patch-orphan-tool-results.d.ts +12 -0
- package/dist/proxy/patch/deepseek/patch-orphan-tool-results.js +90 -0
- package/dist/proxy/patch/deepseek/patch-thinking-blocks.d.ts +6 -0
- package/dist/proxy/patch/deepseek/patch-thinking-blocks.js +24 -0
- package/dist/proxy/patch/index.d.ts +9 -0
- package/dist/proxy/patch/index.js +17 -0
- package/dist/proxy/proxy-core.d.ts +9 -6
- package/dist/proxy/proxy-core.js +24 -4
- package/dist/proxy/proxy-handler.d.ts +1 -1
- package/dist/proxy/proxy-handler.js +86 -141
- package/dist/proxy/proxy-logging.d.ts +0 -2
- package/dist/proxy/proxy-logging.js +24 -5
- package/dist/proxy/resilience.d.ts +9 -2
- package/dist/proxy/resilience.js +24 -8
- package/dist/proxy/strategy/failover.js +2 -7
- package/dist/proxy/strategy/random.js +2 -2
- package/dist/proxy/strategy/round-robin.js +2 -2
- package/dist/proxy/strategy/scheduled.js +1 -8
- package/dist/proxy/strategy/targets-rule.d.ts +1 -0
- package/dist/proxy/strategy/targets-rule.js +5 -0
- package/dist/proxy/strategy/types.d.ts +2 -0
- package/dist/proxy/stream-proxy.js +2 -1
- package/dist/proxy/transport-fn.d.ts +25 -0
- package/dist/proxy/transport-fn.js +56 -0
- package/dist/proxy/transport.d.ts +0 -25
- package/dist/proxy/transport.js +3 -40
- package/dist/proxy/types.d.ts +3 -1
- package/dist/proxy/types.js +5 -1
- package/dist/upgrade/checker.d.ts +1 -1
- package/dist/upgrade/checker.js +19 -1
- package/dist/upgrade/deployment.js +2 -2
- package/dist/utils/password.js +4 -2
- package/dist/utils/time-range.d.ts +9 -0
- package/dist/utils/time-range.js +55 -0
- package/frontend-dist/assets/CardContent-GNY_j_L3.js +1 -0
- package/frontend-dist/assets/CardTitle-BhXJbSoh.js +1 -0
- package/frontend-dist/assets/Checkbox-n_sh6Lvx.js +1 -0
- package/frontend-dist/assets/CollapsibleTrigger-DDCUOXDR.js +1 -0
- package/frontend-dist/assets/Collection-DbtqQ1jF.js +1 -0
- package/frontend-dist/assets/Dashboard-Dy9frcgO.js +3 -0
- package/frontend-dist/assets/DialogTitle-BEWUnuJQ.js +1 -0
- package/frontend-dist/assets/{Input-D0kpZB31.js → Input-CmibY9Fx.js} +1 -1
- package/frontend-dist/assets/Label-Cs__wFH0.js +1 -0
- package/frontend-dist/assets/Login-BciEc1TW.js +1 -0
- package/frontend-dist/assets/Logs-BkqwWW0-.js +1 -0
- package/frontend-dist/assets/ModelMappings-DrCJ_TCf.js +1 -0
- package/frontend-dist/assets/Monitor-C-b4qyuI.js +1 -0
- package/frontend-dist/assets/PopoverTrigger-DaKOMSVs.js +1 -0
- package/frontend-dist/assets/PopperContent-DZ6plcjf.js +1 -0
- package/frontend-dist/assets/Providers-u8utX74M.js +1 -0
- package/frontend-dist/assets/ProxyEnhancement-8_xhndGt.js +5 -0
- package/frontend-dist/assets/RetryRules-D1psYDEP.js +1 -0
- package/frontend-dist/assets/RouterKeys-ovPFGhjy.js +1 -0
- package/frontend-dist/assets/RovingFocusItem-Dsv9AkP7.js +1 -0
- package/frontend-dist/assets/SelectValue-BoUWfZAg.js +1 -0
- package/frontend-dist/assets/Settings-DXF-6A8C.js +6 -0
- package/frontend-dist/assets/Setup-rVLqiz0d.js +1 -0
- package/frontend-dist/assets/Switch-po5ZVBE3.js +1 -0
- package/frontend-dist/assets/TableHeader-Zyvq_0p2.js +1 -0
- package/frontend-dist/assets/TabsTrigger-CgDhZGkT.js +1 -0
- package/frontend-dist/assets/Teleport-CgTHarey.js +3 -0
- package/frontend-dist/assets/TooltipTrigger-C2qO21dQ.js +1 -0
- package/frontend-dist/assets/UnifiedRequestDialog-Dksad8eN.js +3 -0
- package/frontend-dist/assets/{VisuallyHidden-CjuTDGlC.js → VisuallyHidden-fbPmoMwi.js} +1 -1
- package/frontend-dist/assets/VisuallyHiddenInput-7j8wkPrW.js +1 -0
- package/frontend-dist/assets/alert-dialog-DbT3PzoF.js +1 -0
- package/frontend-dist/assets/badge-BVxnlnsH.js +1 -0
- package/frontend-dist/assets/{button-BmxhlpN-.js → button-BCrIpNwA.js} +2 -2
- package/frontend-dist/assets/chevron-down-CWBwGxSp.js +1 -0
- package/frontend-dist/assets/circle-question-mark-DRkkqjgG.js +1 -0
- package/frontend-dist/assets/dialog-BNlCZpHK.js +1 -0
- package/frontend-dist/assets/file-text-BavS6SrF.js +1 -0
- package/frontend-dist/assets/format-K3VR67cG.js +1 -0
- package/frontend-dist/assets/index-BP4imfye.css +1 -0
- package/frontend-dist/assets/index-DrBJPq6d.js +1 -0
- package/frontend-dist/assets/lib-CGpNhf06.js +1 -0
- package/frontend-dist/assets/loader-circle-Cpd89XQ7.js +1 -0
- package/frontend-dist/assets/ohash.D__AXeF1-DkJnWU8a.js +1 -0
- package/frontend-dist/assets/{useClipboard-CuE5xXIg.js → useClipboard-Bq8yZunx.js} +1 -1
- package/frontend-dist/assets/useLogRetention-BWPm3G_A.js +1 -0
- package/frontend-dist/assets/useNonce-D5lpSPNk.js +1 -0
- package/frontend-dist/assets/x-BFIp7DLt.js +1 -0
- package/frontend-dist/index.html +21 -18
- package/package.json +3 -1
- package/dist/proxy/directive-parser.d.ts +0 -7
- package/dist/proxy/directive-parser.js +0 -70
- package/dist/proxy/enhancement-handler.js +0 -169
- package/frontend-dist/assets/CardContent-Deyvo1TQ.js +0 -1
- package/frontend-dist/assets/CardTitle-DujSYXja.js +0 -1
- package/frontend-dist/assets/Checkbox-BJxf-QuV.js +0 -1
- package/frontend-dist/assets/CollapsibleTrigger-ByCvAsW0.js +0 -1
- package/frontend-dist/assets/Collection-V6gcBlwC.js +0 -1
- package/frontend-dist/assets/Dashboard-xqf6PcmE.js +0 -3
- package/frontend-dist/assets/DialogTitle-D0nwX87v.js +0 -1
- package/frontend-dist/assets/Label-BvYK0rd6.js +0 -1
- package/frontend-dist/assets/Login-C9oPKRcu.js +0 -1
- package/frontend-dist/assets/Logs-DVgenFav.js +0 -1
- package/frontend-dist/assets/ModelMappings-BoG2P9Rh.js +0 -1
- package/frontend-dist/assets/Monitor-W441wik3.js +0 -1
- package/frontend-dist/assets/PopperContent-DVJ4IxLF.js +0 -1
- package/frontend-dist/assets/Providers-D2rzb_Qk.js +0 -1
- package/frontend-dist/assets/ProxyEnhancement-DahQkV1g.js +0 -5
- package/frontend-dist/assets/RetryRules-Bg9p50oc.js +0 -1
- package/frontend-dist/assets/RouterKeys-C1LhXbqf.js +0 -1
- package/frontend-dist/assets/SelectValue-CAEBdE04.js +0 -1
- package/frontend-dist/assets/Settings-3lR8QVQt.js +0 -6
- package/frontend-dist/assets/Setup-Dzj1XvgF.js +0 -1
- package/frontend-dist/assets/Switch-CST3045A.js +0 -1
- package/frontend-dist/assets/TableHeader-CIrxcNRh.js +0 -1
- package/frontend-dist/assets/TabsContent-B4nroq3-.js +0 -1
- package/frontend-dist/assets/TabsTrigger-FsELRpyc.js +0 -1
- package/frontend-dist/assets/Teleport-DVgMe9KS.js +0 -3
- package/frontend-dist/assets/UnifiedRequestDialog-Fe2TfhTD.js +0 -3
- package/frontend-dist/assets/VisuallyHiddenInput-BaW-2aEF.js +0 -1
- package/frontend-dist/assets/alert-dialog-Bv6dVarS.js +0 -1
- package/frontend-dist/assets/badge-CEfcely6.js +0 -1
- package/frontend-dist/assets/createLucideIcon-UWoYUKtZ.js +0 -1
- package/frontend-dist/assets/dialog-QaGxKbze.js +0 -1
- package/frontend-dist/assets/file-text-D38GtYz2.js +0 -1
- package/frontend-dist/assets/format-CPdJtjZ5.js +0 -1
- package/frontend-dist/assets/index-CMBzqUyT.css +0 -1
- package/frontend-dist/assets/index-D484ZFa9.js +0 -1
- package/frontend-dist/assets/lib-CSYRBKqn.js +0 -1
- package/frontend-dist/assets/ohash.D__AXeF1-BUMsW586.js +0 -1
- package/frontend-dist/assets/useLogRetention-DesMKwIU.js +0 -1
- package/frontend-dist/assets/useNonce-FLqOooWA.js +0 -1
- package/frontend-dist/assets/x-BEUXSxcj.js +0 -1
- /package/dist/proxy/{enhancement-handler.d.ts → enhancement/enhancement-handler.d.ts} +0 -0
- /package/dist/proxy/{response-cleaner.d.ts → enhancement/response-cleaner.d.ts} +0 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"group": "DeepSeek",
|
|
4
|
+
"presets": [
|
|
5
|
+
{
|
|
6
|
+
"plan": "Anthropic",
|
|
7
|
+
"presetName": "deepseek",
|
|
8
|
+
"apiType": "anthropic",
|
|
9
|
+
"baseUrl": "https://api.deepseek.com/anthropic",
|
|
10
|
+
"models": ["deepseek-chat", "deepseek-reasoner"]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"plan": "OpenAI",
|
|
14
|
+
"presetName": "deepseek-openai",
|
|
15
|
+
"apiType": "openai",
|
|
16
|
+
"baseUrl": "https://api.deepseek.com",
|
|
17
|
+
"models": ["deepseek-chat", "deepseek-reasoner"]
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"group": "百度千帆",
|
|
23
|
+
"presets": [
|
|
24
|
+
{
|
|
25
|
+
"plan": "API",
|
|
26
|
+
"presetName": "qianfan",
|
|
27
|
+
"apiType": "openai",
|
|
28
|
+
"baseUrl": "https://qianfan.baidubce.com/v2",
|
|
29
|
+
"models": [
|
|
30
|
+
"ernie-4.0-8k",
|
|
31
|
+
"ernie-4.0-turbo-8k",
|
|
32
|
+
"ernie-3.5-8k",
|
|
33
|
+
"ernie-speed-8k",
|
|
34
|
+
"ernie-lite-8k",
|
|
35
|
+
"ernie-x1-32k-preview",
|
|
36
|
+
"deepseek-v3",
|
|
37
|
+
"deepseek-r1"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"group": "科大讯飞",
|
|
44
|
+
"presets": [
|
|
45
|
+
{
|
|
46
|
+
"plan": "API",
|
|
47
|
+
"presetName": "iflytek-spark",
|
|
48
|
+
"apiType": "openai",
|
|
49
|
+
"baseUrl": "https://spark-api-open.xf-yun.com/v1",
|
|
50
|
+
"models": [
|
|
51
|
+
"4.0Ultra",
|
|
52
|
+
"generalv3.5",
|
|
53
|
+
"max-32k",
|
|
54
|
+
"generalv3",
|
|
55
|
+
"pro-128k",
|
|
56
|
+
"lite"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"group": "硅基流动",
|
|
63
|
+
"presets": [
|
|
64
|
+
{
|
|
65
|
+
"plan": "API",
|
|
66
|
+
"presetName": "siliconflow",
|
|
67
|
+
"apiType": "openai",
|
|
68
|
+
"baseUrl": "https://api.siliconflow.cn/v1",
|
|
69
|
+
"models": [
|
|
70
|
+
"deepseek-ai/DeepSeek-V3.2-Exp",
|
|
71
|
+
"deepseek-ai/DeepSeek-R1",
|
|
72
|
+
"Qwen/Qwen3-8B",
|
|
73
|
+
"Qwen/Qwen2.5-72B-Instruct",
|
|
74
|
+
"Qwen/Qwen2.5-Coder-32B-Instruct",
|
|
75
|
+
"moonshotai/Kimi-K2-Instruct",
|
|
76
|
+
"moonshotai/Kimi-K2.5"
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"group": "智谱",
|
|
83
|
+
"presets": [
|
|
84
|
+
{
|
|
85
|
+
"plan": "Coding Plan",
|
|
86
|
+
"presetName": "zhipu-coding-plan",
|
|
87
|
+
"apiType": "anthropic",
|
|
88
|
+
"baseUrl": "https://open.bigmodel.cn/api/anthropic",
|
|
89
|
+
"models": ["glm-5.1", "glm-5", "glm-4.7", "glm-4.5-air"]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"plan": "API",
|
|
93
|
+
"presetName": "zhipu",
|
|
94
|
+
"apiType": "openai",
|
|
95
|
+
"baseUrl": "https://open.bigmodel.cn/api/paas/v4",
|
|
96
|
+
"models": [
|
|
97
|
+
"glm-5.1",
|
|
98
|
+
"glm-5",
|
|
99
|
+
"glm-5-turbo",
|
|
100
|
+
"glm-4.7",
|
|
101
|
+
"glm-4.7-flash",
|
|
102
|
+
"glm-4.6"
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"group": "KIMI",
|
|
109
|
+
"presets": [
|
|
110
|
+
{
|
|
111
|
+
"plan": "Coding Plan",
|
|
112
|
+
"presetName": "kimi-coding-plan",
|
|
113
|
+
"apiType": "anthropic",
|
|
114
|
+
"baseUrl": "https://api.kimi.com/coding",
|
|
115
|
+
"models": ["kimi-for-coding", "kimi-k2.5"]
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"plan": "API",
|
|
119
|
+
"presetName": "kimi",
|
|
120
|
+
"apiType": "openai",
|
|
121
|
+
"baseUrl": "https://api.moonshot.cn",
|
|
122
|
+
"models": [
|
|
123
|
+
"kimi-k2.6",
|
|
124
|
+
"kimi-k2.5",
|
|
125
|
+
"kimi-k2-turbo-preview",
|
|
126
|
+
"kimi-k2-thinking",
|
|
127
|
+
"moonshot-v1-128k"
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"group": "Minimax",
|
|
134
|
+
"presets": [
|
|
135
|
+
{
|
|
136
|
+
"plan": "Token Plan",
|
|
137
|
+
"presetName": "minimax-token-plan",
|
|
138
|
+
"apiType": "anthropic",
|
|
139
|
+
"baseUrl": "https://api.minimaxi.com/anthropic",
|
|
140
|
+
"models": ["MiniMax-M2.7"]
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"plan": "API",
|
|
144
|
+
"presetName": "minimax",
|
|
145
|
+
"apiType": "openai",
|
|
146
|
+
"baseUrl": "https://api.minimax.chat",
|
|
147
|
+
"models": [
|
|
148
|
+
"MiniMax-M2.7",
|
|
149
|
+
"MiniMax-M2.7-highspeed",
|
|
150
|
+
"MiniMax-M2.5",
|
|
151
|
+
"MiniMax-M2.5-highspeed",
|
|
152
|
+
"MiniMax-M2.1",
|
|
153
|
+
"MiniMax-M2"
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"group": "火山引擎",
|
|
160
|
+
"presets": [
|
|
161
|
+
{
|
|
162
|
+
"plan": "Coding Plan",
|
|
163
|
+
"presetName": "volcengine-coding-plan",
|
|
164
|
+
"apiType": "anthropic",
|
|
165
|
+
"baseUrl": "https://ark.cn-beijing.volces.com/api/compatible",
|
|
166
|
+
"models": [
|
|
167
|
+
"ark-code-latest",
|
|
168
|
+
"doubao-seed-2.0-code",
|
|
169
|
+
"kimi-k2.5",
|
|
170
|
+
"glm-4.7",
|
|
171
|
+
"deepseek-v3.2"
|
|
172
|
+
]
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"plan": "API",
|
|
176
|
+
"presetName": "volcengine",
|
|
177
|
+
"apiType": "openai",
|
|
178
|
+
"baseUrl": "https://ark.cn-beijing.volces.com/api/v3",
|
|
179
|
+
"models": [
|
|
180
|
+
"doubao-seed-2-0-pro-260215",
|
|
181
|
+
"doubao-seed-1-8-251228",
|
|
182
|
+
"doubao-seed-code-preview-251028"
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"group": "阿里云",
|
|
189
|
+
"presets": [
|
|
190
|
+
{
|
|
191
|
+
"plan": "Coding Plan",
|
|
192
|
+
"presetName": "aliyun-coding-plan",
|
|
193
|
+
"apiType": "anthropic",
|
|
194
|
+
"baseUrl": "https://coding.dashscope.aliyuncs.com/apps/anthropic",
|
|
195
|
+
"models": [
|
|
196
|
+
"qwen3.6-plus",
|
|
197
|
+
"qwen3-coder-next",
|
|
198
|
+
"qwen3-coder-plus",
|
|
199
|
+
"kimi-k2.5",
|
|
200
|
+
"glm-5",
|
|
201
|
+
"MiniMax-M2.5"
|
|
202
|
+
]
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"plan": "API",
|
|
206
|
+
"presetName": "aliyun",
|
|
207
|
+
"apiType": "openai",
|
|
208
|
+
"baseUrl": "https://dashscope.aliyuncs.com/compatible-mode",
|
|
209
|
+
"models": [
|
|
210
|
+
"qwen3.6-plus",
|
|
211
|
+
"qwen3.5-plus",
|
|
212
|
+
"qwen3-max",
|
|
213
|
+
"qwen3.5-flash",
|
|
214
|
+
"qwen3-coder-plus",
|
|
215
|
+
"qwen3-coder-next"
|
|
216
|
+
]
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"group": "腾讯云",
|
|
222
|
+
"presets": [
|
|
223
|
+
{
|
|
224
|
+
"plan": "Coding Plan",
|
|
225
|
+
"presetName": "tencent-coding-plan",
|
|
226
|
+
"apiType": "anthropic",
|
|
227
|
+
"baseUrl": "https://api.lkeap.cloud.tencent.com/coding/anthropic",
|
|
228
|
+
"models": [
|
|
229
|
+
"tc-code-latest",
|
|
230
|
+
"hunyuan-2.0-instruct",
|
|
231
|
+
"hunyuan-2.0-thinking",
|
|
232
|
+
"hunyuan-turbos",
|
|
233
|
+
"hunyuan-t1",
|
|
234
|
+
"glm-5",
|
|
235
|
+
"kimi-k2.5"
|
|
236
|
+
]
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"plan": "API",
|
|
240
|
+
"presetName": "tencent",
|
|
241
|
+
"apiType": "openai",
|
|
242
|
+
"baseUrl": "https://api.hunyuan.cloud.tencent.com",
|
|
243
|
+
"models": [
|
|
244
|
+
"hunyuan-2.0-thinking",
|
|
245
|
+
"hunyuan-2.0-instruct",
|
|
246
|
+
"hunyuan-t1-latest",
|
|
247
|
+
"hunyuan-a13b",
|
|
248
|
+
"hunyuan-turbos-latest"
|
|
249
|
+
]
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"group": "OpenCode Go",
|
|
255
|
+
"presets": [
|
|
256
|
+
{
|
|
257
|
+
"plan": "Go",
|
|
258
|
+
"presetName": "opencode-go-anthropic",
|
|
259
|
+
"apiType": "anthropic",
|
|
260
|
+
"baseUrl": "https://opencode.ai/zen/go/v1/messages",
|
|
261
|
+
"models": ["deepseek-v4-pro", "deepseek-v4-flash", "minimax-m2.7", "minimax-m2.5"]
|
|
262
|
+
}
|
|
263
|
+
]
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"group": "阶跃星辰",
|
|
267
|
+
"presets": [
|
|
268
|
+
{
|
|
269
|
+
"plan": "Step Plan",
|
|
270
|
+
"presetName": "stepfun-step-plan",
|
|
271
|
+
"apiType": "anthropic",
|
|
272
|
+
"baseUrl": "https://api.stepfun.com/step_plan",
|
|
273
|
+
"models": ["step-3.5-flash-2603", "step-3.5-flash"]
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"plan": "API",
|
|
277
|
+
"presetName": "stepfun",
|
|
278
|
+
"apiType": "openai",
|
|
279
|
+
"baseUrl": "https://api.stepfun.com",
|
|
280
|
+
"models": [
|
|
281
|
+
"step-3.5-flash",
|
|
282
|
+
"step-3",
|
|
283
|
+
"step-2-mini",
|
|
284
|
+
"step-2-16k",
|
|
285
|
+
"step-1-8k",
|
|
286
|
+
"step-1-32k"
|
|
287
|
+
]
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
}
|
|
291
|
+
]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "name": "429 Too Many Requests", "status_code": 429, "body_pattern": ".*", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 10, "max_delay_ms": 60000 },
|
|
3
|
+
{ "name": "503 Service Unavailable", "status_code": 503, "body_pattern": ".*", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 10, "max_delay_ms": 60000 },
|
|
4
|
+
{ "name": "ZAI 网络错误 (code 1234)", "status_code": 400, "body_pattern": "\"type\"\\s*:\\s*\"error\".*\"code\"\\s*:\\s*\"1234\"", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 10, "max_delay_ms": 60000 },
|
|
5
|
+
{ "name": "ZAI 临时不可用", "status_code": 400, "body_pattern": "\"type\"\\s*:\\s*\"error\".*请稍后重试", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 10, "max_delay_ms": 60000 },
|
|
6
|
+
{ "name": "ZAI 操作失败 (code 500)", "status_code": 400, "body_pattern": "\"type\"\\s*:\\s*\"error\".*\"code\"\\s*:\\s*\"500\"", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 10, "max_delay_ms": 60000 },
|
|
7
|
+
{ "name": "ZAI 速率限制 (HTTP 200, code 1302)", "status_code": 200, "body_pattern": "\"error\".*\"code\"\\s*:\\s*\"1302\"", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 10, "max_delay_ms": 60000 },
|
|
8
|
+
{ "name": "ZAI SSE 错误 (HTTP 200, code 500)", "status_code": 200, "body_pattern": "\"error\".*\"code\"\\s*:\\s*\"500\"", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 10, "max_delay_ms": 60000 },
|
|
9
|
+
{ "name": "ZAI SSE 错误 (HTTP 200, code 1234)", "status_code": 200, "body_pattern": "\"error\".*\"code\"\\s*:\\s*\"1234\"", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 10, "max_delay_ms": 60000 }
|
|
10
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** 统一信封格式 */
|
|
2
|
+
export interface ApiResponse<T> {
|
|
3
|
+
code: number;
|
|
4
|
+
message: string;
|
|
5
|
+
data: T | null;
|
|
6
|
+
}
|
|
7
|
+
/** 错误码常量 — XXYYZ 格式:前两位=HTTP 类别,后三位=业务序号 */
|
|
8
|
+
export declare const API_CODE: {
|
|
9
|
+
readonly SUCCESS: 0;
|
|
10
|
+
readonly BAD_REQUEST: 40001;
|
|
11
|
+
readonly VALIDATION_FAILED: 40002;
|
|
12
|
+
readonly INVALID_REGEX: 40003;
|
|
13
|
+
readonly WRONG_PASSWORD: 40101;
|
|
14
|
+
readonly TOKEN_INVALID: 40102;
|
|
15
|
+
readonly NOT_INITIALIZED: 40103;
|
|
16
|
+
readonly NOT_FOUND: 40401;
|
|
17
|
+
readonly CONFLICT_NAME: 40901;
|
|
18
|
+
readonly CONFLICT_REFERENCED: 40902;
|
|
19
|
+
readonly ALREADY_INITIALIZED: 40903;
|
|
20
|
+
readonly INTERNAL_ERROR: 50001;
|
|
21
|
+
};
|
|
22
|
+
export declare function statusToApiCode(status: number): number;
|
|
23
|
+
/** 判断是否为 Admin API 路由(需要信封包装) */
|
|
24
|
+
export declare function isAdminApiResponse(url: string, contentType?: string): boolean;
|
|
25
|
+
/** 构造错误响应 */
|
|
26
|
+
export declare function apiError(code: number, message: string): ApiResponse<null>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/admin/api-response.ts
|
|
2
|
+
/** 错误码常量 — XXYYZ 格式:前两位=HTTP 类别,后三位=业务序号 */
|
|
3
|
+
export const API_CODE = {
|
|
4
|
+
SUCCESS: 0,
|
|
5
|
+
BAD_REQUEST: 40001,
|
|
6
|
+
VALIDATION_FAILED: 40002,
|
|
7
|
+
INVALID_REGEX: 40003,
|
|
8
|
+
WRONG_PASSWORD: 40101,
|
|
9
|
+
TOKEN_INVALID: 40102,
|
|
10
|
+
NOT_INITIALIZED: 40103,
|
|
11
|
+
NOT_FOUND: 40401,
|
|
12
|
+
CONFLICT_NAME: 40901,
|
|
13
|
+
CONFLICT_REFERENCED: 40902,
|
|
14
|
+
ALREADY_INITIALIZED: 40903,
|
|
15
|
+
INTERNAL_ERROR: 50001,
|
|
16
|
+
};
|
|
17
|
+
/** HTTP status → 默认 API_CODE 映射(errorHandler 兜底用) */
|
|
18
|
+
const STATUS_BAD_REQUEST = 400;
|
|
19
|
+
const STATUS_UNAUTHORIZED = 401;
|
|
20
|
+
const STATUS_NOT_FOUND = 404;
|
|
21
|
+
const STATUS_CONFLICT = 409;
|
|
22
|
+
export function statusToApiCode(status) {
|
|
23
|
+
if (status === STATUS_BAD_REQUEST)
|
|
24
|
+
return API_CODE.BAD_REQUEST;
|
|
25
|
+
if (status === STATUS_UNAUTHORIZED)
|
|
26
|
+
return API_CODE.TOKEN_INVALID;
|
|
27
|
+
if (status === STATUS_NOT_FOUND)
|
|
28
|
+
return API_CODE.NOT_FOUND;
|
|
29
|
+
if (status === STATUS_CONFLICT)
|
|
30
|
+
return API_CODE.CONFLICT_NAME;
|
|
31
|
+
return API_CODE.INTERNAL_ERROR;
|
|
32
|
+
}
|
|
33
|
+
/** 判断是否为 Admin API 路由(需要信封包装) */
|
|
34
|
+
export function isAdminApiResponse(url, contentType) {
|
|
35
|
+
if (!url.startsWith('/admin/api/'))
|
|
36
|
+
return false;
|
|
37
|
+
if (contentType?.includes('text/event-stream'))
|
|
38
|
+
return false;
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
/** 构造错误响应 */
|
|
42
|
+
export function apiError(code, message) {
|
|
43
|
+
return { code, message, data: null };
|
|
44
|
+
}
|
|
@@ -1,3 +1 @@
|
|
|
1
|
-
import type { FastifyReply } from "fastify";
|
|
2
1
|
export { HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_FORBIDDEN, HTTP_NOT_FOUND, HTTP_CONFLICT, HTTP_INTERNAL_ERROR, HTTP_BAD_GATEWAY, HTTP_SERVICE_UNAVAILABLE, } from "../constants.js";
|
|
3
|
-
export declare function sendErrorResponse(reply: FastifyReply, statusCode: number, message: string): FastifyReply<import("fastify").RouteGenericInterface, import("fastify").RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>;
|
package/dist/admin/constants.js
CHANGED
|
@@ -1,5 +1,2 @@
|
|
|
1
1
|
// HTTP 状态码统一从 src/constants.ts 导入,避免重复定义
|
|
2
2
|
export { HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_FORBIDDEN, HTTP_NOT_FOUND, HTTP_CONFLICT, HTTP_INTERNAL_ERROR, HTTP_BAD_GATEWAY, HTTP_SERVICE_UNAVAILABLE, } from "../constants.js";
|
|
3
|
-
export function sendErrorResponse(reply, statusCode, message) {
|
|
4
|
-
return reply.code(statusCode).send({ error: { message } });
|
|
5
|
-
}
|
package/dist/admin/groups.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import { getAllMappingGroups, createMappingGroup, updateMappingGroup, deleteMappingGroup, getProviderById, getMappingGroupById, } from "../db/index.js";
|
|
3
3
|
import { STRATEGY_NAMES } from "../proxy/strategy/types.js";
|
|
4
|
-
import { HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_CONFLICT } from "./constants.js";
|
|
4
|
+
import { HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_CONFLICT, HTTP_NOT_FOUND } from "./constants.js";
|
|
5
|
+
import { API_CODE, apiError } from "./api-response.js";
|
|
5
6
|
const MIN_FAILOVER_TARGETS = 2;
|
|
6
7
|
const CreateGroupSchema = Type.Object({
|
|
7
8
|
client_model: Type.String({ minLength: 1 }),
|
|
@@ -13,6 +14,23 @@ const UpdateGroupSchema = Type.Object({
|
|
|
13
14
|
strategy: Type.Optional(Type.String({ minLength: 1 })),
|
|
14
15
|
rule: Type.Optional(Type.String()),
|
|
15
16
|
});
|
|
17
|
+
function validateOverflow(db, target, label) {
|
|
18
|
+
const hasOverflowProvider = !!target.overflow_provider_id;
|
|
19
|
+
const hasOverflowModel = !!target.overflow_model;
|
|
20
|
+
if (hasOverflowProvider && !hasOverflowModel) {
|
|
21
|
+
return `${label}: overflow_provider_id requires overflow_model`;
|
|
22
|
+
}
|
|
23
|
+
if (hasOverflowModel && !hasOverflowProvider) {
|
|
24
|
+
return `${label}: overflow_model requires overflow_provider_id`;
|
|
25
|
+
}
|
|
26
|
+
if (hasOverflowProvider) {
|
|
27
|
+
const p = getProviderById(db, target.overflow_provider_id);
|
|
28
|
+
if (!p) {
|
|
29
|
+
return `${label}: overflow_provider_id '${target.overflow_provider_id}' not found`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
16
34
|
async function validateRule(db, strategy, ruleJson) {
|
|
17
35
|
const VALID_STRATEGIES = new Set(Object.values(STRATEGY_NAMES));
|
|
18
36
|
if (!VALID_STRATEGIES.has(strategy)) {
|
|
@@ -34,6 +52,9 @@ async function validateRule(db, strategy, ruleJson) {
|
|
|
34
52
|
if (!defaultProvider) {
|
|
35
53
|
return `provider_id '${r.default.provider_id}' not found`;
|
|
36
54
|
}
|
|
55
|
+
const overflowErr = validateOverflow(db, r.default, "rule.default");
|
|
56
|
+
if (overflowErr)
|
|
57
|
+
return overflowErr;
|
|
37
58
|
if (r.windows !== undefined && !Array.isArray(r.windows)) {
|
|
38
59
|
return "rule.windows must be an array";
|
|
39
60
|
}
|
|
@@ -47,6 +68,9 @@ async function validateRule(db, strategy, ruleJson) {
|
|
|
47
68
|
if (!p) {
|
|
48
69
|
return `window[${i}] provider_id '${w.target.provider_id}' not found`;
|
|
49
70
|
}
|
|
71
|
+
const wOverflowErr = validateOverflow(db, w.target, `window[${i}]`);
|
|
72
|
+
if (wOverflowErr)
|
|
73
|
+
return wOverflowErr;
|
|
50
74
|
}
|
|
51
75
|
}
|
|
52
76
|
}
|
|
@@ -68,6 +92,9 @@ async function validateRule(db, strategy, ruleJson) {
|
|
|
68
92
|
if (!p) {
|
|
69
93
|
return `targets[${i}] provider_id '${t.provider_id}' not found`;
|
|
70
94
|
}
|
|
95
|
+
const overflowErr = validateOverflow(db, t, `targets[${i}]`);
|
|
96
|
+
if (overflowErr)
|
|
97
|
+
return overflowErr;
|
|
71
98
|
}
|
|
72
99
|
}
|
|
73
100
|
return undefined;
|
|
@@ -82,7 +109,7 @@ export const adminGroupRoutes = (app, options, done) => {
|
|
|
82
109
|
const body = request.body;
|
|
83
110
|
const validationError = await validateRule(db, body.strategy, body.rule);
|
|
84
111
|
if (validationError) {
|
|
85
|
-
return reply.code(HTTP_BAD_REQUEST).send(
|
|
112
|
+
return reply.code(HTTP_BAD_REQUEST).send(apiError(API_CODE.BAD_REQUEST, validationError));
|
|
86
113
|
}
|
|
87
114
|
try {
|
|
88
115
|
const id = createMappingGroup(db, {
|
|
@@ -94,7 +121,7 @@ export const adminGroupRoutes = (app, options, done) => {
|
|
|
94
121
|
}
|
|
95
122
|
catch (err) {
|
|
96
123
|
if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
|
|
97
|
-
return reply.code(HTTP_CONFLICT).send(
|
|
124
|
+
return reply.code(HTTP_CONFLICT).send(apiError(API_CODE.CONFLICT_NAME, "client_model already exists"));
|
|
98
125
|
}
|
|
99
126
|
throw err;
|
|
100
127
|
}
|
|
@@ -113,7 +140,7 @@ export const adminGroupRoutes = (app, options, done) => {
|
|
|
113
140
|
const ruleJson = body.rule ?? findGroupRule(db, id);
|
|
114
141
|
const validationError = await validateRule(db, strategy, ruleJson);
|
|
115
142
|
if (validationError) {
|
|
116
|
-
return reply.code(HTTP_BAD_REQUEST).send(
|
|
143
|
+
return reply.code(HTTP_BAD_REQUEST).send(apiError(API_CODE.BAD_REQUEST, validationError));
|
|
117
144
|
}
|
|
118
145
|
try {
|
|
119
146
|
updateMappingGroup(db, id, fields);
|
|
@@ -121,16 +148,28 @@ export const adminGroupRoutes = (app, options, done) => {
|
|
|
121
148
|
}
|
|
122
149
|
catch (err) {
|
|
123
150
|
if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
|
|
124
|
-
return reply.code(HTTP_CONFLICT).send(
|
|
151
|
+
return reply.code(HTTP_CONFLICT).send(apiError(API_CODE.CONFLICT_NAME, "client_model already exists"));
|
|
125
152
|
}
|
|
126
153
|
throw err;
|
|
127
154
|
}
|
|
128
155
|
});
|
|
129
156
|
app.delete("/admin/api/mapping-groups/:id", async (request, reply) => {
|
|
130
157
|
const { id } = request.params;
|
|
158
|
+
const existing = getMappingGroupById(db, id);
|
|
159
|
+
if (!existing)
|
|
160
|
+
return reply.code(HTTP_NOT_FOUND).send(apiError(API_CODE.NOT_FOUND, "Mapping group not found"));
|
|
131
161
|
deleteMappingGroup(db, id);
|
|
132
162
|
return reply.send({ success: true });
|
|
133
163
|
});
|
|
164
|
+
app.post("/admin/api/mapping-groups/:id/toggle", async (request, reply) => {
|
|
165
|
+
const { id } = request.params;
|
|
166
|
+
const existing = getMappingGroupById(db, id);
|
|
167
|
+
if (!existing)
|
|
168
|
+
return reply.code(HTTP_NOT_FOUND).send(apiError(API_CODE.NOT_FOUND, "Mapping group not found"));
|
|
169
|
+
const newActive = existing.is_active ? 0 : 1;
|
|
170
|
+
updateMappingGroup(db, id, { is_active: newActive });
|
|
171
|
+
return reply.send({ success: true, is_active: newActive });
|
|
172
|
+
});
|
|
134
173
|
done();
|
|
135
174
|
};
|
|
136
175
|
function findGroupStrategy(db, id) {
|
package/dist/admin/logs.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import { getRequestLogs, getRequestLogsGrouped, getRequestLogById, getRequestLogChildren, deleteLogsBefore } from "../db/index.js";
|
|
3
3
|
import { HTTP_NOT_FOUND } from "./constants.js";
|
|
4
|
+
import { API_CODE, apiError } from "./api-response.js";
|
|
4
5
|
const LogQuerySchema = Type.Object({
|
|
5
6
|
page: Type.Optional(Type.String()),
|
|
6
7
|
limit: Type.Optional(Type.String()),
|
|
@@ -42,7 +43,7 @@ export const adminLogRoutes = (app, options, done) => {
|
|
|
42
43
|
const params = request.params;
|
|
43
44
|
const log = getRequestLogById(db, params.id);
|
|
44
45
|
if (!log) {
|
|
45
|
-
return reply.code(HTTP_NOT_FOUND).send(
|
|
46
|
+
return reply.code(HTTP_NOT_FOUND).send(apiError(API_CODE.NOT_FOUND, "Log not found"));
|
|
46
47
|
}
|
|
47
48
|
return reply.send(log);
|
|
48
49
|
});
|
|
@@ -50,7 +51,7 @@ export const adminLogRoutes = (app, options, done) => {
|
|
|
50
51
|
const params = request.params;
|
|
51
52
|
const parent = getRequestLogById(db, params.id);
|
|
52
53
|
if (!parent) {
|
|
53
|
-
return reply.code(HTTP_NOT_FOUND).send(
|
|
54
|
+
return reply.code(HTTP_NOT_FOUND).send(apiError(API_CODE.NOT_FOUND, "Log not found"));
|
|
54
55
|
}
|
|
55
56
|
const rows = getRequestLogChildren(db, params.id);
|
|
56
57
|
return reply.send(rows);
|
package/dist/admin/mappings.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Type } from "@sinclair/typebox";
|
|
|
2
2
|
import { getAllMappingGroups, createMappingGroup, updateMappingGroup, deleteMappingGroup, getProviderById, getMappingGroupById, getMappingGroup, } from "../db/index.js";
|
|
3
3
|
import { STRATEGY_NAMES } from "../proxy/strategy/types.js";
|
|
4
4
|
import { HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_NOT_FOUND, HTTP_CONFLICT } from "./constants.js";
|
|
5
|
+
import { API_CODE, apiError } from "./api-response.js";
|
|
5
6
|
const CreateMappingSchema = Type.Object({
|
|
6
7
|
client_model: Type.String({ minLength: 1 }),
|
|
7
8
|
backend_model: Type.String({ minLength: 1 }),
|
|
@@ -46,7 +47,7 @@ export const adminMappingRoutes = (app, options, done) => {
|
|
|
46
47
|
const body = request.body;
|
|
47
48
|
const provider = getProviderById(db, body.provider_id);
|
|
48
49
|
if (!provider) {
|
|
49
|
-
return reply.code(HTTP_BAD_REQUEST).send(
|
|
50
|
+
return reply.code(HTTP_BAD_REQUEST).send(apiError(API_CODE.NOT_FOUND, "provider_id not found"));
|
|
50
51
|
}
|
|
51
52
|
try {
|
|
52
53
|
const id = createMappingGroup(db, {
|
|
@@ -61,7 +62,7 @@ export const adminMappingRoutes = (app, options, done) => {
|
|
|
61
62
|
}
|
|
62
63
|
catch (err) {
|
|
63
64
|
if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
|
|
64
|
-
return reply.code(HTTP_CONFLICT).send(
|
|
65
|
+
return reply.code(HTTP_CONFLICT).send(apiError(API_CODE.CONFLICT_NAME, "client_model already exists"));
|
|
65
66
|
}
|
|
66
67
|
throw err;
|
|
67
68
|
}
|
|
@@ -70,7 +71,7 @@ export const adminMappingRoutes = (app, options, done) => {
|
|
|
70
71
|
const { id } = request.params;
|
|
71
72
|
const group = findGroupByIdOrClientModel(db, id);
|
|
72
73
|
if (!group) {
|
|
73
|
-
return reply.code(HTTP_NOT_FOUND).send(
|
|
74
|
+
return reply.code(HTTP_NOT_FOUND).send(apiError(API_CODE.NOT_FOUND, "Mapping not found"));
|
|
74
75
|
}
|
|
75
76
|
const body = request.body;
|
|
76
77
|
let rule;
|
|
@@ -86,7 +87,7 @@ export const adminMappingRoutes = (app, options, done) => {
|
|
|
86
87
|
if (body.provider_id !== undefined) {
|
|
87
88
|
const provider = getProviderById(db, body.provider_id);
|
|
88
89
|
if (!provider) {
|
|
89
|
-
return reply.code(HTTP_BAD_REQUEST).send(
|
|
90
|
+
return reply.code(HTTP_BAD_REQUEST).send(apiError(API_CODE.NOT_FOUND, "provider_id not found"));
|
|
90
91
|
}
|
|
91
92
|
defaultTarget.provider_id = body.provider_id;
|
|
92
93
|
}
|
|
@@ -102,7 +103,7 @@ export const adminMappingRoutes = (app, options, done) => {
|
|
|
102
103
|
}
|
|
103
104
|
catch (err) {
|
|
104
105
|
if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
|
|
105
|
-
return reply.code(HTTP_CONFLICT).send(
|
|
106
|
+
return reply.code(HTTP_CONFLICT).send(apiError(API_CODE.CONFLICT_NAME, "client_model already exists"));
|
|
106
107
|
}
|
|
107
108
|
throw err;
|
|
108
109
|
}
|
|
@@ -111,7 +112,7 @@ export const adminMappingRoutes = (app, options, done) => {
|
|
|
111
112
|
const { id } = request.params;
|
|
112
113
|
const group = findGroupByIdOrClientModel(db, id);
|
|
113
114
|
if (!group) {
|
|
114
|
-
return reply.code(HTTP_NOT_FOUND).send(
|
|
115
|
+
return reply.code(HTTP_NOT_FOUND).send(apiError(API_CODE.NOT_FOUND, "Mapping not found"));
|
|
115
116
|
}
|
|
116
117
|
deleteMappingGroup(db, group.id);
|
|
117
118
|
return reply.send({ success: true });
|
package/dist/admin/metrics.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import { getMetricsSummary, getMetricsTimeseries } from "../db/index.js";
|
|
3
|
-
|
|
3
|
+
import { resolveTimeRange } from "../utils/time-range.js";
|
|
4
|
+
const LegacyPeriodEnum = Type.Union([
|
|
4
5
|
Type.Literal("1h"), Type.Literal("5h"), Type.Literal("6h"), Type.Literal("24h"),
|
|
5
6
|
Type.Literal("7d"), Type.Literal("30d"),
|
|
6
7
|
]);
|
|
8
|
+
const DashboardPeriodEnum = Type.Union([
|
|
9
|
+
Type.Literal("window"), Type.Literal("weekly"), Type.Literal("monthly"),
|
|
10
|
+
]);
|
|
11
|
+
const PeriodEnum = Type.Union([LegacyPeriodEnum, DashboardPeriodEnum]);
|
|
7
12
|
const MetricEnum = Type.Union([
|
|
8
13
|
Type.Literal("ttft"), Type.Literal("tps"), Type.Literal("tokens"),
|
|
9
14
|
Type.Literal("cache_rate"), Type.Literal("request_count"),
|
|
@@ -27,18 +32,31 @@ const TimeseriesQuerySchema = Type.Object({
|
|
|
27
32
|
start_time: Type.Optional(Type.String()),
|
|
28
33
|
end_time: Type.Optional(Type.String()),
|
|
29
34
|
});
|
|
35
|
+
const DASHBOARD_PERIODS = new Set(["window", "weekly", "monthly"]);
|
|
36
|
+
function resolveMetricsTime(query, db, routerKeyId) {
|
|
37
|
+
if (query.start_time && query.end_time) {
|
|
38
|
+
return { startTime: query.start_time, endTime: query.end_time, legacyPeriod: "30d" };
|
|
39
|
+
}
|
|
40
|
+
const period = query.period ?? "weekly";
|
|
41
|
+
if (DASHBOARD_PERIODS.has(period)) {
|
|
42
|
+
const range = resolveTimeRange(period, db, routerKeyId);
|
|
43
|
+
return { startTime: range.startTime, endTime: range.endTime, legacyPeriod: "5h" };
|
|
44
|
+
}
|
|
45
|
+
return { legacyPeriod: period };
|
|
46
|
+
}
|
|
30
47
|
export const adminMetricsRoutes = (app, options, done) => {
|
|
48
|
+
const { db } = options;
|
|
31
49
|
app.get("/admin/api/metrics/summary", { schema: { querystring: SummaryQuerySchema } }, async (request, reply) => {
|
|
32
50
|
const query = request.query;
|
|
33
|
-
const
|
|
34
|
-
const summary = getMetricsSummary(
|
|
51
|
+
const { startTime, endTime, legacyPeriod } = resolveMetricsTime(query, db, query.router_key_id);
|
|
52
|
+
const summary = getMetricsSummary(db, legacyPeriod, query.provider_id, query.backend_model, query.router_key_id, startTime, endTime);
|
|
35
53
|
return reply.send(summary);
|
|
36
54
|
});
|
|
37
55
|
app.get("/admin/api/metrics/timeseries", { schema: { querystring: TimeseriesQuerySchema } }, async (request, reply) => {
|
|
38
56
|
const query = request.query;
|
|
39
|
-
const period = (query.period ?? "24h");
|
|
40
57
|
const metric = query.metric;
|
|
41
|
-
const
|
|
58
|
+
const { startTime, endTime, legacyPeriod } = resolveMetricsTime(query, db, query.router_key_id);
|
|
59
|
+
const timeseries = getMetricsTimeseries(db, legacyPeriod, metric, query.provider_id, query.backend_model, query.router_key_id, startTime, endTime);
|
|
42
60
|
return reply.send(timeseries);
|
|
43
61
|
});
|
|
44
62
|
done();
|
package/dist/admin/monitor.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HTTP_NOT_FOUND } from "./constants.js";
|
|
2
|
+
import { API_CODE, apiError } from "./api-response.js";
|
|
2
3
|
const HTTP_OK = 200;
|
|
3
4
|
export const adminMonitorRoutes = (app, options, done) => {
|
|
4
5
|
const { tracker } = options;
|
|
@@ -12,6 +13,8 @@ export const adminMonitorRoutes = (app, options, done) => {
|
|
|
12
13
|
app.get("/admin/api/monitor/concurrency", async () => tracker.getConcurrency());
|
|
13
14
|
app.get("/admin/api/monitor/runtime", async () => tracker.getRuntime());
|
|
14
15
|
app.get("/admin/api/monitor/stream", (request, reply) => {
|
|
16
|
+
// hijack() 让 Fastify 完全放弃响应管理,避免 onSend hook 向 SSE 流注入信封 JSON
|
|
17
|
+
reply.hijack();
|
|
15
18
|
reply.raw.writeHead(HTTP_OK, {
|
|
16
19
|
"Content-Type": "text/event-stream",
|
|
17
20
|
"Cache-Control": "no-cache",
|
|
@@ -26,7 +29,7 @@ export const adminMonitorRoutes = (app, options, done) => {
|
|
|
26
29
|
const { id } = request.params;
|
|
27
30
|
const req = tracker.getRequestById(id);
|
|
28
31
|
if (!req)
|
|
29
|
-
return reply.
|
|
32
|
+
return reply.code(HTTP_NOT_FOUND).send(apiError(API_CODE.NOT_FOUND, "Not found"));
|
|
30
33
|
return req;
|
|
31
34
|
});
|
|
32
35
|
done();
|