@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/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 = resolve5.call(this, root2, ref);
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 resolve5(root2, ref) {
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 resolve5(baseURI, relativeURI, options) {
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: resolve5,
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 join8 } from "path";
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 join8(tmpdir(), `local-router-stream-${requestId}-${Date.now()}.sse.raw`);
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 headers = buildUpstreamHeaders(c2.req.raw.headers, options.apiKey, options.authType);
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(options.targetUrl, {
54510
+ upstreamRes = await fetch(targetUrl, {
54181
54511
  method: c2.req.method,
54182
54512
  headers,
54183
- body: options.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, options.targetUrl, proxy, Date.now(), {
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, options.targetUrl, proxy, Date.now(), {
54230
- upstream_status: upstreamRes.status,
54587
+ logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), {
54588
+ upstream_status: sseStatus,
54231
54589
  content_type_res: contentTypeRes,
54232
- response_headers: responseHeaders,
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
- return new Response(clientStream, {
54241
- status: upstreamRes.status,
54242
- headers: responseHeaders
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: responseHeaders,
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, options.targetUrl, proxy, Date.now(), eventOverrides));
54649
+ logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), eventOverrides));
54261
54650
  return new Response(responseText, {
54262
- status: upstreamRes.status,
54263
- headers: responseHeaders
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