llm-simple-router 1.0.21 → 1.0.23-beta.96c9b98
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/dist/admin/groups.js +1 -1
- package/dist/admin/logs.js +1 -1
- package/dist/admin/mappings.js +1 -1
- package/dist/admin/monitor.js +1 -1
- package/dist/admin/providers.d.ts +5 -3
- package/dist/admin/providers.js +7 -16
- package/dist/admin/proxy-enhancement.d.ts +2 -0
- package/dist/admin/proxy-enhancement.js +2 -3
- package/dist/admin/quick-setup.js +2 -2
- package/dist/admin/retry-rules.js +1 -1
- package/dist/admin/router-keys.js +1 -1
- package/dist/admin/routes.d.ts +4 -2
- package/dist/admin/routes.js +3 -4
- package/dist/admin/schedules.js +3 -40
- package/dist/admin/settings.js +1 -1
- package/dist/admin/setup.js +1 -1
- package/dist/admin/utils.d.ts +11 -0
- package/dist/admin/utils.js +57 -0
- package/dist/app/compose-container.d.ts +19 -0
- package/dist/app/compose-container.js +84 -0
- package/dist/app/create-app.d.ts +14 -0
- package/dist/app/create-app.js +148 -0
- package/dist/app/register-hooks.d.ts +20 -0
- package/dist/app/register-hooks.js +50 -0
- package/dist/app/register-routes.d.ts +33 -0
- package/dist/app/register-routes.js +111 -0
- package/dist/core/log-detail-policy.d.ts +12 -0
- package/dist/core/log-detail-policy.js +21 -0
- package/dist/core/provider-connectivity.d.ts +22 -0
- package/dist/core/provider-connectivity.js +5 -0
- package/dist/core/proxy-agent-types.d.ts +9 -0
- package/dist/core/proxy-agent-types.js +6 -0
- package/dist/core/registry.d.ts +7 -0
- package/dist/db/logs.d.ts +1 -1
- package/dist/db/logs.js +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +40 -345
- package/dist/proxy/handler/failover-loop.d.ts +2 -114
- package/dist/proxy/handler/failover-loop.js +7 -322
- package/dist/proxy/handler/iteration-setup.d.ts +63 -0
- package/dist/proxy/handler/iteration-setup.js +118 -0
- package/dist/proxy/handler/reject-helpers.d.ts +44 -0
- package/dist/proxy/handler/reject-helpers.js +37 -0
- package/dist/proxy/handler/resilience-processor.d.ts +63 -0
- package/dist/proxy/handler/resilience-processor.js +183 -0
- package/dist/proxy/hooks/builtin/allowed-models.js +1 -0
- package/dist/proxy/hooks/builtin/enhancement-preprocess.js +1 -0
- package/dist/proxy/hooks/builtin/error-logging.js +1 -0
- package/dist/proxy/hooks/builtin/overflow-redirect.js +1 -0
- package/dist/proxy/log-detail-policy.d.ts +1 -12
- package/dist/proxy/log-detail-policy.js +2 -20
- package/dist/proxy/pipeline/pipeline.d.ts +8 -0
- package/dist/proxy/pipeline/pipeline.js +30 -1
- package/dist/proxy/pipeline/register-hooks.js +2 -5
- package/dist/proxy/pipeline/types.d.ts +2 -0
- package/dist/proxy/transform/stream-oa2ant.d.ts +9 -3
- package/dist/proxy/transform/stream-oa2ant.js +85 -110
- package/dist/proxy/transport/provider-connectivity.d.ts +11 -0
- package/dist/proxy/transport/provider-connectivity.js +11 -0
- package/dist/proxy/transport/proxy-agent.d.ts +2 -1
- package/dist/proxy/transport/stream.js +0 -1
- package/frontend-dist/assets/{AuthLayout-UzmdwXpC.js → AuthLayout-dg3KBZ9r.js} +1 -1
- package/frontend-dist/assets/{Card-BAJ7K-oq.js → Card-BtWZKVCy.js} +1 -1
- package/frontend-dist/assets/{CardContent-D4ma-cic.js → CardContent-VWHCdHnK.js} +1 -1
- package/frontend-dist/assets/{CardTitle-B3frXOF6.js → CardTitle-fQ8y5dsH.js} +1 -1
- package/frontend-dist/assets/{CascadingModelSelect-D1iTboYP.js → CascadingModelSelect-B1KiOuXa.js} +1 -1
- package/frontend-dist/assets/{Checkbox-FFHFrFEk.js → Checkbox-DPc26EYh.js} +1 -1
- package/frontend-dist/assets/{CollapsibleContent-CsQhKwq1.js → CollapsibleContent-O0-LGCQG.js} +1 -1
- package/frontend-dist/assets/{CollapsibleTrigger-x1DJz63o.js → CollapsibleTrigger-CgUsNis9.js} +1 -1
- package/frontend-dist/assets/{ConcurrencyControl-CjQi9JM0.js → ConcurrencyControl-CGhJ-zHV.js} +1 -1
- package/frontend-dist/assets/{Dashboard-DKm9udvl.js → Dashboard-B8aBTiQ6.js} +1 -1
- package/frontend-dist/assets/{Input-4bkkJSKj.js → Input-CjKYZ0D8.js} +1 -1
- package/frontend-dist/assets/{Label-D9CEaBof.js → Label-BfdXGQOW.js} +1 -1
- package/frontend-dist/assets/{Login-h4I4rmnC.js → Login-CyzRPRyM.js} +1 -1
- package/frontend-dist/assets/{Logs-CAU6FiMD.js → Logs-CSYek4dB.js} +1 -1
- package/frontend-dist/assets/{ModelMappings-B6rCeno-.js → ModelMappings-BA-Eiryy.js} +1 -1
- package/frontend-dist/assets/{Monitor-Dmb_-qsO.js → Monitor-Csc5tIhk.js} +1 -1
- package/frontend-dist/assets/{Providers-U4m7hTSJ.js → Providers-BXl9Df4O.js} +1 -1
- package/frontend-dist/assets/{ProxyEnhancement-DRgjcB7n.js → ProxyEnhancement-DbtSdApD.js} +1 -1
- package/frontend-dist/assets/{QuickSetup-BNmLBUdC.js → QuickSetup-WqMVzxKd.js} +1 -1
- package/frontend-dist/assets/{RetryRules-Bl3ZVdvu.js → RetryRules-BiFCvyBY.js} +1 -1
- package/frontend-dist/assets/{RouterKeys-BwnEUWyG.js → RouterKeys-C58Zsr-L.js} +1 -1
- package/frontend-dist/assets/{RovingFocusItem-DsNw2OyU.js → RovingFocusItem-b9aotU-L.js} +1 -1
- package/frontend-dist/assets/{Schedules-D2N8icq0.js → Schedules-D5cpvN0w.js} +1 -1
- package/frontend-dist/assets/{Separator-BAEMvYDZ.js → Separator-D-X_Q1XL.js} +1 -1
- package/frontend-dist/assets/{Settings-C4ZqRV31.js → Settings-CF9WahUV.js} +1 -1
- package/frontend-dist/assets/{Setup-BXEaeu0C.js → Setup-BfgHWNMA.js} +1 -1
- package/frontend-dist/assets/{Skeleton-BwwGudhK.js → Skeleton-C7F6dweG.js} +1 -1
- package/frontend-dist/assets/{Switch-C9A1URA9.js → Switch-CRr2sDhP.js} +1 -1
- package/frontend-dist/assets/{TableHeader-B8tbY3uE.js → TableHeader-DYAMmo22.js} +1 -1
- package/frontend-dist/assets/{TabsTrigger-BciD13aS.js → TabsTrigger-HlMkFIbm.js} +1 -1
- package/frontend-dist/assets/{UnifiedRequestDialog-Dnhxxnoi.js → UnifiedRequestDialog-BxApLj9k.js} +1 -1
- package/frontend-dist/assets/{VisuallyHiddenInput-Deh-FeJI.js → VisuallyHiddenInput-Bc6NL8Kp.js} +1 -1
- package/frontend-dist/assets/arrow-down-DW2oDMIG.js +1 -0
- package/frontend-dist/assets/{badge-_5ZpB7R1.js → badge-BejREzyW.js} +1 -1
- package/frontend-dist/assets/{button-BcmABzob.js → button-CQrCAzwF.js} +2 -2
- package/frontend-dist/assets/chevron-right-DPamapFi.js +1 -0
- package/frontend-dist/assets/{dialog-BDw6KIzh.js → dialog-Cvp1TN0j.js} +1 -1
- package/frontend-dist/assets/{image-BQjpozCO.js → image-BNqTJrVI.js} +1 -1
- package/frontend-dist/assets/{index-CpvOTf7s.js → index-BFAggMdr.js} +2 -2
- package/frontend-dist/assets/{model-patches-Bz5q_usR.js → model-patches-ClI6MRrJ.js} +1 -1
- package/frontend-dist/assets/{pencil-QUrS_Knn.js → pencil-Bn_RiJpu.js} +1 -1
- package/frontend-dist/assets/plus-ZzjDEnZg.js +1 -0
- package/frontend-dist/assets/search-DHVuA-yt.js +1 -0
- package/frontend-dist/assets/{sparkles-D26eNUGF.js → sparkles-DHZ5sHFM.js} +1 -1
- package/frontend-dist/assets/{transform-domain-BiEMeUvX.js → transform-domain-6ICCPqGT.js} +1 -1
- package/frontend-dist/assets/{trash-2-4HdxTT6S.js → trash-2-B819qjkG.js} +1 -1
- package/frontend-dist/assets/{useClipboard-Bv5gUQAX.js → useClipboard-T1Zo83kZ.js} +1 -1
- package/frontend-dist/assets/{useLogRetention-NFhOwCBf.js → useLogRetention-BYNSHDwr.js} +1 -1
- package/frontend-dist/assets/{useProviderGroups-Z3iRaBYM.js → useProviderGroups-Bl-jkzaG.js} +1 -1
- package/frontend-dist/index.html +2 -2
- package/package.json +1 -1
- package/dist/admin/constants.d.ts +0 -1
- package/dist/admin/constants.js +0 -2
- package/dist/proxy/pipeline/hook-registry.d.ts +0 -20
- package/dist/proxy/pipeline/hook-registry.js +0 -24
- package/frontend-dist/assets/arrow-down-CL1-lpk5.js +0 -1
- package/frontend-dist/assets/chevron-right-Bf3Fdg6M.js +0 -1
- package/frontend-dist/assets/plus-COwF_5ra.js +0 -1
- package/frontend-dist/assets/search-aFGV_wt3.js +0 -1
package/dist/admin/groups.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Type } from "@sinclair/typebox";
|
|
|
2
2
|
import { getAllMappingGroups, createMappingGroup, updateMappingGroup, deleteMappingGroup, getProviderById, getMappingGroupById, getAllProviders, } from "../db/index.js";
|
|
3
3
|
import { getSetting } from "../db/settings.js";
|
|
4
4
|
import { serializeProviders } from "./providers.js";
|
|
5
|
-
import { HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_CONFLICT, HTTP_NOT_FOUND } from "
|
|
5
|
+
import { HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_CONFLICT, HTTP_NOT_FOUND } from "../core/constants.js";
|
|
6
6
|
import { parseModels } from "../config/model-context.js";
|
|
7
7
|
import { API_CODE, apiError } from "./api-response.js";
|
|
8
8
|
const CreateGroupSchema = Type.Object({
|
package/dist/admin/logs.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import { getRequestLogs, getRequestLogsGrouped, getRequestLogById, getRequestLogChildren, deleteLogsBefore, extractThinkingLevel, getAllProviders, getAllRouterKeys, getAllMappingGroups } from "../db/index.js";
|
|
3
3
|
import { getLogRetentionDays } from "../db/settings.js";
|
|
4
|
-
import { HTTP_NOT_FOUND } from "
|
|
4
|
+
import { HTTP_NOT_FOUND } from "../core/constants.js";
|
|
5
5
|
import { API_CODE, apiError } from "./api-response.js";
|
|
6
6
|
const LogQuerySchema = Type.Object({
|
|
7
7
|
page: Type.Optional(Type.String()),
|
package/dist/admin/mappings.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import { getAllMappingGroups, createMappingGroup, updateMappingGroup, deleteMappingGroup, getProviderById, getMappingGroupById, getMappingGroup, } from "../db/index.js";
|
|
3
|
-
import { HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_NOT_FOUND, HTTP_CONFLICT } from "
|
|
3
|
+
import { HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_NOT_FOUND, HTTP_CONFLICT } from "../core/constants.js";
|
|
4
4
|
import { API_CODE, apiError } from "./api-response.js";
|
|
5
5
|
const CreateMappingSchema = Type.Object({
|
|
6
6
|
client_model: Type.String({ minLength: 1 }),
|
package/dist/admin/monitor.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { adaptSSEClient } from "../core/sse-client-adapter.js";
|
|
2
|
-
import { HTTP_NOT_FOUND } from "
|
|
2
|
+
import { HTTP_NOT_FOUND } from "../core/constants.js";
|
|
3
3
|
import { API_CODE, apiError } from "./api-response.js";
|
|
4
4
|
const HTTP_OK = 200;
|
|
5
5
|
export const adminMonitorRoutes = (app, options, done) => {
|
|
@@ -4,13 +4,15 @@ import type { Provider } from "../db/index.js";
|
|
|
4
4
|
import type { StateRegistry } from "../core/registry.js";
|
|
5
5
|
import type { AdaptiveController } from "../core/concurrency/index.js";
|
|
6
6
|
import type { RequestTracker } from "../core/monitor/index.js";
|
|
7
|
-
import type {
|
|
7
|
+
import type { ProviderConnectivityChecker } from "../core/provider-connectivity.js";
|
|
8
|
+
import type { IProxyAgentInvalidator } from "../core/proxy-agent-types.js";
|
|
8
9
|
interface ProviderRoutesOptions {
|
|
9
10
|
db: Database.Database;
|
|
10
11
|
stateRegistry?: StateRegistry;
|
|
11
12
|
tracker?: RequestTracker;
|
|
12
13
|
adaptiveController?: AdaptiveController;
|
|
13
|
-
proxyAgentFactory?:
|
|
14
|
+
proxyAgentFactory?: IProxyAgentInvalidator;
|
|
15
|
+
connectivityChecker?: ProviderConnectivityChecker;
|
|
14
16
|
}
|
|
15
17
|
/** 序列化 provider 列表,解密敏感字段、展开 models/endpoints。供多个端点复用 */
|
|
16
18
|
export declare function serializeProviders(db: Database.Database, providers: Provider[], encryptionKey: string, concurrencyStatus?: (id: string) => {
|
|
@@ -19,7 +21,7 @@ export declare function serializeProviders(db: Database.Database, providers: Pro
|
|
|
19
21
|
}): {
|
|
20
22
|
id: string;
|
|
21
23
|
name: string;
|
|
22
|
-
api_type: "
|
|
24
|
+
api_type: "openai" | "openai-responses" | "anthropic";
|
|
23
25
|
base_url: string;
|
|
24
26
|
upstream_path: string | null;
|
|
25
27
|
api_key: string;
|
package/dist/admin/providers.js
CHANGED
|
@@ -4,12 +4,10 @@ import { parseEndpoints, serializeEndpoints } from "../db/providers.js";
|
|
|
4
4
|
import { getRecommendedProviders } from "../config/recommended.js";
|
|
5
5
|
import { encrypt, decrypt } from "../utils/crypto.js";
|
|
6
6
|
import { getSetting } from "../db/settings.js";
|
|
7
|
-
import { HTTP_CREATED, HTTP_NOT_FOUND, HTTP_CONFLICT, HTTP_BAD_REQUEST, HTTP_OK } from "
|
|
7
|
+
import { HTTP_CREATED, HTTP_NOT_FOUND, HTTP_CONFLICT, HTTP_BAD_REQUEST, HTTP_OK, HTTP_SERVICE_UNAVAILABLE } from "../core/constants.js";
|
|
8
8
|
import { API_CODE, apiError } from "./api-response.js";
|
|
9
9
|
import { parseModels, buildModelInfoList, normalizePatchName, lookupCapabilities } from "../config/model-context.js";
|
|
10
10
|
import { getModelInfoForProvider, setModelInfoForProvider, deleteAllModelInfoForProvider } from "../db/model-info.js";
|
|
11
|
-
import { buildUpstreamHeaders } from "../proxy/proxy-core.js";
|
|
12
|
-
import { callGet } from "../proxy/transport/http.js";
|
|
13
11
|
const API_KEY_PREVIEW_MIN_LENGTH = 8;
|
|
14
12
|
const FETCH_MODELS_BODY_PREVIEW_LENGTH = 200;
|
|
15
13
|
function cascadeProviderDisable(db, providerId) {
|
|
@@ -87,17 +85,7 @@ function extractModelOverrides(models) {
|
|
|
87
85
|
return { entries, overrides };
|
|
88
86
|
}
|
|
89
87
|
const API_KEY_PREVIEW_PREFIX_LEN = 4;
|
|
90
|
-
|
|
91
|
-
/** 校验 base_url 是否为合法的 HTTP(S) URL */
|
|
92
|
-
function isValidHttpUrl(str) {
|
|
93
|
-
try {
|
|
94
|
-
const url = new URL(str);
|
|
95
|
-
return url.protocol === "http:" || url.protocol === "https:";
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
88
|
+
import { PROVIDER_NAME_RE, isValidHttpUrl } from "./utils.js";
|
|
101
89
|
/** 校验 endpoints 数组并加密 api_key。成功返回处理后的数组,失败返回错误信息 */
|
|
102
90
|
function validateAndEncryptEndpoints(endpoints, encryptionKey) {
|
|
103
91
|
const apiTypes = endpoints.map(e => e.api_type);
|
|
@@ -304,7 +292,7 @@ export function serializeProviders(db, providers, encryptionKey, concurrencyStat
|
|
|
304
292
|
});
|
|
305
293
|
}
|
|
306
294
|
export const adminProviderRoutes = (app, options, done) => {
|
|
307
|
-
const { db, stateRegistry, tracker, adaptiveController, proxyAgentFactory } = options;
|
|
295
|
+
const { db, stateRegistry, tracker, adaptiveController, proxyAgentFactory, connectivityChecker } = options;
|
|
308
296
|
app.get("/admin/api/providers", async (_request, reply) => {
|
|
309
297
|
const encryptionKey = getSetting(db, "encryption_key");
|
|
310
298
|
const providers = getAllProviders(db);
|
|
@@ -523,8 +511,11 @@ export const adminProviderRoutes = (app, options, done) => {
|
|
|
523
511
|
const { base_url, models_endpoint, api_key, api_type } = request.body;
|
|
524
512
|
const backend = { base_url };
|
|
525
513
|
const clientHeaders = {};
|
|
514
|
+
if (!connectivityChecker) {
|
|
515
|
+
return reply.code(HTTP_SERVICE_UNAVAILABLE).send(apiError(API_CODE.BAD_REQUEST, "Connectivity checker not available"));
|
|
516
|
+
}
|
|
526
517
|
try {
|
|
527
|
-
const result = await
|
|
518
|
+
const result = await connectivityChecker.fetchModels(backend, api_key, clientHeaders, models_endpoint, api_type);
|
|
528
519
|
if (result.statusCode !== HTTP_OK) {
|
|
529
520
|
return reply.code(HTTP_BAD_REQUEST).send(apiError(API_CODE.BAD_REQUEST, `上游返回 HTTP ${result.statusCode}: ${result.body.substring(0, FETCH_MODELS_BODY_PREVIEW_LENGTH)}`));
|
|
530
521
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { FastifyPluginCallback } from "fastify";
|
|
2
2
|
import Database from "better-sqlite3";
|
|
3
|
+
import type { StateRegistry } from "../core/registry.js";
|
|
3
4
|
interface ProxyEnhancementOptions {
|
|
4
5
|
db: Database.Database;
|
|
6
|
+
stateRegistry?: StateRegistry;
|
|
5
7
|
}
|
|
6
8
|
export declare const adminProxyEnhancementRoutes: FastifyPluginCallback<ProxyEnhancementOptions>;
|
|
7
9
|
export {};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import { getSetting, setSetting } from "../db/settings.js";
|
|
3
|
-
import { clearEnhancementConfigCache } from "../proxy/routing/enhancement-config.js";
|
|
4
3
|
import { getAllProviders } from "../db/index.js";
|
|
5
4
|
import { serializeProviders } from "./providers.js";
|
|
6
5
|
const UpdateProxyEnhancementSchema = Type.Object({
|
|
@@ -14,7 +13,7 @@ const UpdateProxyEnhancementSchema = Type.Object({
|
|
|
14
13
|
])),
|
|
15
14
|
});
|
|
16
15
|
export const adminProxyEnhancementRoutes = (app, options, done) => {
|
|
17
|
-
const { db } = options;
|
|
16
|
+
const { db, stateRegistry } = options;
|
|
18
17
|
app.get("/admin/api/proxy-enhancement", async (_request, reply) => {
|
|
19
18
|
const raw = getSetting(db, "proxy_enhancement");
|
|
20
19
|
const defaults = { tool_call_loop_enabled: false, stream_loop_enabled: false, tool_round_limit_enabled: true, tool_error_logging_enabled: false };
|
|
@@ -54,7 +53,7 @@ export const adminProxyEnhancementRoutes = (app, options, done) => {
|
|
|
54
53
|
tool_error_logging_enabled: enhancementFields.tool_error_logging_enabled,
|
|
55
54
|
};
|
|
56
55
|
setSetting(db, "proxy_enhancement", JSON.stringify(config));
|
|
57
|
-
|
|
56
|
+
stateRegistry?.clearEnhancementCache();
|
|
58
57
|
// ai_retry_config is stored in a separate settings key
|
|
59
58
|
if (ai_retry_config !== undefined) {
|
|
60
59
|
setSetting(db, "ai_retry_config", ai_retry_config ? JSON.stringify(ai_retry_config) : "");
|
|
@@ -11,9 +11,9 @@ import { getRecommendedProviders, getRecommendedRetryRules } from "../config/rec
|
|
|
11
11
|
import { lookupCapabilities } from "../config/model-context.js";
|
|
12
12
|
import { getAllMappingGroups, getAllProviders } from "../db/index.js";
|
|
13
13
|
import { serializeProviders } from "./providers.js";
|
|
14
|
-
import { HTTP_CREATED, HTTP_BAD_REQUEST, HTTP_BAD_GATEWAY, HTTP_CONFLICT } from "
|
|
14
|
+
import { HTTP_CREATED, HTTP_BAD_REQUEST, HTTP_BAD_GATEWAY, HTTP_CONFLICT } from "../core/constants.js";
|
|
15
15
|
import { API_CODE, apiError } from "./api-response.js";
|
|
16
|
-
|
|
16
|
+
import { PROVIDER_NAME_RE } from "./utils.js";
|
|
17
17
|
const API_KEY_PREVIEW_MIN_LENGTH = 8;
|
|
18
18
|
const API_KEY_PREVIEW_PREFIX_LEN = 4;
|
|
19
19
|
const NEW_PROVIDER_ID = "__new__";
|
|
@@ -7,7 +7,7 @@ import { getRequestLogById } from "../db/logs.js";
|
|
|
7
7
|
import { getProviderById } from "../db/providers.js";
|
|
8
8
|
import { getSetting } from "../db/settings.js";
|
|
9
9
|
import { decrypt } from "../utils/crypto.js";
|
|
10
|
-
import { HTTP_OK, HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_NOT_FOUND } from "
|
|
10
|
+
import { HTTP_OK, HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_NOT_FOUND } from "../core/constants.js";
|
|
11
11
|
import { API_CODE, apiError } from "./api-response.js";
|
|
12
12
|
// AI 重试规则的 system prompt 模板(内联避免运行时文件依赖)
|
|
13
13
|
const AI_RETRY_PROMPT_TEMPLATE = `You are an API retry rule expert. Your ONLY job is to analyze the error response and output a JSON retry rule.
|
|
@@ -3,7 +3,7 @@ import { Type } from "@sinclair/typebox";
|
|
|
3
3
|
import { encrypt, decrypt } from "../utils/crypto.js";
|
|
4
4
|
import { getAllRouterKeys, getRouterKeyById, createRouterKey, updateRouterKey, deleteRouterKey, getAvailableModels, } from "../db/index.js";
|
|
5
5
|
import { getSetting } from "../db/settings.js";
|
|
6
|
-
import { HTTP_CREATED, HTTP_NOT_FOUND } from "
|
|
6
|
+
import { HTTP_CREATED, HTTP_NOT_FOUND } from "../core/constants.js";
|
|
7
7
|
import { API_CODE, apiError } from "./api-response.js";
|
|
8
8
|
const KEY_RANDOM_BYTES = 32;
|
|
9
9
|
const KEY_PREFIX_LENGTH = 8;
|
package/dist/admin/routes.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ import Database from "better-sqlite3";
|
|
|
3
3
|
import type { StateRegistry } from "../core/registry.js";
|
|
4
4
|
import type { RequestTracker } from "../core/monitor/index.js";
|
|
5
5
|
import type { AdaptiveController } from "../core/concurrency/index.js";
|
|
6
|
-
import type {
|
|
6
|
+
import type { IProxyAgentInvalidator } from "../core/proxy-agent-types.js";
|
|
7
|
+
import type { ProviderConnectivityChecker } from "../core/provider-connectivity.js";
|
|
7
8
|
interface AdminRoutesOptions {
|
|
8
9
|
db: Database.Database;
|
|
9
10
|
stateRegistry: StateRegistry;
|
|
@@ -13,7 +14,8 @@ interface AdminRoutesOptions {
|
|
|
13
14
|
logsDir?: string;
|
|
14
15
|
pluginRegistry?: import("../proxy/transform/plugin-registry.js").PluginRegistry;
|
|
15
16
|
closeFn?: () => Promise<void>;
|
|
16
|
-
proxyAgentFactory?:
|
|
17
|
+
proxyAgentFactory?: IProxyAgentInvalidator;
|
|
18
|
+
connectivityChecker?: ProviderConnectivityChecker;
|
|
17
19
|
}
|
|
18
20
|
export declare const adminRoutes: FastifyPluginCallback<AdminRoutesOptions>;
|
|
19
21
|
export {};
|
package/dist/admin/routes.js
CHANGED
|
@@ -19,13 +19,12 @@ import { adminImportExportRoutes } from "./settings-import-export.js";
|
|
|
19
19
|
import { adminTransformRuleRoutes } from "./transform-rules.js";
|
|
20
20
|
import { adminDashboardRoutes } from "./dashboard.js";
|
|
21
21
|
import { adminScheduleRoutes } from "./schedules.js";
|
|
22
|
-
import { hookRegistry } from "../proxy/pipeline/hook-registry.js";
|
|
23
22
|
export const adminRoutes = (app, options, done) => {
|
|
24
23
|
// Setup 路由不需要 auth
|
|
25
24
|
app.register(adminSetupRoutes, { db: options.db });
|
|
26
25
|
app.register(adminAuthPlugin, { db: options.db });
|
|
27
26
|
app.register(adminLoginRoutes, { db: options.db });
|
|
28
|
-
app.register(adminProviderRoutes, { db: options.db, stateRegistry: options.stateRegistry, tracker: options.tracker, adaptiveController: options.adaptiveController, proxyAgentFactory: options.proxyAgentFactory });
|
|
27
|
+
app.register(adminProviderRoutes, { db: options.db, stateRegistry: options.stateRegistry, tracker: options.tracker, adaptiveController: options.adaptiveController, proxyAgentFactory: options.proxyAgentFactory, connectivityChecker: options.connectivityChecker });
|
|
29
28
|
app.register(adminMappingRoutes, { db: options.db });
|
|
30
29
|
app.register(adminGroupRoutes, { db: options.db });
|
|
31
30
|
app.register(adminScheduleRoutes, { db: options.db });
|
|
@@ -34,7 +33,7 @@ export const adminRoutes = (app, options, done) => {
|
|
|
34
33
|
app.register(adminRouterKeyRoutes, { db: options.db });
|
|
35
34
|
app.register(adminStatsRoutes, { db: options.db });
|
|
36
35
|
app.register(adminMetricsRoutes, { db: options.db });
|
|
37
|
-
app.register(adminProxyEnhancementRoutes, { db: options.db });
|
|
36
|
+
app.register(adminProxyEnhancementRoutes, { db: options.db, stateRegistry: options.stateRegistry });
|
|
38
37
|
app.register(adminMonitorRoutes, { tracker: options.tracker });
|
|
39
38
|
app.register(adminSettingsRoutes, { db: options.db, logsDir: options.logsDir });
|
|
40
39
|
app.register(adminImportExportRoutes, { db: options.db, stateRegistry: options.stateRegistry, pluginRegistry: options.pluginRegistry });
|
|
@@ -46,7 +45,7 @@ export const adminRoutes = (app, options, done) => {
|
|
|
46
45
|
app.register(adminTransformRuleRoutes, { db: options.db, pluginRegistry: options.pluginRegistry });
|
|
47
46
|
// Pipeline hooks 查询
|
|
48
47
|
app.get("/admin/api/pipeline/hooks", async () => {
|
|
49
|
-
return { hooks:
|
|
48
|
+
return { hooks: options.stateRegistry.getPipelineHooks() };
|
|
50
49
|
});
|
|
51
50
|
done();
|
|
52
51
|
};
|
package/dist/admin/schedules.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import { getSchedulesByGroup, getAllSchedules, getScheduleById, createSchedule, updateSchedule, deleteSchedule, getAllMappingGroups, getAllProviders, } from "../db/index.js";
|
|
3
|
-
import { getMappingGroupById
|
|
3
|
+
import { getMappingGroupById } from "../db/index.js";
|
|
4
4
|
import { serializeProviders } from "./providers.js";
|
|
5
5
|
import { getSetting } from "../db/settings.js";
|
|
6
|
-
import { HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_NOT_FOUND } from "
|
|
6
|
+
import { HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_NOT_FOUND } from "../core/constants.js";
|
|
7
7
|
import { API_CODE, apiError } from "./api-response.js";
|
|
8
8
|
const CreateScheduleSchema = Type.Object({
|
|
9
9
|
mapping_group_id: Type.String({ minLength: 1 }),
|
|
@@ -25,44 +25,7 @@ const UpdateScheduleSchema = Type.Object({
|
|
|
25
25
|
concurrency_rule: Type.Optional(Type.String()),
|
|
26
26
|
transform_rule: Type.Optional(Type.String()),
|
|
27
27
|
});
|
|
28
|
-
|
|
29
|
-
let rule;
|
|
30
|
-
try {
|
|
31
|
-
rule = JSON.parse(ruleJson);
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return "Invalid mapping_rule JSON";
|
|
35
|
-
}
|
|
36
|
-
if (typeof rule !== "object" || rule === null)
|
|
37
|
-
return "Invalid mapping_rule";
|
|
38
|
-
const r = rule;
|
|
39
|
-
if (!Array.isArray(r.targets) || r.targets.length === 0) {
|
|
40
|
-
return "mapping_rule.targets must be a non-empty array";
|
|
41
|
-
}
|
|
42
|
-
for (let i = 0; i < r.targets.length; i++) {
|
|
43
|
-
const t = r.targets[i];
|
|
44
|
-
if (!t.backend_model || !t.provider_id) {
|
|
45
|
-
return `targets[${i}] missing backend_model or provider_id`;
|
|
46
|
-
}
|
|
47
|
-
const p = getProviderById(db, t.provider_id);
|
|
48
|
-
if (!p)
|
|
49
|
-
return `targets[${i}] provider_id '${t.provider_id}' not found`;
|
|
50
|
-
const hasOverflowProvider = !!t.overflow_provider_id;
|
|
51
|
-
const hasOverflowModel = !!t.overflow_model;
|
|
52
|
-
if (hasOverflowProvider && !hasOverflowModel) {
|
|
53
|
-
return `targets[${i}]: overflow_provider_id requires overflow_model`;
|
|
54
|
-
}
|
|
55
|
-
if (hasOverflowModel && !hasOverflowProvider) {
|
|
56
|
-
return `targets[${i}]: overflow_model requires overflow_provider_id`;
|
|
57
|
-
}
|
|
58
|
-
if (hasOverflowProvider) {
|
|
59
|
-
const op = getProviderById(db, t.overflow_provider_id);
|
|
60
|
-
if (!op)
|
|
61
|
-
return `targets[${i}]: overflow_provider_id '${t.overflow_provider_id}' not found`;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return undefined;
|
|
65
|
-
}
|
|
28
|
+
import { validateMappingRule } from "./utils.js";
|
|
66
29
|
/** 解析 week JSON 为数字数组,失败返回 null */
|
|
67
30
|
const MAX_WEEK_DAY = 6;
|
|
68
31
|
function parseWeekSafe(weekJson) {
|
package/dist/admin/settings.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { statSync, readdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { getLogRetentionDays, setLogRetentionDays, getDbMaxSizeMb, setDbMaxSizeMb, getLogTableMaxSizeMb, setLogTableMaxSizeMb, getSetting, getTokenEstimationEnabled, setTokenEstimationEnabled, getClientSessionHeaders, setClientSessionHeaders, getMetricsDetailDays, setMetricsDetailDays, } from "../db/settings.js";
|
|
4
|
-
import { HTTP_BAD_REQUEST } from "
|
|
4
|
+
import { HTTP_BAD_REQUEST } from "../core/constants.js";
|
|
5
5
|
import { API_CODE, apiError } from "./api-response.js";
|
|
6
6
|
export const adminSettingsRoutes = (app, options, done) => {
|
|
7
7
|
const { db, logsDir } = options;
|
package/dist/admin/setup.js
CHANGED
|
@@ -3,7 +3,7 @@ import jwt from "jsonwebtoken";
|
|
|
3
3
|
import { getSetting, setSetting, isInitialized } from "../db/settings.js";
|
|
4
4
|
import { hashPassword } from "../utils/password.js";
|
|
5
5
|
import { isForwardedProtoHttps } from "../utils/cookie-secure.js";
|
|
6
|
-
import { HTTP_BAD_REQUEST, HTTP_CONFLICT } from "
|
|
6
|
+
import { HTTP_BAD_REQUEST, HTTP_CONFLICT } from "../core/constants.js";
|
|
7
7
|
import { API_CODE, apiError } from "./api-response.js";
|
|
8
8
|
const CRYPTO_BYTES_LENGTH = 32;
|
|
9
9
|
const MIN_PASSWORD_LENGTH = 6;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin 层共享校验工具函数和常量。
|
|
3
|
+
* 各 admin 文件统一从此模块导入,消除重复定义。
|
|
4
|
+
*/
|
|
5
|
+
import type Database from "better-sqlite3";
|
|
6
|
+
/** Provider 名称合法字符:英文、数字、横线、下划线 */
|
|
7
|
+
export declare const PROVIDER_NAME_RE: RegExp;
|
|
8
|
+
/** 校验 base_url 是否为合法的 HTTP(S) URL */
|
|
9
|
+
export declare function isValidHttpUrl(str: string): boolean;
|
|
10
|
+
/** 校验 mapping_rule JSON 结构和引用完整性 */
|
|
11
|
+
export declare function validateMappingRule(db: Database.Database, ruleJson: string): string | undefined;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin 层共享校验工具函数和常量。
|
|
3
|
+
* 各 admin 文件统一从此模块导入,消除重复定义。
|
|
4
|
+
*/
|
|
5
|
+
import { getProviderById } from "../db/index.js";
|
|
6
|
+
/** Provider 名称合法字符:英文、数字、横线、下划线 */
|
|
7
|
+
export const PROVIDER_NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
8
|
+
/** 校验 base_url 是否为合法的 HTTP(S) URL */
|
|
9
|
+
export function isValidHttpUrl(str) {
|
|
10
|
+
try {
|
|
11
|
+
const url = new URL(str);
|
|
12
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** 校验 mapping_rule JSON 结构和引用完整性 */
|
|
19
|
+
export function validateMappingRule(db, ruleJson) {
|
|
20
|
+
let rule;
|
|
21
|
+
try {
|
|
22
|
+
rule = JSON.parse(ruleJson);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return "Invalid mapping_rule JSON";
|
|
26
|
+
}
|
|
27
|
+
if (typeof rule !== "object" || rule === null)
|
|
28
|
+
return "Invalid mapping_rule";
|
|
29
|
+
const r = rule;
|
|
30
|
+
if (!Array.isArray(r.targets) || r.targets.length === 0) {
|
|
31
|
+
return "mapping_rule.targets must be a non-empty array";
|
|
32
|
+
}
|
|
33
|
+
for (let i = 0; i < r.targets.length; i++) {
|
|
34
|
+
const t = r.targets[i];
|
|
35
|
+
if (!t.backend_model || !t.provider_id) {
|
|
36
|
+
return `targets[${i}] missing backend_model or provider_id`;
|
|
37
|
+
}
|
|
38
|
+
const p = getProviderById(db, t.provider_id);
|
|
39
|
+
if (!p)
|
|
40
|
+
return `targets[${i}] provider_id '${t.provider_id}' not found`;
|
|
41
|
+
const hasOverflowProvider = !!t.overflow_provider_id;
|
|
42
|
+
const hasOverflowModel = !!t.overflow_model;
|
|
43
|
+
if (hasOverflowProvider && !hasOverflowModel) {
|
|
44
|
+
return `targets[${i}]: overflow_provider_id requires overflow_model`;
|
|
45
|
+
}
|
|
46
|
+
if (hasOverflowModel && !hasOverflowProvider) {
|
|
47
|
+
return `targets[${i}]: overflow_model requires overflow_provider_id`;
|
|
48
|
+
}
|
|
49
|
+
if (hasOverflowProvider) {
|
|
50
|
+
const op = getProviderById(db, t.overflow_provider_id);
|
|
51
|
+
if (!op) {
|
|
52
|
+
return `targets[${i}]: overflow_provider_id '${t.overflow_provider_id}' not found`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify";
|
|
2
|
+
import type Database from "better-sqlite3";
|
|
3
|
+
import { ServiceContainer } from "../core/container.js";
|
|
4
|
+
import { LogFileWriter } from "../storage/log-file-writer.js";
|
|
5
|
+
import type { Config } from "../config/index.js";
|
|
6
|
+
export interface ComposeContainerResult {
|
|
7
|
+
container: ServiceContainer;
|
|
8
|
+
logFileWriter: LogFileWriter | null;
|
|
9
|
+
logsDir: string;
|
|
10
|
+
isMemoryDb: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface ComposeContainerOptions {
|
|
13
|
+
config: Config;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 注册所有服务到 ServiceContainer。
|
|
17
|
+
* 返回 container 及 logFileWriter(close 时需要 flush)。
|
|
18
|
+
*/
|
|
19
|
+
export declare function composeContainer(db: Database.Database, opts: ComposeContainerOptions, app: FastifyInstance): ComposeContainerResult;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { ServiceContainer, SERVICE_KEYS } from "../core/container.js";
|
|
5
|
+
import { RetryRuleMatcher } from "../proxy/orchestration/retry-rules.js";
|
|
6
|
+
import { PluginRegistry } from "../proxy/transform/plugin-registry.js";
|
|
7
|
+
import { FormatRegistry } from "../proxy/format/registry.js";
|
|
8
|
+
import { openaiAdapter } from "../proxy/format/adapters/openai.js";
|
|
9
|
+
import { anthropicAdapter } from "../proxy/format/adapters/anthropic.js";
|
|
10
|
+
import { responsesAdapter } from "../proxy/format/adapters/responses.js";
|
|
11
|
+
import { openaiToAnthropicConverter } from "../proxy/format/converters/openai-anthropic.js";
|
|
12
|
+
import { anthropicToOpenAIConverter } from "../proxy/format/converters/anthropic-openai.js";
|
|
13
|
+
import { openaiToResponsesConverter } from "../proxy/format/converters/openai-responses.js";
|
|
14
|
+
import { responsesToOpenAIConverter } from "../proxy/format/converters/responses-openai.js";
|
|
15
|
+
import { responsesToAnthropicConverter } from "../proxy/format/converters/responses-anthropic.js";
|
|
16
|
+
import { anthropicToResponsesConverter } from "../proxy/format/converters/anthropic-responses.js";
|
|
17
|
+
import { SemaphoreManager, AdaptiveController } from "../core/concurrency/index.js";
|
|
18
|
+
import { RequestTracker } from "../core/monitor/index.js";
|
|
19
|
+
import { UsageWindowTracker } from "../proxy/routing/usage-window-tracker.js";
|
|
20
|
+
import { SessionTracker, DEFAULT_LOOP_PREVENTION_CONFIG } from "../core/loop-prevention/index.js";
|
|
21
|
+
import { LogFileWriter } from "../storage/log-file-writer.js";
|
|
22
|
+
import { ProxyAgentFactory } from "../proxy/transport/proxy-agent.js";
|
|
23
|
+
import { getDetailLogEnabled } from "../db/settings.js";
|
|
24
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
25
|
+
const __dirname = path.dirname(__filename);
|
|
26
|
+
/**
|
|
27
|
+
* 注册所有服务到 ServiceContainer。
|
|
28
|
+
* 返回 container 及 logFileWriter(close 时需要 flush)。
|
|
29
|
+
*/
|
|
30
|
+
export function composeContainer(db, opts, app) {
|
|
31
|
+
const { config } = opts;
|
|
32
|
+
const container = new ServiceContainer();
|
|
33
|
+
container.register(SERVICE_KEYS.db, () => db);
|
|
34
|
+
container.register(SERVICE_KEYS.matcher, (c) => {
|
|
35
|
+
const m = new RetryRuleMatcher();
|
|
36
|
+
m.load(c.resolve(SERVICE_KEYS.db));
|
|
37
|
+
return m;
|
|
38
|
+
});
|
|
39
|
+
container.register(SERVICE_KEYS.semaphoreManager, () => new SemaphoreManager());
|
|
40
|
+
container.register(SERVICE_KEYS.tracker, (c) => {
|
|
41
|
+
const t = new RequestTracker({ semaphoreManager: c.resolve(SERVICE_KEYS.semaphoreManager), logger: app.log });
|
|
42
|
+
t.startPushInterval();
|
|
43
|
+
return t;
|
|
44
|
+
});
|
|
45
|
+
container.register(SERVICE_KEYS.usageWindowTracker, (c) => {
|
|
46
|
+
const uwt = new UsageWindowTracker(c.resolve(SERVICE_KEYS.db));
|
|
47
|
+
uwt.reconcileOnStartup();
|
|
48
|
+
return uwt;
|
|
49
|
+
});
|
|
50
|
+
container.register(SERVICE_KEYS.sessionTracker, () => new SessionTracker(DEFAULT_LOOP_PREVENTION_CONFIG.sessionTracker));
|
|
51
|
+
// 文件日志写入器
|
|
52
|
+
const isMemoryDb = config.DB_PATH === ":memory:";
|
|
53
|
+
const logsDir = isMemoryDb ? "" : join(dirname(config.DB_PATH), "logs");
|
|
54
|
+
const logFileWriter = isMemoryDb
|
|
55
|
+
? null
|
|
56
|
+
: new LogFileWriter(logsDir, { enabled: getDetailLogEnabled(db) });
|
|
57
|
+
container.register(SERVICE_KEYS.logFileWriter, () => logFileWriter);
|
|
58
|
+
// AdaptiveController(依赖已注册的 semaphoreManager)
|
|
59
|
+
container.register(SERVICE_KEYS.adaptiveController, (c) => {
|
|
60
|
+
const ac = new AdaptiveController(c.resolve(SERVICE_KEYS.semaphoreManager), app.log);
|
|
61
|
+
return ac;
|
|
62
|
+
});
|
|
63
|
+
// PluginRegistry
|
|
64
|
+
const pluginRegistry = new PluginRegistry();
|
|
65
|
+
pluginRegistry.loadFromDB(db);
|
|
66
|
+
const pluginsDir = path.resolve(__dirname, "../../plugins/transform");
|
|
67
|
+
pluginRegistry.scanPluginsDir(pluginsDir);
|
|
68
|
+
container.register(SERVICE_KEYS.pluginRegistry, () => pluginRegistry);
|
|
69
|
+
// FormatRegistry(3 adapters + 6 converters)
|
|
70
|
+
const formatRegistry = new FormatRegistry();
|
|
71
|
+
formatRegistry.registerAdapter(openaiAdapter);
|
|
72
|
+
formatRegistry.registerAdapter(anthropicAdapter);
|
|
73
|
+
formatRegistry.registerAdapter(responsesAdapter);
|
|
74
|
+
formatRegistry.registerConverter(openaiToAnthropicConverter);
|
|
75
|
+
formatRegistry.registerConverter(anthropicToOpenAIConverter);
|
|
76
|
+
formatRegistry.registerConverter(openaiToResponsesConverter);
|
|
77
|
+
formatRegistry.registerConverter(responsesToOpenAIConverter);
|
|
78
|
+
formatRegistry.registerConverter(responsesToAnthropicConverter);
|
|
79
|
+
formatRegistry.registerConverter(anthropicToResponsesConverter);
|
|
80
|
+
container.register(SERVICE_KEYS.formatRegistry, () => formatRegistry);
|
|
81
|
+
// ProxyAgentFactory
|
|
82
|
+
container.register(SERVICE_KEYS.proxyAgentFactory, () => new ProxyAgentFactory());
|
|
83
|
+
return { container, logFileWriter, logsDir, isMemoryDb };
|
|
84
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type FastifyInstance } from "fastify";
|
|
2
|
+
import type { CheckerOptions } from "../upgrade/checker.js";
|
|
3
|
+
import type { Config } from "../config/index.js";
|
|
4
|
+
import type Database from "better-sqlite3";
|
|
5
|
+
export interface CreateAppOptions {
|
|
6
|
+
config: Config;
|
|
7
|
+
db: Database.Database;
|
|
8
|
+
upgradeCheckerOptions?: CheckerOptions;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 创建 Fastify 实例并注册全局 hooks(errorHandler、onSend 信封包装等)。
|
|
12
|
+
* 纯基础设施层,不涉及业务容器或路由。
|
|
13
|
+
*/
|
|
14
|
+
export declare function createAppInstance(opts: CreateAppOptions): FastifyInstance;
|