@lakphy/local-router 0.3.3 → 0.4.0-beta.2
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.schema.json +26 -0
- package/dist/cli.js +470 -57
- package/dist/entry.js +455 -42
- package/dist/web/assets/index-DuaLtWlv.js +191 -0
- package/dist/web/assets/index-RuflkSNT.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-CbUvycUp.css +0 -2
- package/dist/web/assets/index-Dz4pO265.js +0 -191
package/dist/entry.js
CHANGED
|
@@ -4934,7 +4934,7 @@ var require_compile = __commonJS((exports) => {
|
|
|
4934
4934
|
const schOrFunc = root2.refs[ref];
|
|
4935
4935
|
if (schOrFunc)
|
|
4936
4936
|
return schOrFunc;
|
|
4937
|
-
let _sch =
|
|
4937
|
+
let _sch = resolve6.call(this, root2, ref);
|
|
4938
4938
|
if (_sch === undefined) {
|
|
4939
4939
|
const schema = (_a21 = root2.localRefs) === null || _a21 === undefined ? undefined : _a21[ref];
|
|
4940
4940
|
const { schemaId } = this.opts;
|
|
@@ -4961,7 +4961,7 @@ var require_compile = __commonJS((exports) => {
|
|
|
4961
4961
|
function sameSchemaEnv(s1, s2) {
|
|
4962
4962
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
4963
4963
|
}
|
|
4964
|
-
function
|
|
4964
|
+
function resolve6(root2, ref) {
|
|
4965
4965
|
let sch;
|
|
4966
4966
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
4967
4967
|
ref = sch;
|
|
@@ -5491,7 +5491,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
5491
5491
|
}
|
|
5492
5492
|
return uri;
|
|
5493
5493
|
}
|
|
5494
|
-
function
|
|
5494
|
+
function resolve6(baseURI, relativeURI, options) {
|
|
5495
5495
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
5496
5496
|
const resolved = resolveComponent(parse7(baseURI, schemelessOptions), parse7(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
5497
5497
|
schemelessOptions.skipEscape = true;
|
|
@@ -5719,7 +5719,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
5719
5719
|
var fastUri = {
|
|
5720
5720
|
SCHEMES,
|
|
5721
5721
|
normalize,
|
|
5722
|
-
resolve:
|
|
5722
|
+
resolve: resolve6,
|
|
5723
5723
|
resolveComponent,
|
|
5724
5724
|
equal,
|
|
5725
5725
|
serialize,
|
|
@@ -9285,6 +9285,7 @@ var require_dist2 = __commonJS((exports, module) => {
|
|
|
9285
9285
|
|
|
9286
9286
|
// src/index.ts
|
|
9287
9287
|
import { readFileSync as readFileSync4 } from "fs";
|
|
9288
|
+
import { dirname as dirname2, resolve as resolve6 } from "path";
|
|
9288
9289
|
|
|
9289
9290
|
// node_modules/.bun/@ai-sdk+provider@3.0.8/node_modules/@ai-sdk/provider/dist/index.mjs
|
|
9290
9291
|
var marker = "vercel.ai.error";
|
|
@@ -52102,6 +52103,14 @@ async function buildLogEventDetail(id, parsed, location, context2) {
|
|
|
52102
52103
|
const responseBodyAvailable = event.response_body !== undefined;
|
|
52103
52104
|
const streamCaptured = Boolean(event.stream_file);
|
|
52104
52105
|
const { content: streamContent, warning: streamWarning } = readStreamContent(resolveLogBaseDir(context2.logConfig), event.stream_file);
|
|
52106
|
+
const hasPluginData = event.plugins_request || event.plugins_response || event.request_body_after_plugins !== undefined || event.request_url_after_plugins !== undefined || event.response_body_after_plugins !== undefined;
|
|
52107
|
+
const pluginsSection = hasPluginData ? {
|
|
52108
|
+
request: event.plugins_request,
|
|
52109
|
+
response: event.plugins_response,
|
|
52110
|
+
requestBodyAfterPlugins: event.request_body_after_plugins,
|
|
52111
|
+
requestUrlAfterPlugins: event.request_url_after_plugins,
|
|
52112
|
+
responseBodyAfterPlugins: event.response_body_after_plugins
|
|
52113
|
+
} : undefined;
|
|
52105
52114
|
return {
|
|
52106
52115
|
id,
|
|
52107
52116
|
summary: {
|
|
@@ -52153,6 +52162,7 @@ async function buildLogEventDetail(id, parsed, location, context2) {
|
|
|
52153
52162
|
...streamWarning ? [streamWarning] : []
|
|
52154
52163
|
]
|
|
52155
52164
|
},
|
|
52165
|
+
...pluginsSection && { plugins: pluginsSection },
|
|
52156
52166
|
rawEvent: event,
|
|
52157
52167
|
location
|
|
52158
52168
|
};
|
|
@@ -54065,10 +54075,301 @@ var openAPISpec = {
|
|
|
54065
54075
|
}
|
|
54066
54076
|
};
|
|
54067
54077
|
|
|
54078
|
+
// src/plugin-loader.ts
|
|
54079
|
+
import { resolve as resolve5, join as join8 } from "path";
|
|
54080
|
+
import { tmpdir } from "os";
|
|
54081
|
+
import { mkdtemp, writeFile, rm } from "fs/promises";
|
|
54082
|
+
function isLocalPath(pkg) {
|
|
54083
|
+
return pkg.startsWith("./") || pkg.startsWith("../") || pkg.startsWith("/") || /^[A-Za-z]:[\\/]/.test(pkg);
|
|
54084
|
+
}
|
|
54085
|
+
function isRemoteUrl(pkg) {
|
|
54086
|
+
return pkg.startsWith("http://") || pkg.startsWith("https://");
|
|
54087
|
+
}
|
|
54088
|
+
function inferExtension(url2, contentType) {
|
|
54089
|
+
const pathname = new URL(url2).pathname;
|
|
54090
|
+
if (pathname.endsWith(".ts") || pathname.endsWith(".tsx"))
|
|
54091
|
+
return ".ts";
|
|
54092
|
+
if (pathname.endsWith(".mjs"))
|
|
54093
|
+
return ".mjs";
|
|
54094
|
+
if (pathname.endsWith(".cjs"))
|
|
54095
|
+
return ".cjs";
|
|
54096
|
+
if (contentType?.includes("typescript"))
|
|
54097
|
+
return ".ts";
|
|
54098
|
+
return ".js";
|
|
54099
|
+
}
|
|
54100
|
+
var remoteTmpDir = null;
|
|
54101
|
+
var remoteTmpFiles = [];
|
|
54102
|
+
async function ensureRemoteTmpDir() {
|
|
54103
|
+
if (!remoteTmpDir) {
|
|
54104
|
+
remoteTmpDir = await mkdtemp(join8(tmpdir(), "local-router-plugins-"));
|
|
54105
|
+
}
|
|
54106
|
+
return remoteTmpDir;
|
|
54107
|
+
}
|
|
54108
|
+
async function fetchRemotePlugin(url2) {
|
|
54109
|
+
const response = await fetch(url2);
|
|
54110
|
+
if (!response.ok) {
|
|
54111
|
+
throw new Error(`\u4E0B\u8F7D\u8FDC\u7A0B\u63D2\u4EF6\u5931\u8D25: HTTP ${response.status} ${response.statusText} (${url2})`);
|
|
54112
|
+
}
|
|
54113
|
+
const content = await response.text();
|
|
54114
|
+
const ext = inferExtension(url2, response.headers.get("content-type"));
|
|
54115
|
+
const dir = await ensureRemoteTmpDir();
|
|
54116
|
+
const fileName = `plugin_${Date.now()}_${Math.random().toString(36).slice(2, 8)}${ext}`;
|
|
54117
|
+
const filePath = join8(dir, fileName);
|
|
54118
|
+
await writeFile(filePath, content, "utf-8");
|
|
54119
|
+
remoteTmpFiles.push(filePath);
|
|
54120
|
+
return filePath;
|
|
54121
|
+
}
|
|
54122
|
+
async function cleanupRemoteTmpFiles() {
|
|
54123
|
+
if (remoteTmpDir) {
|
|
54124
|
+
try {
|
|
54125
|
+
await rm(remoteTmpDir, { recursive: true, force: true });
|
|
54126
|
+
} catch {}
|
|
54127
|
+
remoteTmpDir = null;
|
|
54128
|
+
remoteTmpFiles.length = 0;
|
|
54129
|
+
}
|
|
54130
|
+
}
|
|
54131
|
+
async function importPlugin(pkg, configDir) {
|
|
54132
|
+
let modulePath;
|
|
54133
|
+
if (isRemoteUrl(pkg)) {
|
|
54134
|
+
const localPath = await fetchRemotePlugin(pkg);
|
|
54135
|
+
modulePath = `${localPath}?t=${Date.now()}`;
|
|
54136
|
+
} else if (isLocalPath(pkg)) {
|
|
54137
|
+
const absolutePath = resolve5(configDir, pkg);
|
|
54138
|
+
modulePath = `${absolutePath}?t=${Date.now()}`;
|
|
54139
|
+
} else {
|
|
54140
|
+
modulePath = pkg;
|
|
54141
|
+
}
|
|
54142
|
+
const mod = await import(modulePath);
|
|
54143
|
+
const definition = mod.default ?? mod;
|
|
54144
|
+
if (!definition || typeof definition.name !== "string" || typeof definition.create !== "function") {
|
|
54145
|
+
throw new Error(`\u63D2\u4EF6 "${pkg}" \u5BFC\u51FA\u683C\u5F0F\u4E0D\u6B63\u786E\uFF0C\u9700\u5BFC\u51FA\u5305\u542B name \u548C create \u7684 PluginDefinition`);
|
|
54146
|
+
}
|
|
54147
|
+
return definition;
|
|
54148
|
+
}
|
|
54149
|
+
|
|
54150
|
+
class PluginManager {
|
|
54151
|
+
plugins = new Map;
|
|
54152
|
+
configDir;
|
|
54153
|
+
constructor(configDir) {
|
|
54154
|
+
this.configDir = configDir;
|
|
54155
|
+
}
|
|
54156
|
+
async loadPluginsForProvider(providerName, pluginConfigs) {
|
|
54157
|
+
const loaded = [];
|
|
54158
|
+
const failures = [];
|
|
54159
|
+
for (const config2 of pluginConfigs) {
|
|
54160
|
+
try {
|
|
54161
|
+
const definition = await importPlugin(config2.package, this.configDir);
|
|
54162
|
+
const instance2 = await definition.create(config2.params ?? {});
|
|
54163
|
+
loaded.push({ config: config2, definition, instance: instance2 });
|
|
54164
|
+
} catch (err) {
|
|
54165
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
54166
|
+
console.error(`[plugin] \u52A0\u8F7D\u63D2\u4EF6 "${config2.package}" \u5931\u8D25 (provider: ${providerName}):`, errorMsg);
|
|
54167
|
+
failures.push({ provider: providerName, package: config2.package, error: errorMsg });
|
|
54168
|
+
}
|
|
54169
|
+
}
|
|
54170
|
+
return { loaded, failures };
|
|
54171
|
+
}
|
|
54172
|
+
async reloadAll(providers) {
|
|
54173
|
+
const newPlugins = new Map;
|
|
54174
|
+
const allFailures = [];
|
|
54175
|
+
const oldPluginsToDispose = [];
|
|
54176
|
+
for (const [providerName, providerConfig] of Object.entries(providers)) {
|
|
54177
|
+
if (providerConfig.plugins && providerConfig.plugins.length > 0) {
|
|
54178
|
+
const { loaded, failures } = await this.loadPluginsForProvider(providerName, providerConfig.plugins);
|
|
54179
|
+
allFailures.push(...failures);
|
|
54180
|
+
if (failures.length > 0) {
|
|
54181
|
+
console.warn(`[plugin] provider "${providerName}" \u6709\u63D2\u4EF6\u52A0\u8F7D\u5931\u8D25\uFF0C\u4FDD\u7559\u65E7\u63D2\u4EF6\u94FE`);
|
|
54182
|
+
const oldLoaded = this.plugins.get(providerName);
|
|
54183
|
+
if (oldLoaded) {
|
|
54184
|
+
newPlugins.set(providerName, oldLoaded);
|
|
54185
|
+
}
|
|
54186
|
+
for (const { instance: instance2, config: config2 } of loaded) {
|
|
54187
|
+
try {
|
|
54188
|
+
await instance2.dispose?.();
|
|
54189
|
+
} catch (err) {
|
|
54190
|
+
console.error(`[plugin] \u56DE\u6EDA\u9500\u6BC1\u63D2\u4EF6 "${config2.package}" \u5931\u8D25:`, err instanceof Error ? err.message : err);
|
|
54191
|
+
}
|
|
54192
|
+
}
|
|
54193
|
+
} else {
|
|
54194
|
+
newPlugins.set(providerName, loaded);
|
|
54195
|
+
const oldLoaded = this.plugins.get(providerName);
|
|
54196
|
+
if (oldLoaded) {
|
|
54197
|
+
oldPluginsToDispose.push(...oldLoaded);
|
|
54198
|
+
}
|
|
54199
|
+
}
|
|
54200
|
+
}
|
|
54201
|
+
}
|
|
54202
|
+
for (const [providerName, oldLoaded] of this.plugins) {
|
|
54203
|
+
if (!newPlugins.has(providerName)) {
|
|
54204
|
+
oldPluginsToDispose.push(...oldLoaded);
|
|
54205
|
+
}
|
|
54206
|
+
}
|
|
54207
|
+
this.plugins = newPlugins;
|
|
54208
|
+
if (oldPluginsToDispose.length > 0) {
|
|
54209
|
+
setTimeout(() => {
|
|
54210
|
+
this.disposePluginList(oldPluginsToDispose).catch((err) => {
|
|
54211
|
+
console.error("[plugin] \u65E7\u63D2\u4EF6\u9500\u6BC1\u5931\u8D25:", err);
|
|
54212
|
+
});
|
|
54213
|
+
}, 5000);
|
|
54214
|
+
}
|
|
54215
|
+
if (allFailures.length > 0) {
|
|
54216
|
+
console.warn(`[plugin] \u70ED\u91CD\u8F7D\u5B8C\u6210\uFF0C\u4F46\u6709 ${allFailures.length} \u4E2A\u63D2\u4EF6\u52A0\u8F7D\u5931\u8D25:`, allFailures.map((f) => `${f.provider}/${f.package}`).join(", "));
|
|
54217
|
+
}
|
|
54218
|
+
return { ok: allFailures.length === 0, failures: allFailures };
|
|
54219
|
+
}
|
|
54220
|
+
getPlugins(providerName) {
|
|
54221
|
+
const loaded = this.plugins.get(providerName);
|
|
54222
|
+
if (!loaded)
|
|
54223
|
+
return [];
|
|
54224
|
+
return loaded.map((l) => l.instance);
|
|
54225
|
+
}
|
|
54226
|
+
getLoadedPlugins(providerName) {
|
|
54227
|
+
return this.plugins.get(providerName) ?? [];
|
|
54228
|
+
}
|
|
54229
|
+
async disposeAll() {
|
|
54230
|
+
const allPlugins = [];
|
|
54231
|
+
for (const [, loadedPlugins] of this.plugins) {
|
|
54232
|
+
allPlugins.push(...loadedPlugins);
|
|
54233
|
+
}
|
|
54234
|
+
this.plugins.clear();
|
|
54235
|
+
await this.disposePluginList(allPlugins);
|
|
54236
|
+
await cleanupRemoteTmpFiles();
|
|
54237
|
+
}
|
|
54238
|
+
async disposePluginList(plugins) {
|
|
54239
|
+
for (const { instance: instance2, config: config2 } of plugins) {
|
|
54240
|
+
try {
|
|
54241
|
+
await instance2.dispose?.();
|
|
54242
|
+
} catch (err) {
|
|
54243
|
+
console.error(`[plugin] \u9500\u6BC1\u63D2\u4EF6 "${config2.package}" \u5931\u8D25:`, err instanceof Error ? err.message : err);
|
|
54244
|
+
}
|
|
54245
|
+
}
|
|
54246
|
+
}
|
|
54247
|
+
async disposePluginMap(pluginMap) {
|
|
54248
|
+
const allPlugins = [];
|
|
54249
|
+
for (const [, loadedPlugins] of pluginMap) {
|
|
54250
|
+
allPlugins.push(...loadedPlugins);
|
|
54251
|
+
}
|
|
54252
|
+
await this.disposePluginList(allPlugins);
|
|
54253
|
+
}
|
|
54254
|
+
}
|
|
54255
|
+
|
|
54068
54256
|
// src/proxy.ts
|
|
54069
54257
|
import { appendFile, readFile, unlink } from "fs/promises";
|
|
54070
|
-
import { join as
|
|
54071
|
-
import { tmpdir } from "os";
|
|
54258
|
+
import { join as join9 } from "path";
|
|
54259
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
54260
|
+
|
|
54261
|
+
// src/plugin-engine.ts
|
|
54262
|
+
async function executeRequestPlugins(plugins, ctx, url2, headers, body) {
|
|
54263
|
+
let currentUrl = url2;
|
|
54264
|
+
let currentHeaders = headers;
|
|
54265
|
+
let currentBody = body;
|
|
54266
|
+
for (const plugin of plugins) {
|
|
54267
|
+
if (!plugin.onRequest)
|
|
54268
|
+
continue;
|
|
54269
|
+
try {
|
|
54270
|
+
const result = await plugin.onRequest({
|
|
54271
|
+
ctx,
|
|
54272
|
+
url: currentUrl,
|
|
54273
|
+
headers: currentHeaders,
|
|
54274
|
+
body: currentBody
|
|
54275
|
+
});
|
|
54276
|
+
if (result) {
|
|
54277
|
+
if (result.url !== undefined)
|
|
54278
|
+
currentUrl = result.url;
|
|
54279
|
+
if (result.headers !== undefined)
|
|
54280
|
+
currentHeaders = result.headers;
|
|
54281
|
+
if (result.body !== undefined)
|
|
54282
|
+
currentBody = result.body;
|
|
54283
|
+
}
|
|
54284
|
+
} catch (err) {
|
|
54285
|
+
const error48 = err instanceof Error ? err : new Error(String(err));
|
|
54286
|
+
try {
|
|
54287
|
+
await plugin.onError?.({ ctx, phase: "request", error: error48 });
|
|
54288
|
+
} catch {}
|
|
54289
|
+
}
|
|
54290
|
+
}
|
|
54291
|
+
return { url: currentUrl, headers: currentHeaders, body: currentBody };
|
|
54292
|
+
}
|
|
54293
|
+
async function executeJsonResponsePlugins(plugins, ctx, status, headers, body) {
|
|
54294
|
+
let currentStatus = status;
|
|
54295
|
+
let currentHeaders = headers;
|
|
54296
|
+
let currentBody = body;
|
|
54297
|
+
for (let i = plugins.length - 1;i >= 0; i--) {
|
|
54298
|
+
const plugin = plugins[i];
|
|
54299
|
+
if (!plugin.onResponse)
|
|
54300
|
+
continue;
|
|
54301
|
+
try {
|
|
54302
|
+
const result = await plugin.onResponse({
|
|
54303
|
+
ctx,
|
|
54304
|
+
status: currentStatus,
|
|
54305
|
+
headers: currentHeaders,
|
|
54306
|
+
body: currentBody
|
|
54307
|
+
});
|
|
54308
|
+
if (result) {
|
|
54309
|
+
if (result.status !== undefined)
|
|
54310
|
+
currentStatus = result.status;
|
|
54311
|
+
if (result.headers !== undefined)
|
|
54312
|
+
currentHeaders = result.headers;
|
|
54313
|
+
if (result.body !== undefined)
|
|
54314
|
+
currentBody = result.body;
|
|
54315
|
+
}
|
|
54316
|
+
} catch (err) {
|
|
54317
|
+
const error48 = err instanceof Error ? err : new Error(String(err));
|
|
54318
|
+
try {
|
|
54319
|
+
await plugin.onError?.({ ctx, phase: "response", error: error48 });
|
|
54320
|
+
} catch {}
|
|
54321
|
+
}
|
|
54322
|
+
}
|
|
54323
|
+
return { status: currentStatus, headers: currentHeaders, body: currentBody };
|
|
54324
|
+
}
|
|
54325
|
+
async function createSSEPluginTransform(plugins, ctx, status, headers) {
|
|
54326
|
+
let currentStatus = status;
|
|
54327
|
+
let currentHeaders = headers;
|
|
54328
|
+
const transforms = [];
|
|
54329
|
+
for (let i = plugins.length - 1;i >= 0; i--) {
|
|
54330
|
+
const plugin = plugins[i];
|
|
54331
|
+
if (!plugin.onSSEResponse)
|
|
54332
|
+
continue;
|
|
54333
|
+
try {
|
|
54334
|
+
const result = await plugin.onSSEResponse({
|
|
54335
|
+
ctx,
|
|
54336
|
+
status: currentStatus,
|
|
54337
|
+
headers: currentHeaders
|
|
54338
|
+
});
|
|
54339
|
+
if (result) {
|
|
54340
|
+
if (result.status !== undefined)
|
|
54341
|
+
currentStatus = result.status;
|
|
54342
|
+
if (result.headers !== undefined)
|
|
54343
|
+
currentHeaders = result.headers;
|
|
54344
|
+
if (result.transform)
|
|
54345
|
+
transforms.push(result.transform);
|
|
54346
|
+
}
|
|
54347
|
+
} catch (err) {
|
|
54348
|
+
const error48 = err instanceof Error ? err : new Error(String(err));
|
|
54349
|
+
try {
|
|
54350
|
+
await plugin.onError?.({ ctx, phase: "response", error: error48 });
|
|
54351
|
+
} catch {}
|
|
54352
|
+
}
|
|
54353
|
+
}
|
|
54354
|
+
if (transforms.length === 0) {
|
|
54355
|
+
return { status: currentStatus, headers: currentHeaders, transform: null };
|
|
54356
|
+
}
|
|
54357
|
+
if (transforms.length === 1) {
|
|
54358
|
+
return { status: currentStatus, headers: currentHeaders, transform: transforms[0] };
|
|
54359
|
+
}
|
|
54360
|
+
const entry = new TransformStream;
|
|
54361
|
+
let stream = entry.readable;
|
|
54362
|
+
for (const t of transforms) {
|
|
54363
|
+
stream = stream.pipeThrough(t);
|
|
54364
|
+
}
|
|
54365
|
+
return {
|
|
54366
|
+
status: currentStatus,
|
|
54367
|
+
headers: currentHeaders,
|
|
54368
|
+
transform: { writable: entry.writable, readable: stream }
|
|
54369
|
+
};
|
|
54370
|
+
}
|
|
54371
|
+
|
|
54372
|
+
// src/proxy.ts
|
|
54072
54373
|
var HOP_BY_HOP_HEADERS = new Set([
|
|
54073
54374
|
"connection",
|
|
54074
54375
|
"keep-alive",
|
|
@@ -54147,7 +54448,7 @@ function buildLogEvent(logMeta, targetUrl, proxyUrl, tsEnd, overrides) {
|
|
|
54147
54448
|
};
|
|
54148
54449
|
}
|
|
54149
54450
|
function createTempStreamCapturePath(requestId) {
|
|
54150
|
-
return
|
|
54451
|
+
return join9(tmpdir2(), `local-router-stream-${requestId}-${Date.now()}.sse.raw`);
|
|
54151
54452
|
}
|
|
54152
54453
|
async function appendTempStreamCapture(filePath, chunk) {
|
|
54153
54454
|
await appendFile(filePath, chunk);
|
|
@@ -54169,33 +54470,63 @@ async function flushTempCaptureToLogger(tempPath, requestId, dateStr, logger) {
|
|
|
54169
54470
|
}
|
|
54170
54471
|
}
|
|
54171
54472
|
async function proxyRequest(c2, options) {
|
|
54172
|
-
const { logMeta } = options;
|
|
54473
|
+
const { logMeta, plugins, pluginConfigs } = options;
|
|
54173
54474
|
const logger = getLogger();
|
|
54174
54475
|
const shouldLog = logger?.enabled ?? false;
|
|
54175
|
-
const
|
|
54476
|
+
const hasPlugins = plugins && plugins.length > 0;
|
|
54477
|
+
let targetUrl = options.targetUrl;
|
|
54478
|
+
let headers = buildUpstreamHeaders(c2.req.raw.headers, options.apiKey, options.authType);
|
|
54479
|
+
let bodyStr = options.body;
|
|
54480
|
+
const pluginLogOverrides = {};
|
|
54481
|
+
if (hasPlugins) {
|
|
54482
|
+
const bodyObj = JSON.parse(bodyStr);
|
|
54483
|
+
const ctx = {
|
|
54484
|
+
requestId: logMeta.requestId,
|
|
54485
|
+
provider: logMeta.provider,
|
|
54486
|
+
modelIn: logMeta.modelIn,
|
|
54487
|
+
modelOut: logMeta.modelOut,
|
|
54488
|
+
routeType: logMeta.routeType,
|
|
54489
|
+
isStream: logMeta.isStream
|
|
54490
|
+
};
|
|
54491
|
+
const result = await executeRequestPlugins(plugins, ctx, targetUrl, headers, bodyObj);
|
|
54492
|
+
if (pluginConfigs) {
|
|
54493
|
+
pluginLogOverrides.plugins_request = pluginConfigs;
|
|
54494
|
+
}
|
|
54495
|
+
if (result.url !== targetUrl) {
|
|
54496
|
+
targetUrl = result.url;
|
|
54497
|
+
pluginLogOverrides.request_url_after_plugins = targetUrl;
|
|
54498
|
+
}
|
|
54499
|
+
headers = result.headers;
|
|
54500
|
+
const newBodyStr = JSON.stringify(result.body);
|
|
54501
|
+
if (newBodyStr !== bodyStr) {
|
|
54502
|
+
bodyStr = newBodyStr;
|
|
54503
|
+
pluginLogOverrides.request_body_after_plugins = result.body;
|
|
54504
|
+
}
|
|
54505
|
+
}
|
|
54176
54506
|
const requestBody = shouldLog && logger?.bodyPolicy !== "off" ? JSON.parse(options.body) : undefined;
|
|
54177
54507
|
const proxy = options.proxy?.trim() ? options.proxy.trim() : undefined;
|
|
54178
54508
|
let upstreamRes;
|
|
54179
54509
|
try {
|
|
54180
|
-
upstreamRes = await fetch(
|
|
54510
|
+
upstreamRes = await fetch(targetUrl, {
|
|
54181
54511
|
method: c2.req.method,
|
|
54182
54512
|
headers,
|
|
54183
|
-
body:
|
|
54513
|
+
body: bodyStr,
|
|
54184
54514
|
...proxy ? { proxy } : {},
|
|
54185
54515
|
decompress: true
|
|
54186
54516
|
});
|
|
54187
54517
|
} catch (err) {
|
|
54188
54518
|
if (shouldLog) {
|
|
54189
|
-
logger?.writeEvent(buildLogEvent(logMeta,
|
|
54519
|
+
logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), {
|
|
54190
54520
|
error_type: err instanceof Error ? err.constructor.name : "UnknownError",
|
|
54191
54521
|
error_message: err instanceof Error ? err.message : String(err),
|
|
54192
|
-
...requestBody !== undefined && { request_body: requestBody }
|
|
54522
|
+
...requestBody !== undefined && { request_body: requestBody },
|
|
54523
|
+
...pluginLogOverrides
|
|
54193
54524
|
}));
|
|
54194
54525
|
}
|
|
54195
54526
|
throw err;
|
|
54196
54527
|
}
|
|
54197
54528
|
const responseHeaders = buildResponseHeaders(upstreamRes.headers);
|
|
54198
|
-
if (!shouldLog) {
|
|
54529
|
+
if (!shouldLog && !hasPlugins) {
|
|
54199
54530
|
return new Response(upstreamRes.body, {
|
|
54200
54531
|
status: upstreamRes.status,
|
|
54201
54532
|
headers: responseHeaders
|
|
@@ -54205,6 +54536,33 @@ async function proxyRequest(c2, options) {
|
|
|
54205
54536
|
const providerRequestId = extractProviderRequestId(upstreamRes.headers);
|
|
54206
54537
|
const dateStr = new Date(logMeta.tsStart).toISOString().slice(0, 10);
|
|
54207
54538
|
if (logMeta.isStream && upstreamRes.body) {
|
|
54539
|
+
let sseStatus = upstreamRes.status;
|
|
54540
|
+
let sseHeaders = responseHeaders;
|
|
54541
|
+
let sseTransform = null;
|
|
54542
|
+
if (hasPlugins) {
|
|
54543
|
+
const ctx = {
|
|
54544
|
+
requestId: logMeta.requestId,
|
|
54545
|
+
provider: logMeta.provider,
|
|
54546
|
+
modelIn: logMeta.modelIn,
|
|
54547
|
+
modelOut: logMeta.modelOut,
|
|
54548
|
+
routeType: logMeta.routeType,
|
|
54549
|
+
isStream: logMeta.isStream
|
|
54550
|
+
};
|
|
54551
|
+
const sseResult = await createSSEPluginTransform(plugins, ctx, upstreamRes.status, responseHeaders);
|
|
54552
|
+
sseStatus = sseResult.status;
|
|
54553
|
+
sseHeaders = sseResult.headers;
|
|
54554
|
+
sseTransform = sseResult.transform;
|
|
54555
|
+
if (pluginConfigs) {
|
|
54556
|
+
pluginLogOverrides.plugins_response = pluginConfigs;
|
|
54557
|
+
}
|
|
54558
|
+
}
|
|
54559
|
+
if (!shouldLog) {
|
|
54560
|
+
const outputBody2 = sseTransform ? upstreamRes.body.pipeThrough(sseTransform) : upstreamRes.body;
|
|
54561
|
+
return new Response(outputBody2, {
|
|
54562
|
+
status: sseStatus,
|
|
54563
|
+
headers: sseHeaders
|
|
54564
|
+
});
|
|
54565
|
+
}
|
|
54208
54566
|
const [clientStream, logStream] = upstreamRes.body.tee();
|
|
54209
54567
|
(async () => {
|
|
54210
54568
|
const tempPath = createTempStreamCapturePath(logMeta.requestId);
|
|
@@ -54226,30 +54584,61 @@ async function proxyRequest(c2, options) {
|
|
|
54226
54584
|
});
|
|
54227
54585
|
console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\u5904\u7406\u5931\u8D25:", err);
|
|
54228
54586
|
} finally {
|
|
54229
|
-
logger?.writeEvent(buildLogEvent(logMeta,
|
|
54230
|
-
upstream_status:
|
|
54587
|
+
logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), {
|
|
54588
|
+
upstream_status: sseStatus,
|
|
54231
54589
|
content_type_res: contentTypeRes,
|
|
54232
|
-
response_headers:
|
|
54590
|
+
response_headers: sseHeaders,
|
|
54233
54591
|
stream_bytes: streamBytes,
|
|
54234
54592
|
provider_request_id: providerRequestId,
|
|
54235
54593
|
...streamFile != null && { stream_file: streamFile },
|
|
54236
|
-
...requestBody !== undefined && { request_body: requestBody }
|
|
54594
|
+
...requestBody !== undefined && { request_body: requestBody },
|
|
54595
|
+
...pluginLogOverrides
|
|
54237
54596
|
}));
|
|
54238
54597
|
}
|
|
54239
54598
|
})();
|
|
54240
|
-
|
|
54241
|
-
|
|
54242
|
-
|
|
54599
|
+
const outputBody = sseTransform ? clientStream.pipeThrough(sseTransform) : clientStream;
|
|
54600
|
+
return new Response(outputBody, {
|
|
54601
|
+
status: sseStatus,
|
|
54602
|
+
headers: sseHeaders
|
|
54603
|
+
});
|
|
54604
|
+
}
|
|
54605
|
+
let responseText = await upstreamRes.text();
|
|
54606
|
+
let responseStatus = upstreamRes.status;
|
|
54607
|
+
let finalResponseHeaders = responseHeaders;
|
|
54608
|
+
if (hasPlugins) {
|
|
54609
|
+
const ctx = {
|
|
54610
|
+
requestId: logMeta.requestId,
|
|
54611
|
+
provider: logMeta.provider,
|
|
54612
|
+
modelIn: logMeta.modelIn,
|
|
54613
|
+
modelOut: logMeta.modelOut,
|
|
54614
|
+
routeType: logMeta.routeType,
|
|
54615
|
+
isStream: logMeta.isStream
|
|
54616
|
+
};
|
|
54617
|
+
const result = await executeJsonResponsePlugins(plugins, ctx, upstreamRes.status, responseHeaders, responseText);
|
|
54618
|
+
if (pluginConfigs) {
|
|
54619
|
+
pluginLogOverrides.plugins_response = pluginConfigs;
|
|
54620
|
+
}
|
|
54621
|
+
if (result.body !== responseText) {
|
|
54622
|
+
pluginLogOverrides.response_body_after_plugins = result.body;
|
|
54623
|
+
}
|
|
54624
|
+
responseStatus = result.status;
|
|
54625
|
+
finalResponseHeaders = result.headers;
|
|
54626
|
+
responseText = result.body;
|
|
54627
|
+
}
|
|
54628
|
+
if (!shouldLog) {
|
|
54629
|
+
return new Response(responseText, {
|
|
54630
|
+
status: responseStatus,
|
|
54631
|
+
headers: finalResponseHeaders
|
|
54243
54632
|
});
|
|
54244
54633
|
}
|
|
54245
|
-
const responseText = await upstreamRes.text();
|
|
54246
54634
|
const responseBytes = Buffer.byteLength(responseText, "utf-8");
|
|
54247
54635
|
const eventOverrides = {
|
|
54248
54636
|
upstream_status: upstreamRes.status,
|
|
54249
54637
|
content_type_res: contentTypeRes,
|
|
54250
|
-
response_headers:
|
|
54638
|
+
response_headers: finalResponseHeaders,
|
|
54251
54639
|
response_bytes: responseBytes,
|
|
54252
|
-
provider_request_id: providerRequestId
|
|
54640
|
+
provider_request_id: providerRequestId,
|
|
54641
|
+
...pluginLogOverrides
|
|
54253
54642
|
};
|
|
54254
54643
|
if (requestBody !== undefined) {
|
|
54255
54644
|
eventOverrides.request_body = requestBody;
|
|
@@ -54257,10 +54646,10 @@ async function proxyRequest(c2, options) {
|
|
|
54257
54646
|
if (logger?.bodyPolicy !== "off") {
|
|
54258
54647
|
eventOverrides.response_body = responseText;
|
|
54259
54648
|
}
|
|
54260
|
-
logger?.writeEvent(buildLogEvent(logMeta,
|
|
54649
|
+
logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), eventOverrides));
|
|
54261
54650
|
return new Response(responseText, {
|
|
54262
|
-
status:
|
|
54263
|
-
headers:
|
|
54651
|
+
status: responseStatus,
|
|
54652
|
+
headers: finalResponseHeaders
|
|
54264
54653
|
});
|
|
54265
54654
|
}
|
|
54266
54655
|
|
|
@@ -54275,7 +54664,7 @@ function resolveRoute(modelMap, incomingModel) {
|
|
|
54275
54664
|
return;
|
|
54276
54665
|
}
|
|
54277
54666
|
function createModelRoutingHandler(options) {
|
|
54278
|
-
const { routeType, store, authType, buildTargetUrl } = options;
|
|
54667
|
+
const { routeType, store, authType, buildTargetUrl, pluginManager } = options;
|
|
54279
54668
|
return async (c2) => {
|
|
54280
54669
|
const config2 = store.get();
|
|
54281
54670
|
const modelMap = config2.routes[routeType];
|
|
@@ -54317,49 +54706,60 @@ function createModelRoutingHandler(options) {
|
|
|
54317
54706
|
requestBytes: Buffer.byteLength(body, "utf-8"),
|
|
54318
54707
|
requestHeaders: collectHeaders(c2.req.raw.headers)
|
|
54319
54708
|
};
|
|
54709
|
+
const plugins = pluginManager?.getPlugins(target.provider) ?? [];
|
|
54710
|
+
const pluginConfigs = pluginManager?.getLoadedPlugins(target.provider) ?? [];
|
|
54320
54711
|
return proxyRequest(c2, {
|
|
54321
54712
|
targetUrl,
|
|
54322
54713
|
apiKey: provider.apiKey,
|
|
54323
54714
|
proxy: provider.proxy,
|
|
54324
54715
|
authType,
|
|
54325
54716
|
body,
|
|
54326
|
-
logMeta
|
|
54717
|
+
logMeta,
|
|
54718
|
+
plugins: plugins.length > 0 ? plugins : undefined,
|
|
54719
|
+
pluginConfigs: pluginConfigs.length > 0 ? pluginConfigs.map((lp) => ({
|
|
54720
|
+
name: lp.definition.name,
|
|
54721
|
+
package: lp.config.package,
|
|
54722
|
+
params: lp.config.params ?? {}
|
|
54723
|
+
})) : undefined
|
|
54327
54724
|
});
|
|
54328
54725
|
};
|
|
54329
54726
|
}
|
|
54330
54727
|
|
|
54331
54728
|
// src/routes/anthropic-messages.ts
|
|
54332
|
-
function createAnthropicMessagesRoutes(routeType, store) {
|
|
54729
|
+
function createAnthropicMessagesRoutes(routeType, store, pluginManager) {
|
|
54333
54730
|
const routes = new Hono2;
|
|
54334
54731
|
routes.post("/v1/messages", createModelRoutingHandler({
|
|
54335
54732
|
routeType,
|
|
54336
54733
|
store,
|
|
54337
54734
|
authType: "x-api-key",
|
|
54338
|
-
buildTargetUrl: (base) => `${base}/v1/messages
|
|
54735
|
+
buildTargetUrl: (base) => `${base}/v1/messages`,
|
|
54736
|
+
pluginManager
|
|
54339
54737
|
}));
|
|
54340
54738
|
return routes;
|
|
54341
54739
|
}
|
|
54342
54740
|
|
|
54343
54741
|
// src/routes/openai-completions.ts
|
|
54344
|
-
function createOpenaiCompletionsRoutes(routeType, store) {
|
|
54742
|
+
function createOpenaiCompletionsRoutes(routeType, store, pluginManager) {
|
|
54345
54743
|
const routes = new Hono2;
|
|
54346
54744
|
routes.post("/v1/chat/completions", createModelRoutingHandler({
|
|
54347
54745
|
routeType,
|
|
54348
54746
|
store,
|
|
54349
54747
|
authType: "bearer",
|
|
54350
|
-
buildTargetUrl: (base) => `${base}/v1/chat/completions
|
|
54748
|
+
buildTargetUrl: (base) => `${base}/v1/chat/completions`,
|
|
54749
|
+
pluginManager
|
|
54351
54750
|
}));
|
|
54352
54751
|
return routes;
|
|
54353
54752
|
}
|
|
54354
54753
|
|
|
54355
54754
|
// src/routes/openai-responses.ts
|
|
54356
|
-
function createOpenaiResponsesRoutes(routeType, store) {
|
|
54755
|
+
function createOpenaiResponsesRoutes(routeType, store, pluginManager) {
|
|
54357
54756
|
const routes = new Hono2;
|
|
54358
54757
|
routes.post("/v1/responses", createModelRoutingHandler({
|
|
54359
54758
|
routeType,
|
|
54360
54759
|
store,
|
|
54361
54760
|
authType: "bearer",
|
|
54362
|
-
buildTargetUrl: (base) => `${base}/v1/responses
|
|
54761
|
+
buildTargetUrl: (base) => `${base}/v1/responses`,
|
|
54762
|
+
pluginManager
|
|
54363
54763
|
}));
|
|
54364
54764
|
return routes;
|
|
54365
54765
|
}
|
|
@@ -54520,7 +54920,7 @@ function createChatProxyModel(providerName, providerConfig, model) {
|
|
|
54520
54920
|
throw new Error(`\u6682\u4E0D\u652F\u6301\u7684 provider \u7C7B\u578B: ${providerConfig.type}`);
|
|
54521
54921
|
}
|
|
54522
54922
|
}
|
|
54523
|
-
function createAdminApiRoutes(store, registerCleanup) {
|
|
54923
|
+
function createAdminApiRoutes(store, pluginManager, registerCleanup) {
|
|
54524
54924
|
const api2 = new Hono2;
|
|
54525
54925
|
const cryptoSessions = new Map;
|
|
54526
54926
|
const CRYPTO_SESSION_TTL_MS = 2 * 60 * 1000;
|
|
@@ -54631,18 +55031,22 @@ function createAdminApiRoutes(store, registerCleanup) {
|
|
|
54631
55031
|
session.dispose();
|
|
54632
55032
|
}
|
|
54633
55033
|
});
|
|
54634
|
-
api2.post("/config/apply", (_c) => {
|
|
55034
|
+
api2.post("/config/apply", async (_c) => {
|
|
54635
55035
|
try {
|
|
54636
55036
|
const config2 = store.reload();
|
|
54637
55037
|
if (config2.log) {
|
|
54638
55038
|
const logBaseDir = resolveLogBaseDir(config2.log);
|
|
54639
55039
|
initLogger(logBaseDir, config2.log);
|
|
54640
55040
|
}
|
|
55041
|
+
const pluginResult = await pluginManager.reloadAll(config2.providers);
|
|
54641
55042
|
return _c.json({
|
|
54642
55043
|
ok: true,
|
|
54643
55044
|
summary: {
|
|
54644
55045
|
providers: Object.keys(config2.providers).length,
|
|
54645
55046
|
routes: Object.keys(config2.routes).length
|
|
55047
|
+
},
|
|
55048
|
+
...pluginResult.failures.length > 0 && {
|
|
55049
|
+
pluginWarnings: pluginResult.failures
|
|
54646
55050
|
}
|
|
54647
55051
|
});
|
|
54648
55052
|
} catch (err) {
|
|
@@ -55067,7 +55471,7 @@ async function proxyAdminToDevServer(c2, origin) {
|
|
|
55067
55471
|
headers: buildProxyResponseHeaders(upstreamRes.headers)
|
|
55068
55472
|
});
|
|
55069
55473
|
}
|
|
55070
|
-
function createApp(store, options) {
|
|
55474
|
+
async function createApp(store, options) {
|
|
55071
55475
|
const config2 = store.get();
|
|
55072
55476
|
console.log(`\u5DF2\u52A0\u8F7D\u914D\u7F6E: ${store.getPath()}`);
|
|
55073
55477
|
if (config2.log) {
|
|
@@ -55078,15 +55482,24 @@ function createApp(store, options) {
|
|
|
55078
55482
|
}
|
|
55079
55483
|
const stopLogStorageTask = startLogStorageBackgroundTask(config2.log);
|
|
55080
55484
|
options?.registerCleanup?.(stopLogStorageTask);
|
|
55485
|
+
const configDir = dirname2(resolve6(store.getPath()));
|
|
55486
|
+
const pluginManager = new PluginManager(configDir);
|
|
55487
|
+
const reloadResult = await pluginManager.reloadAll(config2.providers);
|
|
55488
|
+
if (!reloadResult.ok) {
|
|
55489
|
+
console.warn(`[plugin] \u63D2\u4EF6\u521D\u59CB\u5316\u5B8C\u6210\uFF0C\u4F46\u6709 ${reloadResult.failures.length} \u4E2A\u63D2\u4EF6\u52A0\u8F7D\u5931\u8D25`);
|
|
55490
|
+
}
|
|
55491
|
+
options?.registerCleanup?.(() => {
|
|
55492
|
+
pluginManager.disposeAll().catch(() => {});
|
|
55493
|
+
});
|
|
55081
55494
|
printIntegrationGuide(config2);
|
|
55082
55495
|
const app = new Hono2;
|
|
55083
55496
|
app.get("/", (c2) => c2.text("local-router is running"));
|
|
55084
55497
|
for (const [routeType, entry] of Object.entries(ROUTE_REGISTRY)) {
|
|
55085
|
-
const subApp = entry.create(routeType, store);
|
|
55498
|
+
const subApp = entry.create(routeType, store, pluginManager);
|
|
55086
55499
|
app.route(entry.mountPrefix, subApp);
|
|
55087
55500
|
console.log(`\u5DF2\u6CE8\u518C\u8DEF\u7531: ${routeType} -> ${entry.mountPrefix}`);
|
|
55088
55501
|
}
|
|
55089
|
-
app.route("/api", createAdminApiRoutes(store, options?.registerCleanup));
|
|
55502
|
+
app.route("/api", createAdminApiRoutes(store, pluginManager, options?.registerCleanup));
|
|
55090
55503
|
console.log("\u5DF2\u6CE8\u518C\u7BA1\u7406 API: /api");
|
|
55091
55504
|
app.get("/api/docs", middleware({ url: "/api/openapi.json" }));
|
|
55092
55505
|
app.get("/api/openapi.json", (c2) => c2.json(openAPISpec));
|
|
@@ -55115,14 +55528,14 @@ function createApp(store, options) {
|
|
|
55115
55528
|
}
|
|
55116
55529
|
return app;
|
|
55117
55530
|
}
|
|
55118
|
-
function createDefaultAppFromProcessArgs() {
|
|
55531
|
+
async function createDefaultAppFromProcessArgs() {
|
|
55119
55532
|
const configPath = parseConfigPath();
|
|
55120
55533
|
const store = new ConfigStore(configPath);
|
|
55121
55534
|
return createApp(store);
|
|
55122
55535
|
}
|
|
55123
55536
|
|
|
55124
55537
|
// src/entry.ts
|
|
55125
|
-
var app = createDefaultAppFromProcessArgs();
|
|
55538
|
+
var app = await createDefaultAppFromProcessArgs();
|
|
55126
55539
|
var entry_default = app;
|
|
55127
55540
|
export {
|
|
55128
55541
|
entry_default as default
|