@lakphy/local-router 0.4.1 → 0.5.0

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 = resolve6.call(this, root2, ref);
4937
+ let _sch = resolve4.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 resolve6(root2, ref) {
4964
+ function resolve4(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 resolve6(baseURI, relativeURI, options) {
5494
+ function resolve4(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: resolve6,
5722
+ resolve: resolve4,
5723
5723
  resolveComponent,
5724
5724
  equal,
5725
5725
  serialize,
@@ -5856,7 +5856,7 @@ var require_core = __commonJS((exports) => {
5856
5856
  opts = this.opts = { ...opts, ...requiredOptions(opts) };
5857
5857
  const { es5, lines } = this.opts.code;
5858
5858
  this.scope = new codegen_2.ValueScope({ scope: {}, prefixes: EXT_SCOPE_NAMES, es5, lines });
5859
- this.logger = getLogger2(opts.logger);
5859
+ this.logger = getLogger(opts.logger);
5860
5860
  const formatOpt = opts.validateFormats;
5861
5861
  opts.validateFormats = false;
5862
5862
  this.RULES = (0, rules_1.getRules)();
@@ -6253,7 +6253,7 @@ var require_core = __commonJS((exports) => {
6253
6253
  return metaOpts;
6254
6254
  }
6255
6255
  var noLogs = { log() {}, warn() {}, error() {} };
6256
- function getLogger2(logger) {
6256
+ function getLogger(logger) {
6257
6257
  if (logger === false)
6258
6258
  return noLogs;
6259
6259
  if (logger === undefined)
@@ -40602,6 +40602,267 @@ function createOpenAICompatible(options) {
40602
40602
  provider.imageModel = createImageModel;
40603
40603
  return provider;
40604
40604
  }
40605
+
40606
+ // node_modules/.bun/hono@4.12.5/node_modules/hono/dist/utils/html.js
40607
+ var HtmlEscapedCallbackPhase = {
40608
+ Stringify: 1,
40609
+ BeforeStream: 2,
40610
+ Stream: 3
40611
+ };
40612
+ var raw = (value, callbacks) => {
40613
+ const escapedString = new String(value);
40614
+ escapedString.isEscaped = true;
40615
+ escapedString.callbacks = callbacks;
40616
+ return escapedString;
40617
+ };
40618
+ var escapeRe = /[&<>'"]/;
40619
+ var stringBufferToString = async (buffer, callbacks) => {
40620
+ let str = "";
40621
+ callbacks ||= [];
40622
+ const resolvedBuffer = await Promise.all(buffer);
40623
+ for (let i = resolvedBuffer.length - 1;; i--) {
40624
+ str += resolvedBuffer[i];
40625
+ i--;
40626
+ if (i < 0) {
40627
+ break;
40628
+ }
40629
+ let r = resolvedBuffer[i];
40630
+ if (typeof r === "object") {
40631
+ callbacks.push(...r.callbacks || []);
40632
+ }
40633
+ const isEscaped = r.isEscaped;
40634
+ r = await (typeof r === "object" ? r.toString() : r);
40635
+ if (typeof r === "object") {
40636
+ callbacks.push(...r.callbacks || []);
40637
+ }
40638
+ if (r.isEscaped ?? isEscaped) {
40639
+ str += r;
40640
+ } else {
40641
+ const buf = [str];
40642
+ escapeToBuffer(r, buf);
40643
+ str = buf[0];
40644
+ }
40645
+ }
40646
+ return raw(str, callbacks);
40647
+ };
40648
+ var escapeToBuffer = (str, buffer) => {
40649
+ const match = str.search(escapeRe);
40650
+ if (match === -1) {
40651
+ buffer[0] += str;
40652
+ return;
40653
+ }
40654
+ let escape2;
40655
+ let index;
40656
+ let lastIndex = 0;
40657
+ for (index = match;index < str.length; index++) {
40658
+ switch (str.charCodeAt(index)) {
40659
+ case 34:
40660
+ escape2 = "&quot;";
40661
+ break;
40662
+ case 39:
40663
+ escape2 = "&#39;";
40664
+ break;
40665
+ case 38:
40666
+ escape2 = "&amp;";
40667
+ break;
40668
+ case 60:
40669
+ escape2 = "&lt;";
40670
+ break;
40671
+ case 62:
40672
+ escape2 = "&gt;";
40673
+ break;
40674
+ default:
40675
+ continue;
40676
+ }
40677
+ buffer[0] += str.substring(lastIndex, index) + escape2;
40678
+ lastIndex = index + 1;
40679
+ }
40680
+ buffer[0] += str.substring(lastIndex, index);
40681
+ };
40682
+ var resolveCallbackSync = (str) => {
40683
+ const callbacks = str.callbacks;
40684
+ if (!callbacks?.length) {
40685
+ return str;
40686
+ }
40687
+ const buffer = [str];
40688
+ const context = {};
40689
+ callbacks.forEach((c) => c({ phase: HtmlEscapedCallbackPhase.Stringify, buffer, context }));
40690
+ return buffer[0];
40691
+ };
40692
+ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
40693
+ if (typeof str === "object" && !(str instanceof String)) {
40694
+ if (!(str instanceof Promise)) {
40695
+ str = str.toString();
40696
+ }
40697
+ if (str instanceof Promise) {
40698
+ str = await str;
40699
+ }
40700
+ }
40701
+ const callbacks = str.callbacks;
40702
+ if (!callbacks?.length) {
40703
+ return Promise.resolve(str);
40704
+ }
40705
+ if (buffer) {
40706
+ buffer[0] += str;
40707
+ } else {
40708
+ buffer = [str];
40709
+ }
40710
+ const resStr = Promise.all(callbacks.map((c) => c({ phase, buffer, context }))).then((res) => Promise.all(res.filter(Boolean).map((str2) => resolveCallback(str2, phase, false, context, buffer))).then(() => buffer[0]));
40711
+ if (preserveCallbacks) {
40712
+ return raw(await resStr, callbacks);
40713
+ } else {
40714
+ return resStr;
40715
+ }
40716
+ };
40717
+
40718
+ // node_modules/.bun/hono@4.12.5/node_modules/hono/dist/helper/html/index.js
40719
+ var html = (strings, ...values) => {
40720
+ const buffer = [""];
40721
+ for (let i = 0, len = strings.length - 1;i < len; i++) {
40722
+ buffer[0] += strings[i];
40723
+ const children = Array.isArray(values[i]) ? values[i].flat(Infinity) : [values[i]];
40724
+ for (let i2 = 0, len2 = children.length;i2 < len2; i2++) {
40725
+ const child = children[i2];
40726
+ if (typeof child === "string") {
40727
+ escapeToBuffer(child, buffer);
40728
+ } else if (typeof child === "number") {
40729
+ buffer[0] += child;
40730
+ } else if (typeof child === "boolean" || child === null || child === undefined) {
40731
+ continue;
40732
+ } else if (typeof child === "object" && child.isEscaped) {
40733
+ if (child.callbacks) {
40734
+ buffer.unshift("", child);
40735
+ } else {
40736
+ const tmp = child.toString();
40737
+ if (tmp instanceof Promise) {
40738
+ buffer.unshift("", tmp);
40739
+ } else {
40740
+ buffer[0] += tmp;
40741
+ }
40742
+ }
40743
+ } else if (child instanceof Promise) {
40744
+ buffer.unshift("", child);
40745
+ } else {
40746
+ escapeToBuffer(child.toString(), buffer);
40747
+ }
40748
+ }
40749
+ }
40750
+ buffer[0] += strings.at(-1);
40751
+ return buffer.length === 1 ? "callbacks" in buffer ? raw(resolveCallbackSync(raw(buffer[0], buffer.callbacks))) : raw(buffer[0]) : stringBufferToString(buffer, buffer.callbacks);
40752
+ };
40753
+
40754
+ // node_modules/.bun/@hono+swagger-ui@0.5.3+bac6debbfb5ded19/node_modules/@hono/swagger-ui/dist/index.js
40755
+ var RENDER_TYPE = {
40756
+ STRING_ARRAY: "string_array",
40757
+ STRING: "string",
40758
+ JSON_STRING: "json_string",
40759
+ RAW: "raw"
40760
+ };
40761
+ var RENDER_TYPE_MAP = {
40762
+ configUrl: RENDER_TYPE.STRING,
40763
+ deepLinking: RENDER_TYPE.RAW,
40764
+ presets: RENDER_TYPE.STRING_ARRAY,
40765
+ plugins: RENDER_TYPE.STRING_ARRAY,
40766
+ spec: RENDER_TYPE.JSON_STRING,
40767
+ url: RENDER_TYPE.STRING,
40768
+ urls: RENDER_TYPE.JSON_STRING,
40769
+ layout: RENDER_TYPE.STRING,
40770
+ docExpansion: RENDER_TYPE.STRING,
40771
+ maxDisplayedTags: RENDER_TYPE.RAW,
40772
+ operationsSorter: RENDER_TYPE.RAW,
40773
+ requestInterceptor: RENDER_TYPE.RAW,
40774
+ responseInterceptor: RENDER_TYPE.RAW,
40775
+ persistAuthorization: RENDER_TYPE.RAW,
40776
+ defaultModelsExpandDepth: RENDER_TYPE.RAW,
40777
+ defaultModelExpandDepth: RENDER_TYPE.RAW,
40778
+ defaultModelRendering: RENDER_TYPE.STRING,
40779
+ displayRequestDuration: RENDER_TYPE.RAW,
40780
+ filter: RENDER_TYPE.RAW,
40781
+ showExtensions: RENDER_TYPE.RAW,
40782
+ showCommonExtensions: RENDER_TYPE.RAW,
40783
+ queryConfigEnabled: RENDER_TYPE.RAW,
40784
+ displayOperationId: RENDER_TYPE.RAW,
40785
+ tagsSorter: RENDER_TYPE.RAW,
40786
+ onComplete: RENDER_TYPE.RAW,
40787
+ syntaxHighlight: RENDER_TYPE.JSON_STRING,
40788
+ tryItOutEnabled: RENDER_TYPE.RAW,
40789
+ requestSnippetsEnabled: RENDER_TYPE.RAW,
40790
+ requestSnippets: RENDER_TYPE.JSON_STRING,
40791
+ oauth2RedirectUrl: RENDER_TYPE.STRING,
40792
+ showMutabledRequest: RENDER_TYPE.RAW,
40793
+ request: RENDER_TYPE.JSON_STRING,
40794
+ supportedSubmitMethods: RENDER_TYPE.JSON_STRING,
40795
+ validatorUrl: RENDER_TYPE.STRING,
40796
+ withCredentials: RENDER_TYPE.RAW,
40797
+ modelPropertyMacro: RENDER_TYPE.RAW,
40798
+ parameterMacro: RENDER_TYPE.RAW
40799
+ };
40800
+ var renderSwaggerUIOptions = (options) => {
40801
+ return Object.entries(options).map(([k, v]) => {
40802
+ const key = k;
40803
+ if (!RENDER_TYPE_MAP[key] || v === undefined)
40804
+ return "";
40805
+ switch (RENDER_TYPE_MAP[key]) {
40806
+ case RENDER_TYPE.STRING:
40807
+ return `${key}: '${v}'`;
40808
+ case RENDER_TYPE.STRING_ARRAY:
40809
+ if (!Array.isArray(v))
40810
+ return "";
40811
+ return `${key}: [${v.map((ve) => `${ve}`).join(",")}]`;
40812
+ case RENDER_TYPE.JSON_STRING:
40813
+ return `${key}: ${JSON.stringify(v)}`;
40814
+ case RENDER_TYPE.RAW:
40815
+ return `${key}: ${v}`;
40816
+ default:
40817
+ return "";
40818
+ }
40819
+ }).filter((item) => item !== "").join(",");
40820
+ };
40821
+ var remoteAssets = ({ version: version2 }) => {
40822
+ const url2 = `https://cdn.jsdelivr.net/npm/swagger-ui-dist${version2 !== undefined ? `@${version2}` : ""}`;
40823
+ return {
40824
+ css: [`${url2}/swagger-ui.css`],
40825
+ js: [`${url2}/swagger-ui-bundle.js`]
40826
+ };
40827
+ };
40828
+ var SwaggerUI = (options) => {
40829
+ const asset = remoteAssets({ version: options?.version });
40830
+ delete options.version;
40831
+ if (options.manuallySwaggerUIHtml)
40832
+ return options.manuallySwaggerUIHtml(asset);
40833
+ const optionsStrings = renderSwaggerUIOptions(options);
40834
+ return `
40835
+ <div>
40836
+ <div id="swagger-ui"></div>
40837
+ ${asset.css.map((url2) => html`<link rel="stylesheet" href="${url2}" />`)}
40838
+ ${asset.js.map((url2) => html`<script src="${url2}" crossorigin="anonymous"><\/script>`)}
40839
+ <script>
40840
+ window.onload = () => {
40841
+ window.ui = SwaggerUIBundle({
40842
+ dom_id: '#swagger-ui',${optionsStrings},
40843
+ })
40844
+ }
40845
+ </script>
40846
+ </div>
40847
+ `;
40848
+ };
40849
+ var middleware = (options) => async (c) => {
40850
+ const title = options?.title ?? "SwaggerUI";
40851
+ return c.html(`
40852
+ <!doctype html>
40853
+ <html lang="en">
40854
+ <head>
40855
+ <meta charset="utf-8" />
40856
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
40857
+ <meta name="description" content="SwaggerUI" />
40858
+ <title>${title}</title>
40859
+ </head>
40860
+ <body>
40861
+ ${SwaggerUI(options)}
40862
+ </body>
40863
+ </html>
40864
+ `);
40865
+ };
40605
40866
  // node_modules/.bun/@ai-sdk+gateway@3.0.66+3c5d820c62823f0b/node_modules/@ai-sdk/gateway/dist/index.mjs
40606
40867
  var import_oidc = __toESM(require_dist(), 1);
40607
40868
  var import_oidc2 = __toESM(require_dist(), 1);
@@ -48100,267 +48361,6 @@ var _a20;
48100
48361
  _a20 = symbol20;
48101
48362
  var defaultDownload2 = createDownload();
48102
48363
 
48103
- // node_modules/.bun/hono@4.12.5/node_modules/hono/dist/utils/html.js
48104
- var HtmlEscapedCallbackPhase = {
48105
- Stringify: 1,
48106
- BeforeStream: 2,
48107
- Stream: 3
48108
- };
48109
- var raw = (value, callbacks) => {
48110
- const escapedString = new String(value);
48111
- escapedString.isEscaped = true;
48112
- escapedString.callbacks = callbacks;
48113
- return escapedString;
48114
- };
48115
- var escapeRe = /[&<>'"]/;
48116
- var stringBufferToString = async (buffer, callbacks) => {
48117
- let str = "";
48118
- callbacks ||= [];
48119
- const resolvedBuffer = await Promise.all(buffer);
48120
- for (let i = resolvedBuffer.length - 1;; i--) {
48121
- str += resolvedBuffer[i];
48122
- i--;
48123
- if (i < 0) {
48124
- break;
48125
- }
48126
- let r = resolvedBuffer[i];
48127
- if (typeof r === "object") {
48128
- callbacks.push(...r.callbacks || []);
48129
- }
48130
- const isEscaped = r.isEscaped;
48131
- r = await (typeof r === "object" ? r.toString() : r);
48132
- if (typeof r === "object") {
48133
- callbacks.push(...r.callbacks || []);
48134
- }
48135
- if (r.isEscaped ?? isEscaped) {
48136
- str += r;
48137
- } else {
48138
- const buf = [str];
48139
- escapeToBuffer(r, buf);
48140
- str = buf[0];
48141
- }
48142
- }
48143
- return raw(str, callbacks);
48144
- };
48145
- var escapeToBuffer = (str, buffer) => {
48146
- const match = str.search(escapeRe);
48147
- if (match === -1) {
48148
- buffer[0] += str;
48149
- return;
48150
- }
48151
- let escape2;
48152
- let index;
48153
- let lastIndex = 0;
48154
- for (index = match;index < str.length; index++) {
48155
- switch (str.charCodeAt(index)) {
48156
- case 34:
48157
- escape2 = "&quot;";
48158
- break;
48159
- case 39:
48160
- escape2 = "&#39;";
48161
- break;
48162
- case 38:
48163
- escape2 = "&amp;";
48164
- break;
48165
- case 60:
48166
- escape2 = "&lt;";
48167
- break;
48168
- case 62:
48169
- escape2 = "&gt;";
48170
- break;
48171
- default:
48172
- continue;
48173
- }
48174
- buffer[0] += str.substring(lastIndex, index) + escape2;
48175
- lastIndex = index + 1;
48176
- }
48177
- buffer[0] += str.substring(lastIndex, index);
48178
- };
48179
- var resolveCallbackSync = (str) => {
48180
- const callbacks = str.callbacks;
48181
- if (!callbacks?.length) {
48182
- return str;
48183
- }
48184
- const buffer = [str];
48185
- const context2 = {};
48186
- callbacks.forEach((c) => c({ phase: HtmlEscapedCallbackPhase.Stringify, buffer, context: context2 }));
48187
- return buffer[0];
48188
- };
48189
- var resolveCallback = async (str, phase, preserveCallbacks, context2, buffer) => {
48190
- if (typeof str === "object" && !(str instanceof String)) {
48191
- if (!(str instanceof Promise)) {
48192
- str = str.toString();
48193
- }
48194
- if (str instanceof Promise) {
48195
- str = await str;
48196
- }
48197
- }
48198
- const callbacks = str.callbacks;
48199
- if (!callbacks?.length) {
48200
- return Promise.resolve(str);
48201
- }
48202
- if (buffer) {
48203
- buffer[0] += str;
48204
- } else {
48205
- buffer = [str];
48206
- }
48207
- const resStr = Promise.all(callbacks.map((c) => c({ phase, buffer, context: context2 }))).then((res) => Promise.all(res.filter(Boolean).map((str2) => resolveCallback(str2, phase, false, context2, buffer))).then(() => buffer[0]));
48208
- if (preserveCallbacks) {
48209
- return raw(await resStr, callbacks);
48210
- } else {
48211
- return resStr;
48212
- }
48213
- };
48214
-
48215
- // node_modules/.bun/hono@4.12.5/node_modules/hono/dist/helper/html/index.js
48216
- var html = (strings, ...values) => {
48217
- const buffer = [""];
48218
- for (let i = 0, len = strings.length - 1;i < len; i++) {
48219
- buffer[0] += strings[i];
48220
- const children = Array.isArray(values[i]) ? values[i].flat(Infinity) : [values[i]];
48221
- for (let i2 = 0, len2 = children.length;i2 < len2; i2++) {
48222
- const child = children[i2];
48223
- if (typeof child === "string") {
48224
- escapeToBuffer(child, buffer);
48225
- } else if (typeof child === "number") {
48226
- buffer[0] += child;
48227
- } else if (typeof child === "boolean" || child === null || child === undefined) {
48228
- continue;
48229
- } else if (typeof child === "object" && child.isEscaped) {
48230
- if (child.callbacks) {
48231
- buffer.unshift("", child);
48232
- } else {
48233
- const tmp = child.toString();
48234
- if (tmp instanceof Promise) {
48235
- buffer.unshift("", tmp);
48236
- } else {
48237
- buffer[0] += tmp;
48238
- }
48239
- }
48240
- } else if (child instanceof Promise) {
48241
- buffer.unshift("", child);
48242
- } else {
48243
- escapeToBuffer(child.toString(), buffer);
48244
- }
48245
- }
48246
- }
48247
- buffer[0] += strings.at(-1);
48248
- return buffer.length === 1 ? "callbacks" in buffer ? raw(resolveCallbackSync(raw(buffer[0], buffer.callbacks))) : raw(buffer[0]) : stringBufferToString(buffer, buffer.callbacks);
48249
- };
48250
-
48251
- // node_modules/.bun/@hono+swagger-ui@0.5.3+bac6debbfb5ded19/node_modules/@hono/swagger-ui/dist/index.js
48252
- var RENDER_TYPE = {
48253
- STRING_ARRAY: "string_array",
48254
- STRING: "string",
48255
- JSON_STRING: "json_string",
48256
- RAW: "raw"
48257
- };
48258
- var RENDER_TYPE_MAP = {
48259
- configUrl: RENDER_TYPE.STRING,
48260
- deepLinking: RENDER_TYPE.RAW,
48261
- presets: RENDER_TYPE.STRING_ARRAY,
48262
- plugins: RENDER_TYPE.STRING_ARRAY,
48263
- spec: RENDER_TYPE.JSON_STRING,
48264
- url: RENDER_TYPE.STRING,
48265
- urls: RENDER_TYPE.JSON_STRING,
48266
- layout: RENDER_TYPE.STRING,
48267
- docExpansion: RENDER_TYPE.STRING,
48268
- maxDisplayedTags: RENDER_TYPE.RAW,
48269
- operationsSorter: RENDER_TYPE.RAW,
48270
- requestInterceptor: RENDER_TYPE.RAW,
48271
- responseInterceptor: RENDER_TYPE.RAW,
48272
- persistAuthorization: RENDER_TYPE.RAW,
48273
- defaultModelsExpandDepth: RENDER_TYPE.RAW,
48274
- defaultModelExpandDepth: RENDER_TYPE.RAW,
48275
- defaultModelRendering: RENDER_TYPE.STRING,
48276
- displayRequestDuration: RENDER_TYPE.RAW,
48277
- filter: RENDER_TYPE.RAW,
48278
- showExtensions: RENDER_TYPE.RAW,
48279
- showCommonExtensions: RENDER_TYPE.RAW,
48280
- queryConfigEnabled: RENDER_TYPE.RAW,
48281
- displayOperationId: RENDER_TYPE.RAW,
48282
- tagsSorter: RENDER_TYPE.RAW,
48283
- onComplete: RENDER_TYPE.RAW,
48284
- syntaxHighlight: RENDER_TYPE.JSON_STRING,
48285
- tryItOutEnabled: RENDER_TYPE.RAW,
48286
- requestSnippetsEnabled: RENDER_TYPE.RAW,
48287
- requestSnippets: RENDER_TYPE.JSON_STRING,
48288
- oauth2RedirectUrl: RENDER_TYPE.STRING,
48289
- showMutabledRequest: RENDER_TYPE.RAW,
48290
- request: RENDER_TYPE.JSON_STRING,
48291
- supportedSubmitMethods: RENDER_TYPE.JSON_STRING,
48292
- validatorUrl: RENDER_TYPE.STRING,
48293
- withCredentials: RENDER_TYPE.RAW,
48294
- modelPropertyMacro: RENDER_TYPE.RAW,
48295
- parameterMacro: RENDER_TYPE.RAW
48296
- };
48297
- var renderSwaggerUIOptions = (options) => {
48298
- return Object.entries(options).map(([k, v]) => {
48299
- const key = k;
48300
- if (!RENDER_TYPE_MAP[key] || v === undefined)
48301
- return "";
48302
- switch (RENDER_TYPE_MAP[key]) {
48303
- case RENDER_TYPE.STRING:
48304
- return `${key}: '${v}'`;
48305
- case RENDER_TYPE.STRING_ARRAY:
48306
- if (!Array.isArray(v))
48307
- return "";
48308
- return `${key}: [${v.map((ve) => `${ve}`).join(",")}]`;
48309
- case RENDER_TYPE.JSON_STRING:
48310
- return `${key}: ${JSON.stringify(v)}`;
48311
- case RENDER_TYPE.RAW:
48312
- return `${key}: ${v}`;
48313
- default:
48314
- return "";
48315
- }
48316
- }).filter((item) => item !== "").join(",");
48317
- };
48318
- var remoteAssets = ({ version: version2 }) => {
48319
- const url2 = `https://cdn.jsdelivr.net/npm/swagger-ui-dist${version2 !== undefined ? `@${version2}` : ""}`;
48320
- return {
48321
- css: [`${url2}/swagger-ui.css`],
48322
- js: [`${url2}/swagger-ui-bundle.js`]
48323
- };
48324
- };
48325
- var SwaggerUI = (options) => {
48326
- const asset = remoteAssets({ version: options?.version });
48327
- delete options.version;
48328
- if (options.manuallySwaggerUIHtml)
48329
- return options.manuallySwaggerUIHtml(asset);
48330
- const optionsStrings = renderSwaggerUIOptions(options);
48331
- return `
48332
- <div>
48333
- <div id="swagger-ui"></div>
48334
- ${asset.css.map((url2) => html`<link rel="stylesheet" href="${url2}" />`)}
48335
- ${asset.js.map((url2) => html`<script src="${url2}" crossorigin="anonymous"><\/script>`)}
48336
- <script>
48337
- window.onload = () => {
48338
- window.ui = SwaggerUIBundle({
48339
- dom_id: '#swagger-ui',${optionsStrings},
48340
- })
48341
- }
48342
- </script>
48343
- </div>
48344
- `;
48345
- };
48346
- var middleware = (options) => async (c) => {
48347
- const title = options?.title ?? "SwaggerUI";
48348
- return c.html(`
48349
- <!doctype html>
48350
- <html lang="en">
48351
- <head>
48352
- <meta charset="utf-8" />
48353
- <meta name="viewport" content="width=device-width, initial-scale=1" />
48354
- <meta name="description" content="SwaggerUI" />
48355
- <title>${title}</title>
48356
- </head>
48357
- <body>
48358
- ${SwaggerUI(options)}
48359
- </body>
48360
- </html>
48361
- `);
48362
- };
48363
-
48364
48364
  // node_modules/.bun/hono@4.12.5/node_modules/hono/dist/compose.js
48365
48365
  var compose = (middleware2, onError, onNotFound) => {
48366
48366
  return (context2, next) => {
@@ -51277,6 +51277,14 @@ var DEFAULT_CONFIG = `{
51277
51277
  // },
51278
51278
  },
51279
51279
 
51280
+ // \u901A\u7528\u670D\u52A1\u8BBE\u7F6E
51281
+ server: {
51282
+ // \u9ED8\u8BA4\u5173\u95ED\u5C40\u57DF\u7F51\u8BBF\u95EE\u3002\u5F00\u542F\u540E\uFF0C\u5C40\u57DF\u7F51\u5185\u5176\u4ED6\u8BBE\u5907\u53EF\u8BBF\u95EE\u672C\u670D\u52A1\u3002
51283
+ lanAccess: {
51284
+ enabled: false,
51285
+ },
51286
+ },
51287
+
51280
51288
  // \u65E5\u5FD7\u914D\u7F6E\uFF08\u53EF\u9009\uFF0C\u4E0D\u914D\u7F6E\u5219\u4E0D\u542F\u7528\u65E5\u5FD7\u8BB0\u5F55\uFF09
51281
51289
  // log: {
51282
51290
  // enabled: true,
@@ -51347,8 +51355,8 @@ function resolveLogBaseDir(logConfig) {
51347
51355
  }
51348
51356
 
51349
51357
  // src/config-store.ts
51350
- import { resolve as resolve3 } from "path";
51351
51358
  import { writeFileSync as writeFileSync2 } from "fs";
51359
+ import { resolve as resolve3 } from "path";
51352
51360
  class ConfigStore {
51353
51361
  config;
51354
51362
  absolutePath;
@@ -51384,6 +51392,51 @@ class ConfigStore {
51384
51392
  }
51385
51393
  }
51386
51394
 
51395
+ // src/config-validate.ts
51396
+ var import__2020 = __toESM(require_2020(), 1);
51397
+ var import_ajv_formats = __toESM(require_dist2(), 1);
51398
+ import { readFileSync as readFileSync2 } from "fs";
51399
+
51400
+ // src/runtime-assets.ts
51401
+ import { fileURLToPath } from "url";
51402
+ function resolveBundledAssetPath(relativePath) {
51403
+ return fileURLToPath(new URL(relativePath, import.meta.url));
51404
+ }
51405
+ function getBundledSchemaPath() {
51406
+ return resolveBundledAssetPath("../config.schema.json");
51407
+ }
51408
+ function getBundledWebRoot() {
51409
+ return resolveBundledAssetPath("../dist/web/");
51410
+ }
51411
+
51412
+ // src/config-validate.ts
51413
+ function validateBusinessRules(config2) {
51414
+ for (const [routeType, modelMap] of Object.entries(config2.routes)) {
51415
+ if (!modelMap["*"]) {
51416
+ throw new Error(`\u8DEF\u7531 "${routeType}" \u7F3A\u5C11 "*" \u515C\u5E95\u89C4\u5219`);
51417
+ }
51418
+ for (const target of Object.values(modelMap)) {
51419
+ if (!config2.providers[target.provider]) {
51420
+ throw new Error(`\u8DEF\u7531 "${routeType}" \u5F15\u7528\u4E86\u4E0D\u5B58\u5728\u7684 provider "${target.provider}"`);
51421
+ }
51422
+ }
51423
+ }
51424
+ }
51425
+ function validateConfigOrThrow(config2) {
51426
+ validateBusinessRules(config2);
51427
+ const ajv = new import__2020.default({ allErrors: true, strict: false });
51428
+ import_ajv_formats.default(ajv);
51429
+ const schemaJson = JSON.parse(readFileSync2(getBundledSchemaPath(), "utf-8"));
51430
+ const validateBySchema = ajv.compile(schemaJson);
51431
+ const valid = validateBySchema(config2);
51432
+ if (!valid) {
51433
+ const firstError = validateBySchema.errors?.[0];
51434
+ const path = firstError?.instancePath || "(root)";
51435
+ const message = firstError?.message ?? "unknown schema validation error";
51436
+ throw new Error(`Schema \u6821\u9A8C\u5931\u8D25: ${path} ${message}`);
51437
+ }
51438
+ }
51439
+
51387
51440
  // src/crypto.ts
51388
51441
  var ECDH_PARAMS = { name: "ECDH", namedCurve: "P-256" };
51389
51442
  function base64Encode(buf) {
@@ -51736,10 +51789,23 @@ async function getLogMetrics(options) {
51736
51789
  }
51737
51790
 
51738
51791
  // src/log-query.ts
51739
- import { createReadStream as createReadStream2, existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
51740
- import { join as join4, resolve as resolve4 } from "path";
51792
+ import { createReadStream as createReadStream3, existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
51793
+ import { join as join5, resolve as resolve4 } from "path";
51741
51794
  import { createInterface as createInterface2 } from "readline";
51742
51795
 
51796
+ // src/log-index.ts
51797
+ import { Database } from "bun:sqlite";
51798
+ import {
51799
+ closeSync,
51800
+ createReadStream as createReadStream2,
51801
+ existsSync as existsSync3,
51802
+ mkdirSync as mkdirSync2,
51803
+ openSync,
51804
+ readSync,
51805
+ statSync
51806
+ } from "fs";
51807
+ import { join as join4 } from "path";
51808
+
51743
51809
  // src/log-session-identity.ts
51744
51810
  var USER_SESSION_DELIMITER = "_account__session_";
51745
51811
  function toRecord(value) {
@@ -51816,6 +51882,824 @@ function resolveLogSessionIdentity(requestBody) {
51816
51882
  };
51817
51883
  }
51818
51884
 
51885
+ // src/log-index.ts
51886
+ var SCHEMA_VERSION = 1;
51887
+ var MAX_INDEX_QUEUE = 20000;
51888
+ var INDEX_BATCH_SIZE = 250;
51889
+ var INDEX_FLUSH_DELAY_MS = 50;
51890
+ var LIKE_SEARCH_THRESHOLD = 2;
51891
+ var FTS_TOKEN_PATTERN = /[\p{L}\p{N}_-]+/gu;
51892
+ var singleton = null;
51893
+ function encodeBase64Url(value) {
51894
+ return Buffer.from(value, "utf-8").toString("base64url");
51895
+ }
51896
+ function decodeBase64Url(value) {
51897
+ return Buffer.from(value, "base64url").toString("utf-8");
51898
+ }
51899
+ function encodeOffsetLogEventId(date5, offset) {
51900
+ return encodeBase64Url(JSON.stringify({ v: 2, d: date5, o: offset }));
51901
+ }
51902
+ function decodeOffsetLogEventId(id) {
51903
+ try {
51904
+ const parsed = JSON.parse(decodeBase64Url(id));
51905
+ if (parsed.v !== 2 || typeof parsed.d !== "string" || !Number.isInteger(parsed.o)) {
51906
+ return null;
51907
+ }
51908
+ const offset = Number(parsed.o);
51909
+ if (offset < 0)
51910
+ return null;
51911
+ return { v: 2, date: parsed.d, offset };
51912
+ } catch {
51913
+ return null;
51914
+ }
51915
+ }
51916
+ function encodeCursor(data) {
51917
+ return encodeBase64Url(JSON.stringify(data));
51918
+ }
51919
+ function decodeCursor(raw2) {
51920
+ const parsed = JSON.parse(decodeBase64Url(raw2));
51921
+ if (parsed.v !== 2 || parsed.sort !== "time_desc" && parsed.sort !== "time_asc" || typeof parsed.id !== "string" || typeof parsed.queryHash !== "string" || !Number.isFinite(parsed.tsMs)) {
51922
+ throw new Error("cursor \u975E\u6CD5");
51923
+ }
51924
+ return parsed;
51925
+ }
51926
+ function toDayStart2(ms) {
51927
+ const date5 = new Date(ms);
51928
+ return Date.UTC(date5.getUTCFullYear(), date5.getUTCMonth(), date5.getUTCDate());
51929
+ }
51930
+ function listDateStrings2(fromMs, toMs) {
51931
+ const result = [];
51932
+ for (let day = toDayStart2(fromMs);day <= toDayStart2(toMs); day += 24 * 60 * 60 * 1000) {
51933
+ result.push(new Date(day).toISOString().slice(0, 10));
51934
+ }
51935
+ return result;
51936
+ }
51937
+ function getStatusClass2(event) {
51938
+ if (event.error_type)
51939
+ return "network_error";
51940
+ const status = event.upstream_status ?? 0;
51941
+ if (status >= 200 && status < 300)
51942
+ return "2xx";
51943
+ if (status >= 400 && status < 500)
51944
+ return "4xx";
51945
+ if (status >= 500)
51946
+ return "5xx";
51947
+ return "network_error";
51948
+ }
51949
+ function isErrorEvent2(event) {
51950
+ if (event.error_type)
51951
+ return true;
51952
+ const status = event.upstream_status ?? 0;
51953
+ return status < 200 || status >= 400;
51954
+ }
51955
+ function getLevel(event) {
51956
+ return isErrorEvent2(event) ? "error" : "info";
51957
+ }
51958
+ function buildMessage(event) {
51959
+ if (event.error_message)
51960
+ return event.error_message;
51961
+ if (event.error_type)
51962
+ return event.error_type;
51963
+ const status = event.upstream_status ?? 0;
51964
+ return `${event.method} ${event.path} -> ${status}`;
51965
+ }
51966
+ function toPercent2(numerator, denominator) {
51967
+ if (denominator <= 0)
51968
+ return 0;
51969
+ return Number((numerator / denominator * 100).toFixed(2));
51970
+ }
51971
+ function hashQuery(query) {
51972
+ const stable = {
51973
+ fromMs: query.fromMs,
51974
+ toMs: query.toMs,
51975
+ levels: [...query.levels].sort(),
51976
+ providers: [...query.providers].sort(),
51977
+ routeTypes: [...query.routeTypes].sort(),
51978
+ models: [...query.models].sort(),
51979
+ modelIns: [...query.modelIns].sort(),
51980
+ modelOuts: [...query.modelOuts].sort(),
51981
+ users: [...query.users].sort(),
51982
+ sessions: [...query.sessions].sort(),
51983
+ statusClasses: [...query.statusClasses].sort(),
51984
+ hasError: query.hasError,
51985
+ q: query.q
51986
+ };
51987
+ return Bun.hash(JSON.stringify(stable)).toString(36);
51988
+ }
51989
+ function buildSearchText(event) {
51990
+ const identity = resolveLogSessionIdentity(event.request_body);
51991
+ return [
51992
+ event.request_id,
51993
+ event.path,
51994
+ event.provider,
51995
+ event.model_in,
51996
+ event.model_out,
51997
+ event.route_type,
51998
+ identity.userIdRaw ?? "",
51999
+ identity.userKey ?? "",
52000
+ identity.sessionId ?? "",
52001
+ event.error_type ?? "",
52002
+ event.error_message ?? "",
52003
+ buildMessage(event)
52004
+ ].join(" ").toLowerCase();
52005
+ }
52006
+ function buildFtsQuery(q) {
52007
+ const tokens = q.match(FTS_TOKEN_PATTERN)?.map((token2) => token2.trim()).filter(Boolean) ?? [];
52008
+ if (tokens.length === 0)
52009
+ return null;
52010
+ return tokens.map((token2) => `"${token2.replaceAll('"', '""')}"`).join(" AND ");
52011
+ }
52012
+ function shouldUseFts(q) {
52013
+ return q.trim().length >= LIKE_SEARCH_THRESHOLD && buildFtsQuery(q) !== null;
52014
+ }
52015
+ function escapeLikePattern(value) {
52016
+ return value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
52017
+ }
52018
+ function eventToRow(input) {
52019
+ const { event } = input;
52020
+ if (!event.ts_start)
52021
+ return null;
52022
+ const tsMs = Date.parse(event.ts_start);
52023
+ if (!Number.isFinite(tsMs))
52024
+ return null;
52025
+ const identity = resolveLogSessionIdentity(event.request_body);
52026
+ const level = getLevel(event);
52027
+ const statusClass = getStatusClass2(event);
52028
+ const latencyMs = Math.max(0, event.latency_ms ?? 0);
52029
+ const model = event.model_out || event.model_in;
52030
+ return {
52031
+ id: input.id,
52032
+ ts_ms: tsMs,
52033
+ ts_start: event.ts_start,
52034
+ level,
52035
+ provider: event.provider,
52036
+ route_type: event.route_type,
52037
+ model,
52038
+ model_in: event.model_in,
52039
+ model_out: event.model_out,
52040
+ path: event.path,
52041
+ request_id: event.request_id,
52042
+ latency_ms: latencyMs,
52043
+ upstream_status: event.upstream_status ?? 0,
52044
+ status_class: statusClass,
52045
+ has_error: level === "error" ? 1 : 0,
52046
+ message: buildMessage(event),
52047
+ error_type: event.error_type,
52048
+ has_metadata: identity.hasMetadata ? 1 : 0,
52049
+ user_id_raw: identity.userIdRaw,
52050
+ user_key: identity.userKey,
52051
+ session_id: identity.sessionId,
52052
+ source_date: input.date,
52053
+ source_file: input.filePath,
52054
+ line_number: input.lineNumber,
52055
+ byte_offset: input.offset,
52056
+ byte_length: input.byteLength,
52057
+ search_text: buildSearchText(event)
52058
+ };
52059
+ }
52060
+ function rowToSummary(row) {
52061
+ return {
52062
+ id: row.id,
52063
+ ts: row.ts_start,
52064
+ level: row.level,
52065
+ provider: row.provider,
52066
+ routeType: row.route_type,
52067
+ model: row.model,
52068
+ modelIn: row.model_in,
52069
+ modelOut: row.model_out,
52070
+ path: row.path,
52071
+ requestId: row.request_id,
52072
+ latencyMs: row.latency_ms,
52073
+ upstreamStatus: row.upstream_status,
52074
+ statusClass: row.status_class,
52075
+ hasError: row.has_error === 1,
52076
+ message: row.message,
52077
+ errorType: row.error_type,
52078
+ hasMetadata: row.has_metadata === 1,
52079
+ userIdRaw: row.user_id_raw,
52080
+ userKey: row.user_key,
52081
+ sessionId: row.session_id
52082
+ };
52083
+ }
52084
+ async function* readJsonlLinesWithOffsets(filePath) {
52085
+ const stream = createReadStream2(filePath);
52086
+ let buffer2 = Buffer.alloc(0);
52087
+ let bufferOffset = 0;
52088
+ let lineNumber = 0;
52089
+ for await (const chunk of stream) {
52090
+ const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
52091
+ buffer2 = buffer2.length === 0 ? chunkBuffer : Buffer.concat([buffer2, chunkBuffer]);
52092
+ let newlineIndex = buffer2.indexOf(10);
52093
+ while (newlineIndex !== -1) {
52094
+ const lineBuffer = buffer2.subarray(0, newlineIndex);
52095
+ const byteLength = newlineIndex + 1;
52096
+ const lineOffset = bufferOffset;
52097
+ lineNumber += 1;
52098
+ yield {
52099
+ line: lineBuffer.toString("utf-8").replace(/\r$/, ""),
52100
+ offset: lineOffset,
52101
+ lineNumber,
52102
+ byteLength
52103
+ };
52104
+ buffer2 = buffer2.subarray(byteLength);
52105
+ bufferOffset += byteLength;
52106
+ newlineIndex = buffer2.indexOf(10);
52107
+ }
52108
+ }
52109
+ if (buffer2.length > 0) {
52110
+ lineNumber += 1;
52111
+ yield {
52112
+ line: buffer2.toString("utf-8").replace(/\r$/, ""),
52113
+ offset: bufferOffset,
52114
+ lineNumber,
52115
+ byteLength: buffer2.length
52116
+ };
52117
+ }
52118
+ }
52119
+ function readLineAtOffset(filePath, offset) {
52120
+ const fd = openSync(filePath, "r");
52121
+ try {
52122
+ const chunks = [];
52123
+ const buffer2 = Buffer.allocUnsafe(64 * 1024);
52124
+ let position = offset;
52125
+ while (true) {
52126
+ const bytesRead = readSync(fd, buffer2, 0, buffer2.length, position);
52127
+ if (bytesRead <= 0)
52128
+ break;
52129
+ const readable = buffer2.subarray(0, bytesRead);
52130
+ const newline = readable.indexOf(10);
52131
+ if (newline >= 0 && newline < bytesRead) {
52132
+ chunks.push(Buffer.from(readable.subarray(0, newline)));
52133
+ break;
52134
+ }
52135
+ chunks.push(Buffer.from(readable));
52136
+ position += bytesRead;
52137
+ }
52138
+ if (chunks.length === 0)
52139
+ return null;
52140
+ return Buffer.concat(chunks).toString("utf-8").replace(/\r$/, "");
52141
+ } finally {
52142
+ closeSync(fd);
52143
+ }
52144
+ }
52145
+ function createEmptyStats() {
52146
+ return {
52147
+ total: 0,
52148
+ errorCount: 0,
52149
+ errorRate: 0,
52150
+ avgLatencyMs: 0,
52151
+ p95LatencyMs: 0
52152
+ };
52153
+ }
52154
+ function createEmptyQueryResult(query, meta3 = {}) {
52155
+ return {
52156
+ items: [],
52157
+ nextCursor: null,
52158
+ hasMore: false,
52159
+ stats: createEmptyStats(),
52160
+ meta: {
52161
+ scannedFiles: 0,
52162
+ scannedLines: 0,
52163
+ parseErrors: 0,
52164
+ truncated: false,
52165
+ indexUsed: true,
52166
+ indexFresh: true,
52167
+ usesFts: shouldUseFts(query.q),
52168
+ queryMs: 0,
52169
+ rowsReturned: 0,
52170
+ statsMode: "exact",
52171
+ ...meta3
52172
+ }
52173
+ };
52174
+ }
52175
+ function appendInClause(clauses, params, column2, values) {
52176
+ if (values.length === 0)
52177
+ return;
52178
+ clauses.push(`${column2} IN (${values.map(() => "?").join(", ")})`);
52179
+ params.push(...values);
52180
+ }
52181
+ function buildWhereClause(query, options = {}) {
52182
+ const clauses = ["e.ts_ms >= ?", "e.ts_ms <= ?"];
52183
+ const params = [query.fromMs, query.toMs];
52184
+ const usesFts = !options.forceLikeSearch && shouldUseFts(query.q);
52185
+ appendInClause(clauses, params, "e.level", query.levels);
52186
+ appendInClause(clauses, params, "e.provider", query.providers);
52187
+ appendInClause(clauses, params, "e.route_type", query.routeTypes);
52188
+ appendInClause(clauses, params, "e.model", query.models);
52189
+ appendInClause(clauses, params, "e.model_in", query.modelIns);
52190
+ appendInClause(clauses, params, "e.model_out", query.modelOuts);
52191
+ appendInClause(clauses, params, "e.status_class", query.statusClasses);
52192
+ if (query.users.length > 0) {
52193
+ clauses.push(`(e.user_id_raw IN (${query.users.map(() => "?").join(", ")}) OR e.user_key IN (${query.users.map(() => "?").join(", ")}))`);
52194
+ params.push(...query.users, ...query.users);
52195
+ }
52196
+ appendInClause(clauses, params, "e.session_id", query.sessions);
52197
+ if (query.hasError !== null) {
52198
+ clauses.push("e.has_error = ?");
52199
+ params.push(query.hasError ? 1 : 0);
52200
+ }
52201
+ if (query.q) {
52202
+ if (usesFts) {
52203
+ const ftsQuery = buildFtsQuery(query.q);
52204
+ clauses.push("e.id IN (SELECT event_id FROM log_events_fts WHERE log_events_fts MATCH ?)");
52205
+ params.push(ftsQuery);
52206
+ } else {
52207
+ clauses.push("e.search_text LIKE ? ESCAPE '\\'");
52208
+ params.push(`%${escapeLikePattern(query.q.toLowerCase())}%`);
52209
+ }
52210
+ }
52211
+ return {
52212
+ whereSql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
52213
+ params,
52214
+ usesFts
52215
+ };
52216
+ }
52217
+
52218
+ class LogIndex {
52219
+ baseDir;
52220
+ config;
52221
+ db;
52222
+ queue = [];
52223
+ flushTimer = null;
52224
+ disposed = false;
52225
+ rebuildingFiles = new Set;
52226
+ dirtyFiles = new Set;
52227
+ insertEventStmt;
52228
+ insertFtsStmt;
52229
+ deleteFtsStmt;
52230
+ upsertFileStmt;
52231
+ constructor(baseDir, config2) {
52232
+ this.baseDir = baseDir;
52233
+ this.config = config2;
52234
+ mkdirSync2(baseDir, { recursive: true });
52235
+ const dbPath = join4(baseDir, "logs-index.sqlite");
52236
+ this.db = new Database(dbPath, { create: true, strict: true });
52237
+ this.configure();
52238
+ this.migrate();
52239
+ this.insertEventStmt = this.db.prepare(`
52240
+ INSERT OR REPLACE INTO log_events (
52241
+ id, ts_ms, ts_start, level, provider, route_type, model, model_in, model_out,
52242
+ path, request_id, latency_ms, upstream_status, status_class, has_error,
52243
+ message, error_type, has_metadata, user_id_raw, user_key, session_id,
52244
+ source_date, source_file, line_number, byte_offset, byte_length, search_text
52245
+ ) VALUES (
52246
+ $id, $ts_ms, $ts_start, $level, $provider, $route_type, $model, $model_in, $model_out,
52247
+ $path, $request_id, $latency_ms, $upstream_status, $status_class, $has_error,
52248
+ $message, $error_type, $has_metadata, $user_id_raw, $user_key, $session_id,
52249
+ $source_date, $source_file, $line_number, $byte_offset, $byte_length, $search_text
52250
+ )
52251
+ `);
52252
+ this.deleteFtsStmt = this.db.prepare("DELETE FROM log_events_fts WHERE event_id = ?");
52253
+ this.insertFtsStmt = this.db.prepare("INSERT INTO log_events_fts(event_id, search_text) VALUES (?, ?)");
52254
+ this.upsertFileStmt = this.db.prepare(`
52255
+ INSERT INTO log_index_files(file_path, source_date, size_bytes, mtime_ms, indexed_at)
52256
+ VALUES (?, ?, ?, ?, ?)
52257
+ ON CONFLICT(file_path) DO UPDATE SET
52258
+ source_date = excluded.source_date,
52259
+ size_bytes = excluded.size_bytes,
52260
+ mtime_ms = excluded.mtime_ms,
52261
+ indexed_at = excluded.indexed_at
52262
+ `);
52263
+ }
52264
+ dispose() {
52265
+ if (this.flushTimer) {
52266
+ clearTimeout(this.flushTimer);
52267
+ this.flushTimer = null;
52268
+ }
52269
+ this.queue = [];
52270
+ this.disposed = true;
52271
+ this.db.close();
52272
+ }
52273
+ enqueue(item) {
52274
+ if (this.disposed)
52275
+ return;
52276
+ if (this.queue.length >= MAX_INDEX_QUEUE) {
52277
+ const dropped = this.queue.shift();
52278
+ if (dropped) {
52279
+ this.dirtyFiles.add(dropped.filePath);
52280
+ }
52281
+ }
52282
+ this.queue.push(item);
52283
+ if (!this.flushTimer) {
52284
+ this.flushTimer = setTimeout(() => {
52285
+ this.flushTimer = null;
52286
+ this.flushQueue();
52287
+ }, INDEX_FLUSH_DELAY_MS);
52288
+ this.flushTimer.unref?.();
52289
+ }
52290
+ }
52291
+ async ensureRangeIndexed(fromMs, toMs) {
52292
+ let scannedFiles = 0;
52293
+ let scannedLines = 0;
52294
+ let parseErrors = 0;
52295
+ const eventsDir = join4(this.baseDir, "events");
52296
+ const dates = listDateStrings2(fromMs, toMs);
52297
+ for (const date5 of dates) {
52298
+ const filePath = join4(eventsDir, `${date5}.jsonl`);
52299
+ if (!existsSync3(filePath))
52300
+ continue;
52301
+ const stats = statSync(filePath);
52302
+ const fileRow = this.db.query("SELECT size_bytes, mtime_ms FROM log_index_files WHERE file_path = ?").get(filePath);
52303
+ const sizeBytes = stats.size;
52304
+ const mtimeMs = Math.trunc(stats.mtimeMs);
52305
+ if (!this.dirtyFiles.has(filePath) && fileRow && fileRow.size_bytes === sizeBytes && fileRow.mtime_ms === mtimeMs) {
52306
+ continue;
52307
+ }
52308
+ const result = await this.rebuildFile(filePath, date5, sizeBytes, mtimeMs);
52309
+ scannedFiles += 1;
52310
+ scannedLines += result.scannedLines;
52311
+ parseErrors += result.parseErrors;
52312
+ }
52313
+ return { scannedFiles, scannedLines, parseErrors };
52314
+ }
52315
+ queryEvents(query, options = {}) {
52316
+ const startedAt = performance.now();
52317
+ const queryHash = hashQuery(query);
52318
+ const decodedCursor = query.cursor ? decodeCursor(query.cursor) : null;
52319
+ if (decodedCursor) {
52320
+ if (decodedCursor.sort !== query.sort || decodedCursor.queryHash !== queryHash) {
52321
+ throw new Error("cursor \u4E0E\u5F53\u524D\u67E5\u8BE2\u6761\u4EF6\u4E0D\u5339\u914D");
52322
+ }
52323
+ }
52324
+ const { whereSql, params, usesFts } = buildWhereClause(query, options);
52325
+ const cursorClause = decodedCursor ? query.sort === "time_desc" ? "AND (e.ts_ms < ? OR (e.ts_ms = ? AND e.id < ?))" : "AND (e.ts_ms > ? OR (e.ts_ms = ? AND e.id > ?))" : "";
52326
+ const cursorParams = decodedCursor ? [decodedCursor.tsMs, decodedCursor.tsMs, decodedCursor.id] : [];
52327
+ const orderSql = query.sort === "time_desc" ? "ORDER BY e.ts_ms DESC, e.id DESC" : "ORDER BY e.ts_ms ASC, e.id ASC";
52328
+ const limit = Math.max(1, query.limit);
52329
+ let rows;
52330
+ try {
52331
+ rows = this.db.query(`
52332
+ SELECT
52333
+ e.id, e.ts_start, e.level, e.provider, e.route_type, e.model, e.model_in,
52334
+ e.model_out, e.path, e.request_id, e.latency_ms, e.upstream_status,
52335
+ e.status_class, e.has_error, e.message, e.error_type, e.has_metadata,
52336
+ e.user_id_raw, e.user_key, e.session_id, e.ts_ms
52337
+ FROM log_events e
52338
+ ${whereSql}
52339
+ ${cursorClause}
52340
+ ${orderSql}
52341
+ LIMIT ?
52342
+ `).all(...params, ...cursorParams, limit + 1);
52343
+ } catch (err) {
52344
+ if (!usesFts)
52345
+ throw err;
52346
+ const fallback = this.queryEvents(query, { forceLikeSearch: true });
52347
+ return {
52348
+ ...fallback,
52349
+ meta: {
52350
+ ...fallback.meta,
52351
+ usesFts: false,
52352
+ fallbackReason: `FTS \u67E5\u8BE2\u5931\u8D25\uFF0C\u5DF2\u9000\u56DE\u7D22\u5F15 LIKE/\u5185\u5B58\u8FC7\u6EE4: ${err instanceof Error ? err.message : String(err)}`
52353
+ }
52354
+ };
52355
+ }
52356
+ const pageRows = rows.slice(0, limit);
52357
+ const hasMore = rows.length > limit;
52358
+ const lastRow = pageRows[pageRows.length - 1];
52359
+ const stats = this.queryStats(whereSql, params);
52360
+ return {
52361
+ items: pageRows.map(rowToSummary),
52362
+ nextCursor: hasMore && lastRow ? encodeCursor({
52363
+ v: 2,
52364
+ sort: query.sort,
52365
+ tsMs: lastRow.ts_ms,
52366
+ id: lastRow.id,
52367
+ queryHash
52368
+ }) : null,
52369
+ hasMore,
52370
+ stats,
52371
+ meta: {
52372
+ scannedFiles: 0,
52373
+ scannedLines: 0,
52374
+ parseErrors: 0,
52375
+ truncated: false,
52376
+ indexUsed: true,
52377
+ indexFresh: true,
52378
+ usesFts,
52379
+ queryMs: Math.round((performance.now() - startedAt) * 100) / 100,
52380
+ rowsReturned: pageRows.length,
52381
+ statsMode: "exact"
52382
+ }
52383
+ };
52384
+ }
52385
+ getEventRecordByOffsetId(id) {
52386
+ const parsedId = decodeOffsetLogEventId(id);
52387
+ if (!parsedId)
52388
+ return null;
52389
+ const row = this.db.query("SELECT source_date, source_file, line_number, byte_offset FROM log_events WHERE id = ?").get(id);
52390
+ const filePath = row?.source_file ?? join4(this.baseDir, "events", `${parsedId.date}.jsonl`);
52391
+ if (!existsSync3(filePath))
52392
+ return null;
52393
+ const line2 = readLineAtOffset(filePath, row?.byte_offset ?? parsedId.offset);
52394
+ if (!line2?.trim())
52395
+ return null;
52396
+ const event = JSON.parse(line2);
52397
+ return {
52398
+ event,
52399
+ location: {
52400
+ id,
52401
+ date: row?.source_date ?? parsedId.date,
52402
+ file: filePath,
52403
+ line: row?.line_number ?? null,
52404
+ offset: row?.byte_offset ?? parsedId.offset
52405
+ }
52406
+ };
52407
+ }
52408
+ configure() {
52409
+ this.db.exec(`
52410
+ PRAGMA journal_mode = WAL;
52411
+ PRAGMA synchronous = NORMAL;
52412
+ PRAGMA temp_store = MEMORY;
52413
+ PRAGMA busy_timeout = 3000;
52414
+ PRAGMA foreign_keys = ON;
52415
+ `);
52416
+ }
52417
+ migrate() {
52418
+ this.db.exec(`
52419
+ CREATE TABLE IF NOT EXISTS log_index_meta (
52420
+ key TEXT PRIMARY KEY,
52421
+ value TEXT NOT NULL
52422
+ );
52423
+
52424
+ CREATE TABLE IF NOT EXISTS log_index_files (
52425
+ file_path TEXT PRIMARY KEY,
52426
+ source_date TEXT NOT NULL,
52427
+ size_bytes INTEGER NOT NULL,
52428
+ mtime_ms INTEGER NOT NULL,
52429
+ indexed_at INTEGER NOT NULL
52430
+ );
52431
+
52432
+ CREATE TABLE IF NOT EXISTS log_events (
52433
+ id TEXT PRIMARY KEY,
52434
+ ts_ms INTEGER NOT NULL,
52435
+ ts_start TEXT NOT NULL,
52436
+ level TEXT NOT NULL,
52437
+ provider TEXT NOT NULL,
52438
+ route_type TEXT NOT NULL,
52439
+ model TEXT NOT NULL,
52440
+ model_in TEXT NOT NULL,
52441
+ model_out TEXT NOT NULL,
52442
+ path TEXT NOT NULL,
52443
+ request_id TEXT NOT NULL,
52444
+ latency_ms INTEGER NOT NULL,
52445
+ upstream_status INTEGER NOT NULL,
52446
+ status_class TEXT NOT NULL,
52447
+ has_error INTEGER NOT NULL,
52448
+ message TEXT NOT NULL,
52449
+ error_type TEXT,
52450
+ has_metadata INTEGER NOT NULL,
52451
+ user_id_raw TEXT,
52452
+ user_key TEXT,
52453
+ session_id TEXT,
52454
+ source_date TEXT NOT NULL,
52455
+ source_file TEXT NOT NULL,
52456
+ line_number INTEGER,
52457
+ byte_offset INTEGER NOT NULL,
52458
+ byte_length INTEGER NOT NULL,
52459
+ search_text TEXT NOT NULL
52460
+ );
52461
+
52462
+ CREATE VIRTUAL TABLE IF NOT EXISTS log_events_fts
52463
+ USING fts5(event_id UNINDEXED, search_text);
52464
+
52465
+ CREATE INDEX IF NOT EXISTS idx_log_events_time_desc ON log_events(ts_ms DESC, id DESC);
52466
+ CREATE INDEX IF NOT EXISTS idx_log_events_time_asc ON log_events(ts_ms ASC, id ASC);
52467
+ CREATE INDEX IF NOT EXISTS idx_log_events_level_time ON log_events(level, ts_ms DESC);
52468
+ CREATE INDEX IF NOT EXISTS idx_log_events_provider_time ON log_events(provider, ts_ms DESC);
52469
+ CREATE INDEX IF NOT EXISTS idx_log_events_route_time ON log_events(route_type, ts_ms DESC);
52470
+ CREATE INDEX IF NOT EXISTS idx_log_events_model_time ON log_events(model, ts_ms DESC);
52471
+ CREATE INDEX IF NOT EXISTS idx_log_events_status_time ON log_events(status_class, ts_ms DESC);
52472
+ CREATE INDEX IF NOT EXISTS idx_log_events_error_time ON log_events(has_error, ts_ms DESC);
52473
+ CREATE INDEX IF NOT EXISTS idx_log_events_user_time ON log_events(user_key, ts_ms DESC);
52474
+ CREATE INDEX IF NOT EXISTS idx_log_events_session_time ON log_events(session_id, ts_ms DESC);
52475
+ CREATE INDEX IF NOT EXISTS idx_log_events_file ON log_events(source_file);
52476
+ `);
52477
+ this.db.prepare(`
52478
+ INSERT INTO log_index_meta(key, value)
52479
+ VALUES ('schema_version', ?)
52480
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
52481
+ `).run(String(SCHEMA_VERSION));
52482
+ }
52483
+ flushQueue() {
52484
+ if (this.queue.length === 0 || this.disposed)
52485
+ return;
52486
+ const batch = this.queue.splice(0, INDEX_BATCH_SIZE);
52487
+ const transaction = this.db.transaction((items) => {
52488
+ for (const item of items) {
52489
+ this.insertQueueItem(item);
52490
+ }
52491
+ });
52492
+ try {
52493
+ transaction(batch);
52494
+ } catch (err) {
52495
+ console.error("[log-index] \u589E\u91CF\u7D22\u5F15\u5199\u5165\u5931\u8D25:", err);
52496
+ }
52497
+ if (this.queue.length > 0 && !this.flushTimer) {
52498
+ this.flushTimer = setTimeout(() => {
52499
+ this.flushTimer = null;
52500
+ this.flushQueue();
52501
+ }, INDEX_FLUSH_DELAY_MS);
52502
+ this.flushTimer.unref?.();
52503
+ }
52504
+ }
52505
+ insertQueueItem(item) {
52506
+ if (this.rebuildingFiles.has(item.filePath))
52507
+ return;
52508
+ const id = encodeOffsetLogEventId(item.date, item.offset);
52509
+ const row = eventToRow({
52510
+ id,
52511
+ date: item.date,
52512
+ filePath: item.filePath,
52513
+ lineNumber: null,
52514
+ offset: item.offset,
52515
+ byteLength: item.byteLength,
52516
+ event: item.event
52517
+ });
52518
+ if (!row)
52519
+ return;
52520
+ this.insertEventStmt.run(row);
52521
+ this.deleteFtsStmt.run(id);
52522
+ this.insertFtsStmt.run(id, row.search_text);
52523
+ if (!this.dirtyFiles.has(item.filePath)) {
52524
+ try {
52525
+ const stats = statSync(item.filePath);
52526
+ const indexedThrough = item.offset + item.byteLength;
52527
+ this.upsertFileStmt.run(item.filePath, item.date, Math.min(indexedThrough, stats.size), Math.trunc(stats.mtimeMs), Date.now());
52528
+ } catch {}
52529
+ }
52530
+ }
52531
+ async rebuildFile(filePath, date5, sizeBytes, mtimeMs) {
52532
+ if (this.rebuildingFiles.has(filePath)) {
52533
+ return { scannedLines: 0, parseErrors: 0 };
52534
+ }
52535
+ this.rebuildingFiles.add(filePath);
52536
+ let scannedLines = 0;
52537
+ let parseErrors = 0;
52538
+ const rows = [];
52539
+ try {
52540
+ for await (const item of readJsonlLinesWithOffsets(filePath)) {
52541
+ scannedLines += 1;
52542
+ if (!item.line.trim())
52543
+ continue;
52544
+ let event;
52545
+ try {
52546
+ event = JSON.parse(item.line);
52547
+ } catch {
52548
+ parseErrors += 1;
52549
+ continue;
52550
+ }
52551
+ const row = eventToRow({
52552
+ id: encodeOffsetLogEventId(date5, item.offset),
52553
+ date: date5,
52554
+ filePath,
52555
+ lineNumber: item.lineNumber,
52556
+ offset: item.offset,
52557
+ byteLength: item.byteLength,
52558
+ event
52559
+ });
52560
+ if (row)
52561
+ rows.push(row);
52562
+ }
52563
+ const transaction = this.db.transaction((eventRows) => {
52564
+ this.db.prepare("DELETE FROM log_events_fts WHERE event_id IN (SELECT id FROM log_events WHERE source_file = ?)").run(filePath);
52565
+ this.db.prepare("DELETE FROM log_events WHERE source_file = ?").run(filePath);
52566
+ for (const row of eventRows) {
52567
+ this.insertEventStmt.run(row);
52568
+ this.insertFtsStmt.run(row.id, row.search_text);
52569
+ }
52570
+ this.upsertFileStmt.run(filePath, date5, sizeBytes, mtimeMs, Date.now());
52571
+ });
52572
+ transaction(rows);
52573
+ this.dirtyFiles.delete(filePath);
52574
+ return { scannedLines, parseErrors };
52575
+ } finally {
52576
+ this.rebuildingFiles.delete(filePath);
52577
+ }
52578
+ }
52579
+ queryStats(whereSql, params) {
52580
+ const aggregate = this.db.query(`
52581
+ SELECT
52582
+ COUNT(*) AS total,
52583
+ COALESCE(SUM(has_error), 0) AS errorCount,
52584
+ COALESCE(AVG(latency_ms), 0) AS avgLatencyMs
52585
+ FROM log_events e
52586
+ ${whereSql}
52587
+ `).get(...params);
52588
+ const total = Number(aggregate.total) || 0;
52589
+ if (total <= 0)
52590
+ return createEmptyStats();
52591
+ const p95Offset = Math.max(0, Math.ceil(total * 0.95) - 1);
52592
+ const p95Row = this.db.query(`
52593
+ SELECT latency_ms
52594
+ FROM log_events e
52595
+ ${whereSql}
52596
+ ORDER BY latency_ms ASC
52597
+ LIMIT 1 OFFSET ?
52598
+ `).get(...params, p95Offset);
52599
+ const errorCount = Number(aggregate.errorCount) || 0;
52600
+ return {
52601
+ total,
52602
+ errorCount,
52603
+ errorRate: toPercent2(errorCount, total),
52604
+ avgLatencyMs: Math.round(Number(aggregate.avgLatencyMs) || 0),
52605
+ p95LatencyMs: Math.round(p95Row?.latency_ms ?? 0)
52606
+ };
52607
+ }
52608
+ }
52609
+ function initLogIndex(baseDir, config2) {
52610
+ disposeLogIndex();
52611
+ if (config2?.enabled === false)
52612
+ return;
52613
+ try {
52614
+ singleton = new LogIndex(baseDir, config2);
52615
+ } catch (err) {
52616
+ singleton = null;
52617
+ console.error("[log-index] SQLite \u7D22\u5F15\u521D\u59CB\u5316\u5931\u8D25\uFF0C\u5C06\u9000\u56DE JSONL \u626B\u63CF:", err);
52618
+ }
52619
+ }
52620
+ function disposeLogIndex() {
52621
+ if (!singleton)
52622
+ return;
52623
+ try {
52624
+ singleton.dispose();
52625
+ } catch (err) {
52626
+ console.error("[log-index] SQLite \u7D22\u5F15\u5173\u95ED\u5931\u8D25:", err);
52627
+ } finally {
52628
+ singleton = null;
52629
+ }
52630
+ }
52631
+ function getLogIndex(baseDir) {
52632
+ if (!singleton)
52633
+ return null;
52634
+ if (baseDir && singleton.baseDir !== baseDir)
52635
+ return null;
52636
+ return singleton;
52637
+ }
52638
+ function enqueueLogEventForIndex(input) {
52639
+ const index = getLogIndex(input.baseDir);
52640
+ index?.enqueue(input);
52641
+ }
52642
+ async function queryIndexedLogEvents(logConfig, query) {
52643
+ if (!logConfig || logConfig.enabled === false)
52644
+ return createEmptyQueryResult(query);
52645
+ const baseDir = resolveLogBaseDir(logConfig);
52646
+ const index = getLogIndex(baseDir);
52647
+ if (!index)
52648
+ return null;
52649
+ try {
52650
+ const freshness = await index.ensureRangeIndexed(query.fromMs, query.toMs);
52651
+ const result = index.queryEvents(query);
52652
+ return {
52653
+ ...result,
52654
+ meta: {
52655
+ ...result.meta,
52656
+ scannedFiles: freshness.scannedFiles,
52657
+ scannedLines: freshness.scannedLines,
52658
+ parseErrors: freshness.parseErrors
52659
+ }
52660
+ };
52661
+ } catch (err) {
52662
+ if (err instanceof Error && err.message.includes("cursor")) {
52663
+ throw err;
52664
+ }
52665
+ return {
52666
+ ...createEmptyQueryResult(query, {
52667
+ indexUsed: false,
52668
+ indexFresh: false,
52669
+ fallbackReason: err instanceof Error ? err.message : String(err),
52670
+ statsMode: "none"
52671
+ })
52672
+ };
52673
+ }
52674
+ }
52675
+ function getIndexedLogEventDetail(logConfig, id) {
52676
+ if (!logConfig || logConfig.enabled === false)
52677
+ return null;
52678
+ const baseDir = resolveLogBaseDir(logConfig);
52679
+ const indexed = getLogIndex(baseDir)?.getEventRecordByOffsetId(id);
52680
+ if (indexed)
52681
+ return indexed;
52682
+ const parsedId = decodeOffsetLogEventId(id);
52683
+ if (!parsedId)
52684
+ return null;
52685
+ const filePath = join4(baseDir, "events", `${parsedId.date}.jsonl`);
52686
+ if (!existsSync3(filePath))
52687
+ return null;
52688
+ const line2 = readLineAtOffset(filePath, parsedId.offset);
52689
+ if (!line2?.trim())
52690
+ return null;
52691
+ return {
52692
+ event: JSON.parse(line2),
52693
+ location: {
52694
+ id,
52695
+ date: parsedId.date,
52696
+ file: filePath,
52697
+ line: null,
52698
+ offset: parsedId.offset
52699
+ }
52700
+ };
52701
+ }
52702
+
51819
52703
  // src/log-query.ts
51820
52704
  var WINDOW_MS2 = {
51821
52705
  "1h": 60 * 60 * 1000,
@@ -51827,49 +52711,57 @@ var MAX_QUERY_LIMIT = 200;
51827
52711
  var DEFAULT_QUERY_LIMIT = 50;
51828
52712
  var MAX_EXPORT_ROWS = 5000;
51829
52713
  var MAX_Q_LENGTH = 200;
51830
- function encodeBase64Url(value) {
52714
+ function encodeBase64Url2(value) {
51831
52715
  return Buffer.from(value, "utf-8").toString("base64url");
51832
52716
  }
51833
- function decodeBase64Url(value) {
52717
+ function decodeBase64Url2(value) {
51834
52718
  return Buffer.from(value, "base64url").toString("utf-8");
51835
52719
  }
51836
- function encodeCursor(data) {
51837
- return encodeBase64Url(JSON.stringify(data));
52720
+ function encodeCursor2(data) {
52721
+ return encodeBase64Url2(JSON.stringify(data));
51838
52722
  }
51839
- function decodeCursor(raw2) {
51840
- const parsed = JSON.parse(decodeBase64Url(raw2));
52723
+ function decodeCursor2(raw2) {
52724
+ const parsed = JSON.parse(decodeBase64Url2(raw2));
51841
52725
  if (!Number.isInteger(parsed.offset) || parsed.offset < 0) {
51842
52726
  throw new Error("cursor \u975E\u6CD5");
51843
52727
  }
51844
52728
  return parsed;
51845
52729
  }
52730
+ function isLegacyOffsetCursor(raw2) {
52731
+ try {
52732
+ const parsed = JSON.parse(decodeBase64Url2(raw2));
52733
+ return Number.isInteger(parsed.offset) && Number(parsed.offset) >= 0;
52734
+ } catch {
52735
+ return false;
52736
+ }
52737
+ }
51846
52738
  function encodeEventId(date5, line2) {
51847
- return encodeBase64Url(JSON.stringify({ d: date5, l: line2 }));
52739
+ return encodeBase64Url2(JSON.stringify({ d: date5, l: line2 }));
51848
52740
  }
51849
52741
  function decodeEventId(id) {
51850
- const parsed = JSON.parse(decodeBase64Url(id));
52742
+ const parsed = JSON.parse(decodeBase64Url2(id));
51851
52743
  if (typeof parsed.d !== "string" || !Number.isInteger(parsed.l) || Number(parsed.l) <= 0) {
51852
52744
  throw new Error("id \u975E\u6CD5");
51853
52745
  }
51854
52746
  return { date: parsed.d, line: Number(parsed.l) };
51855
52747
  }
51856
- function toPercent2(numerator, denominator) {
52748
+ function toPercent3(numerator, denominator) {
51857
52749
  if (denominator <= 0)
51858
52750
  return 0;
51859
52751
  return Number((numerator / denominator * 100).toFixed(2));
51860
52752
  }
51861
- function toDayStart2(ms) {
52753
+ function toDayStart3(ms) {
51862
52754
  const date5 = new Date(ms);
51863
52755
  return Date.UTC(date5.getUTCFullYear(), date5.getUTCMonth(), date5.getUTCDate());
51864
52756
  }
51865
- function listDateStrings2(fromMs, toMs) {
52757
+ function listDateStrings3(fromMs, toMs) {
51866
52758
  const result = [];
51867
- for (let day = toDayStart2(fromMs);day <= toDayStart2(toMs); day += 24 * 60 * 60 * 1000) {
52759
+ for (let day = toDayStart3(fromMs);day <= toDayStart3(toMs); day += 24 * 60 * 60 * 1000) {
51868
52760
  result.push(new Date(day).toISOString().slice(0, 10));
51869
52761
  }
51870
52762
  return result;
51871
52763
  }
51872
- function getStatusClass2(event) {
52764
+ function getStatusClass3(event) {
51873
52765
  if (event.error_type)
51874
52766
  return "network_error";
51875
52767
  const status = event.upstream_status ?? 0;
@@ -51881,16 +52773,16 @@ function getStatusClass2(event) {
51881
52773
  return "5xx";
51882
52774
  return "network_error";
51883
52775
  }
51884
- function isErrorEvent2(event) {
52776
+ function isErrorEvent3(event) {
51885
52777
  if (event.error_type)
51886
52778
  return true;
51887
52779
  const status = event.upstream_status ?? 0;
51888
52780
  return status < 200 || status >= 400;
51889
52781
  }
51890
- function getLevel(event) {
51891
- return isErrorEvent2(event) ? "error" : "info";
52782
+ function getLevel2(event) {
52783
+ return isErrorEvent3(event) ? "error" : "info";
51892
52784
  }
51893
- function buildMessage(event) {
52785
+ function buildMessage2(event) {
51894
52786
  if (event.error_message)
51895
52787
  return event.error_message;
51896
52788
  if (event.error_type)
@@ -51915,7 +52807,7 @@ function containsKeyword(event, q) {
51915
52807
  identity.sessionId ?? "",
51916
52808
  event.error_type ?? "",
51917
52809
  event.error_message ?? "",
51918
- buildMessage(event)
52810
+ buildMessage2(event)
51919
52811
  ].join(" ").toLowerCase();
51920
52812
  return haystack.includes(keyword);
51921
52813
  }
@@ -51964,7 +52856,7 @@ function finalizeStats(stats) {
51964
52856
  return {
51965
52857
  total: stats.total,
51966
52858
  errorCount: stats.errorCount,
51967
- errorRate: toPercent2(stats.errorCount, stats.total),
52859
+ errorRate: toPercent3(stats.errorCount, stats.total),
51968
52860
  avgLatencyMs: Math.round(stats.latencySum / stats.total),
51969
52861
  p95LatencyMs: percentileFromCounts(stats.latencyCounts, stats.total, 0.95)
51970
52862
  };
@@ -51984,17 +52876,17 @@ function insertBoundedEvent(items, item, sort, maxKeep) {
51984
52876
  items.pop();
51985
52877
  }
51986
52878
  }
51987
- function clampLimit(limit) {
52879
+ function clampLimit(limit, maxLimit = MAX_QUERY_LIMIT) {
51988
52880
  if (!Number.isFinite(limit))
51989
52881
  return DEFAULT_QUERY_LIMIT;
51990
52882
  const integer2 = Math.floor(limit);
51991
52883
  if (integer2 <= 0)
51992
52884
  return DEFAULT_QUERY_LIMIT;
51993
- return Math.min(MAX_QUERY_LIMIT, integer2);
52885
+ return Math.min(maxLimit, integer2);
51994
52886
  }
51995
- function normalizeQuery(input) {
52887
+ function normalizeQuery(input, maxLimit = MAX_QUERY_LIMIT) {
51996
52888
  const sort = input.sort ?? "time_desc";
51997
- const limit = clampLimit(input.limit);
52889
+ const limit = clampLimit(input.limit, maxLimit);
51998
52890
  const qRaw = (input.q ?? "").trim();
51999
52891
  const q = qRaw.length > MAX_Q_LENGTH ? qRaw.slice(0, MAX_Q_LENGTH) : qRaw;
52000
52892
  return {
@@ -52034,7 +52926,7 @@ function eventToSummary(item) {
52034
52926
  upstreamStatus: event.upstream_status ?? 0,
52035
52927
  statusClass: item.statusClass,
52036
52928
  hasError: item.level === "error",
52037
- message: buildMessage(event),
52929
+ message: buildMessage2(event),
52038
52930
  errorType: event.error_type,
52039
52931
  hasMetadata: identity.hasMetadata,
52040
52932
  userIdRaw: identity.userIdRaw,
@@ -52042,6 +52934,57 @@ function eventToSummary(item) {
52042
52934
  sessionId: identity.sessionId
52043
52935
  };
52044
52936
  }
52937
+ function createLogEventSummaryFromEvent(event, location) {
52938
+ const ts = Date.parse(event.ts_start);
52939
+ return eventToSummary({
52940
+ id: location.id,
52941
+ date: location.date,
52942
+ line: location.line ?? 0,
52943
+ ts: Number.isFinite(ts) ? ts : 0,
52944
+ level: getLevel2(event),
52945
+ statusClass: getStatusClass3(event),
52946
+ event
52947
+ });
52948
+ }
52949
+ function logEventMatchesQuery(event, query) {
52950
+ if (!event.ts_start)
52951
+ return false;
52952
+ const ts = Date.parse(event.ts_start);
52953
+ if (!Number.isFinite(ts) || ts < query.fromMs || ts > query.toMs)
52954
+ return false;
52955
+ const level = getLevel2(event);
52956
+ const statusClass = getStatusClass3(event);
52957
+ if (query.levels.length > 0 && !query.levels.includes(level))
52958
+ return false;
52959
+ if (query.providers.length > 0 && !query.providers.includes(event.provider))
52960
+ return false;
52961
+ if (query.routeTypes.length > 0 && !query.routeTypes.includes(event.route_type))
52962
+ return false;
52963
+ const eventModel = event.model_out || event.model_in;
52964
+ if (query.models.length > 0 && !query.models.includes(eventModel))
52965
+ return false;
52966
+ if (query.modelIns.length > 0 && !query.modelIns.includes(event.model_in))
52967
+ return false;
52968
+ if (query.modelOuts.length > 0 && !query.modelOuts.includes(event.model_out))
52969
+ return false;
52970
+ const identity = resolveLogSessionIdentity(event.request_body);
52971
+ if (query.users.length > 0) {
52972
+ const matchedByRaw = identity.userIdRaw ? query.users.includes(identity.userIdRaw) : false;
52973
+ const matchedByUserKey = identity.userKey ? query.users.includes(identity.userKey) : false;
52974
+ if (!matchedByRaw && !matchedByUserKey)
52975
+ return false;
52976
+ }
52977
+ if (query.sessions.length > 0) {
52978
+ if (!identity.sessionId || !query.sessions.includes(identity.sessionId))
52979
+ return false;
52980
+ }
52981
+ if (query.statusClasses.length > 0 && !query.statusClasses.includes(statusClass))
52982
+ return false;
52983
+ const hasError = level === "error";
52984
+ if (query.hasError !== null && query.hasError !== hasError)
52985
+ return false;
52986
+ return containsKeyword(event, query.q);
52987
+ }
52045
52988
  function detectBodyPolicy(event) {
52046
52989
  const hasRequestBody = event.request_body !== undefined;
52047
52990
  const hasResponseBody = event.response_body !== undefined;
@@ -52095,17 +53038,17 @@ function readStreamContent(baseDir, streamFile) {
52095
53038
  if (!looksLikeStreamFile) {
52096
53039
  return { content: null, warning: "stream_file \u4E0D\u662F .sse.raw \u6587\u4EF6\uFF0C\u5DF2\u8DF3\u8FC7\u8BFB\u53D6\u3002" };
52097
53040
  }
52098
- if (existsSync3(resolvedFromFile)) {
52099
- return { content: readFileSync2(resolvedFromFile, "utf-8"), warning: null };
53041
+ if (existsSync4(resolvedFromFile)) {
53042
+ return { content: readFileSync3(resolvedFromFile, "utf-8"), warning: null };
52100
53043
  }
52101
53044
  const fallbackPath = resolve4(resolvedBase, streamFile);
52102
53045
  if (!fallbackPath.startsWith(`${resolvedBase}/`) && fallbackPath !== resolvedBase) {
52103
53046
  return { content: null, warning: "stream_file \u8DEF\u5F84\u975E\u6CD5\uFF0C\u5DF2\u62D2\u7EDD\u8BFB\u53D6\u3002" };
52104
53047
  }
52105
- if (!existsSync3(fallbackPath)) {
53048
+ if (!existsSync4(fallbackPath)) {
52106
53049
  return { content: null, warning: "stream_file \u4E0D\u5B58\u5728\uFF0C\u53EF\u80FD\u5DF2\u88AB\u6E05\u7406\u3002" };
52107
53050
  }
52108
- return { content: readFileSync2(fallbackPath, "utf-8"), warning: null };
53051
+ return { content: readFileSync3(fallbackPath, "utf-8"), warning: null };
52109
53052
  } catch (err) {
52110
53053
  return {
52111
53054
  content: null,
@@ -52115,8 +53058,8 @@ function readStreamContent(baseDir, streamFile) {
52115
53058
  }
52116
53059
  async function buildLogEventDetail(id, parsed, location, context2) {
52117
53060
  const event = parsed;
52118
- const level = getLevel(event);
52119
- const statusClass = getStatusClass2(event);
53061
+ const level = getLevel2(event);
53062
+ const statusClass = getStatusClass3(event);
52120
53063
  const bodyPolicy = detectBodyPolicy(event);
52121
53064
  const requestBodyAvailable = event.request_body !== undefined;
52122
53065
  const responseBodyAvailable = event.response_body !== undefined;
@@ -52188,8 +53131,8 @@ async function buildLogEventDetail(id, parsed, location, context2) {
52188
53131
  };
52189
53132
  }
52190
53133
  async function scanEvents(baseDir, query) {
52191
- const eventsDir = join4(baseDir, "events");
52192
- if (!existsSync3(eventsDir)) {
53134
+ const eventsDir = join5(baseDir, "events");
53135
+ if (!existsSync4(eventsDir)) {
52193
53136
  return {
52194
53137
  items: [],
52195
53138
  stats: {
@@ -52207,8 +53150,8 @@ async function scanEvents(baseDir, query) {
52207
53150
  }
52208
53151
  };
52209
53152
  }
52210
- const dates = listDateStrings2(query.fromMs, query.toMs);
52211
- const offset = query.cursor ? decodeCursor(query.cursor).offset : 0;
53153
+ const dates = listDateStrings3(query.fromMs, query.toMs);
53154
+ const offset = query.cursor ? decodeCursor2(query.cursor).offset : 0;
52212
53155
  const maxKeep = offset + query.limit;
52213
53156
  const items = [];
52214
53157
  const runningStats = createRunningStats();
@@ -52221,11 +53164,11 @@ async function scanEvents(baseDir, query) {
52221
53164
  truncated = true;
52222
53165
  break;
52223
53166
  }
52224
- const filePath = join4(eventsDir, `${date5}.jsonl`);
52225
- if (!existsSync3(filePath))
53167
+ const filePath = join5(eventsDir, `${date5}.jsonl`);
53168
+ if (!existsSync4(filePath))
52226
53169
  continue;
52227
53170
  scannedFiles += 1;
52228
- const stream = createReadStream2(filePath, { encoding: "utf-8" });
53171
+ const stream = createReadStream3(filePath, { encoding: "utf-8" });
52229
53172
  const rl = createInterface2({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
52230
53173
  let lineNumber = 0;
52231
53174
  for await (const line2 of rl) {
@@ -52251,8 +53194,8 @@ async function scanEvents(baseDir, query) {
52251
53194
  const ts = Date.parse(event.ts_start);
52252
53195
  if (!Number.isFinite(ts) || ts < query.fromMs || ts > query.toMs)
52253
53196
  continue;
52254
- const level = getLevel(event);
52255
- const statusClass = getStatusClass2(event);
53197
+ const level = getLevel2(event);
53198
+ const statusClass = getStatusClass3(event);
52256
53199
  if (query.levels.length > 0 && !query.levels.includes(level))
52257
53200
  continue;
52258
53201
  if (query.providers.length > 0 && !query.providers.includes(event.provider))
@@ -52342,6 +53285,9 @@ function validateSort(value) {
52342
53285
  return value === "time_desc" || value === "time_asc";
52343
53286
  }
52344
53287
  async function queryLogEvents(context2, input) {
53288
+ return queryLogEventsInternal(context2, input, MAX_QUERY_LIMIT);
53289
+ }
53290
+ async function queryLogEventsInternal(context2, input, maxLimit) {
52345
53291
  const logEnabled = !!context2.logConfig && context2.logConfig.enabled !== false;
52346
53292
  if (!logEnabled) {
52347
53293
  return {
@@ -52364,30 +53310,51 @@ async function queryLogEvents(context2, input) {
52364
53310
  };
52365
53311
  }
52366
53312
  const baseDir = resolveLogBaseDir(context2.logConfig);
52367
- const query = normalizeQuery(input);
52368
- const offset = query.cursor ? decodeCursor(query.cursor).offset : 0;
53313
+ const query = normalizeQuery(input, maxLimit);
53314
+ const indexed = await queryIndexedLogEvents(context2.logConfig, query);
53315
+ if (indexed?.meta.indexUsed) {
53316
+ return indexed;
53317
+ }
53318
+ if (query.cursor && !isLegacyOffsetCursor(query.cursor)) {
53319
+ throw new Error(`\u7D22\u5F15\u67E5\u8BE2\u5931\u8D25\uFF0C\u65E0\u6CD5\u4F7F\u7528\u7D22\u5F15 cursor \u56DE\u9000\u5230 JSONL\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u7B2C\u4E00\u9875${indexed?.meta.fallbackReason ? `: ${indexed.meta.fallbackReason}` : ""}`);
53320
+ }
53321
+ const offset = query.cursor ? decodeCursor2(query.cursor).offset : 0;
52369
53322
  const scanned = await scanEvents(baseDir, query);
52370
53323
  const pageItems = scanned.items.slice(offset, offset + query.limit);
52371
53324
  const hasMore = scanned.stats.total > offset + query.limit;
52372
53325
  const nextOffset = offset + pageItems.length;
52373
53326
  return {
52374
53327
  items: pageItems.map(eventToSummary),
52375
- nextCursor: hasMore ? encodeCursor({ offset: nextOffset }) : null,
53328
+ nextCursor: hasMore ? encodeCursor2({ offset: nextOffset }) : null,
52376
53329
  hasMore,
52377
53330
  stats: scanned.stats,
52378
- meta: scanned.meta
53331
+ meta: {
53332
+ ...scanned.meta,
53333
+ indexUsed: false,
53334
+ indexFresh: false,
53335
+ fallbackReason: indexed?.meta.fallbackReason,
53336
+ statsMode: "exact"
53337
+ }
52379
53338
  };
52380
53339
  }
52381
53340
  async function getLogEventDetailById(context2, id) {
52382
53341
  const logEnabled = !!context2.logConfig && context2.logConfig.enabled !== false;
52383
53342
  if (!logEnabled)
52384
53343
  return null;
53344
+ const indexed = getIndexedLogEventDetail(context2.logConfig, id);
53345
+ if (indexed) {
53346
+ return buildLogEventDetail(id, indexed.event, {
53347
+ date: indexed.location.date,
53348
+ line: indexed.location.line ?? 0,
53349
+ file: indexed.location.file
53350
+ }, context2);
53351
+ }
52385
53352
  const { date: date5, line: line2 } = decodeEventId(id);
52386
53353
  const baseDir = resolveLogBaseDir(context2.logConfig);
52387
- const filePath = join4(baseDir, "events", `${date5}.jsonl`);
52388
- if (!existsSync3(filePath))
53354
+ const filePath = join5(baseDir, "events", `${date5}.jsonl`);
53355
+ if (!existsSync4(filePath))
52389
53356
  return null;
52390
- const stream = createReadStream2(filePath, { encoding: "utf-8" });
53357
+ const stream = createReadStream3(filePath, { encoding: "utf-8" });
52391
53358
  const rl = createInterface2({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
52392
53359
  let lineNumber = 0;
52393
53360
  for await (const lineText of rl) {
@@ -52497,11 +53464,11 @@ function createJsonExportStream(data) {
52497
53464
  });
52498
53465
  }
52499
53466
  async function exportLogEvents(context2, input, format) {
52500
- const data = await queryLogEvents(context2, {
53467
+ const data = await queryLogEventsInternal(context2, {
52501
53468
  ...input,
52502
53469
  cursor: null,
52503
53470
  limit: MAX_EXPORT_ROWS
52504
- });
53471
+ }, MAX_EXPORT_ROWS);
52505
53472
  const now2 = new Date().toISOString().replace(/[:.]/g, "-");
52506
53473
  if (format === "csv") {
52507
53474
  return {
@@ -52540,18 +53507,18 @@ function parseBooleanFlag(value) {
52540
53507
  }
52541
53508
 
52542
53509
  // src/log-sessions.ts
52543
- import { createReadStream as createReadStream3, existsSync as existsSync4 } from "fs";
52544
- import { join as join5 } from "path";
53510
+ import { createReadStream as createReadStream4, existsSync as existsSync5 } from "fs";
53511
+ import { join as join6 } from "path";
52545
53512
  import { createInterface as createInterface3 } from "readline";
52546
53513
  var MAX_LINES_SCANNED3 = 250000;
52547
53514
  var MAX_Q_LENGTH2 = 200;
52548
- function toDayStart3(ms) {
53515
+ function toDayStart4(ms) {
52549
53516
  const date5 = new Date(ms);
52550
53517
  return Date.UTC(date5.getUTCFullYear(), date5.getUTCMonth(), date5.getUTCDate());
52551
53518
  }
52552
- function listDateStrings3(fromMs, toMs) {
53519
+ function listDateStrings4(fromMs, toMs) {
52553
53520
  const result = [];
52554
- for (let day = toDayStart3(fromMs);day <= toDayStart3(toMs); day += 24 * 60 * 60 * 1000) {
53521
+ for (let day = toDayStart4(fromMs);day <= toDayStart4(toMs); day += 24 * 60 * 60 * 1000) {
52555
53522
  result.push(new Date(day).toISOString().slice(0, 10));
52556
53523
  }
52557
53524
  return result;
@@ -52662,8 +53629,8 @@ async function queryLogSessions(context2, input) {
52662
53629
  return createEmptyResult(normalized.fromMs, normalized.toMs);
52663
53630
  }
52664
53631
  const baseDir = resolveLogBaseDir(context2.logConfig);
52665
- const eventsDir = join5(baseDir, "events");
52666
- if (!existsSync4(eventsDir)) {
53632
+ const eventsDir = join6(baseDir, "events");
53633
+ if (!existsSync5(eventsDir)) {
52667
53634
  return createEmptyResult(normalized.fromMs, normalized.toMs);
52668
53635
  }
52669
53636
  const usersMap = new Map;
@@ -52675,17 +53642,17 @@ async function queryLogSessions(context2, input) {
52675
53642
  let scannedLines = 0;
52676
53643
  let parseErrors = 0;
52677
53644
  let truncated = false;
52678
- const dateStrings = listDateStrings3(normalized.fromMs, normalized.toMs);
53645
+ const dateStrings = listDateStrings4(normalized.fromMs, normalized.toMs);
52679
53646
  for (const date5 of dateStrings) {
52680
53647
  if (scannedLines >= MAX_LINES_SCANNED3) {
52681
53648
  truncated = true;
52682
53649
  break;
52683
53650
  }
52684
- const filePath = join5(eventsDir, `${date5}.jsonl`);
52685
- if (!existsSync4(filePath))
53651
+ const filePath = join6(eventsDir, `${date5}.jsonl`);
53652
+ if (!existsSync5(filePath))
52686
53653
  continue;
52687
53654
  scannedFiles += 1;
52688
- const stream = createReadStream3(filePath, { encoding: "utf-8" });
53655
+ const stream = createReadStream4(filePath, { encoding: "utf-8" });
52689
53656
  const rl = createInterface3({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
52690
53657
  for await (const line2 of rl) {
52691
53658
  if (scannedLines >= MAX_LINES_SCANNED3) {
@@ -52790,8 +53757,8 @@ async function queryLogSessions(context2, input) {
52790
53757
  }
52791
53758
 
52792
53759
  // src/log-storage.ts
52793
- import { existsSync as existsSync5, promises as fsPromises } from "fs";
52794
- import { join as join6 } from "path";
53760
+ import { existsSync as existsSync6, promises as fsPromises } from "fs";
53761
+ import { join as join7 } from "path";
52795
53762
  var cachedStorage = null;
52796
53763
  var calculationPromise = null;
52797
53764
  var lastCalculationTime = 0;
@@ -52799,7 +53766,7 @@ var CACHE_TTL_MS2 = 60 * 60 * 1000;
52799
53766
  var CALCULATION_INTERVAL_MS = 60 * 60 * 1000;
52800
53767
  var MIN_CALCULATION_INTERVAL_MS = 5 * 60 * 1000;
52801
53768
  async function calculateDirSize(dirPath) {
52802
- if (!existsSync5(dirPath)) {
53769
+ if (!existsSync6(dirPath)) {
52803
53770
  return { bytes: 0, fileCount: 0 };
52804
53771
  }
52805
53772
  let bytes = 0;
@@ -52808,7 +53775,7 @@ async function calculateDirSize(dirPath) {
52808
53775
  try {
52809
53776
  const entries = await fsPromises.readdir(currentPath, { withFileTypes: true });
52810
53777
  for (const entry of entries) {
52811
- const fullPath = join6(currentPath, entry.name);
53778
+ const fullPath = join7(currentPath, entry.name);
52812
53779
  if (entry.isDirectory()) {
52813
53780
  await walk(fullPath);
52814
53781
  } else if (entry.isFile()) {
@@ -52831,6 +53798,7 @@ async function doCalculateStorage(logConfig) {
52831
53798
  totalBytes: 0,
52832
53799
  eventsBytes: 0,
52833
53800
  streamsBytes: 0,
53801
+ indexBytes: 0,
52834
53802
  fileCount: 0,
52835
53803
  lastUpdatedAt: new Date().toISOString(),
52836
53804
  isCalculating: false
@@ -52838,18 +53806,40 @@ async function doCalculateStorage(logConfig) {
52838
53806
  }
52839
53807
  const baseDir = resolveLogBaseDir(logConfig);
52840
53808
  const [eventsResult, streamsResult] = await Promise.all([
52841
- calculateDirSize(join6(baseDir, "events")),
52842
- calculateDirSize(join6(baseDir, "streams"))
53809
+ calculateDirSize(join7(baseDir, "events")),
53810
+ calculateDirSize(join7(baseDir, "streams"))
52843
53811
  ]);
53812
+ const indexResult = await calculateIndexSize(baseDir);
52844
53813
  return {
52845
- totalBytes: eventsResult.bytes + streamsResult.bytes,
53814
+ totalBytes: eventsResult.bytes + streamsResult.bytes + indexResult.bytes,
52846
53815
  eventsBytes: eventsResult.bytes,
52847
53816
  streamsBytes: streamsResult.bytes,
52848
- fileCount: eventsResult.fileCount + streamsResult.fileCount,
53817
+ indexBytes: indexResult.bytes,
53818
+ fileCount: eventsResult.fileCount + streamsResult.fileCount + indexResult.fileCount,
52849
53819
  lastUpdatedAt: new Date().toISOString(),
52850
53820
  isCalculating: false
52851
53821
  };
52852
53822
  }
53823
+ async function calculateIndexSize(baseDir) {
53824
+ if (!existsSync6(baseDir)) {
53825
+ return { bytes: 0, fileCount: 0 };
53826
+ }
53827
+ let bytes = 0;
53828
+ let fileCount = 0;
53829
+ try {
53830
+ const entries = await fsPromises.readdir(baseDir, { withFileTypes: true });
53831
+ for (const entry of entries) {
53832
+ if (!entry.isFile() || !entry.name.startsWith("logs-index.sqlite"))
53833
+ continue;
53834
+ const stats = await fsPromises.stat(join7(baseDir, entry.name));
53835
+ bytes += stats.size;
53836
+ fileCount += 1;
53837
+ }
53838
+ } catch {
53839
+ return { bytes, fileCount };
53840
+ }
53841
+ return { bytes, fileCount };
53842
+ }
52853
53843
  async function getLogStorageInfo(options) {
52854
53844
  const { logConfig, forceRefresh = false, nowMs = Date.now() } = options;
52855
53845
  if (!forceRefresh && cachedStorage && cachedStorage.expiresAt > nowMs) {
@@ -52892,10 +53882,25 @@ function startLogStorageBackgroundTask(logConfig) {
52892
53882
  };
52893
53883
  }
52894
53884
 
52895
- // src/logger.ts
52896
- import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
52897
- import { join as join7 } from "path";
53885
+ // src/log-tail.ts
53886
+ var subscribers = new Set;
53887
+ function publishLogEvent(event) {
53888
+ for (const subscriber of subscribers) {
53889
+ try {
53890
+ subscriber(event);
53891
+ } catch {}
53892
+ }
53893
+ }
53894
+ function subscribeLogEvents(subscriber) {
53895
+ subscribers.add(subscriber);
53896
+ return () => {
53897
+ subscribers.delete(subscriber);
53898
+ };
53899
+ }
52898
53900
 
53901
+ // src/logger.ts
53902
+ import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync3, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
53903
+ import { join as join8 } from "path";
52899
53904
  class Logger {
52900
53905
  baseDir;
52901
53906
  eventsDir;
@@ -52910,8 +53915,8 @@ class Logger {
52910
53915
  this._bodyPolicy = config2.bodyPolicy ?? "off";
52911
53916
  this._streamsEnabled = config2.streams?.enabled !== false;
52912
53917
  this.maxStreamBytes = config2.streams?.maxBytesPerRequest ?? 10 * 1024 * 1024;
52913
- this.eventsDir = join7(baseDir, "events");
52914
- this.streamsDir = join7(baseDir, "streams");
53918
+ this.eventsDir = join8(baseDir, "events");
53919
+ this.streamsDir = join8(baseDir, "streams");
52915
53920
  if (this._enabled)
52916
53921
  this.ensureDirs();
52917
53922
  }
@@ -52923,14 +53928,14 @@ class Logger {
52923
53928
  }
52924
53929
  ensureDirs() {
52925
53930
  for (const dir of [this.baseDir, this.eventsDir, this.streamsDir]) {
52926
- if (!existsSync6(dir))
52927
- mkdirSync2(dir, { recursive: true });
53931
+ if (!existsSync7(dir))
53932
+ mkdirSync3(dir, { recursive: true });
52928
53933
  }
52929
53934
  }
52930
53935
  ensureStreamDateDir(dateStr) {
52931
- const dir = join7(this.streamsDir, dateStr);
52932
- if (!existsSync6(dir))
52933
- mkdirSync2(dir, { recursive: true });
53936
+ const dir = join8(this.streamsDir, dateStr);
53937
+ if (!existsSync7(dir))
53938
+ mkdirSync3(dir, { recursive: true });
52934
53939
  return dir;
52935
53940
  }
52936
53941
  writeEvent(event) {
@@ -52939,9 +53944,21 @@ class Logger {
52939
53944
  try {
52940
53945
  this.ensureDirs();
52941
53946
  const dateStr = event.ts_start.slice(0, 10);
52942
- const filePath = join7(this.eventsDir, `${dateStr}.jsonl`);
52943
- appendFileSync(filePath, `${JSON.stringify(event)}
52944
- `);
53947
+ const filePath = join8(this.eventsDir, `${dateStr}.jsonl`);
53948
+ const offset = existsSync7(filePath) ? statSync2(filePath).size : 0;
53949
+ const line2 = `${JSON.stringify(event)}
53950
+ `;
53951
+ appendFileSync(filePath, line2);
53952
+ const id = encodeOffsetLogEventId(dateStr, offset);
53953
+ enqueueLogEventForIndex({
53954
+ baseDir: this.baseDir,
53955
+ filePath,
53956
+ date: dateStr,
53957
+ offset,
53958
+ byteLength: Buffer.byteLength(line2),
53959
+ event
53960
+ });
53961
+ publishLogEvent({ id, date: dateStr, filePath, offset, event });
52945
53962
  } catch (err) {
52946
53963
  console.error("[logger] \u4E8B\u4EF6\u65E5\u5FD7\u5199\u5165\u5931\u8D25:", err);
52947
53964
  }
@@ -52951,7 +53968,7 @@ class Logger {
52951
53968
  return null;
52952
53969
  try {
52953
53970
  const dir = this.ensureStreamDateDir(dateStr);
52954
- const filePath = join7(dir, `${requestId}.sse.raw`);
53971
+ const filePath = join8(dir, `${requestId}.sse.raw`);
52955
53972
  const toWrite = content.length > this.maxStreamBytes ? `${content.slice(0, this.maxStreamBytes)}
52956
53973
  [TRUNCATED]` : content;
52957
53974
  writeFileSync3(filePath, toWrite);
@@ -52965,6 +53982,7 @@ class Logger {
52965
53982
  var instance = null;
52966
53983
  function initLogger(baseDir, config2) {
52967
53984
  instance = new Logger(baseDir, config2);
53985
+ initLogIndex(baseDir, config2);
52968
53986
  if (instance.enabled) {
52969
53987
  console.log(`[logger] \u65E5\u5FD7\u7CFB\u7EDF\u5DF2\u521D\u59CB\u5316: ${baseDir}`);
52970
53988
  }
@@ -52974,6 +53992,7 @@ function getLogger() {
52974
53992
  }
52975
53993
  function resetLogger() {
52976
53994
  instance = null;
53995
+ disposeLogIndex();
52977
53996
  }
52978
53997
  function collectHeaders(headers) {
52979
53998
  const result = {};
@@ -52994,6 +54013,85 @@ function normalizeUrl(rawUrl) {
52994
54013
  return rawUrl;
52995
54014
  }
52996
54015
 
54016
+ // src/network-access.ts
54017
+ var REMOTE_ADDRESS_ENV_KEY = "LOCAL_ROUTER_REMOTE_ADDRESS";
54018
+ function parseIpv4(address) {
54019
+ const parts = address.split(".");
54020
+ if (parts.length !== 4)
54021
+ return null;
54022
+ const octets = parts.map((part) => {
54023
+ if (!/^\d{1,3}$/.test(part))
54024
+ return Number.NaN;
54025
+ const value = Number.parseInt(part, 10);
54026
+ return value >= 0 && value <= 255 ? value : Number.NaN;
54027
+ });
54028
+ return octets.every(Number.isFinite) ? octets : null;
54029
+ }
54030
+ function normalizeIpAddress(raw2) {
54031
+ let address = raw2.trim().toLowerCase();
54032
+ if (address.startsWith("[")) {
54033
+ const end = address.indexOf("]");
54034
+ if (end !== -1)
54035
+ address = address.slice(1, end);
54036
+ }
54037
+ const mappedIpv4 = address.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/);
54038
+ if (mappedIpv4) {
54039
+ return mappedIpv4[1];
54040
+ }
54041
+ return address;
54042
+ }
54043
+ function isLoopbackAddress(raw2) {
54044
+ if (!raw2)
54045
+ return false;
54046
+ const address = normalizeIpAddress(raw2);
54047
+ const ipv43 = parseIpv4(address);
54048
+ if (ipv43)
54049
+ return ipv43[0] === 127;
54050
+ return address === "::1";
54051
+ }
54052
+ function isLanAddress(raw2) {
54053
+ if (!raw2)
54054
+ return false;
54055
+ const address = normalizeIpAddress(raw2);
54056
+ const ipv43 = parseIpv4(address);
54057
+ if (ipv43) {
54058
+ const [a, b] = ipv43;
54059
+ return a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
54060
+ }
54061
+ return address.startsWith("fc") || address.startsWith("fd") || /^fe[89ab]/.test(address);
54062
+ }
54063
+ function decideNetworkAccess(serverConfig, rawRemoteAddress) {
54064
+ const remoteAddress = rawRemoteAddress ? normalizeIpAddress(rawRemoteAddress) : null;
54065
+ if (!remoteAddress || isLoopbackAddress(remoteAddress)) {
54066
+ return { allowed: true, remoteAddress };
54067
+ }
54068
+ const lanEnabled = serverConfig?.lanAccess?.enabled === true;
54069
+ if (!lanEnabled) {
54070
+ return { allowed: false, remoteAddress, reason: "lan-disabled" };
54071
+ }
54072
+ if (!isLanAddress(remoteAddress)) {
54073
+ return { allowed: false, remoteAddress, reason: "non-lan-address" };
54074
+ }
54075
+ return { allowed: true, remoteAddress };
54076
+ }
54077
+ function getRemoteAddressFromContext(c2) {
54078
+ const env = c2.env;
54079
+ const value = env?.[REMOTE_ADDRESS_ENV_KEY];
54080
+ return typeof value === "string" && value.trim() ? value : null;
54081
+ }
54082
+ function createNetworkAccessMiddleware(store) {
54083
+ return async (c2, next) => {
54084
+ const decision = decideNetworkAccess(store.get().server, getRemoteAddressFromContext(c2));
54085
+ if (!decision.allowed) {
54086
+ return c2.json({
54087
+ error: decision.reason === "lan-disabled" ? "\u5C40\u57DF\u7F51\u670D\u52A1\u672A\u5F00\u542F\uFF0C\u5DF2\u62D2\u7EDD\u975E\u672C\u673A\u8BF7\u6C42" : "\u4EC5\u5141\u8BB8\u672C\u673A\u6216\u5C40\u57DF\u7F51\u6765\u6E90\u8BBF\u95EE",
54088
+ remoteAddress: decision.remoteAddress
54089
+ }, 403);
54090
+ }
54091
+ await next();
54092
+ };
54093
+ }
54094
+
52997
54095
  // src/openapi.ts
52998
54096
  var openAPISpec = {
52999
54097
  openapi: "3.0.0",
@@ -53442,7 +54540,9 @@ var openAPISpec = {
53442
54540
  type: "object",
53443
54541
  additionalProperties: { type: "string" }
53444
54542
  },
53445
- requestBody: { type: ["object", "array", "string", "number", "boolean", "null"] }
54543
+ requestBody: {
54544
+ type: ["object", "array", "string", "number", "boolean", "null"]
54545
+ }
53446
54546
  }
53447
54547
  },
53448
54548
  response: {
@@ -54096,9 +55196,9 @@ var openAPISpec = {
54096
55196
  };
54097
55197
 
54098
55198
  // src/plugin-loader.ts
54099
- import { resolve as resolve5, join as join8 } from "path";
55199
+ import { mkdtemp, rm, writeFile } from "fs/promises";
54100
55200
  import { tmpdir } from "os";
54101
- import { mkdtemp, writeFile, rm } from "fs/promises";
55201
+ import { join as join9, resolve as resolve5 } from "path";
54102
55202
  function isLocalPath(pkg) {
54103
55203
  return pkg.startsWith("./") || pkg.startsWith("../") || pkg.startsWith("/") || /^[A-Za-z]:[\\/]/.test(pkg);
54104
55204
  }
@@ -54121,7 +55221,7 @@ var remoteTmpDir = null;
54121
55221
  var remoteTmpFiles = [];
54122
55222
  async function ensureRemoteTmpDir() {
54123
55223
  if (!remoteTmpDir) {
54124
- remoteTmpDir = await mkdtemp(join8(tmpdir(), "local-router-plugins-"));
55224
+ remoteTmpDir = await mkdtemp(join9(tmpdir(), "local-router-plugins-"));
54125
55225
  }
54126
55226
  return remoteTmpDir;
54127
55227
  }
@@ -54134,7 +55234,7 @@ async function fetchRemotePlugin(url2) {
54134
55234
  const ext = inferExtension(url2, response.headers.get("content-type"));
54135
55235
  const dir = await ensureRemoteTmpDir();
54136
55236
  const fileName = `plugin_${Date.now()}_${Math.random().toString(36).slice(2, 8)}${ext}`;
54137
- const filePath = join8(dir, fileName);
55237
+ const filePath = join9(dir, fileName);
54138
55238
  await writeFile(filePath, content, "utf-8");
54139
55239
  remoteTmpFiles.push(filePath);
54140
55240
  return filePath;
@@ -54173,13 +55273,17 @@ class PluginManager {
54173
55273
  constructor(configDir) {
54174
55274
  this.configDir = configDir;
54175
55275
  }
54176
- async loadPluginsForProvider(providerName, pluginConfigs) {
55276
+ async loadPluginsForProvider(providerName, providerConfig, pluginConfigs) {
54177
55277
  const loaded = [];
54178
55278
  const failures = [];
54179
55279
  for (const config2 of pluginConfigs) {
54180
55280
  try {
54181
55281
  const definition = await importPlugin(config2.package, this.configDir);
54182
- const instance2 = await definition.create(config2.params ?? {});
55282
+ const params = { ...config2.params ?? {} };
55283
+ if (definition.name === "protocol-adapter" && params.targetFormat === undefined) {
55284
+ params.targetFormat = providerConfig.type;
55285
+ }
55286
+ const instance2 = await definition.create(params);
54183
55287
  loaded.push({ config: config2, definition, instance: instance2 });
54184
55288
  } catch (err) {
54185
55289
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -54195,7 +55299,7 @@ class PluginManager {
54195
55299
  const oldPluginsToDispose = [];
54196
55300
  for (const [providerName, providerConfig] of Object.entries(providers)) {
54197
55301
  if (providerConfig.plugins && providerConfig.plugins.length > 0) {
54198
- const { loaded, failures } = await this.loadPluginsForProvider(providerName, providerConfig.plugins);
55302
+ const { loaded, failures } = await this.loadPluginsForProvider(providerName, providerConfig, providerConfig.plugins);
54199
55303
  allFailures.push(...failures);
54200
55304
  if (failures.length > 0) {
54201
55305
  console.warn(`[plugin] provider "${providerName}" \u6709\u63D2\u4EF6\u52A0\u8F7D\u5931\u8D25\uFF0C\u4FDD\u7559\u65E7\u63D2\u4EF6\u94FE`);
@@ -54264,19 +55368,12 @@ class PluginManager {
54264
55368
  }
54265
55369
  }
54266
55370
  }
54267
- async disposePluginMap(pluginMap) {
54268
- const allPlugins = [];
54269
- for (const [, loadedPlugins] of pluginMap) {
54270
- allPlugins.push(...loadedPlugins);
54271
- }
54272
- await this.disposePluginList(allPlugins);
54273
- }
54274
55371
  }
54275
55372
 
54276
55373
  // src/proxy.ts
54277
55374
  import { appendFile, readFile, unlink } from "fs/promises";
54278
- import { join as join9 } from "path";
54279
55375
  import { tmpdir as tmpdir2 } from "os";
55376
+ import { join as join10 } from "path";
54280
55377
 
54281
55378
  // src/plugin-engine.ts
54282
55379
  async function executeRequestPlugins(plugins, ctx, url2, headers, body) {
@@ -54468,7 +55565,7 @@ function buildLogEvent(logMeta, targetUrl, proxyUrl, tsEnd, overrides) {
54468
55565
  };
54469
55566
  }
54470
55567
  function createTempStreamCapturePath(requestId) {
54471
- return join9(tmpdir2(), `local-router-stream-${requestId}-${Date.now()}.sse.raw`);
55568
+ return join10(tmpdir2(), `local-router-stream-${requestId}-${Date.now()}.sse.raw`);
54472
55569
  }
54473
55570
  async function appendTempStreamCapture(filePath, chunk) {
54474
55571
  await appendFile(filePath, chunk);
@@ -54787,47 +55884,34 @@ function createOpenaiResponsesRoutes(routeType, store, pluginManager) {
54787
55884
  return routes;
54788
55885
  }
54789
55886
 
54790
- // src/runtime-assets.ts
54791
- import { fileURLToPath } from "url";
54792
- function resolveBundledAssetPath(relativePath) {
54793
- return fileURLToPath(new URL(relativePath, import.meta.url));
54794
- }
54795
- function getBundledSchemaPath() {
54796
- return resolveBundledAssetPath("../config.schema.json");
54797
- }
54798
- function getBundledWebRoot() {
54799
- return resolveBundledAssetPath("../dist/web/");
54800
- }
54801
-
54802
- // src/config-validate.ts
54803
- var import__2020 = __toESM(require_2020(), 1);
54804
- var import_ajv_formats = __toESM(require_dist2(), 1);
54805
- import { readFileSync as readFileSync3 } from "fs";
54806
- function validateBusinessRules(config2) {
54807
- for (const [routeType, modelMap] of Object.entries(config2.routes)) {
54808
- if (!modelMap["*"]) {
54809
- throw new Error(`\u8DEF\u7531 "${routeType}" \u7F3A\u5C11 "*" \u515C\u5E95\u89C4\u5219`);
54810
- }
54811
- for (const target of Object.values(modelMap)) {
54812
- if (!config2.providers[target.provider]) {
54813
- throw new Error(`\u8DEF\u7531 "${routeType}" \u5F15\u7528\u4E86\u4E0D\u5B58\u5728\u7684 provider "${target.provider}"`);
54814
- }
54815
- }
55887
+ // src/server-address.ts
55888
+ function formatUrlHost(host) {
55889
+ const trimmed = host.trim();
55890
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
55891
+ return trimmed;
54816
55892
  }
55893
+ return trimmed.includes(":") ? `[${trimmed}]` : trimmed;
54817
55894
  }
54818
- function validateConfigOrThrow(config2) {
54819
- validateBusinessRules(config2);
54820
- const ajv = new import__2020.default({ allErrors: true, strict: false });
54821
- import_ajv_formats.default(ajv);
54822
- const schemaJson = JSON.parse(readFileSync3(getBundledSchemaPath(), "utf-8"));
54823
- const validateBySchema = ajv.compile(schemaJson);
54824
- const valid = validateBySchema(config2);
54825
- if (!valid) {
54826
- const firstError = validateBySchema.errors?.[0];
54827
- const path = firstError?.instancePath || "(root)";
54828
- const message = firstError?.message ?? "unknown schema validation error";
54829
- throw new Error(`Schema \u6821\u9A8C\u5931\u8D25: ${path} ${message}`);
55895
+ function resolveLocalAccessHost(listenHost) {
55896
+ const host = listenHost.trim().toLowerCase();
55897
+ if (host === "0.0.0.0" || host === "") {
55898
+ return "127.0.0.1";
54830
55899
  }
55900
+ if (host === "::" || host === "[::]") {
55901
+ return "::1";
55902
+ }
55903
+ return listenHost.trim();
55904
+ }
55905
+ function createServerAddressInfo(listenHost, port) {
55906
+ const normalizedListenHost = listenHost.trim() || "0.0.0.0";
55907
+ const localHost = resolveLocalAccessHost(normalizedListenHost);
55908
+ return {
55909
+ listenHost: normalizedListenHost,
55910
+ localHost,
55911
+ port,
55912
+ listenUrl: `http://${formatUrlHost(normalizedListenHost)}:${port}`,
55913
+ localUrl: `http://${formatUrlHost(localHost)}:${port}`
55914
+ };
54831
55915
  }
54832
55916
 
54833
55917
  // src/index.ts
@@ -54869,13 +55953,18 @@ var ROUTE_REGISTRY = {
54869
55953
  create: createAnthropicMessagesRoutes
54870
55954
  }
54871
55955
  };
54872
- function printIntegrationGuide(config2) {
54873
- const host = process.env.HOST ?? "127.0.0.1";
54874
- const port = process.env.PORT ?? "4099";
54875
- const baseUrl = `http://${host}:${port}`;
55956
+ function printIntegrationGuide(config2, listen = {}) {
55957
+ const host = listen.host ?? process.env.HOST ?? "0.0.0.0";
55958
+ const parsedPort = listen.port ?? Number.parseInt(process.env.PORT ?? "4099", 10);
55959
+ const address = createServerAddressInfo(host, Number.isFinite(parsedPort) ? parsedPort : 4099);
55960
+ const baseUrl = address.localUrl;
54876
55961
  console.log(`
54877
55962
  ================ local-router \u63A5\u5165\u6307\u5357 ================`);
54878
- console.log(`\u672C\u5730\u670D\u52A1\u5730\u5740: ${baseUrl}`);
55963
+ console.log(`\u670D\u52A1\u76D1\u542C\u5730\u5740: ${address.listenUrl}`);
55964
+ console.log(`\u672C\u673A\u8BBF\u95EE\u5730\u5740: ${address.localUrl}`);
55965
+ if (address.listenHost === "0.0.0.0" || address.listenHost === "::" || address.listenHost === "[::]") {
55966
+ console.log("\u5C40\u57DF\u7F51\u8BBF\u95EE: \u5F00\u542F\u5C40\u57DF\u7F51\u670D\u52A1\u540E\uFF0C\u4F7F\u7528\u672C\u673A\u5C40\u57DF\u7F51 IP \u66FF\u6362\u4E0A\u9762\u7684\u672C\u673A\u5730\u5740\u3002");
55967
+ }
54879
55968
  console.log("\u5065\u5EB7\u68C0\u67E5: GET /");
54880
55969
  console.log(`API \u6587\u6863: ${baseUrl}/api/docs`);
54881
55970
  console.log(`\u7BA1\u7406\u9762\u677F: ${baseUrl}/admin`);
@@ -55302,7 +56391,6 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
55302
56391
  }
55303
56392
  });
55304
56393
  api2.get("/logs/tail", async (c2) => {
55305
- const config2 = store.get();
55306
56394
  const target = c2.req.raw;
55307
56395
  const windowRaw = c2.req.query("window") ?? "1h";
55308
56396
  if (!isLogQueryWindow(windowRaw)) {
@@ -55330,11 +56418,15 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
55330
56418
  }
55331
56419
  const encoder = new TextEncoder;
55332
56420
  let closed = false;
55333
- let lastSeenTs = Date.now() - 60 * 1000;
56421
+ const maxPendingItems = 500;
55334
56422
  let closeStream = null;
55335
56423
  const stream = new ReadableStream({
55336
56424
  start(controller) {
55337
- let timer = null;
56425
+ let heartbeatTimer = null;
56426
+ let unsubscribe = null;
56427
+ let flushQueued = false;
56428
+ let droppedItems = 0;
56429
+ const pending = [];
55338
56430
  const push2 = (event, payload) => {
55339
56431
  if (closed)
55340
56432
  return;
@@ -55348,60 +56440,106 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
55348
56440
  if (closed)
55349
56441
  return;
55350
56442
  closed = true;
55351
- if (timer) {
55352
- clearInterval(timer);
55353
- timer = null;
56443
+ if (heartbeatTimer) {
56444
+ clearInterval(heartbeatTimer);
56445
+ heartbeatTimer = null;
55354
56446
  }
56447
+ unsubscribe?.();
56448
+ unsubscribe = null;
55355
56449
  target.signal.removeEventListener("abort", close);
55356
56450
  try {
55357
56451
  controller.close();
55358
56452
  } catch {}
55359
56453
  };
55360
56454
  closeStream = close;
55361
- push2("ready", { ok: true, now: new Date().toISOString() });
55362
- timer = setInterval(async () => {
56455
+ const buildTailQuery = () => ({
56456
+ ...resolveLogQueryRange({
56457
+ window: windowRaw,
56458
+ from: c2.req.query("from"),
56459
+ to: c2.req.query("to")
56460
+ }),
56461
+ levels,
56462
+ providers: parseCommaSeparated(c2.req.query("provider")),
56463
+ routeTypes: parseCommaSeparated(c2.req.query("routeType")),
56464
+ models: parseCommaSeparated(c2.req.query("model")),
56465
+ modelIns: parseCommaSeparated(c2.req.query("modelIn")),
56466
+ modelOuts: parseCommaSeparated(c2.req.query("modelOut")),
56467
+ users: parseCommaSeparated(c2.req.query("user")),
56468
+ sessions: parseCommaSeparated(c2.req.query("session")),
56469
+ statusClasses,
56470
+ hasError,
56471
+ q: c2.req.query("q") ?? "",
56472
+ sort: sortRaw,
56473
+ limit: 100,
56474
+ cursor: null
56475
+ });
56476
+ const flush = () => {
56477
+ flushQueued = false;
56478
+ if (closed || pending.length === 0)
56479
+ return;
56480
+ const items = pending.splice(0, pending.length);
56481
+ const overflowMessage = droppedItems > 0 ? `\u5B9E\u65F6\u8FFD\u8E2A\u961F\u5217\u5DF2\u4E22\u5F03 ${droppedItems} \u6761\u4E8B\u4EF6\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u4EE5\u8865\u9F50\u3002` : undefined;
56482
+ droppedItems = 0;
56483
+ push2("events", {
56484
+ items,
56485
+ nextCursor: null,
56486
+ hasMore: false,
56487
+ stats: {
56488
+ total: 0,
56489
+ errorCount: 0,
56490
+ errorRate: 0,
56491
+ avgLatencyMs: 0,
56492
+ p95LatencyMs: 0
56493
+ },
56494
+ meta: {
56495
+ scannedFiles: 0,
56496
+ scannedLines: 0,
56497
+ parseErrors: 0,
56498
+ truncated: false,
56499
+ indexUsed: true,
56500
+ indexFresh: true,
56501
+ usesFts: false,
56502
+ queryMs: 0,
56503
+ rowsReturned: items.length,
56504
+ fallbackReason: overflowMessage,
56505
+ statsMode: "none"
56506
+ }
56507
+ });
56508
+ };
56509
+ const queueFlush = () => {
56510
+ if (flushQueued)
56511
+ return;
56512
+ flushQueued = true;
56513
+ queueMicrotask(flush);
56514
+ };
56515
+ unsubscribe = subscribeLogEvents((published) => {
55363
56516
  if (closed)
55364
56517
  return;
55365
56518
  try {
55366
- const toMs = Date.now();
55367
- const data = await queryLogEvents({ logConfig: config2.log }, {
55368
- fromMs: Math.max(lastSeenTs, toMs - 60 * 60 * 1000),
55369
- toMs,
55370
- levels,
55371
- providers: parseCommaSeparated(c2.req.query("provider")),
55372
- routeTypes: parseCommaSeparated(c2.req.query("routeType")),
55373
- models: parseCommaSeparated(c2.req.query("model")),
55374
- modelIns: parseCommaSeparated(c2.req.query("modelIn")),
55375
- modelOuts: parseCommaSeparated(c2.req.query("modelOut")),
55376
- users: parseCommaSeparated(c2.req.query("user")),
55377
- sessions: parseCommaSeparated(c2.req.query("session")),
55378
- statusClasses,
55379
- hasError,
55380
- q: c2.req.query("q") ?? "",
55381
- sort: sortRaw,
55382
- limit: 100
55383
- });
55384
- if (closed)
56519
+ const query = buildTailQuery();
56520
+ if (!logEventMatchesQuery(published.event, query))
55385
56521
  return;
55386
- if (data.items.length > 0) {
55387
- const maxTs = Math.max(...data.items.map((item) => Date.parse(item.ts)).filter(Number.isFinite));
55388
- if (Number.isFinite(maxTs)) {
55389
- lastSeenTs = Math.max(lastSeenTs, maxTs + 1);
55390
- }
55391
- push2("events", {
55392
- items: data.items,
55393
- stats: data.stats,
55394
- meta: data.meta
55395
- });
55396
- } else {
55397
- push2("heartbeat", { ts: new Date().toISOString() });
56522
+ if (pending.length >= maxPendingItems) {
56523
+ pending.shift();
56524
+ droppedItems += 1;
55398
56525
  }
56526
+ pending.push(createLogEventSummaryFromEvent(published.event, {
56527
+ id: published.id,
56528
+ date: published.date,
56529
+ line: null
56530
+ }));
56531
+ queueFlush();
55399
56532
  } catch (err) {
55400
- if (closed)
55401
- return;
55402
56533
  push2("error", { error: err instanceof Error ? err.message : String(err) });
55403
56534
  }
55404
- }, 3000);
56535
+ });
56536
+ push2("ready", { ok: true, now: new Date().toISOString() });
56537
+ heartbeatTimer = setInterval(() => {
56538
+ if (closed)
56539
+ return;
56540
+ push2("heartbeat", { ts: new Date().toISOString() });
56541
+ }, 15000);
56542
+ heartbeatTimer.unref?.();
55405
56543
  target.signal.addEventListener("abort", close);
55406
56544
  },
55407
56545
  cancel() {
@@ -55514,8 +56652,9 @@ async function createApp(store, options) {
55514
56652
  options?.registerCleanup?.(() => {
55515
56653
  pluginManager.disposeAll().catch(() => {});
55516
56654
  });
55517
- printIntegrationGuide(config2);
56655
+ printIntegrationGuide(config2, options?.listen);
55518
56656
  const app = new Hono2;
56657
+ app.use("*", createNetworkAccessMiddleware(store));
55519
56658
  app.get("/", (c2) => c2.text("local-router is running"));
55520
56659
  for (const [routeType, entry] of Object.entries(ROUTE_REGISTRY)) {
55521
56660
  const subApp = entry.create(routeType, store, pluginManager);
@@ -55554,12 +56693,26 @@ async function createApp(store, options) {
55554
56693
  async function createDefaultAppFromProcessArgs() {
55555
56694
  const configPath = parseConfigPath();
55556
56695
  const store = new ConfigStore(configPath);
55557
- return createApp(store);
56696
+ return createApp(store, {
56697
+ listen: {
56698
+ host: process.env.HOST ?? "0.0.0.0",
56699
+ port: Number.parseInt(process.env.PORT ?? "4099", 10)
56700
+ }
56701
+ });
55558
56702
  }
55559
56703
 
55560
56704
  // src/entry.ts
55561
56705
  var app = await createDefaultAppFromProcessArgs();
55562
- var entry_default = app;
56706
+ var entry_default = {
56707
+ hostname: process.env.HOST ?? "0.0.0.0",
56708
+ port: Number.parseInt(process.env.PORT ?? "4099", 10),
56709
+ fetch(request, server) {
56710
+ const remoteAddress = server.requestIP(request)?.address ?? null;
56711
+ return app.fetch(request, {
56712
+ [REMOTE_ADDRESS_ENV_KEY]: remoteAddress
56713
+ });
56714
+ }
56715
+ };
55563
56716
  export {
55564
56717
  entry_default as default
55565
56718
  };