@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/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 = resolve5.call(this, root2, ref);
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 resolve5(root2, ref) {
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 resolve5(baseURI, relativeURI, options) {
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: resolve5,
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 headers = buildUpstreamHeaders(c2.req.raw.headers, options.apiKey, options.authType);
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(options.targetUrl, {
54474
+ upstreamRes = await fetch(targetUrl, {
54197
54475
  method: c2.req.method,
54198
54476
  headers,
54199
- body: options.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, options.targetUrl, proxy, Date.now(), {
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, options.targetUrl, proxy, Date.now(), {
54246
- upstream_status: upstreamRes.status,
54551
+ logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), {
54552
+ upstream_status: sseStatus,
54247
54553
  content_type_res: contentTypeRes,
54248
- response_headers: responseHeaders,
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
- return new Response(clientStream, {
54257
- status: upstreamRes.status,
54258
- headers: responseHeaders
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: responseHeaders,
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, options.targetUrl, proxy, Date.now(), eventOverrides));
54613
+ logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), eventOverrides));
54277
54614
  return new Response(responseText, {
54278
- status: upstreamRes.status,
54279
- headers: responseHeaders
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 resolve5 } from "path";
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 resolve5(pathValue);
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 dirname2, join as join10 } from "path";
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(dirname2(path), ".backups");
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");