@lakphy/local-router 0.3.3 → 0.4.0-beta.1
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 +407 -46
- package/dist/entry.js +400 -39
- 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/cli.js
CHANGED
|
@@ -4935,7 +4935,7 @@ var require_compile = __commonJS((exports) => {
|
|
|
4935
4935
|
const schOrFunc = root2.refs[ref];
|
|
4936
4936
|
if (schOrFunc)
|
|
4937
4937
|
return schOrFunc;
|
|
4938
|
-
let _sch =
|
|
4938
|
+
let _sch = resolve6.call(this, root2, ref);
|
|
4939
4939
|
if (_sch === undefined) {
|
|
4940
4940
|
const schema = (_a21 = root2.localRefs) === null || _a21 === undefined ? undefined : _a21[ref];
|
|
4941
4941
|
const { schemaId } = this.opts;
|
|
@@ -4962,7 +4962,7 @@ var require_compile = __commonJS((exports) => {
|
|
|
4962
4962
|
function sameSchemaEnv(s1, s2) {
|
|
4963
4963
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
4964
4964
|
}
|
|
4965
|
-
function
|
|
4965
|
+
function resolve6(root2, ref) {
|
|
4966
4966
|
let sch;
|
|
4967
4967
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
4968
4968
|
ref = sch;
|
|
@@ -5492,7 +5492,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
5492
5492
|
}
|
|
5493
5493
|
return uri;
|
|
5494
5494
|
}
|
|
5495
|
-
function
|
|
5495
|
+
function resolve6(baseURI, relativeURI, options) {
|
|
5496
5496
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
5497
5497
|
const resolved = resolveComponent(parse7(baseURI, schemelessOptions), parse7(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
5498
5498
|
schemelessOptions.skipEscape = true;
|
|
@@ -5720,7 +5720,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
5720
5720
|
var fastUri = {
|
|
5721
5721
|
SCHEMES,
|
|
5722
5722
|
normalize,
|
|
5723
|
-
resolve:
|
|
5723
|
+
resolve: resolve6,
|
|
5724
5724
|
resolveComponent,
|
|
5725
5725
|
equal,
|
|
5726
5726
|
serialize,
|
|
@@ -10518,6 +10518,7 @@ import { parseArgs } from "util";
|
|
|
10518
10518
|
|
|
10519
10519
|
// src/index.ts
|
|
10520
10520
|
import { readFileSync as readFileSync4 } from "fs";
|
|
10521
|
+
import { dirname as dirname3, resolve as resolve6 } from "path";
|
|
10521
10522
|
|
|
10522
10523
|
// node_modules/.bun/@ai-sdk+provider@3.0.8/node_modules/@ai-sdk/provider/dist/index.mjs
|
|
10523
10524
|
var marker = "vercel.ai.error";
|
|
@@ -52118,6 +52119,14 @@ async function buildLogEventDetail(id, parsed, location, context2) {
|
|
|
52118
52119
|
const responseBodyAvailable = event.response_body !== undefined;
|
|
52119
52120
|
const streamCaptured = Boolean(event.stream_file);
|
|
52120
52121
|
const { content: streamContent, warning: streamWarning } = readStreamContent(resolveLogBaseDir(context2.logConfig), event.stream_file);
|
|
52122
|
+
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;
|
|
52123
|
+
const pluginsSection = hasPluginData ? {
|
|
52124
|
+
request: event.plugins_request,
|
|
52125
|
+
response: event.plugins_response,
|
|
52126
|
+
requestBodyAfterPlugins: event.request_body_after_plugins,
|
|
52127
|
+
requestUrlAfterPlugins: event.request_url_after_plugins,
|
|
52128
|
+
responseBodyAfterPlugins: event.response_body_after_plugins
|
|
52129
|
+
} : undefined;
|
|
52121
52130
|
return {
|
|
52122
52131
|
id,
|
|
52123
52132
|
summary: {
|
|
@@ -52169,6 +52178,7 @@ async function buildLogEventDetail(id, parsed, location, context2) {
|
|
|
52169
52178
|
...streamWarning ? [streamWarning] : []
|
|
52170
52179
|
]
|
|
52171
52180
|
},
|
|
52181
|
+
...pluginsSection && { plugins: pluginsSection },
|
|
52172
52182
|
rawEvent: event,
|
|
52173
52183
|
location
|
|
52174
52184
|
};
|
|
@@ -54081,10 +54091,249 @@ var openAPISpec = {
|
|
|
54081
54091
|
}
|
|
54082
54092
|
};
|
|
54083
54093
|
|
|
54094
|
+
// src/plugin-loader.ts
|
|
54095
|
+
import { resolve as resolve5 } from "path";
|
|
54096
|
+
function isLocalPath(pkg) {
|
|
54097
|
+
return pkg.startsWith("./") || pkg.startsWith("../") || pkg.startsWith("/") || /^[A-Za-z]:[\\/]/.test(pkg);
|
|
54098
|
+
}
|
|
54099
|
+
async function importPlugin(pkg, configDir) {
|
|
54100
|
+
let modulePath;
|
|
54101
|
+
if (isLocalPath(pkg)) {
|
|
54102
|
+
const absolutePath = resolve5(configDir, pkg);
|
|
54103
|
+
modulePath = `${absolutePath}?t=${Date.now()}`;
|
|
54104
|
+
} else {
|
|
54105
|
+
modulePath = pkg;
|
|
54106
|
+
}
|
|
54107
|
+
const mod = await import(modulePath);
|
|
54108
|
+
const definition = mod.default ?? mod;
|
|
54109
|
+
if (!definition || typeof definition.name !== "string" || typeof definition.create !== "function") {
|
|
54110
|
+
throw new Error(`\u63D2\u4EF6 "${pkg}" \u5BFC\u51FA\u683C\u5F0F\u4E0D\u6B63\u786E\uFF0C\u9700\u5BFC\u51FA\u5305\u542B name \u548C create \u7684 PluginDefinition`);
|
|
54111
|
+
}
|
|
54112
|
+
return definition;
|
|
54113
|
+
}
|
|
54114
|
+
|
|
54115
|
+
class PluginManager {
|
|
54116
|
+
plugins = new Map;
|
|
54117
|
+
configDir;
|
|
54118
|
+
constructor(configDir) {
|
|
54119
|
+
this.configDir = configDir;
|
|
54120
|
+
}
|
|
54121
|
+
async loadPluginsForProvider(providerName, pluginConfigs) {
|
|
54122
|
+
const loaded = [];
|
|
54123
|
+
const failures = [];
|
|
54124
|
+
for (const config2 of pluginConfigs) {
|
|
54125
|
+
try {
|
|
54126
|
+
const definition = await importPlugin(config2.package, this.configDir);
|
|
54127
|
+
const instance2 = await definition.create(config2.params ?? {});
|
|
54128
|
+
loaded.push({ config: config2, definition, instance: instance2 });
|
|
54129
|
+
} catch (err) {
|
|
54130
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
54131
|
+
console.error(`[plugin] \u52A0\u8F7D\u63D2\u4EF6 "${config2.package}" \u5931\u8D25 (provider: ${providerName}):`, errorMsg);
|
|
54132
|
+
failures.push({ provider: providerName, package: config2.package, error: errorMsg });
|
|
54133
|
+
}
|
|
54134
|
+
}
|
|
54135
|
+
return { loaded, failures };
|
|
54136
|
+
}
|
|
54137
|
+
async reloadAll(providers) {
|
|
54138
|
+
const newPlugins = new Map;
|
|
54139
|
+
const allFailures = [];
|
|
54140
|
+
const oldPluginsToDispose = [];
|
|
54141
|
+
for (const [providerName, providerConfig] of Object.entries(providers)) {
|
|
54142
|
+
if (providerConfig.plugins && providerConfig.plugins.length > 0) {
|
|
54143
|
+
const { loaded, failures } = await this.loadPluginsForProvider(providerName, providerConfig.plugins);
|
|
54144
|
+
allFailures.push(...failures);
|
|
54145
|
+
if (failures.length > 0) {
|
|
54146
|
+
console.warn(`[plugin] provider "${providerName}" \u6709\u63D2\u4EF6\u52A0\u8F7D\u5931\u8D25\uFF0C\u4FDD\u7559\u65E7\u63D2\u4EF6\u94FE`);
|
|
54147
|
+
const oldLoaded = this.plugins.get(providerName);
|
|
54148
|
+
if (oldLoaded) {
|
|
54149
|
+
newPlugins.set(providerName, oldLoaded);
|
|
54150
|
+
}
|
|
54151
|
+
for (const { instance: instance2, config: config2 } of loaded) {
|
|
54152
|
+
try {
|
|
54153
|
+
await instance2.dispose?.();
|
|
54154
|
+
} catch (err) {
|
|
54155
|
+
console.error(`[plugin] \u56DE\u6EDA\u9500\u6BC1\u63D2\u4EF6 "${config2.package}" \u5931\u8D25:`, err instanceof Error ? err.message : err);
|
|
54156
|
+
}
|
|
54157
|
+
}
|
|
54158
|
+
} else {
|
|
54159
|
+
newPlugins.set(providerName, loaded);
|
|
54160
|
+
const oldLoaded = this.plugins.get(providerName);
|
|
54161
|
+
if (oldLoaded) {
|
|
54162
|
+
oldPluginsToDispose.push(...oldLoaded);
|
|
54163
|
+
}
|
|
54164
|
+
}
|
|
54165
|
+
}
|
|
54166
|
+
}
|
|
54167
|
+
for (const [providerName, oldLoaded] of this.plugins) {
|
|
54168
|
+
if (!newPlugins.has(providerName)) {
|
|
54169
|
+
oldPluginsToDispose.push(...oldLoaded);
|
|
54170
|
+
}
|
|
54171
|
+
}
|
|
54172
|
+
this.plugins = newPlugins;
|
|
54173
|
+
if (oldPluginsToDispose.length > 0) {
|
|
54174
|
+
setTimeout(() => {
|
|
54175
|
+
this.disposePluginList(oldPluginsToDispose).catch((err) => {
|
|
54176
|
+
console.error("[plugin] \u65E7\u63D2\u4EF6\u9500\u6BC1\u5931\u8D25:", err);
|
|
54177
|
+
});
|
|
54178
|
+
}, 5000);
|
|
54179
|
+
}
|
|
54180
|
+
if (allFailures.length > 0) {
|
|
54181
|
+
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(", "));
|
|
54182
|
+
}
|
|
54183
|
+
return { ok: allFailures.length === 0, failures: allFailures };
|
|
54184
|
+
}
|
|
54185
|
+
getPlugins(providerName) {
|
|
54186
|
+
const loaded = this.plugins.get(providerName);
|
|
54187
|
+
if (!loaded)
|
|
54188
|
+
return [];
|
|
54189
|
+
return loaded.map((l) => l.instance);
|
|
54190
|
+
}
|
|
54191
|
+
getLoadedPlugins(providerName) {
|
|
54192
|
+
return this.plugins.get(providerName) ?? [];
|
|
54193
|
+
}
|
|
54194
|
+
async disposeAll() {
|
|
54195
|
+
const allPlugins = [];
|
|
54196
|
+
for (const [, loadedPlugins] of this.plugins) {
|
|
54197
|
+
allPlugins.push(...loadedPlugins);
|
|
54198
|
+
}
|
|
54199
|
+
this.plugins.clear();
|
|
54200
|
+
await this.disposePluginList(allPlugins);
|
|
54201
|
+
}
|
|
54202
|
+
async disposePluginList(plugins) {
|
|
54203
|
+
for (const { instance: instance2, config: config2 } of plugins) {
|
|
54204
|
+
try {
|
|
54205
|
+
await instance2.dispose?.();
|
|
54206
|
+
} catch (err) {
|
|
54207
|
+
console.error(`[plugin] \u9500\u6BC1\u63D2\u4EF6 "${config2.package}" \u5931\u8D25:`, err instanceof Error ? err.message : err);
|
|
54208
|
+
}
|
|
54209
|
+
}
|
|
54210
|
+
}
|
|
54211
|
+
async disposePluginMap(pluginMap) {
|
|
54212
|
+
const allPlugins = [];
|
|
54213
|
+
for (const [, loadedPlugins] of pluginMap) {
|
|
54214
|
+
allPlugins.push(...loadedPlugins);
|
|
54215
|
+
}
|
|
54216
|
+
await this.disposePluginList(allPlugins);
|
|
54217
|
+
}
|
|
54218
|
+
}
|
|
54219
|
+
|
|
54084
54220
|
// src/proxy.ts
|
|
54085
54221
|
import { appendFile, readFile, unlink } from "fs/promises";
|
|
54086
54222
|
import { join as join8 } from "path";
|
|
54087
54223
|
import { tmpdir } from "os";
|
|
54224
|
+
|
|
54225
|
+
// src/plugin-engine.ts
|
|
54226
|
+
async function executeRequestPlugins(plugins, ctx, url2, headers, body) {
|
|
54227
|
+
let currentUrl = url2;
|
|
54228
|
+
let currentHeaders = headers;
|
|
54229
|
+
let currentBody = body;
|
|
54230
|
+
for (const plugin of plugins) {
|
|
54231
|
+
if (!plugin.onRequest)
|
|
54232
|
+
continue;
|
|
54233
|
+
try {
|
|
54234
|
+
const result = await plugin.onRequest({
|
|
54235
|
+
ctx,
|
|
54236
|
+
url: currentUrl,
|
|
54237
|
+
headers: currentHeaders,
|
|
54238
|
+
body: currentBody
|
|
54239
|
+
});
|
|
54240
|
+
if (result) {
|
|
54241
|
+
if (result.url !== undefined)
|
|
54242
|
+
currentUrl = result.url;
|
|
54243
|
+
if (result.headers !== undefined)
|
|
54244
|
+
currentHeaders = result.headers;
|
|
54245
|
+
if (result.body !== undefined)
|
|
54246
|
+
currentBody = result.body;
|
|
54247
|
+
}
|
|
54248
|
+
} catch (err) {
|
|
54249
|
+
const error48 = err instanceof Error ? err : new Error(String(err));
|
|
54250
|
+
try {
|
|
54251
|
+
await plugin.onError?.({ ctx, phase: "request", error: error48 });
|
|
54252
|
+
} catch {}
|
|
54253
|
+
}
|
|
54254
|
+
}
|
|
54255
|
+
return { url: currentUrl, headers: currentHeaders, body: currentBody };
|
|
54256
|
+
}
|
|
54257
|
+
async function executeJsonResponsePlugins(plugins, ctx, status, headers, body) {
|
|
54258
|
+
let currentStatus = status;
|
|
54259
|
+
let currentHeaders = headers;
|
|
54260
|
+
let currentBody = body;
|
|
54261
|
+
for (let i = plugins.length - 1;i >= 0; i--) {
|
|
54262
|
+
const plugin = plugins[i];
|
|
54263
|
+
if (!plugin.onResponse)
|
|
54264
|
+
continue;
|
|
54265
|
+
try {
|
|
54266
|
+
const result = await plugin.onResponse({
|
|
54267
|
+
ctx,
|
|
54268
|
+
status: currentStatus,
|
|
54269
|
+
headers: currentHeaders,
|
|
54270
|
+
body: currentBody
|
|
54271
|
+
});
|
|
54272
|
+
if (result) {
|
|
54273
|
+
if (result.status !== undefined)
|
|
54274
|
+
currentStatus = result.status;
|
|
54275
|
+
if (result.headers !== undefined)
|
|
54276
|
+
currentHeaders = result.headers;
|
|
54277
|
+
if (result.body !== undefined)
|
|
54278
|
+
currentBody = result.body;
|
|
54279
|
+
}
|
|
54280
|
+
} catch (err) {
|
|
54281
|
+
const error48 = err instanceof Error ? err : new Error(String(err));
|
|
54282
|
+
try {
|
|
54283
|
+
await plugin.onError?.({ ctx, phase: "response", error: error48 });
|
|
54284
|
+
} catch {}
|
|
54285
|
+
}
|
|
54286
|
+
}
|
|
54287
|
+
return { status: currentStatus, headers: currentHeaders, body: currentBody };
|
|
54288
|
+
}
|
|
54289
|
+
async function createSSEPluginTransform(plugins, ctx, status, headers) {
|
|
54290
|
+
let currentStatus = status;
|
|
54291
|
+
let currentHeaders = headers;
|
|
54292
|
+
const transforms = [];
|
|
54293
|
+
for (let i = plugins.length - 1;i >= 0; i--) {
|
|
54294
|
+
const plugin = plugins[i];
|
|
54295
|
+
if (!plugin.onSSEResponse)
|
|
54296
|
+
continue;
|
|
54297
|
+
try {
|
|
54298
|
+
const result = await plugin.onSSEResponse({
|
|
54299
|
+
ctx,
|
|
54300
|
+
status: currentStatus,
|
|
54301
|
+
headers: currentHeaders
|
|
54302
|
+
});
|
|
54303
|
+
if (result) {
|
|
54304
|
+
if (result.status !== undefined)
|
|
54305
|
+
currentStatus = result.status;
|
|
54306
|
+
if (result.headers !== undefined)
|
|
54307
|
+
currentHeaders = result.headers;
|
|
54308
|
+
if (result.transform)
|
|
54309
|
+
transforms.push(result.transform);
|
|
54310
|
+
}
|
|
54311
|
+
} catch (err) {
|
|
54312
|
+
const error48 = err instanceof Error ? err : new Error(String(err));
|
|
54313
|
+
try {
|
|
54314
|
+
await plugin.onError?.({ ctx, phase: "response", error: error48 });
|
|
54315
|
+
} catch {}
|
|
54316
|
+
}
|
|
54317
|
+
}
|
|
54318
|
+
if (transforms.length === 0) {
|
|
54319
|
+
return { status: currentStatus, headers: currentHeaders, transform: null };
|
|
54320
|
+
}
|
|
54321
|
+
if (transforms.length === 1) {
|
|
54322
|
+
return { status: currentStatus, headers: currentHeaders, transform: transforms[0] };
|
|
54323
|
+
}
|
|
54324
|
+
const entry = new TransformStream;
|
|
54325
|
+
let stream = entry.readable;
|
|
54326
|
+
for (const t of transforms) {
|
|
54327
|
+
stream = stream.pipeThrough(t);
|
|
54328
|
+
}
|
|
54329
|
+
return {
|
|
54330
|
+
status: currentStatus,
|
|
54331
|
+
headers: currentHeaders,
|
|
54332
|
+
transform: { writable: entry.writable, readable: stream }
|
|
54333
|
+
};
|
|
54334
|
+
}
|
|
54335
|
+
|
|
54336
|
+
// src/proxy.ts
|
|
54088
54337
|
var HOP_BY_HOP_HEADERS = new Set([
|
|
54089
54338
|
"connection",
|
|
54090
54339
|
"keep-alive",
|
|
@@ -54185,33 +54434,63 @@ async function flushTempCaptureToLogger(tempPath, requestId, dateStr, logger) {
|
|
|
54185
54434
|
}
|
|
54186
54435
|
}
|
|
54187
54436
|
async function proxyRequest(c2, options) {
|
|
54188
|
-
const { logMeta } = options;
|
|
54437
|
+
const { logMeta, plugins, pluginConfigs } = options;
|
|
54189
54438
|
const logger = getLogger();
|
|
54190
54439
|
const shouldLog = logger?.enabled ?? false;
|
|
54191
|
-
const
|
|
54440
|
+
const hasPlugins = plugins && plugins.length > 0;
|
|
54441
|
+
let targetUrl = options.targetUrl;
|
|
54442
|
+
let headers = buildUpstreamHeaders(c2.req.raw.headers, options.apiKey, options.authType);
|
|
54443
|
+
let bodyStr = options.body;
|
|
54444
|
+
const pluginLogOverrides = {};
|
|
54445
|
+
if (hasPlugins) {
|
|
54446
|
+
const bodyObj = JSON.parse(bodyStr);
|
|
54447
|
+
const ctx = {
|
|
54448
|
+
requestId: logMeta.requestId,
|
|
54449
|
+
provider: logMeta.provider,
|
|
54450
|
+
modelIn: logMeta.modelIn,
|
|
54451
|
+
modelOut: logMeta.modelOut,
|
|
54452
|
+
routeType: logMeta.routeType,
|
|
54453
|
+
isStream: logMeta.isStream
|
|
54454
|
+
};
|
|
54455
|
+
const result = await executeRequestPlugins(plugins, ctx, targetUrl, headers, bodyObj);
|
|
54456
|
+
if (pluginConfigs) {
|
|
54457
|
+
pluginLogOverrides.plugins_request = pluginConfigs;
|
|
54458
|
+
}
|
|
54459
|
+
if (result.url !== targetUrl) {
|
|
54460
|
+
targetUrl = result.url;
|
|
54461
|
+
pluginLogOverrides.request_url_after_plugins = targetUrl;
|
|
54462
|
+
}
|
|
54463
|
+
headers = result.headers;
|
|
54464
|
+
const newBodyStr = JSON.stringify(result.body);
|
|
54465
|
+
if (newBodyStr !== bodyStr) {
|
|
54466
|
+
bodyStr = newBodyStr;
|
|
54467
|
+
pluginLogOverrides.request_body_after_plugins = result.body;
|
|
54468
|
+
}
|
|
54469
|
+
}
|
|
54192
54470
|
const requestBody = shouldLog && logger?.bodyPolicy !== "off" ? JSON.parse(options.body) : undefined;
|
|
54193
54471
|
const proxy = options.proxy?.trim() ? options.proxy.trim() : undefined;
|
|
54194
54472
|
let upstreamRes;
|
|
54195
54473
|
try {
|
|
54196
|
-
upstreamRes = await fetch(
|
|
54474
|
+
upstreamRes = await fetch(targetUrl, {
|
|
54197
54475
|
method: c2.req.method,
|
|
54198
54476
|
headers,
|
|
54199
|
-
body:
|
|
54477
|
+
body: bodyStr,
|
|
54200
54478
|
...proxy ? { proxy } : {},
|
|
54201
54479
|
decompress: true
|
|
54202
54480
|
});
|
|
54203
54481
|
} catch (err) {
|
|
54204
54482
|
if (shouldLog) {
|
|
54205
|
-
logger?.writeEvent(buildLogEvent(logMeta,
|
|
54483
|
+
logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), {
|
|
54206
54484
|
error_type: err instanceof Error ? err.constructor.name : "UnknownError",
|
|
54207
54485
|
error_message: err instanceof Error ? err.message : String(err),
|
|
54208
|
-
...requestBody !== undefined && { request_body: requestBody }
|
|
54486
|
+
...requestBody !== undefined && { request_body: requestBody },
|
|
54487
|
+
...pluginLogOverrides
|
|
54209
54488
|
}));
|
|
54210
54489
|
}
|
|
54211
54490
|
throw err;
|
|
54212
54491
|
}
|
|
54213
54492
|
const responseHeaders = buildResponseHeaders(upstreamRes.headers);
|
|
54214
|
-
if (!shouldLog) {
|
|
54493
|
+
if (!shouldLog && !hasPlugins) {
|
|
54215
54494
|
return new Response(upstreamRes.body, {
|
|
54216
54495
|
status: upstreamRes.status,
|
|
54217
54496
|
headers: responseHeaders
|
|
@@ -54221,6 +54500,33 @@ async function proxyRequest(c2, options) {
|
|
|
54221
54500
|
const providerRequestId = extractProviderRequestId(upstreamRes.headers);
|
|
54222
54501
|
const dateStr = new Date(logMeta.tsStart).toISOString().slice(0, 10);
|
|
54223
54502
|
if (logMeta.isStream && upstreamRes.body) {
|
|
54503
|
+
let sseStatus = upstreamRes.status;
|
|
54504
|
+
let sseHeaders = responseHeaders;
|
|
54505
|
+
let sseTransform = null;
|
|
54506
|
+
if (hasPlugins) {
|
|
54507
|
+
const ctx = {
|
|
54508
|
+
requestId: logMeta.requestId,
|
|
54509
|
+
provider: logMeta.provider,
|
|
54510
|
+
modelIn: logMeta.modelIn,
|
|
54511
|
+
modelOut: logMeta.modelOut,
|
|
54512
|
+
routeType: logMeta.routeType,
|
|
54513
|
+
isStream: logMeta.isStream
|
|
54514
|
+
};
|
|
54515
|
+
const sseResult = await createSSEPluginTransform(plugins, ctx, upstreamRes.status, responseHeaders);
|
|
54516
|
+
sseStatus = sseResult.status;
|
|
54517
|
+
sseHeaders = sseResult.headers;
|
|
54518
|
+
sseTransform = sseResult.transform;
|
|
54519
|
+
if (pluginConfigs) {
|
|
54520
|
+
pluginLogOverrides.plugins_response = pluginConfigs;
|
|
54521
|
+
}
|
|
54522
|
+
}
|
|
54523
|
+
if (!shouldLog) {
|
|
54524
|
+
const outputBody2 = sseTransform ? upstreamRes.body.pipeThrough(sseTransform) : upstreamRes.body;
|
|
54525
|
+
return new Response(outputBody2, {
|
|
54526
|
+
status: sseStatus,
|
|
54527
|
+
headers: sseHeaders
|
|
54528
|
+
});
|
|
54529
|
+
}
|
|
54224
54530
|
const [clientStream, logStream] = upstreamRes.body.tee();
|
|
54225
54531
|
(async () => {
|
|
54226
54532
|
const tempPath = createTempStreamCapturePath(logMeta.requestId);
|
|
@@ -54242,30 +54548,61 @@ async function proxyRequest(c2, options) {
|
|
|
54242
54548
|
});
|
|
54243
54549
|
console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\u5904\u7406\u5931\u8D25:", err);
|
|
54244
54550
|
} finally {
|
|
54245
|
-
logger?.writeEvent(buildLogEvent(logMeta,
|
|
54246
|
-
upstream_status:
|
|
54551
|
+
logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), {
|
|
54552
|
+
upstream_status: sseStatus,
|
|
54247
54553
|
content_type_res: contentTypeRes,
|
|
54248
|
-
response_headers:
|
|
54554
|
+
response_headers: sseHeaders,
|
|
54249
54555
|
stream_bytes: streamBytes,
|
|
54250
54556
|
provider_request_id: providerRequestId,
|
|
54251
54557
|
...streamFile != null && { stream_file: streamFile },
|
|
54252
|
-
...requestBody !== undefined && { request_body: requestBody }
|
|
54558
|
+
...requestBody !== undefined && { request_body: requestBody },
|
|
54559
|
+
...pluginLogOverrides
|
|
54253
54560
|
}));
|
|
54254
54561
|
}
|
|
54255
54562
|
})();
|
|
54256
|
-
|
|
54257
|
-
|
|
54258
|
-
|
|
54563
|
+
const outputBody = sseTransform ? clientStream.pipeThrough(sseTransform) : clientStream;
|
|
54564
|
+
return new Response(outputBody, {
|
|
54565
|
+
status: sseStatus,
|
|
54566
|
+
headers: sseHeaders
|
|
54567
|
+
});
|
|
54568
|
+
}
|
|
54569
|
+
let responseText = await upstreamRes.text();
|
|
54570
|
+
let responseStatus = upstreamRes.status;
|
|
54571
|
+
let finalResponseHeaders = responseHeaders;
|
|
54572
|
+
if (hasPlugins) {
|
|
54573
|
+
const ctx = {
|
|
54574
|
+
requestId: logMeta.requestId,
|
|
54575
|
+
provider: logMeta.provider,
|
|
54576
|
+
modelIn: logMeta.modelIn,
|
|
54577
|
+
modelOut: logMeta.modelOut,
|
|
54578
|
+
routeType: logMeta.routeType,
|
|
54579
|
+
isStream: logMeta.isStream
|
|
54580
|
+
};
|
|
54581
|
+
const result = await executeJsonResponsePlugins(plugins, ctx, upstreamRes.status, responseHeaders, responseText);
|
|
54582
|
+
if (pluginConfigs) {
|
|
54583
|
+
pluginLogOverrides.plugins_response = pluginConfigs;
|
|
54584
|
+
}
|
|
54585
|
+
if (result.body !== responseText) {
|
|
54586
|
+
pluginLogOverrides.response_body_after_plugins = result.body;
|
|
54587
|
+
}
|
|
54588
|
+
responseStatus = result.status;
|
|
54589
|
+
finalResponseHeaders = result.headers;
|
|
54590
|
+
responseText = result.body;
|
|
54591
|
+
}
|
|
54592
|
+
if (!shouldLog) {
|
|
54593
|
+
return new Response(responseText, {
|
|
54594
|
+
status: responseStatus,
|
|
54595
|
+
headers: finalResponseHeaders
|
|
54259
54596
|
});
|
|
54260
54597
|
}
|
|
54261
|
-
const responseText = await upstreamRes.text();
|
|
54262
54598
|
const responseBytes = Buffer.byteLength(responseText, "utf-8");
|
|
54263
54599
|
const eventOverrides = {
|
|
54264
54600
|
upstream_status: upstreamRes.status,
|
|
54265
54601
|
content_type_res: contentTypeRes,
|
|
54266
|
-
response_headers:
|
|
54602
|
+
response_headers: finalResponseHeaders,
|
|
54267
54603
|
response_bytes: responseBytes,
|
|
54268
|
-
provider_request_id: providerRequestId
|
|
54604
|
+
provider_request_id: providerRequestId,
|
|
54605
|
+
...pluginLogOverrides
|
|
54269
54606
|
};
|
|
54270
54607
|
if (requestBody !== undefined) {
|
|
54271
54608
|
eventOverrides.request_body = requestBody;
|
|
@@ -54273,10 +54610,10 @@ async function proxyRequest(c2, options) {
|
|
|
54273
54610
|
if (logger?.bodyPolicy !== "off") {
|
|
54274
54611
|
eventOverrides.response_body = responseText;
|
|
54275
54612
|
}
|
|
54276
|
-
logger?.writeEvent(buildLogEvent(logMeta,
|
|
54613
|
+
logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), eventOverrides));
|
|
54277
54614
|
return new Response(responseText, {
|
|
54278
|
-
status:
|
|
54279
|
-
headers:
|
|
54615
|
+
status: responseStatus,
|
|
54616
|
+
headers: finalResponseHeaders
|
|
54280
54617
|
});
|
|
54281
54618
|
}
|
|
54282
54619
|
|
|
@@ -54291,7 +54628,7 @@ function resolveRoute(modelMap, incomingModel) {
|
|
|
54291
54628
|
return;
|
|
54292
54629
|
}
|
|
54293
54630
|
function createModelRoutingHandler(options) {
|
|
54294
|
-
const { routeType, store, authType, buildTargetUrl } = options;
|
|
54631
|
+
const { routeType, store, authType, buildTargetUrl, pluginManager } = options;
|
|
54295
54632
|
return async (c2) => {
|
|
54296
54633
|
const config2 = store.get();
|
|
54297
54634
|
const modelMap = config2.routes[routeType];
|
|
@@ -54333,49 +54670,60 @@ function createModelRoutingHandler(options) {
|
|
|
54333
54670
|
requestBytes: Buffer.byteLength(body, "utf-8"),
|
|
54334
54671
|
requestHeaders: collectHeaders(c2.req.raw.headers)
|
|
54335
54672
|
};
|
|
54673
|
+
const plugins = pluginManager?.getPlugins(target.provider) ?? [];
|
|
54674
|
+
const pluginConfigs = pluginManager?.getLoadedPlugins(target.provider) ?? [];
|
|
54336
54675
|
return proxyRequest(c2, {
|
|
54337
54676
|
targetUrl,
|
|
54338
54677
|
apiKey: provider.apiKey,
|
|
54339
54678
|
proxy: provider.proxy,
|
|
54340
54679
|
authType,
|
|
54341
54680
|
body,
|
|
54342
|
-
logMeta
|
|
54681
|
+
logMeta,
|
|
54682
|
+
plugins: plugins.length > 0 ? plugins : undefined,
|
|
54683
|
+
pluginConfigs: pluginConfigs.length > 0 ? pluginConfigs.map((lp) => ({
|
|
54684
|
+
name: lp.definition.name,
|
|
54685
|
+
package: lp.config.package,
|
|
54686
|
+
params: lp.config.params ?? {}
|
|
54687
|
+
})) : undefined
|
|
54343
54688
|
});
|
|
54344
54689
|
};
|
|
54345
54690
|
}
|
|
54346
54691
|
|
|
54347
54692
|
// src/routes/anthropic-messages.ts
|
|
54348
|
-
function createAnthropicMessagesRoutes(routeType, store) {
|
|
54693
|
+
function createAnthropicMessagesRoutes(routeType, store, pluginManager) {
|
|
54349
54694
|
const routes = new Hono2;
|
|
54350
54695
|
routes.post("/v1/messages", createModelRoutingHandler({
|
|
54351
54696
|
routeType,
|
|
54352
54697
|
store,
|
|
54353
54698
|
authType: "x-api-key",
|
|
54354
|
-
buildTargetUrl: (base) => `${base}/v1/messages
|
|
54699
|
+
buildTargetUrl: (base) => `${base}/v1/messages`,
|
|
54700
|
+
pluginManager
|
|
54355
54701
|
}));
|
|
54356
54702
|
return routes;
|
|
54357
54703
|
}
|
|
54358
54704
|
|
|
54359
54705
|
// src/routes/openai-completions.ts
|
|
54360
|
-
function createOpenaiCompletionsRoutes(routeType, store) {
|
|
54706
|
+
function createOpenaiCompletionsRoutes(routeType, store, pluginManager) {
|
|
54361
54707
|
const routes = new Hono2;
|
|
54362
54708
|
routes.post("/v1/chat/completions", createModelRoutingHandler({
|
|
54363
54709
|
routeType,
|
|
54364
54710
|
store,
|
|
54365
54711
|
authType: "bearer",
|
|
54366
|
-
buildTargetUrl: (base) => `${base}/v1/chat/completions
|
|
54712
|
+
buildTargetUrl: (base) => `${base}/v1/chat/completions`,
|
|
54713
|
+
pluginManager
|
|
54367
54714
|
}));
|
|
54368
54715
|
return routes;
|
|
54369
54716
|
}
|
|
54370
54717
|
|
|
54371
54718
|
// src/routes/openai-responses.ts
|
|
54372
|
-
function createOpenaiResponsesRoutes(routeType, store) {
|
|
54719
|
+
function createOpenaiResponsesRoutes(routeType, store, pluginManager) {
|
|
54373
54720
|
const routes = new Hono2;
|
|
54374
54721
|
routes.post("/v1/responses", createModelRoutingHandler({
|
|
54375
54722
|
routeType,
|
|
54376
54723
|
store,
|
|
54377
54724
|
authType: "bearer",
|
|
54378
|
-
buildTargetUrl: (base) => `${base}/v1/responses
|
|
54725
|
+
buildTargetUrl: (base) => `${base}/v1/responses`,
|
|
54726
|
+
pluginManager
|
|
54379
54727
|
}));
|
|
54380
54728
|
return routes;
|
|
54381
54729
|
}
|
|
@@ -54536,7 +54884,7 @@ function createChatProxyModel(providerName, providerConfig, model) {
|
|
|
54536
54884
|
throw new Error(`\u6682\u4E0D\u652F\u6301\u7684 provider \u7C7B\u578B: ${providerConfig.type}`);
|
|
54537
54885
|
}
|
|
54538
54886
|
}
|
|
54539
|
-
function createAdminApiRoutes(store, registerCleanup) {
|
|
54887
|
+
function createAdminApiRoutes(store, pluginManager, registerCleanup) {
|
|
54540
54888
|
const api2 = new Hono2;
|
|
54541
54889
|
const cryptoSessions = new Map;
|
|
54542
54890
|
const CRYPTO_SESSION_TTL_MS = 2 * 60 * 1000;
|
|
@@ -54647,18 +54995,22 @@ function createAdminApiRoutes(store, registerCleanup) {
|
|
|
54647
54995
|
session.dispose();
|
|
54648
54996
|
}
|
|
54649
54997
|
});
|
|
54650
|
-
api2.post("/config/apply", (_c) => {
|
|
54998
|
+
api2.post("/config/apply", async (_c) => {
|
|
54651
54999
|
try {
|
|
54652
55000
|
const config2 = store.reload();
|
|
54653
55001
|
if (config2.log) {
|
|
54654
55002
|
const logBaseDir = resolveLogBaseDir(config2.log);
|
|
54655
55003
|
initLogger(logBaseDir, config2.log);
|
|
54656
55004
|
}
|
|
55005
|
+
const pluginResult = await pluginManager.reloadAll(config2.providers);
|
|
54657
55006
|
return _c.json({
|
|
54658
55007
|
ok: true,
|
|
54659
55008
|
summary: {
|
|
54660
55009
|
providers: Object.keys(config2.providers).length,
|
|
54661
55010
|
routes: Object.keys(config2.routes).length
|
|
55011
|
+
},
|
|
55012
|
+
...pluginResult.failures.length > 0 && {
|
|
55013
|
+
pluginWarnings: pluginResult.failures
|
|
54662
55014
|
}
|
|
54663
55015
|
});
|
|
54664
55016
|
} catch (err) {
|
|
@@ -55083,7 +55435,7 @@ async function proxyAdminToDevServer(c2, origin) {
|
|
|
55083
55435
|
headers: buildProxyResponseHeaders(upstreamRes.headers)
|
|
55084
55436
|
});
|
|
55085
55437
|
}
|
|
55086
|
-
function createApp(store, options) {
|
|
55438
|
+
async function createApp(store, options) {
|
|
55087
55439
|
const config2 = store.get();
|
|
55088
55440
|
console.log(`\u5DF2\u52A0\u8F7D\u914D\u7F6E: ${store.getPath()}`);
|
|
55089
55441
|
if (config2.log) {
|
|
@@ -55094,15 +55446,24 @@ function createApp(store, options) {
|
|
|
55094
55446
|
}
|
|
55095
55447
|
const stopLogStorageTask = startLogStorageBackgroundTask(config2.log);
|
|
55096
55448
|
options?.registerCleanup?.(stopLogStorageTask);
|
|
55449
|
+
const configDir = dirname3(resolve6(store.getPath()));
|
|
55450
|
+
const pluginManager = new PluginManager(configDir);
|
|
55451
|
+
const reloadResult = await pluginManager.reloadAll(config2.providers);
|
|
55452
|
+
if (!reloadResult.ok) {
|
|
55453
|
+
console.warn(`[plugin] \u63D2\u4EF6\u521D\u59CB\u5316\u5B8C\u6210\uFF0C\u4F46\u6709 ${reloadResult.failures.length} \u4E2A\u63D2\u4EF6\u52A0\u8F7D\u5931\u8D25`);
|
|
55454
|
+
}
|
|
55455
|
+
options?.registerCleanup?.(() => {
|
|
55456
|
+
pluginManager.disposeAll().catch(() => {});
|
|
55457
|
+
});
|
|
55097
55458
|
printIntegrationGuide(config2);
|
|
55098
55459
|
const app = new Hono2;
|
|
55099
55460
|
app.get("/", (c2) => c2.text("local-router is running"));
|
|
55100
55461
|
for (const [routeType, entry] of Object.entries(ROUTE_REGISTRY)) {
|
|
55101
|
-
const subApp = entry.create(routeType, store);
|
|
55462
|
+
const subApp = entry.create(routeType, store, pluginManager);
|
|
55102
55463
|
app.route(entry.mountPrefix, subApp);
|
|
55103
55464
|
console.log(`\u5DF2\u6CE8\u518C\u8DEF\u7531: ${routeType} -> ${entry.mountPrefix}`);
|
|
55104
55465
|
}
|
|
55105
|
-
app.route("/api", createAdminApiRoutes(store, options?.registerCleanup));
|
|
55466
|
+
app.route("/api", createAdminApiRoutes(store, pluginManager, options?.registerCleanup));
|
|
55106
55467
|
console.log("\u5DF2\u6CE8\u518C\u7BA1\u7406 API: /api");
|
|
55107
55468
|
app.get("/api/docs", middleware({ url: "/api/openapi.json" }));
|
|
55108
55469
|
app.get("/api/openapi.json", (c2) => c2.json(openAPISpec));
|
|
@@ -55131,10 +55492,10 @@ function createApp(store, options) {
|
|
|
55131
55492
|
}
|
|
55132
55493
|
return app;
|
|
55133
55494
|
}
|
|
55134
|
-
function createAppRuntimeFromConfigPath(configPath) {
|
|
55495
|
+
async function createAppRuntimeFromConfigPath(configPath) {
|
|
55135
55496
|
const store = new ConfigStore(configPath);
|
|
55136
55497
|
const cleanups = [];
|
|
55137
|
-
const app = createApp(store, {
|
|
55498
|
+
const app = await createApp(store, {
|
|
55138
55499
|
registerCleanup: (cleanup) => {
|
|
55139
55500
|
cleanups.push(cleanup);
|
|
55140
55501
|
}
|
|
@@ -55167,8 +55528,8 @@ function resolveIdleTimeoutSeconds(explicit) {
|
|
|
55167
55528
|
}
|
|
55168
55529
|
return DEFAULT_IDLE_TIMEOUT_SECONDS;
|
|
55169
55530
|
}
|
|
55170
|
-
function startServer(options) {
|
|
55171
|
-
const runtime = createAppRuntimeFromConfigPath(options.configPath);
|
|
55531
|
+
async function startServer(options) {
|
|
55532
|
+
const runtime = await createAppRuntimeFromConfigPath(options.configPath);
|
|
55172
55533
|
const idleTimeout = resolveIdleTimeoutSeconds(options.idleTimeoutSeconds);
|
|
55173
55534
|
const server = Bun.serve({
|
|
55174
55535
|
fetch: runtime.app.fetch,
|
|
@@ -55193,7 +55554,7 @@ function startServer(options) {
|
|
|
55193
55554
|
// src/cli/runtime.ts
|
|
55194
55555
|
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
55195
55556
|
import { homedir as homedir2 } from "os";
|
|
55196
|
-
import { join as join9, resolve as
|
|
55557
|
+
import { join as join9, resolve as resolve7 } from "path";
|
|
55197
55558
|
function getRuntimeDirs() {
|
|
55198
55559
|
const root2 = join9(homedir2(), ".local-router");
|
|
55199
55560
|
return {
|
|
@@ -55240,7 +55601,7 @@ function clearRuntimeFiles() {
|
|
|
55240
55601
|
rmSync(files.state, { force: true });
|
|
55241
55602
|
}
|
|
55242
55603
|
function resolveConfigArgPath(pathValue) {
|
|
55243
|
-
return
|
|
55604
|
+
return resolve7(pathValue);
|
|
55244
55605
|
}
|
|
55245
55606
|
|
|
55246
55607
|
// src/cli/process.ts
|
|
@@ -55319,7 +55680,7 @@ async function runServerProcess(opts) {
|
|
|
55319
55680
|
const idleTimeoutSeconds = opts.idleTimeoutSeconds ?? Number.parseInt(process.env.LOCAL_ROUTER_IDLE_TIMEOUT ?? "", 10);
|
|
55320
55681
|
let running;
|
|
55321
55682
|
try {
|
|
55322
|
-
running = startServer({
|
|
55683
|
+
running = await startServer({
|
|
55323
55684
|
configPath: ensured.path,
|
|
55324
55685
|
host,
|
|
55325
55686
|
port,
|
|
@@ -55471,7 +55832,7 @@ function readLogDelta(filePath, offset) {
|
|
|
55471
55832
|
// src/cli/config-command.ts
|
|
55472
55833
|
import { createInterface as createInterface4 } from "readline/promises";
|
|
55473
55834
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
55474
|
-
import { dirname as
|
|
55835
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
55475
55836
|
import { parseArgs as parseArgs2 } from "util";
|
|
55476
55837
|
function readConfig(configArg) {
|
|
55477
55838
|
const path = resolveConfigPath(configArg);
|
|
@@ -55479,7 +55840,7 @@ function readConfig(configArg) {
|
|
|
55479
55840
|
}
|
|
55480
55841
|
function saveConfig(path, config2) {
|
|
55481
55842
|
validateConfigOrThrow(config2);
|
|
55482
|
-
const backupDir = join10(
|
|
55843
|
+
const backupDir = join10(dirname4(path), ".backups");
|
|
55483
55844
|
mkdirSync4(backupDir, { recursive: true });
|
|
55484
55845
|
const backupPath = join10(backupDir, `config-${Date.now()}.json5`);
|
|
55485
55846
|
writeFileSync5(backupPath, readFileSync7(path, "utf-8"), "utf-8");
|