@lakphy/local-router 0.5.0 → 0.5.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/README.md +83 -0
- package/dist/cli.js +7369 -3743
- package/dist/entry.js +153 -110
- package/package.json +1 -1
package/dist/entry.js
CHANGED
|
@@ -53899,7 +53899,15 @@ function subscribeLogEvents(subscriber) {
|
|
|
53899
53899
|
}
|
|
53900
53900
|
|
|
53901
53901
|
// src/logger.ts
|
|
53902
|
-
import {
|
|
53902
|
+
import {
|
|
53903
|
+
appendFileSync,
|
|
53904
|
+
closeSync as closeSync2,
|
|
53905
|
+
existsSync as existsSync7,
|
|
53906
|
+
mkdirSync as mkdirSync3,
|
|
53907
|
+
openSync as openSync2,
|
|
53908
|
+
statSync as statSync2,
|
|
53909
|
+
writeSync
|
|
53910
|
+
} from "fs";
|
|
53903
53911
|
import { join as join8 } from "path";
|
|
53904
53912
|
class Logger {
|
|
53905
53913
|
baseDir;
|
|
@@ -53963,22 +53971,73 @@ class Logger {
|
|
|
53963
53971
|
console.error("[logger] \u4E8B\u4EF6\u65E5\u5FD7\u5199\u5165\u5931\u8D25:", err);
|
|
53964
53972
|
}
|
|
53965
53973
|
}
|
|
53966
|
-
|
|
53967
|
-
if (!this._enabled || !this._streamsEnabled)
|
|
53968
|
-
return
|
|
53974
|
+
openStreamCapture(requestId, dateStr) {
|
|
53975
|
+
if (!this._enabled || !this._streamsEnabled) {
|
|
53976
|
+
return makeNoopStreamCaptureHandle();
|
|
53977
|
+
}
|
|
53978
|
+
const maxStreamBytes = this.maxStreamBytes;
|
|
53979
|
+
const truncationMarker = Buffer.from(`
|
|
53980
|
+
[TRUNCATED]`);
|
|
53981
|
+
let filePath;
|
|
53982
|
+
let fd;
|
|
53969
53983
|
try {
|
|
53970
53984
|
const dir = this.ensureStreamDateDir(dateStr);
|
|
53971
|
-
|
|
53972
|
-
|
|
53973
|
-
[TRUNCATED]` : content;
|
|
53974
|
-
writeFileSync3(filePath, toWrite);
|
|
53975
|
-
return filePath;
|
|
53985
|
+
filePath = join8(dir, `${requestId}.sse.raw`);
|
|
53986
|
+
fd = openSync2(filePath, "a");
|
|
53976
53987
|
} catch (err) {
|
|
53977
|
-
console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\
|
|
53978
|
-
return
|
|
53988
|
+
console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\u6253\u5F00\u5931\u8D25:", err);
|
|
53989
|
+
return makeNoopStreamCaptureHandle();
|
|
53979
53990
|
}
|
|
53991
|
+
let bytes = 0;
|
|
53992
|
+
let truncated = false;
|
|
53993
|
+
let finalized = false;
|
|
53994
|
+
return {
|
|
53995
|
+
filePath,
|
|
53996
|
+
write(chunk) {
|
|
53997
|
+
if (finalized || truncated || fd == null)
|
|
53998
|
+
return;
|
|
53999
|
+
try {
|
|
54000
|
+
if (bytes + chunk.byteLength > maxStreamBytes) {
|
|
54001
|
+
const remaining = Math.max(0, maxStreamBytes - bytes);
|
|
54002
|
+
if (remaining > 0) {
|
|
54003
|
+
writeSync(fd, chunk.subarray(0, remaining));
|
|
54004
|
+
bytes += remaining;
|
|
54005
|
+
}
|
|
54006
|
+
writeSync(fd, truncationMarker);
|
|
54007
|
+
truncated = true;
|
|
54008
|
+
return;
|
|
54009
|
+
}
|
|
54010
|
+
writeSync(fd, chunk);
|
|
54011
|
+
bytes += chunk.byteLength;
|
|
54012
|
+
} catch (err) {
|
|
54013
|
+
console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\u5199\u5165\u5931\u8D25:", err);
|
|
54014
|
+
truncated = true;
|
|
54015
|
+
}
|
|
54016
|
+
},
|
|
54017
|
+
finalize() {
|
|
54018
|
+
if (finalized)
|
|
54019
|
+
return { bytesWritten: bytes, truncated, filePath };
|
|
54020
|
+
finalized = true;
|
|
54021
|
+
if (fd != null) {
|
|
54022
|
+
try {
|
|
54023
|
+
closeSync2(fd);
|
|
54024
|
+
} catch {}
|
|
54025
|
+
fd = null;
|
|
54026
|
+
}
|
|
54027
|
+
return { bytesWritten: bytes, truncated, filePath };
|
|
54028
|
+
}
|
|
54029
|
+
};
|
|
53980
54030
|
}
|
|
53981
54031
|
}
|
|
54032
|
+
function makeNoopStreamCaptureHandle() {
|
|
54033
|
+
return {
|
|
54034
|
+
filePath: null,
|
|
54035
|
+
write() {},
|
|
54036
|
+
finalize() {
|
|
54037
|
+
return { bytesWritten: 0, truncated: false, filePath: null };
|
|
54038
|
+
}
|
|
54039
|
+
};
|
|
54040
|
+
}
|
|
53982
54041
|
var instance = null;
|
|
53983
54042
|
function initLogger(baseDir, config2) {
|
|
53984
54043
|
instance = new Logger(baseDir, config2);
|
|
@@ -55370,11 +55429,6 @@ class PluginManager {
|
|
|
55370
55429
|
}
|
|
55371
55430
|
}
|
|
55372
55431
|
|
|
55373
|
-
// src/proxy.ts
|
|
55374
|
-
import { appendFile, readFile, unlink } from "fs/promises";
|
|
55375
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
55376
|
-
import { join as join10 } from "path";
|
|
55377
|
-
|
|
55378
55432
|
// src/plugin-engine.ts
|
|
55379
55433
|
async function executeRequestPlugins(plugins, ctx, url2, headers, body) {
|
|
55380
55434
|
let currentUrl = url2;
|
|
@@ -55564,28 +55618,6 @@ function buildLogEvent(logMeta, targetUrl, proxyUrl, tsEnd, overrides) {
|
|
|
55564
55618
|
...overrides
|
|
55565
55619
|
};
|
|
55566
55620
|
}
|
|
55567
|
-
function createTempStreamCapturePath(requestId) {
|
|
55568
|
-
return join10(tmpdir2(), `local-router-stream-${requestId}-${Date.now()}.sse.raw`);
|
|
55569
|
-
}
|
|
55570
|
-
async function appendTempStreamCapture(filePath, chunk) {
|
|
55571
|
-
await appendFile(filePath, chunk);
|
|
55572
|
-
}
|
|
55573
|
-
async function flushTempCaptureToLogger(tempPath, requestId, dateStr, logger) {
|
|
55574
|
-
if (!logger)
|
|
55575
|
-
return null;
|
|
55576
|
-
try {
|
|
55577
|
-
const text2 = await readFile(tempPath, "utf-8").catch((err) => {
|
|
55578
|
-
if (err.code === "ENOENT")
|
|
55579
|
-
return "";
|
|
55580
|
-
throw err;
|
|
55581
|
-
});
|
|
55582
|
-
return logger.writeStreamFile(requestId, dateStr, text2);
|
|
55583
|
-
} finally {
|
|
55584
|
-
await unlink(tempPath).catch(() => {
|
|
55585
|
-
return;
|
|
55586
|
-
});
|
|
55587
|
-
}
|
|
55588
|
-
}
|
|
55589
55621
|
async function proxyRequest(c2, options) {
|
|
55590
55622
|
const { logMeta, plugins, pluginConfigs } = options;
|
|
55591
55623
|
const logger = getLogger();
|
|
@@ -55593,19 +55625,20 @@ async function proxyRequest(c2, options) {
|
|
|
55593
55625
|
const hasPlugins = plugins && plugins.length > 0;
|
|
55594
55626
|
let targetUrl = options.targetUrl;
|
|
55595
55627
|
let headers = buildUpstreamHeaders(c2.req.raw.headers, options.apiKey, options.authType);
|
|
55596
|
-
let
|
|
55628
|
+
let currentBody = options.body;
|
|
55629
|
+
const pluginCtx = Object.freeze({
|
|
55630
|
+
requestId: logMeta.requestId,
|
|
55631
|
+
provider: logMeta.provider,
|
|
55632
|
+
modelIn: logMeta.modelIn,
|
|
55633
|
+
modelOut: logMeta.modelOut,
|
|
55634
|
+
routeType: logMeta.routeType,
|
|
55635
|
+
isStream: logMeta.isStream
|
|
55636
|
+
});
|
|
55637
|
+
const wantsBodyLog = shouldLog && logger?.bodyPolicy !== "off";
|
|
55638
|
+
const requestBodySnapshot = wantsBodyLog ? JSON.parse(JSON.stringify(options.body)) : undefined;
|
|
55597
55639
|
const pluginLogOverrides = {};
|
|
55598
55640
|
if (hasPlugins) {
|
|
55599
|
-
const
|
|
55600
|
-
const ctx = {
|
|
55601
|
-
requestId: logMeta.requestId,
|
|
55602
|
-
provider: logMeta.provider,
|
|
55603
|
-
modelIn: logMeta.modelIn,
|
|
55604
|
-
modelOut: logMeta.modelOut,
|
|
55605
|
-
routeType: logMeta.routeType,
|
|
55606
|
-
isStream: logMeta.isStream
|
|
55607
|
-
};
|
|
55608
|
-
const result = await executeRequestPlugins(plugins, ctx, targetUrl, headers, bodyObj);
|
|
55641
|
+
const result = await executeRequestPlugins(plugins, pluginCtx, targetUrl, headers, currentBody);
|
|
55609
55642
|
if (pluginConfigs) {
|
|
55610
55643
|
pluginLogOverrides.plugins_request = pluginConfigs;
|
|
55611
55644
|
}
|
|
@@ -55614,20 +55647,20 @@ async function proxyRequest(c2, options) {
|
|
|
55614
55647
|
pluginLogOverrides.request_url_after_plugins = targetUrl;
|
|
55615
55648
|
}
|
|
55616
55649
|
headers = result.headers;
|
|
55617
|
-
|
|
55618
|
-
|
|
55619
|
-
bodyStr = newBodyStr;
|
|
55650
|
+
if (result.body !== currentBody) {
|
|
55651
|
+
currentBody = result.body;
|
|
55620
55652
|
pluginLogOverrides.request_body_after_plugins = result.body;
|
|
55621
55653
|
}
|
|
55622
55654
|
}
|
|
55623
|
-
const
|
|
55655
|
+
const wireBody = JSON.stringify(currentBody);
|
|
55656
|
+
const requestBody = requestBodySnapshot;
|
|
55624
55657
|
const proxy = options.proxy?.trim() ? options.proxy.trim() : undefined;
|
|
55625
55658
|
let upstreamRes;
|
|
55626
55659
|
try {
|
|
55627
55660
|
upstreamRes = await fetch(targetUrl, {
|
|
55628
55661
|
method: c2.req.method,
|
|
55629
55662
|
headers,
|
|
55630
|
-
body:
|
|
55663
|
+
body: wireBody,
|
|
55631
55664
|
...proxy ? { proxy } : {},
|
|
55632
55665
|
decompress: true
|
|
55633
55666
|
});
|
|
@@ -55657,15 +55690,7 @@ async function proxyRequest(c2, options) {
|
|
|
55657
55690
|
let sseHeaders = responseHeaders;
|
|
55658
55691
|
let sseTransform = null;
|
|
55659
55692
|
if (hasPlugins) {
|
|
55660
|
-
const
|
|
55661
|
-
requestId: logMeta.requestId,
|
|
55662
|
-
provider: logMeta.provider,
|
|
55663
|
-
modelIn: logMeta.modelIn,
|
|
55664
|
-
modelOut: logMeta.modelOut,
|
|
55665
|
-
routeType: logMeta.routeType,
|
|
55666
|
-
isStream: logMeta.isStream
|
|
55667
|
-
};
|
|
55668
|
-
const sseResult = await createSSEPluginTransform(plugins, ctx, upstreamRes.status, responseHeaders);
|
|
55693
|
+
const sseResult = await createSSEPluginTransform(plugins, pluginCtx, upstreamRes.status, responseHeaders);
|
|
55669
55694
|
sseStatus = sseResult.status;
|
|
55670
55695
|
sseHeaders = sseResult.headers;
|
|
55671
55696
|
sseTransform = sseResult.transform;
|
|
@@ -55674,47 +55699,71 @@ async function proxyRequest(c2, options) {
|
|
|
55674
55699
|
}
|
|
55675
55700
|
}
|
|
55676
55701
|
if (!shouldLog) {
|
|
55677
|
-
const
|
|
55678
|
-
return new Response(
|
|
55702
|
+
const outputBody = sseTransform ? upstreamRes.body.pipeThrough(sseTransform) : upstreamRes.body;
|
|
55703
|
+
return new Response(outputBody, {
|
|
55679
55704
|
status: sseStatus,
|
|
55680
55705
|
headers: sseHeaders
|
|
55681
55706
|
});
|
|
55682
55707
|
}
|
|
55683
|
-
const
|
|
55684
|
-
|
|
55685
|
-
|
|
55686
|
-
|
|
55687
|
-
|
|
55688
|
-
|
|
55689
|
-
|
|
55690
|
-
|
|
55691
|
-
|
|
55692
|
-
|
|
55693
|
-
|
|
55694
|
-
|
|
55695
|
-
|
|
55708
|
+
const capture = logger?.openStreamCapture(logMeta.requestId, dateStr) ?? null;
|
|
55709
|
+
let upstreamBytes = 0;
|
|
55710
|
+
let writeEventCalled = false;
|
|
55711
|
+
const finalizeAndWriteEvent = () => {
|
|
55712
|
+
if (writeEventCalled)
|
|
55713
|
+
return;
|
|
55714
|
+
writeEventCalled = true;
|
|
55715
|
+
const captureResult = capture?.finalize() ?? {
|
|
55716
|
+
bytesWritten: 0,
|
|
55717
|
+
truncated: false,
|
|
55718
|
+
filePath: null
|
|
55719
|
+
};
|
|
55720
|
+
logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), {
|
|
55721
|
+
upstream_status: sseStatus,
|
|
55722
|
+
content_type_res: contentTypeRes,
|
|
55723
|
+
response_headers: sseHeaders,
|
|
55724
|
+
stream_bytes: upstreamBytes,
|
|
55725
|
+
provider_request_id: providerRequestId,
|
|
55726
|
+
...captureResult.filePath != null && { stream_file: captureResult.filePath },
|
|
55727
|
+
...captureResult.bytesWritten > 0 && {
|
|
55728
|
+
stream_file_bytes: captureResult.bytesWritten
|
|
55729
|
+
},
|
|
55730
|
+
...captureResult.truncated && { stream_file_truncated: true },
|
|
55731
|
+
...requestBody !== undefined && { request_body: requestBody },
|
|
55732
|
+
...pluginLogOverrides
|
|
55733
|
+
}));
|
|
55734
|
+
};
|
|
55735
|
+
const upstreamReader = upstreamRes.body.getReader();
|
|
55736
|
+
const tappedStream = new ReadableStream({
|
|
55737
|
+
async pull(controller) {
|
|
55738
|
+
try {
|
|
55739
|
+
const { done, value } = await upstreamReader.read();
|
|
55740
|
+
if (done) {
|
|
55741
|
+
controller.close();
|
|
55742
|
+
finalizeAndWriteEvent();
|
|
55743
|
+
return;
|
|
55744
|
+
}
|
|
55745
|
+
upstreamBytes += value.byteLength;
|
|
55746
|
+
capture?.write(value);
|
|
55747
|
+
controller.enqueue(value);
|
|
55748
|
+
} catch (err) {
|
|
55749
|
+
finalizeAndWriteEvent();
|
|
55750
|
+
controller.error(err);
|
|
55751
|
+
}
|
|
55752
|
+
},
|
|
55753
|
+
cancel(reason) {
|
|
55754
|
+
try {
|
|
55755
|
+
upstreamReader.cancel(reason).catch(() => {
|
|
55756
|
+
return;
|
|
55757
|
+
});
|
|
55758
|
+
} finally {
|
|
55759
|
+
finalizeAndWriteEvent();
|
|
55696
55760
|
}
|
|
55697
|
-
streamFile = await flushTempCaptureToLogger(tempPath, logMeta.requestId, dateStr, logger);
|
|
55698
|
-
} catch (err) {
|
|
55699
|
-
await unlink(tempPath).catch(() => {
|
|
55700
|
-
return;
|
|
55701
|
-
});
|
|
55702
|
-
console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\u5904\u7406\u5931\u8D25:", err);
|
|
55703
|
-
} finally {
|
|
55704
|
-
logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), {
|
|
55705
|
-
upstream_status: sseStatus,
|
|
55706
|
-
content_type_res: contentTypeRes,
|
|
55707
|
-
response_headers: sseHeaders,
|
|
55708
|
-
stream_bytes: streamBytes,
|
|
55709
|
-
provider_request_id: providerRequestId,
|
|
55710
|
-
...streamFile != null && { stream_file: streamFile },
|
|
55711
|
-
...requestBody !== undefined && { request_body: requestBody },
|
|
55712
|
-
...pluginLogOverrides
|
|
55713
|
-
}));
|
|
55714
55761
|
}
|
|
55715
|
-
})
|
|
55716
|
-
|
|
55717
|
-
|
|
55762
|
+
});
|
|
55763
|
+
let stream = tappedStream;
|
|
55764
|
+
if (sseTransform)
|
|
55765
|
+
stream = stream.pipeThrough(sseTransform);
|
|
55766
|
+
return new Response(stream, {
|
|
55718
55767
|
status: sseStatus,
|
|
55719
55768
|
headers: sseHeaders
|
|
55720
55769
|
});
|
|
@@ -55723,15 +55772,7 @@ async function proxyRequest(c2, options) {
|
|
|
55723
55772
|
let responseStatus = upstreamRes.status;
|
|
55724
55773
|
let finalResponseHeaders = responseHeaders;
|
|
55725
55774
|
if (hasPlugins) {
|
|
55726
|
-
const
|
|
55727
|
-
requestId: logMeta.requestId,
|
|
55728
|
-
provider: logMeta.provider,
|
|
55729
|
-
modelIn: logMeta.modelIn,
|
|
55730
|
-
modelOut: logMeta.modelOut,
|
|
55731
|
-
routeType: logMeta.routeType,
|
|
55732
|
-
isStream: logMeta.isStream
|
|
55733
|
-
};
|
|
55734
|
-
const result = await executeJsonResponsePlugins(plugins, ctx, upstreamRes.status, responseHeaders, responseText);
|
|
55775
|
+
const result = await executeJsonResponsePlugins(plugins, pluginCtx, upstreamRes.status, responseHeaders, responseText);
|
|
55735
55776
|
if (pluginConfigs) {
|
|
55736
55777
|
pluginLogOverrides.plugins_response = pluginConfigs;
|
|
55737
55778
|
}
|
|
@@ -55808,8 +55849,10 @@ function createModelRoutingHandler(options) {
|
|
|
55808
55849
|
return c2.json({ error: `provider "${target.provider}" \u672A\u5728\u914D\u7F6E\u4E2D\u5B9A\u4E49` }, 500);
|
|
55809
55850
|
}
|
|
55810
55851
|
payload.model = target.model;
|
|
55811
|
-
const body = JSON.stringify(payload);
|
|
55812
55852
|
const targetUrl = buildTargetUrl(provider.base);
|
|
55853
|
+
const contentLengthHeader = c2.req.header("content-length");
|
|
55854
|
+
const parsedContentLength = contentLengthHeader ? Number(contentLengthHeader) : NaN;
|
|
55855
|
+
const requestBytes = Number.isInteger(parsedContentLength) && parsedContentLength >= 0 ? parsedContentLength : Buffer.byteLength(JSON.stringify(payload), "utf-8");
|
|
55813
55856
|
const logMeta = {
|
|
55814
55857
|
requestId: crypto.randomUUID(),
|
|
55815
55858
|
tsStart: Date.now(),
|
|
@@ -55823,7 +55866,7 @@ function createModelRoutingHandler(options) {
|
|
|
55823
55866
|
path: c2.req.path,
|
|
55824
55867
|
contentTypeReq: c2.req.header("content-type") ?? null,
|
|
55825
55868
|
userAgent: c2.req.header("user-agent") ?? null,
|
|
55826
|
-
requestBytes
|
|
55869
|
+
requestBytes,
|
|
55827
55870
|
requestHeaders: collectHeaders(c2.req.raw.headers)
|
|
55828
55871
|
};
|
|
55829
55872
|
const plugins = pluginManager?.getPlugins(target.provider) ?? [];
|
|
@@ -55833,7 +55876,7 @@ function createModelRoutingHandler(options) {
|
|
|
55833
55876
|
apiKey: provider.apiKey,
|
|
55834
55877
|
proxy: provider.proxy,
|
|
55835
55878
|
authType,
|
|
55836
|
-
body,
|
|
55879
|
+
body: payload,
|
|
55837
55880
|
logMeta,
|
|
55838
55881
|
plugins: plugins.length > 0 ? plugins : undefined,
|
|
55839
55882
|
pluginConfigs: pluginConfigs.length > 0 ? pluginConfigs.map((lp) => ({
|