@cc-monitoring/proxy-core 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/__tests__/commands.test.js +19 -0
- package/dist/cli/commands/repair.d.ts +1 -0
- package/dist/cli/commands/repair.js +15 -0
- package/dist/cli/index.js +30 -0
- package/dist/docs/404.html +1 -1
- package/dist/docs/assets/{app.DtYNT5zS.js → app.vxoC6jUT.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.lKm6N3Ik.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DpXF9ZAI.js → VPLocalSearchBox.DrfGpz86.js} +1 -1
- package/dist/docs/assets/chunks/{theme.TS_gUyc7.js → theme.CCmzbSn9.js} +2 -2
- package/dist/docs/index.html +2 -2
- package/dist/docs/overview.html +2 -2
- package/dist/docs/packages-utils.html +2 -2
- package/dist/docs/proxy-admin/env-and-build.html +2 -2
- package/dist/docs/proxy-admin/index.html +2 -2
- package/dist/docs/proxy-admin/layout-and-menu.html +2 -2
- package/dist/docs/proxy-admin/pages.html +2 -2
- package/dist/docs/proxy-admin/routing-and-auth.html +2 -2
- package/dist/docs/proxy-admin/services-and-mock.html +2 -2
- package/dist/docs/proxy-cli/api-layer.html +2 -2
- package/dist/docs/proxy-cli/app-shell.html +2 -2
- package/dist/docs/proxy-cli/components-common.html +2 -2
- package/dist/docs/proxy-cli/components-config.html +2 -2
- package/dist/docs/proxy-cli/components-requests.html +2 -2
- package/dist/docs/proxy-cli/components-stats.html +2 -2
- package/dist/docs/proxy-cli/components-status.html +2 -2
- package/dist/docs/proxy-cli/data-hooks.html +2 -2
- package/dist/docs/proxy-cli/index.html +2 -2
- package/dist/docs/proxy-cli/utils-and-i18n.html +2 -2
- package/dist/docs/proxy-core/chat-api.html +2 -2
- package/dist/docs/proxy-core/cli.html +2 -2
- package/dist/docs/proxy-core/config.html +2 -2
- package/dist/docs/proxy-core/data-layer.html +2 -2
- package/dist/docs/proxy-core/entry.html +2 -2
- package/dist/docs/proxy-core/index.html +2 -2
- package/dist/docs/proxy-core/pm2.html +2 -2
- package/dist/docs/proxy-core/providers.html +2 -2
- package/dist/docs/proxy-core/proxy.html +2 -2
- package/dist/docs/proxy-core/stats.html +2 -2
- package/dist/docs/proxy-core/tool.html +2 -2
- package/dist/docs/proxy-core/ui-routes.html +2 -2
- package/dist/docs/proxy-core/utilities.html +2 -2
- package/dist/docs/quickstart.html +2 -2
- package/dist/docs/request-flow.html +2 -2
- package/dist/server/__tests__/data-push.test.js +44 -0
- package/dist/server/__tests__/proxy.test.js +365 -2
- package/dist/server/__tests__/server.test.js +15 -10
- package/dist/server/__tests__/ui-route.test.js +43 -0
- package/dist/server/chat-route.js +3 -3
- package/dist/server/data-push.d.ts +2 -0
- package/dist/server/data-push.js +22 -0
- package/dist/server/index.js +4 -3
- package/dist/server/proxy.js +127 -23
- package/dist/server/ui-route.js +8 -1
- package/dist/ui/assets/{1c-CCx-FOtz.js → 1c-BdWVxVKv.js} +1 -1
- package/dist/ui/assets/{abnf-Bwbi42Z8.js → abnf-hHBtR6kN.js} +1 -1
- package/dist/ui/assets/{abnfDiagram-VRR7QNED-Dm1-8q0E.js → abnfDiagram-VRR7QNED-B0E8zzLB.js} +1 -1
- package/dist/ui/assets/{accesslog-DCytz_-m.js → accesslog-BZmvPz-q.js} +1 -1
- package/dist/ui/assets/{actionscript-3udf15oh.js → actionscript-XtUFCVQF.js} +1 -1
- package/dist/ui/assets/{ada-M5Xa74xG.js → ada-DDsWH3P3.js} +1 -1
- package/dist/ui/assets/{angelscript-qlrFxfW-.js → angelscript-CGLPsuae.js} +1 -1
- package/dist/ui/assets/{apache-B-sF-DQH.js → apache-eGe6GEfx.js} +1 -1
- package/dist/ui/assets/{applescript-4FdcBnbI.js → applescript-Ch2Hdkz_.js} +1 -1
- package/dist/ui/assets/{arc-BB3K1enp.js → arc-Bi_rJJly.js} +1 -1
- package/dist/ui/assets/{arcade-B_yqWaaG.js → arcade-BAeq_5jU.js} +1 -1
- package/dist/ui/assets/{architectureDiagram-ZJ3FMSHR-B39vkY8V.js → architectureDiagram-ZJ3FMSHR-D3rUGwDS.js} +1 -1
- package/dist/ui/assets/{arduino-yAcBoTWU.js → arduino-DJR8nayp.js} +1 -1
- package/dist/ui/assets/{armasm-wIcY84bI.js → armasm-DqDD_ZDN.js} +1 -1
- package/dist/ui/assets/{asciidoc-Bxnu28-Z.js → asciidoc-CgBdhmR5.js} +1 -1
- package/dist/ui/assets/{aspectj-B2cKJA_O.js → aspectj-Dbm_6mY0.js} +1 -1
- package/dist/ui/assets/{autohotkey-B_rMWTtE.js → autohotkey-BkbluSS8.js} +1 -1
- package/dist/ui/assets/{autoit-DwcuvMwv.js → autoit-qUepyZBT.js} +1 -1
- package/dist/ui/assets/{avrasm-heGX1PXK.js → avrasm-2snPxE5L.js} +1 -1
- package/dist/ui/assets/{awk-BiqCQAEf.js → awk-oPcQTdJK.js} +1 -1
- package/dist/ui/assets/{axapta-Cv8M-Ih6.js → axapta-CCu7hfQ-.js} +1 -1
- package/dist/ui/assets/{bash-Divgn3JV.js → bash-CmWN3rPR.js} +1 -1
- package/dist/ui/assets/{basic-B9dnAP55.js → basic-Dg8KcxuC.js} +1 -1
- package/dist/ui/assets/{blockDiagram-677ZJIJ3-DQ1mf7A_.js → blockDiagram-677ZJIJ3-Dusa5zr8.js} +1 -1
- package/dist/ui/assets/{bnf-kIL5mSsP.js → bnf-Zmfygl9A.js} +1 -1
- package/dist/ui/assets/{brainfuck-DqB7xDjH.js → brainfuck-K4-sBD6i.js} +1 -1
- package/dist/ui/assets/{coq-6aKUn3zn.js → c-BGtacyfP.js} +1 -1
- package/dist/ui/assets/{c-like-De5MhE7G.js → c-like-FRDCqB2X.js} +1 -1
- package/dist/ui/assets/{c4Diagram-LMCZKHZV-DQxtYsAx.js → c4Diagram-LMCZKHZV-C4Vo5RAI.js} +1 -1
- package/dist/ui/assets/{cal-mYdfz9en.js → cal-BibfnL8Q.js} +1 -1
- package/dist/ui/assets/{capnproto-D04CRlOt.js → capnproto-D4sJA0ax.js} +1 -1
- package/dist/ui/assets/{ceylon-pi9lFCja.js → ceylon-DuI5ot57.js} +1 -1
- package/dist/ui/assets/channel-CviGbONW.js +1 -0
- package/dist/ui/assets/{chunk-2Q5K7J3B-BDD1nPt8.js → chunk-2Q5K7J3B-C0UTs_LW.js} +1 -1
- package/dist/ui/assets/{chunk-32BRIVSS-DgeQFBb5.js → chunk-32BRIVSS-BMqPhehD.js} +1 -1
- package/dist/ui/assets/{chunk-5VM5RSS4-C7Xf2SnI.js → chunk-5VM5RSS4-CKN4Vw1w.js} +1 -1
- package/dist/ui/assets/{chunk-EX3LRPZG-B_3p1HEv.js → chunk-EX3LRPZG-BYHH22fs.js} +1 -1
- package/dist/ui/assets/{chunk-JWPE2WC7-Dvs7u8eY.js → chunk-JWPE2WC7-b55Vo5mo.js} +1 -1
- package/dist/ui/assets/{chunk-MOJQB5TN-By21wDCN.js → chunk-MOJQB5TN-CvtnHxi4.js} +1 -1
- package/dist/ui/assets/{chunk-RYQCIY6F-6NC700r1.js → chunk-RYQCIY6F-Bi9_eYfb.js} +1 -1
- package/dist/ui/assets/{chunk-V7JOEXUC-vEUqZ3rh.js → chunk-V7JOEXUC-C3ujZT8O.js} +1 -1
- package/dist/ui/assets/{chunk-VR4S4FIN-6tX2dNN9.js → chunk-VR4S4FIN-D4pfDh-j.js} +1 -1
- package/dist/ui/assets/{chunk-XXDRQBXY-qDb2jxAR.js → chunk-XXDRQBXY-DQAjg3aO.js} +1 -1
- package/dist/ui/assets/classDiagram-OUVF2IWQ-BlFcpmEd.js +1 -0
- package/dist/ui/assets/classDiagram-v2-EOCWNBFH-BlFcpmEd.js +1 -0
- package/dist/ui/assets/{clean-CLCsDUl1.js → clean-BVmfQBBV.js} +1 -1
- package/dist/ui/assets/{clojure-dVi5Xifj.js → clojure-BN3C1fnU.js} +1 -1
- package/dist/ui/assets/{clojure-repl-BO_FpFmF.js → clojure-repl-C7Gg0UPF.js} +1 -1
- package/dist/ui/assets/{cmake-CP5aJbyK.js → cmake-CTQFRyul.js} +1 -1
- package/dist/ui/assets/{coffeescript-ZEfu_9pF.js → coffeescript-DGWU3k3W.js} +1 -1
- package/dist/ui/assets/{c-HmsCM6sx.js → coq-Bv9CXGly.js} +1 -1
- package/dist/ui/assets/{cos-BohCnRN2.js → cos-JXNeNyLn.js} +1 -1
- package/dist/ui/assets/{cose-bilkent-JH36ORCC-DyXCbbro.js → cose-bilkent-JH36ORCC-B2e-rPVN.js} +1 -1
- package/dist/ui/assets/{cpp-FOgPWPEt.js → cpp-DVS4QZP-.js} +1 -1
- package/dist/ui/assets/{crmsh-B0OYb-Oe.js → crmsh-CFD7lrxk.js} +1 -1
- package/dist/ui/assets/{crystal-CuWb_dXm.js → crystal-D7vS7dho.js} +1 -1
- package/dist/ui/assets/{csharp-BbcqM09j.js → csharp-BgeVxKaT.js} +1 -1
- package/dist/ui/assets/{csp-rgHnTaDq.js → csp-CmAkjXvi.js} +1 -1
- package/dist/ui/assets/{css-C-ibh7ZG.js → css-DtEkZRCq.js} +1 -1
- package/dist/ui/assets/{cynefin-VYW2F7L2-CkuBhgC4.js → cynefin-VYW2F7L2-bhneX2QQ.js} +1 -1
- package/dist/ui/assets/{cynefinDiagram-TSTJHNR4-eycFyDao.js → cynefinDiagram-TSTJHNR4-BvtRhsVM.js} +1 -1
- package/dist/ui/assets/{d-C0gXxJ0O.js → d-ov1MlWJx.js} +1 -1
- package/dist/ui/assets/{dagre-VKFMJZFB-DzqX9e49.js → dagre-VKFMJZFB-nxN1qAKi.js} +1 -1
- package/dist/ui/assets/{dart-C-fTpuUV.js → dart-DX6s604e.js} +1 -1
- package/dist/ui/assets/{delphi-Chm0B9xv.js → delphi-D0oU57Jt.js} +1 -1
- package/dist/ui/assets/{diagram-FQU43EPY-BEgCqfg1.js → diagram-FQU43EPY-CWkdysy2.js} +1 -1
- package/dist/ui/assets/{diagram-G47NLZAW-Cw11Vs2v.js → diagram-G47NLZAW-L7jPJ-r1.js} +1 -1
- package/dist/ui/assets/{diagram-NH7WQ7WH-DSG_UWpP.js → diagram-NH7WQ7WH-fhOp1mr9.js} +1 -1
- package/dist/ui/assets/{diagram-OA4YK3LP-CJz-WOaJ.js → diagram-OA4YK3LP-B7UMZ-5d.js} +1 -1
- package/dist/ui/assets/{diagram-WEI45ONY-CBXjBCvc.js → diagram-WEI45ONY-B-toJrZu.js} +1 -1
- package/dist/ui/assets/{diff-C-vyuMSL.js → diff-CAzYRr8i.js} +1 -1
- package/dist/ui/assets/{django-CJK7lReT.js → django-DPFPMDLP.js} +1 -1
- package/dist/ui/assets/{dns-D02SHjSy.js → dns-kf25JjIl.js} +1 -1
- package/dist/ui/assets/{dockerfile-trKL1Bqu.js → dockerfile-GP7x-Ptc.js} +1 -1
- package/dist/ui/assets/{dos-C52dy-sK.js → dos-DlXSYH18.js} +1 -1
- package/dist/ui/assets/{dsconfig-MzHgzVrt.js → dsconfig-DrXWPwZN.js} +1 -1
- package/dist/ui/assets/{dts-B137dHyX.js → dts-B9QdwGCS.js} +1 -1
- package/dist/ui/assets/{dust-BcbM0r5C.js → dust-CdplAbVJ.js} +1 -1
- package/dist/ui/assets/{ebnf-DkbpI7bK.js → ebnf-BdCAaKiH.js} +1 -1
- package/dist/ui/assets/{ebnfDiagram-CCIWWBDH-aI5uQ7w1.js → ebnfDiagram-CCIWWBDH-l1kXQDgJ.js} +1 -1
- package/dist/ui/assets/{elixir-BOtDuqQy.js → elixir-WekcTu8c.js} +1 -1
- package/dist/ui/assets/{elm-DS8Tyyu-.js → elm-DKbQ6Ryz.js} +1 -1
- package/dist/ui/assets/{erDiagram-Q63AITRT-2vCbd4Mm.js → erDiagram-Q63AITRT-CCeU8uXM.js} +1 -1
- package/dist/ui/assets/{erb-CSXECRbn.js → erb-DvQ2S9Q6.js} +1 -1
- package/dist/ui/assets/{erlang-BMxlgKPJ.js → erlang-C4cAvWW8.js} +1 -1
- package/dist/ui/assets/{erlang-repl-DPzlolPg.js → erlang-repl-BMKmccqv.js} +1 -1
- package/dist/ui/assets/{excel-CW2Uxed3.js → excel-Cz9AeP71.js} +1 -1
- package/dist/ui/assets/{fix-UW84SlCs.js → fix-BFQxvtLz.js} +1 -1
- package/dist/ui/assets/{flix-4UGiPULd.js → flix-D3TgVgHZ.js} +1 -1
- package/dist/ui/assets/{flowDiagram-23GEKE2U-R5PbCbZI.js → flowDiagram-23GEKE2U-CWYmXYgW.js} +1 -1
- package/dist/ui/assets/{fortran-nhHsngdu.js → fortran-CWmklLY8.js} +1 -1
- package/dist/ui/assets/{fsharp-CDyZ1ST5.js → fsharp-V2Eau__H.js} +1 -1
- package/dist/ui/assets/{gams-DHzkkUie.js → gams-KwiGZby9.js} +1 -1
- package/dist/ui/assets/{ganttDiagram-NO4QXBWP-CGcq4Ie_.js → ganttDiagram-NO4QXBWP-Bw1obkG5.js} +1 -1
- package/dist/ui/assets/{gauss-DdhFGyBv.js → gauss-CRI2FQhz.js} +1 -1
- package/dist/ui/assets/{gcode-ImHh8OLA.js → gcode-Bh0f80Zp.js} +1 -1
- package/dist/ui/assets/{gherkin-CTWAW-XR.js → gherkin-DEjcfzYc.js} +1 -1
- package/dist/ui/assets/{gitGraphDiagram-IHSO6WYX-8nM2Z9lD.js → gitGraphDiagram-IHSO6WYX-OfPpsImd.js} +1 -1
- package/dist/ui/assets/{glsl-0_W_27G2.js → glsl-BrYpeP6h.js} +1 -1
- package/dist/ui/assets/{gml-BoDggu2z.js → gml-m818HPGO.js} +1 -1
- package/dist/ui/assets/{go-DYfFygwl.js → go-BX431CyL.js} +1 -1
- package/dist/ui/assets/{golo-Mlq-TCg9.js → golo-D4m-zAId.js} +1 -1
- package/dist/ui/assets/{gradle-BALAinEB.js → gradle-Cq6wabEA.js} +1 -1
- package/dist/ui/assets/{groovy-Dht--dWU.js → groovy-TjcpUbQS.js} +1 -1
- package/dist/ui/assets/{haml-DpMrxg0d.js → haml-DrCR34E5.js} +1 -1
- package/dist/ui/assets/{handlebars-DqHzujNj.js → handlebars-CVS3O2t9.js} +1 -1
- package/dist/ui/assets/{haskell-DZSchhkY.js → haskell-B8q0nv1_.js} +1 -1
- package/dist/ui/assets/{haxe-C0zBRjdy.js → haxe-DXPsSA4C.js} +1 -1
- package/dist/ui/assets/{hsp-D_PwbSv2.js → hsp-CzpLpdV9.js} +1 -1
- package/dist/ui/assets/{htmlbars-DxAFMG8u.js → htmlbars-Cs_-HclQ.js} +1 -1
- package/dist/ui/assets/{http-DvTRF0Do.js → http-CHD9pG0v.js} +1 -1
- package/dist/ui/assets/{hy-lVO-kNyp.js → hy-B-a7tYC4.js} +1 -1
- package/dist/ui/assets/{index-Ci23SMK2.js → index-CJEzcYeX.js} +21 -21
- package/dist/ui/assets/{index-BbMRVVGt.js → index-DBPwq3d-.js} +3 -3
- package/dist/ui/assets/{infoDiagram-FWYZ7A6U-C7WYDlvc.js → infoDiagram-FWYZ7A6U-DNrZutb1.js} +1 -1
- package/dist/ui/assets/{inform7-B8Tf2n8v.js → inform7-47bZAprM.js} +1 -1
- package/dist/ui/assets/{ini-DSoYMaLF.js → ini-D00zSJOc.js} +1 -1
- package/dist/ui/assets/{irpf90-RCPdkB14.js → irpf90-Cb5adfDu.js} +1 -1
- package/dist/ui/assets/{isbl-C2p75PPj.js → isbl-NcFXKDCH.js} +1 -1
- package/dist/ui/assets/{ishikawaDiagram-FXEZZL3T-Cm6jsPlc.js → ishikawaDiagram-FXEZZL3T-DodxL_A3.js} +1 -1
- package/dist/ui/assets/{java-DeY4Yuia.js → java-DdNdnIDI.js} +1 -1
- package/dist/ui/assets/{javascript-3uX1CO-H.js → javascript-CauBsBfA.js} +1 -1
- package/dist/ui/assets/{jboss-cli-DgnD8mEc.js → jboss-cli-Ce_9aKRs.js} +1 -1
- package/dist/ui/assets/{journeyDiagram-5HDEW3XC-DEGV1uA5.js → journeyDiagram-5HDEW3XC-CaegxpYw.js} +1 -1
- package/dist/ui/assets/{json-BV2PmJJp.js → json-BKT_pfo1.js} +1 -1
- package/dist/ui/assets/{julia-B-m8kQm7.js → julia-bWGt_b_o.js} +1 -1
- package/dist/ui/assets/{julia-repl-C51atU2X.js → julia-repl-D-rjMsRC.js} +1 -1
- package/dist/ui/assets/{kanban-definition-HUTT4EX6-BJ4k5iTX.js → kanban-definition-HUTT4EX6-DocIVGMy.js} +1 -1
- package/dist/ui/assets/{kotlin-CYBziIgl.js → kotlin-CW5ChHJw.js} +1 -1
- package/dist/ui/assets/{lasso-Dw2SBMDW.js → lasso-1z4Xsh4l.js} +1 -1
- package/dist/ui/assets/{latex-Dpd35yke.js → latex-DRw0gvLT.js} +1 -1
- package/dist/ui/assets/{ldif-Ce9EZU__.js → ldif-CC6n1ZWZ.js} +1 -1
- package/dist/ui/assets/{leaf-CpkZzQtC.js → leaf-DtycGYP2.js} +1 -1
- package/dist/ui/assets/{less-Uzo7M0FD.js → less-DHfDdpLT.js} +1 -1
- package/dist/ui/assets/{lisp-DWXDlJYm.js → lisp-F7y5QBiG.js} +1 -1
- package/dist/ui/assets/{livecodeserver-uEyPEgVJ.js → livecodeserver-B4rL97ZE.js} +1 -1
- package/dist/ui/assets/{livescript-BQ5f07oK.js → livescript-C1A66vR3.js} +1 -1
- package/dist/ui/assets/{llvm-BAvP-skE.js → llvm-DrJc4jDQ.js} +1 -1
- package/dist/ui/assets/{lsl-DhbiIKu6.js → lsl-ByygfQlb.js} +1 -1
- package/dist/ui/assets/{lua-COxeUeLx.js → lua-C93sHZoB.js} +1 -1
- package/dist/ui/assets/{makefile-DhDx-IgL.js → makefile-CymUCipv.js} +1 -1
- package/dist/ui/assets/{markdown-DX5_kSxq.js → markdown-BLwVli7a.js} +1 -1
- package/dist/ui/assets/{mathematica-D351ZddB.js → mathematica-DMmVw4oA.js} +1 -1
- package/dist/ui/assets/{matlab-CELqIiwF.js → matlab-DJPyR0-0.js} +1 -1
- package/dist/ui/assets/{maxima-B_YljrMm.js → maxima-B_5tEBD-.js} +1 -1
- package/dist/ui/assets/{mel-DltsbNS8.js → mel-B5AJmhET.js} +1 -1
- package/dist/ui/assets/{mercury-axX-5Vjj.js → mercury-DDm_HlP5.js} +1 -1
- package/dist/ui/assets/{mindmap-definition-LN4V7U3C-Bl1EJo6p.js → mindmap-definition-LN4V7U3C-Ck5xDFhg.js} +1 -1
- package/dist/ui/assets/{mipsasm-BxmerVwX.js → mipsasm-BBf0KZxo.js} +1 -1
- package/dist/ui/assets/{mizar-XNHfbpL2.js → mizar-BvkTEip4.js} +1 -1
- package/dist/ui/assets/{mojolicious-BR0U_7zk.js → mojolicious-t81pYOLs.js} +1 -1
- package/dist/ui/assets/{monkey-lLuu9_f-.js → monkey-B5URMcLy.js} +1 -1
- package/dist/ui/assets/{moonscript-DoaZ3DRQ.js → moonscript-Bbi3L3V5.js} +1 -1
- package/dist/ui/assets/{n1ql-C6TId6Iu.js → n1ql-DE8gguiN.js} +1 -1
- package/dist/ui/assets/{nginx-BezSG-JT.js → nginx-ytmzvrnA.js} +1 -1
- package/dist/ui/assets/{nim-Bb0rfXie.js → nim-28PFAP2a.js} +1 -1
- package/dist/ui/assets/{nix-o3sJ5-54.js → nix-DPGZqLl2.js} +1 -1
- package/dist/ui/assets/{node-repl-Sv5hhcTf.js → node-repl-77Ag73Mp.js} +1 -1
- package/dist/ui/assets/{nsis-B45BJFhF.js → nsis-Bd-Jh2mn.js} +1 -1
- package/dist/ui/assets/{objectivec-CzS6zVgh.js → objectivec-Dut0JGtJ.js} +1 -1
- package/dist/ui/assets/{ocaml-Bqux7PyP.js → ocaml-B8NbbL7d.js} +1 -1
- package/dist/ui/assets/{openscad-4Wf6mF84.js → openscad-3haKn8_6.js} +1 -1
- package/dist/ui/assets/{oxygene-Fvwqgkkt.js → oxygene-CzZcXgOW.js} +1 -1
- package/dist/ui/assets/{parser3-zEfzT5wv.js → parser3-B9_LTx13.js} +1 -1
- package/dist/ui/assets/{pegDiagram-2B236MQR-Dd4rIztM.js → pegDiagram-2B236MQR-CnkbIPdt.js} +1 -1
- package/dist/ui/assets/{perl-CsS1Gs6o.js → perl-CJHWWgIf.js} +1 -1
- package/dist/ui/assets/{pf-BNH1jO0R.js → pf-BpbQ2FKx.js} +1 -1
- package/dist/ui/assets/{pgsql-DqPigHrq.js → pgsql-DNFfp1qF.js} +1 -1
- package/dist/ui/assets/{php-CdOl9bRs.js → php-BHGIfWOP.js} +1 -1
- package/dist/ui/assets/{php-template-DPfirJg2.js → php-template-CajBP9Ht.js} +1 -1
- package/dist/ui/assets/{pieDiagram-ENE6RG2P-DGaJNf4S.js → pieDiagram-ENE6RG2P-BOBkdC1f.js} +1 -1
- package/dist/ui/assets/{plaintext-EHbgBKzw.js → plaintext-CdRaKh_-.js} +1 -1
- package/dist/ui/assets/{pony-DmJajtp2.js → pony-BTFZduq1.js} +1 -1
- package/dist/ui/assets/{powershell-DfGzdWR6.js → powershell-gWcxiMpD.js} +1 -1
- package/dist/ui/assets/{processing-BT0ETgPG.js → processing-DbryYP4B.js} +1 -1
- package/dist/ui/assets/{profile-B3I-x5Lt.js → profile-CHL6RTDR.js} +1 -1
- package/dist/ui/assets/{prolog-B_4QJIvI.js → prolog-CAz3V1M6.js} +1 -1
- package/dist/ui/assets/{properties-BFVI8weS.js → properties-BLvgyNqZ.js} +1 -1
- package/dist/ui/assets/{protobuf-PaF39oZZ.js → protobuf-C-bEtiAJ.js} +1 -1
- package/dist/ui/assets/{puppet-BDC6Q8VF.js → puppet-67xqXQgg.js} +1 -1
- package/dist/ui/assets/{purebasic-B2ejrOZK.js → purebasic-DAwxmhyv.js} +1 -1
- package/dist/ui/assets/{python-repl-BslBh_Mx.js → python-repl-BJmGMIGi.js} +1 -1
- package/dist/ui/assets/{python-BOxtIk9H.js → python-z-wrDm9i.js} +1 -1
- package/dist/ui/assets/{q-BPe8WLGW.js → q-CDMPAvF9.js} +1 -1
- package/dist/ui/assets/{qml-BazRR6fz.js → qml-CbE9R2YT.js} +1 -1
- package/dist/ui/assets/{quadrantDiagram-ABIIQ3AL-BQ7aPkHk.js → quadrantDiagram-ABIIQ3AL-BHAhzSkD.js} +1 -1
- package/dist/ui/assets/{r-C09pGNJC.js → r-B1k7RrWh.js} +1 -1
- package/dist/ui/assets/{railroadDiagram-RFXS5EU6-sCO9VGvE.js → railroadDiagram-RFXS5EU6-CsQep5fB.js} +1 -1
- package/dist/ui/assets/{reasonml-S7dzwVL9.js → reasonml-CghNJ9wO.js} +1 -1
- package/dist/ui/assets/{requirementDiagram-TGXJPOKE-CfT5aVZg.js → requirementDiagram-TGXJPOKE-BTTRG_C-.js} +1 -1
- package/dist/ui/assets/{rib-fM2EwQd4.js → rib-BzZqpngB.js} +1 -1
- package/dist/ui/assets/{roboconf-D7AaeyrW.js → roboconf-Bn6vqIcm.js} +1 -1
- package/dist/ui/assets/{routeros-c6Q7mKuq.js → routeros-CWiIB6Sc.js} +1 -1
- package/dist/ui/assets/{rsl-sCpEQBxh.js → rsl-8XzLd-ex.js} +1 -1
- package/dist/ui/assets/{ruby-lcKhz2w9.js → ruby-CMsox328.js} +1 -1
- package/dist/ui/assets/{ruleslanguage-BW4ATOuX.js → ruleslanguage-sJ93c6T4.js} +1 -1
- package/dist/ui/assets/{rust-BbpU-0VY.js → rust-q4W6ksGp.js} +1 -1
- package/dist/ui/assets/{sankeyDiagram-HTMAVEWB-CaweCpvN.js → sankeyDiagram-HTMAVEWB-_xUP3uiO.js} +1 -1
- package/dist/ui/assets/{sas-BPt5R0dx.js → sas-JzGZkjB8.js} +1 -1
- package/dist/ui/assets/{scala-DxW_vqXs.js → scala-BBRUB1bT.js} +1 -1
- package/dist/ui/assets/{scheme-T2Q_jWzT.js → scheme-gFzNu8q5.js} +1 -1
- package/dist/ui/assets/{scilab-DMORg9lV.js → scilab-0XjfEPk4.js} +1 -1
- package/dist/ui/assets/{scss-t0VsosqT.js → scss-CuET4vU_.js} +1 -1
- package/dist/ui/assets/{sequenceDiagram-DBY2YBRQ-o-H_R4zb.js → sequenceDiagram-DBY2YBRQ-DYM-Lf_t.js} +1 -1
- package/dist/ui/assets/{shell-CnCDa1s9.js → shell-DozhgmQz.js} +1 -1
- package/dist/ui/assets/{sizeCapture-X5ZJPWSS-BNLkHGDS.js → sizeCapture-X5ZJPWSS-enn2ctEl.js} +1 -1
- package/dist/ui/assets/{smali-_n-BxJyG.js → smali-DCS5Fxk1.js} +1 -1
- package/dist/ui/assets/{smalltalk-B90PXJwK.js → smalltalk-BHQ0G1rO.js} +1 -1
- package/dist/ui/assets/{sml-fuY7bj-n.js → sml-Bj9p_O7l.js} +1 -1
- package/dist/ui/assets/{sqf-COpwkyuo.js → sqf-VjHINPVc.js} +1 -1
- package/dist/ui/assets/{sql-CXIT0j-x.js → sql-DNYdjhOa.js} +1 -1
- package/dist/ui/assets/{sql_more-C1Oxq4rx.js → sql_more-DF9pwFii.js} +1 -1
- package/dist/ui/assets/{stan-BZEpaHeC.js → stan-DQ7LiXhT.js} +1 -1
- package/dist/ui/assets/{stata-CkBwaiZM.js → stata-4eQacDMD.js} +1 -1
- package/dist/ui/assets/{stateDiagram-2N3HPSRC-BkdnNF7I.js → stateDiagram-2N3HPSRC-CX-Qz4n7.js} +1 -1
- package/dist/ui/assets/stateDiagram-v2-6OUMAXLB-Ch54U9zs.js +1 -0
- package/dist/ui/assets/{step21-fs9N_U3O.js → step21-BgYnjmFD.js} +1 -1
- package/dist/ui/assets/{stylus-Bs_9cvKZ.js → stylus-ZZ5CxZ05.js} +1 -1
- package/dist/ui/assets/{subunit-DvgcOCqJ.js → subunit-Ci5aUEE2.js} +1 -1
- package/dist/ui/assets/{swift-BE4ONsur.js → swift-DQJ4QPRG.js} +1 -1
- package/dist/ui/assets/{swimlanes-5IMT3BWC-uqTAc7o7.js → swimlanes-5IMT3BWC-vO-7dG2V.js} +2 -2
- package/dist/ui/assets/swimlanesDiagram-G3AALYLV-DxwJcNf4.js +8 -0
- package/dist/ui/assets/{taggerscript-BhmPJ64C.js → taggerscript-DM07ScL_.js} +1 -1
- package/dist/ui/assets/{tap-DkueIVKV.js → tap-C6aPKgww.js} +1 -1
- package/dist/ui/assets/{tcl-DyXOOkFi.js → tcl-DjtvddtN.js} +1 -1
- package/dist/ui/assets/{thrift-Dhgbk56T.js → thrift-BoJuHfsC.js} +1 -1
- package/dist/ui/assets/{timeline-definition-FHXFAJF6-DBAk9Ouc.js → timeline-definition-FHXFAJF6-Cn7z-Bqh.js} +1 -1
- package/dist/ui/assets/{tp-BsXyrRGH.js → tp-7mrfAfpj.js} +1 -1
- package/dist/ui/assets/{twig-J0HqIuHw.js → twig-DOOUfGqf.js} +1 -1
- package/dist/ui/assets/{typescript-Dkwvul39.js → typescript-BbC9hUFK.js} +1 -1
- package/dist/ui/assets/{vala-CRGnf8aY.js → vala-XjKwsdD0.js} +1 -1
- package/dist/ui/assets/{vbnet-BtJAUe3z.js → vbnet-Da3MfWx9.js} +1 -1
- package/dist/ui/assets/{vbscript-RgFFNuM3.js → vbscript-DT_81k7v.js} +1 -1
- package/dist/ui/assets/{vbscript-html-DO32Io9Y.js → vbscript-html-DUFh3J5N.js} +1 -1
- package/dist/ui/assets/{vennDiagram-L72KCM5P-BJi6y0hh.js → vennDiagram-L72KCM5P-BnjZCady.js} +1 -1
- package/dist/ui/assets/{verilog-C4h6MLcI.js → verilog-D5Okzav9.js} +1 -1
- package/dist/ui/assets/{vhdl-DOafePyU.js → vhdl-_hcd5jXC.js} +1 -1
- package/dist/ui/assets/{vim-BAdTA9TY.js → vim-BuKTsAic.js} +1 -1
- package/dist/ui/assets/{wardleyDiagram-EHGQE667-C7c_4IJ6.js → wardleyDiagram-EHGQE667-Bgp49v0-.js} +1 -1
- package/dist/ui/assets/{x86asm-BCL6__QH.js → x86asm-DWOIgEnP.js} +1 -1
- package/dist/ui/assets/{xl-qIzE4HOe.js → xl-D6TFf1q5.js} +1 -1
- package/dist/ui/assets/{xml-DE2sJXYU.js → xml-_5JZFtHD.js} +1 -1
- package/dist/ui/assets/{xquery-CQ35t92y.js → xquery-BC0pvaic.js} +1 -1
- package/dist/ui/assets/{xychartDiagram-FW5EYKEG-D2ILn0jL.js → xychartDiagram-FW5EYKEG-DmyzmXyI.js} +1 -1
- package/dist/ui/assets/{yaml-5KaAvG5C.js → yaml-CLzRjl8_.js} +1 -1
- package/dist/ui/assets/{zephir-CbR3Ryfq.js → zephir-nZ6mLYuU.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/dist/utils/__tests__/db-maintenance.test.d.ts +1 -0
- package/dist/utils/__tests__/db-maintenance.test.js +55 -0
- package/dist/utils/__tests__/db-migration.test.js +79 -1
- package/dist/utils/__tests__/request-writer.test.js +264 -3
- package/dist/utils/__tests__/stats-aggregator.test.d.ts +1 -0
- package/dist/utils/__tests__/stats-aggregator.test.js +114 -0
- package/dist/utils/db-maintenance.d.ts +8 -0
- package/dist/utils/db-maintenance.js +18 -0
- package/dist/utils/db.d.ts +1 -0
- package/dist/utils/db.js +62 -0
- package/dist/utils/request-writer.d.ts +23 -1
- package/dist/utils/request-writer.js +200 -14
- package/dist/utils/stats-aggregator.d.ts +1 -1
- package/dist/utils/stats-aggregator.js +44 -27
- package/ecosystem.config.cjs +2 -1
- package/package.json +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.C4sjikMO.js +0 -1
- package/dist/server/middleware/__tests__/request-id.test.js +0 -28
- package/dist/server/middleware/request-id.d.ts +0 -2
- package/dist/server/middleware/request-id.js +0 -12
- package/dist/ui/assets/channel-ChTtxQDg.js +0 -1
- package/dist/ui/assets/classDiagram-OUVF2IWQ-Ccbvamlu.js +0 -1
- package/dist/ui/assets/classDiagram-v2-EOCWNBFH-Ccbvamlu.js +0 -1
- package/dist/ui/assets/stateDiagram-v2-6OUMAXLB-0JnZ1VSQ.js +0 -1
- package/dist/ui/assets/swimlanesDiagram-G3AALYLV-DQRIIEmi.js +0 -8
- /package/dist/server/{middleware/__tests__/request-id.test.d.ts → __tests__/data-push.test.d.ts} +0 -0
|
@@ -2,7 +2,7 @@ import { mkdtempSync, rmSync } from 'fs';
|
|
|
2
2
|
import { tmpdir } from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { configureDbPath, initDatabase, closeDatabase, getDatabase } from '../db.js';
|
|
5
|
-
import { createRequestRecorder, recordIteration, listRequestDB, interruptStaleRequestsOnStartup } from '../request-writer.js';
|
|
5
|
+
import { createRequestRecorder, recordIteration, listRequestDB, interruptStaleRequestsOnStartup, repairClosedRequests, isStreamCompleteEvents, reconstructUsageFromEvents } from '../request-writer.js';
|
|
6
6
|
import { pushNewRequest } from '../../server/data-push.js';
|
|
7
7
|
jest.mock('../../server/data-push.js', () => ({ pushNewRequest: jest.fn() }));
|
|
8
8
|
const pushMock = pushNewRequest;
|
|
@@ -136,6 +136,62 @@ describe('request-writer', () => {
|
|
|
136
136
|
expect(summary.requestId).toBe(cloudId);
|
|
137
137
|
expect(summary.pendingId).toBe(`${meta.sessionId}:${meta.localRequestId}`);
|
|
138
138
|
});
|
|
139
|
+
it('complete() migration leaves pre-existing events of another session with the same localRequestId untouched', async () => {
|
|
140
|
+
const meta = makeMeta('residue');
|
|
141
|
+
counter += 1;
|
|
142
|
+
const cloudId = `cloud_${counter}`;
|
|
143
|
+
const oldSession = 'sess_old_residue';
|
|
144
|
+
// recorder 创建之前:相同 localRequestId、不同 session_id 的历史残留事件
|
|
145
|
+
const preIns = getDatabase().prepare('INSERT INTO response_events (request_id, session_id, event_index, event_data) VALUES (?, ?, ?, ?)');
|
|
146
|
+
preIns.run(meta.localRequestId, oldSession, 0, '"old0"');
|
|
147
|
+
preIns.run(meta.localRequestId, oldSession, 1, '"old1"');
|
|
148
|
+
const rec = createRequestRecorder(meta);
|
|
149
|
+
await rec.pending();
|
|
150
|
+
for (let i = 0; i < 3; i++) {
|
|
151
|
+
rec.appendEvent({ type: 'chunk', seq: i });
|
|
152
|
+
}
|
|
153
|
+
await rec.complete({ cloudRequestId: cloudId, statusCode: 200 });
|
|
154
|
+
// 预写事件 request_id 不变(未被迁走)
|
|
155
|
+
const staleRows = getEventRows(meta.localRequestId, oldSession);
|
|
156
|
+
expect(staleRows).toHaveLength(2);
|
|
157
|
+
expect(staleRows.map(r => [r.request_id, r.event_index])).toEqual([
|
|
158
|
+
[meta.localRequestId, 0],
|
|
159
|
+
[meta.localRequestId, 1]
|
|
160
|
+
]);
|
|
161
|
+
// 本 recorder 的事件迁到 cloudId,localId 下无事件残留
|
|
162
|
+
expect(getEventRows(meta.localRequestId, meta.sessionId)).toHaveLength(0);
|
|
163
|
+
const moved = getEventRows(cloudId, meta.sessionId);
|
|
164
|
+
expect(moved).toHaveLength(3);
|
|
165
|
+
expect(moved.map(r => r.event_index)).toEqual([0, 1, 2]);
|
|
166
|
+
});
|
|
167
|
+
it('complete() migration leaves same-session historical events with the same localRequestId untouched', async () => {
|
|
168
|
+
const meta = makeMeta('sameresidue');
|
|
169
|
+
counter += 1;
|
|
170
|
+
const cloudId = `cloud_${counter}`;
|
|
171
|
+
// recorder 创建之前:相同 localRequestId、相同 session_id 的历史残留
|
|
172
|
+
// (模拟 req-N 计数器复用后同 session 残留)。用不与新请求重叠的 event_index,
|
|
173
|
+
// 避免唯一索引 + INSERT OR IGNORE 干扰本用例对迁移行为的验证。
|
|
174
|
+
const preIns = getDatabase().prepare('INSERT INTO response_events (request_id, session_id, event_index, event_data) VALUES (?, ?, ?, ?)');
|
|
175
|
+
preIns.run(meta.localRequestId, meta.sessionId, 50, '"old0"');
|
|
176
|
+
preIns.run(meta.localRequestId, meta.sessionId, 51, '"old1"');
|
|
177
|
+
const rec = createRequestRecorder(meta);
|
|
178
|
+
await rec.pending();
|
|
179
|
+
for (let i = 0; i < 3; i++) {
|
|
180
|
+
rec.appendEvent({ type: 'chunk', seq: i });
|
|
181
|
+
}
|
|
182
|
+
await rec.complete({ cloudRequestId: cloudId, statusCode: 200 });
|
|
183
|
+
// 预写的 2 行 request_id 仍为 localRequestId(未被迁走)
|
|
184
|
+
const staleRows = getEventRows(meta.localRequestId, meta.sessionId);
|
|
185
|
+
expect(staleRows).toHaveLength(2);
|
|
186
|
+
expect(staleRows.map(r => [r.request_id, r.event_index])).toEqual([
|
|
187
|
+
[meta.localRequestId, 50],
|
|
188
|
+
[meta.localRequestId, 51]
|
|
189
|
+
]);
|
|
190
|
+
// recorder 自己的事件迁到 cloudId
|
|
191
|
+
const moved = getEventRows(cloudId, meta.sessionId);
|
|
192
|
+
expect(moved).toHaveLength(3);
|
|
193
|
+
expect(moved.map(r => r.event_index)).toEqual([0, 1, 2]);
|
|
194
|
+
});
|
|
139
195
|
it('fail() updates the pending row status/error/closed_at and pushes completed', async () => {
|
|
140
196
|
const meta = makeMeta('fail');
|
|
141
197
|
const rec = createRequestRecorder(meta);
|
|
@@ -240,8 +296,10 @@ describe('request-writer', () => {
|
|
|
240
296
|
const open = requests.find(r => r.requestId === openMeta.localRequestId);
|
|
241
297
|
expect(closed).toBeTruthy();
|
|
242
298
|
expect(closed.closedAt).toEqual(expect.any(String));
|
|
299
|
+
expect(closed.error).toBe('boom');
|
|
243
300
|
expect(open).toBeTruthy();
|
|
244
301
|
expect(open.closedAt).toBeUndefined();
|
|
302
|
+
expect(open.error).toBeUndefined();
|
|
245
303
|
});
|
|
246
304
|
it('interruptStaleRequestsOnStartup() marks aged orphan rows and leaves fresh/completed rows untouched', async () => {
|
|
247
305
|
// 超龄无主行:已收到响应头(200),时间戳回拨到 6 分钟前
|
|
@@ -264,8 +322,9 @@ describe('request-writer', () => {
|
|
|
264
322
|
getDatabase()
|
|
265
323
|
.prepare('UPDATE requests SET timestamp = ? WHERE request_id = ? AND session_id = ?')
|
|
266
324
|
.run(new Date(Date.now() - 6 * 60000).toISOString(), doneMeta.localRequestId, doneMeta.sessionId);
|
|
267
|
-
const
|
|
268
|
-
expect(
|
|
325
|
+
const { completed, interrupted } = interruptStaleRequestsOnStartup();
|
|
326
|
+
expect(completed).toBe(0);
|
|
327
|
+
expect(interrupted).toBe(1);
|
|
269
328
|
const oldRow = getRequestRow(oldMeta.localRequestId, oldMeta.sessionId);
|
|
270
329
|
expect(oldRow.closed_at).toBeTruthy();
|
|
271
330
|
expect(oldRow.error).toBe('interrupted: proxy restarted');
|
|
@@ -277,4 +336,206 @@ describe('request-writer', () => {
|
|
|
277
336
|
const doneRow = getRequestRow(doneMeta.localRequestId, doneMeta.sessionId);
|
|
278
337
|
expect(doneRow.closed_at).toBeNull();
|
|
279
338
|
});
|
|
339
|
+
it('harvests an aged pending row ending with message_stop as completed: completed_at set, closed_at/error empty, tokens backfilled', async () => {
|
|
340
|
+
const meta = makeMeta('stopcomplete');
|
|
341
|
+
const rec = createRequestRecorder(meta);
|
|
342
|
+
await rec.pending();
|
|
343
|
+
rec.headers({ statusCode: 200 });
|
|
344
|
+
rec.appendEvent({
|
|
345
|
+
type: 'message_start',
|
|
346
|
+
usage: { input_tokens: 100, cache_read_input_tokens: 10, cache_creation_input_tokens: 2, output_tokens: 1 }
|
|
347
|
+
});
|
|
348
|
+
// 17 内容事件:总事件数 20(整批落库),message_start 在尾部 10 条窗口之外,
|
|
349
|
+
// 依赖定向 SQL 取锚点事件
|
|
350
|
+
for (let i = 0; i < 17; i++) {
|
|
351
|
+
rec.appendEvent({ type: 'content_block_delta', seq: i });
|
|
352
|
+
}
|
|
353
|
+
rec.appendEvent({ type: 'message_delta', usage: { output_tokens: 50 } });
|
|
354
|
+
rec.appendEvent({ type: 'message_stop' });
|
|
355
|
+
getDatabase()
|
|
356
|
+
.prepare('UPDATE requests SET timestamp = ? WHERE request_id = ? AND session_id = ?')
|
|
357
|
+
.run(new Date(Date.now() - 6 * 60000).toISOString(), meta.localRequestId, meta.sessionId);
|
|
358
|
+
const { completed, interrupted } = interruptStaleRequestsOnStartup();
|
|
359
|
+
expect(completed).toBe(1);
|
|
360
|
+
expect(interrupted).toBe(0);
|
|
361
|
+
const row = getRequestRow(meta.localRequestId, meta.sessionId);
|
|
362
|
+
expect(row.completed_at).toBeTruthy();
|
|
363
|
+
expect(row.closed_at).toBeNull();
|
|
364
|
+
expect(row.error).toBeNull();
|
|
365
|
+
expect(row.status_code).toBe(200);
|
|
366
|
+
expect(row.response_time).toBeGreaterThan(0);
|
|
367
|
+
expect(row.input_tokens).toBe(100);
|
|
368
|
+
expect(row.output_tokens).toBe(50);
|
|
369
|
+
expect(row.cache_read_tokens).toBe(10);
|
|
370
|
+
expect(row.cache_creation_tokens).toBe(2);
|
|
371
|
+
});
|
|
372
|
+
it('aged row without message_stop stays interrupted but tokens are backfilled when usage is recoverable', async () => {
|
|
373
|
+
const meta = makeMeta('stopmissing');
|
|
374
|
+
const rec = createRequestRecorder(meta);
|
|
375
|
+
await rec.pending();
|
|
376
|
+
rec.headers({ statusCode: 200 });
|
|
377
|
+
rec.appendEvent({
|
|
378
|
+
type: 'message_start',
|
|
379
|
+
usage: { input_tokens: 30, cache_read_input_tokens: 3, cache_creation_input_tokens: 1 }
|
|
380
|
+
});
|
|
381
|
+
for (let i = 0; i < 18; i++) {
|
|
382
|
+
rec.appendEvent({ type: 'content_block_delta', seq: i });
|
|
383
|
+
}
|
|
384
|
+
rec.appendEvent({ type: 'message_delta', usage: { output_tokens: 12 } });
|
|
385
|
+
getDatabase()
|
|
386
|
+
.prepare('UPDATE requests SET timestamp = ? WHERE request_id = ? AND session_id = ?')
|
|
387
|
+
.run(new Date(Date.now() - 6 * 60000).toISOString(), meta.localRequestId, meta.sessionId);
|
|
388
|
+
const { completed, interrupted } = interruptStaleRequestsOnStartup();
|
|
389
|
+
expect(completed).toBe(0);
|
|
390
|
+
expect(interrupted).toBe(1);
|
|
391
|
+
const row = getRequestRow(meta.localRequestId, meta.sessionId);
|
|
392
|
+
expect(row.closed_at).toBeTruthy();
|
|
393
|
+
expect(row.error).toBe('interrupted: proxy restarted');
|
|
394
|
+
expect(row.completed_at).toBeNull();
|
|
395
|
+
expect(row.input_tokens).toBe(30);
|
|
396
|
+
expect(row.output_tokens).toBe(12);
|
|
397
|
+
expect(row.cache_read_tokens).toBe(3);
|
|
398
|
+
expect(row.cache_creation_tokens).toBe(1);
|
|
399
|
+
});
|
|
400
|
+
it('reconstructUsageFromEvents merges message_start base with message_delta into all four fields (field-level, not overwrite)', () => {
|
|
401
|
+
const usage = reconstructUsageFromEvents([
|
|
402
|
+
{ type: 'message_start', usage: { input_tokens: 100, output_tokens: 1, cache_read_input_tokens: 80, cache_creation_input_tokens: 5 } },
|
|
403
|
+
{ type: 'content_block_delta' },
|
|
404
|
+
{ type: 'message_delta', usage: { output_tokens: 42 } }
|
|
405
|
+
]);
|
|
406
|
+
expect(usage).toEqual({
|
|
407
|
+
input_tokens: 100,
|
|
408
|
+
output_tokens: 42,
|
|
409
|
+
cache_read_input_tokens: 80,
|
|
410
|
+
cache_creation_input_tokens: 5
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
it('message_delta cache fields are merged too, and undefined is returned when no usage exists', () => {
|
|
414
|
+
const usage = reconstructUsageFromEvents([
|
|
415
|
+
{ type: 'message_start', usage: { input_tokens: 10 } },
|
|
416
|
+
{ type: 'message_delta', usage: { output_tokens: 4, cache_read_input_tokens: 2 } }
|
|
417
|
+
]);
|
|
418
|
+
expect(usage).toEqual({ input_tokens: 10, output_tokens: 4, cache_read_input_tokens: 2 });
|
|
419
|
+
expect(reconstructUsageFromEvents([{ type: 'content_block_delta' }])).toBeUndefined();
|
|
420
|
+
});
|
|
421
|
+
it('OpenAI-style: choices[0].finish_reason non-empty marks complete, and a chunk with usage backfills tokens', async () => {
|
|
422
|
+
expect(isStreamCompleteEvents([
|
|
423
|
+
{ choices: [{ delta: { role: 'assistant' }, finish_reason: null }] }
|
|
424
|
+
])).toBe(false);
|
|
425
|
+
expect(isStreamCompleteEvents([
|
|
426
|
+
{ choices: [{ delta: {}, finish_reason: 'stop' }] }
|
|
427
|
+
])).toBe(true);
|
|
428
|
+
const usage = reconstructUsageFromEvents([
|
|
429
|
+
{ choices: [{ delta: { content: 'hi' }, finish_reason: null }] },
|
|
430
|
+
{ choices: [{ delta: {}, finish_reason: 'stop' }], usage: { prompt_tokens: 9, completion_tokens: 17 } }
|
|
431
|
+
]);
|
|
432
|
+
expect(usage).toEqual({ input_tokens: 9, output_tokens: 17 });
|
|
433
|
+
// 端到端:OpenAI 超龄行(事件 event_type 为 NULL)走完成语义并回填
|
|
434
|
+
const meta = makeMeta('openaistop');
|
|
435
|
+
const rec = createRequestRecorder(meta);
|
|
436
|
+
await rec.pending();
|
|
437
|
+
rec.headers({ statusCode: 200 });
|
|
438
|
+
for (let i = 0; i < 19; i++) {
|
|
439
|
+
rec.appendEvent({ choices: [{ delta: { content: 'x' }, finish_reason: null }] });
|
|
440
|
+
}
|
|
441
|
+
rec.appendEvent({ choices: [{ delta: {}, finish_reason: 'stop' }], usage: { prompt_tokens: 9, completion_tokens: 17 } });
|
|
442
|
+
getDatabase()
|
|
443
|
+
.prepare('UPDATE requests SET timestamp = ? WHERE request_id = ? AND session_id = ?')
|
|
444
|
+
.run(new Date(Date.now() - 6 * 60000).toISOString(), meta.localRequestId, meta.sessionId);
|
|
445
|
+
const { completed, interrupted } = interruptStaleRequestsOnStartup();
|
|
446
|
+
expect(completed).toBe(1);
|
|
447
|
+
expect(interrupted).toBe(0);
|
|
448
|
+
const row = getRequestRow(meta.localRequestId, meta.sessionId);
|
|
449
|
+
expect(row.completed_at).toBeTruthy();
|
|
450
|
+
expect(row.closed_at).toBeNull();
|
|
451
|
+
expect(row.input_tokens).toBe(9);
|
|
452
|
+
expect(row.output_tokens).toBe(17);
|
|
453
|
+
});
|
|
454
|
+
it('interrupt() also backfills recoverable token columns', async () => {
|
|
455
|
+
const meta = makeMeta('interrupttokens');
|
|
456
|
+
const rec = createRequestRecorder(meta);
|
|
457
|
+
await rec.pending();
|
|
458
|
+
rec.appendEvent({
|
|
459
|
+
type: 'message_start',
|
|
460
|
+
usage: { input_tokens: 20, cache_read_input_tokens: 4, cache_creation_input_tokens: 1 }
|
|
461
|
+
});
|
|
462
|
+
for (let i = 0; i < 20; i++) {
|
|
463
|
+
rec.appendEvent({ type: 'content_block_delta', seq: i });
|
|
464
|
+
}
|
|
465
|
+
rec.appendEvent({ type: 'message_delta', usage: { output_tokens: 7 } });
|
|
466
|
+
pushMock.mockClear();
|
|
467
|
+
rec.interrupt({ statusCode: 200, error: 'stream interrupted: socket hang up' });
|
|
468
|
+
const row = getRequestRow(meta.localRequestId, meta.sessionId);
|
|
469
|
+
expect(row.closed_at).toBeTruthy();
|
|
470
|
+
expect(row.error).toBe('stream interrupted: socket hang up');
|
|
471
|
+
expect(row.input_tokens).toBe(20);
|
|
472
|
+
expect(row.output_tokens).toBe(7);
|
|
473
|
+
expect(row.cache_read_tokens).toBe(4);
|
|
474
|
+
expect(row.cache_creation_tokens).toBe(1);
|
|
475
|
+
const completedCalls = pushMock.mock.calls.filter(c => c[1] === 'completed');
|
|
476
|
+
expect(completedCalls[0][0].usage).toEqual(expect.objectContaining({ output_tokens: 7 }));
|
|
477
|
+
});
|
|
478
|
+
it('repairClosedRequests recovers closed complete rows, fills tokens, and is idempotent', async () => {
|
|
479
|
+
// 完整但被旧逻辑误标中断的行:message_stop 已入库,token 全 NULL
|
|
480
|
+
const completeMeta = makeMeta('repaircomplete');
|
|
481
|
+
const completeRec = createRequestRecorder(completeMeta);
|
|
482
|
+
await completeRec.pending();
|
|
483
|
+
completeRec.appendEvent({
|
|
484
|
+
type: 'message_start',
|
|
485
|
+
usage: { input_tokens: 15, cache_read_input_tokens: 2, cache_creation_input_tokens: 1 }
|
|
486
|
+
});
|
|
487
|
+
for (let i = 0; i < 20; i++) {
|
|
488
|
+
completeRec.appendEvent({ type: 'content_block_delta', seq: i });
|
|
489
|
+
}
|
|
490
|
+
completeRec.appendEvent({ type: 'message_delta', usage: { output_tokens: 6 } });
|
|
491
|
+
completeRec.appendEvent({ type: 'message_stop' });
|
|
492
|
+
completeRec.interrupt({ statusCode: 200, error: 'interrupted: proxy restarted' });
|
|
493
|
+
// 模拟旧版本中断行:清空 token 列(旧 interrupt 不回填 usage)
|
|
494
|
+
getDatabase()
|
|
495
|
+
.prepare(`UPDATE requests SET
|
|
496
|
+
input_tokens = NULL, output_tokens = NULL,
|
|
497
|
+
cache_read_tokens = NULL, cache_creation_tokens = NULL
|
|
498
|
+
WHERE request_id = ? AND session_id = ?`)
|
|
499
|
+
.run(completeMeta.localRequestId, completeMeta.sessionId);
|
|
500
|
+
// 真正中断的行:无 message_stop,但 usage 可恢复
|
|
501
|
+
const brokenMeta = makeMeta('repairbroken');
|
|
502
|
+
const brokenRec = createRequestRecorder(brokenMeta);
|
|
503
|
+
await brokenRec.pending();
|
|
504
|
+
brokenRec.appendEvent({
|
|
505
|
+
type: 'message_start',
|
|
506
|
+
usage: { input_tokens: 8, cache_read_input_tokens: 1 }
|
|
507
|
+
});
|
|
508
|
+
for (let i = 0; i < 20; i++) {
|
|
509
|
+
brokenRec.appendEvent({ type: 'content_block_delta', seq: i });
|
|
510
|
+
}
|
|
511
|
+
brokenRec.appendEvent({ type: 'message_delta', usage: { output_tokens: 3 } });
|
|
512
|
+
brokenRec.interrupt({ statusCode: 200, error: 'interrupted: proxy restarted' });
|
|
513
|
+
// 模拟旧版本中断行:清空 token 列
|
|
514
|
+
getDatabase()
|
|
515
|
+
.prepare(`UPDATE requests SET
|
|
516
|
+
input_tokens = NULL, output_tokens = NULL,
|
|
517
|
+
cache_read_tokens = NULL, cache_creation_tokens = NULL
|
|
518
|
+
WHERE request_id = ? AND session_id = ?`)
|
|
519
|
+
.run(brokenMeta.localRequestId, brokenMeta.sessionId);
|
|
520
|
+
const first = repairClosedRequests();
|
|
521
|
+
expect(first.recovered).toBe(1);
|
|
522
|
+
expect(first.tokensFilled).toBe(2);
|
|
523
|
+
const recoveredRow = getRequestRow(completeMeta.localRequestId, completeMeta.sessionId);
|
|
524
|
+
expect(recoveredRow.closed_at).toBeNull();
|
|
525
|
+
expect(recoveredRow.error).toBeNull();
|
|
526
|
+
expect(recoveredRow.completed_at).toBeTruthy();
|
|
527
|
+
expect(recoveredRow.input_tokens).toBe(15);
|
|
528
|
+
expect(recoveredRow.output_tokens).toBe(6);
|
|
529
|
+
const brokenRow = getRequestRow(brokenMeta.localRequestId, brokenMeta.sessionId);
|
|
530
|
+
expect(brokenRow.closed_at).toBeTruthy();
|
|
531
|
+
expect(brokenRow.input_tokens).toBe(8);
|
|
532
|
+
expect(brokenRow.output_tokens).toBe(3);
|
|
533
|
+
// 幂等:第二次不产生任何变更,行状态保持
|
|
534
|
+
const second = repairClosedRequests();
|
|
535
|
+
expect(second).toEqual({ recovered: 0, tokensFilled: 0 });
|
|
536
|
+
const rerunRecovered = getRequestRow(completeMeta.localRequestId, completeMeta.sessionId);
|
|
537
|
+
expect(rerunRecovered.completed_at).toBe(recoveredRow.completed_at);
|
|
538
|
+
const rerunBroken = getRequestRow(brokenMeta.localRequestId, brokenMeta.sessionId);
|
|
539
|
+
expect(rerunBroken.closed_at).toBe(brokenRow.closed_at);
|
|
540
|
+
});
|
|
280
541
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from 'fs';
|
|
2
|
+
import { tmpdir } from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { configureDbPath, initDatabase, closeDatabase, getDatabase } from '../db.js';
|
|
5
|
+
import { aggregateStats, getStatsData, getTodayStats } from '../stats-aggregator.js';
|
|
6
|
+
let counter = 0;
|
|
7
|
+
// 直接往 requests 表插行,绕过 request-writer,聚焦聚合口径
|
|
8
|
+
function insertRequest(opts) {
|
|
9
|
+
counter += 1;
|
|
10
|
+
getDatabase()
|
|
11
|
+
.prepare(`
|
|
12
|
+
INSERT INTO requests (
|
|
13
|
+
request_id, session_id, timestamp, method, path, status_code, response_time,
|
|
14
|
+
model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, closed_at
|
|
15
|
+
) VALUES (?, ?, ?, 'POST', '/v1/messages', 200, 100, ?, ?, ?, ?, ?, ?)
|
|
16
|
+
`)
|
|
17
|
+
.run(`req_${counter}`, `sess_${counter}`, opts.timestamp, opts.model ?? null, opts.inputTokens ?? null, opts.outputTokens ?? null, opts.cacheReadTokens ?? null, opts.cacheCreationTokens ?? null, opts.closedAt ?? null);
|
|
18
|
+
}
|
|
19
|
+
function hourKeyOf(iso) {
|
|
20
|
+
const d = new Date(iso);
|
|
21
|
+
return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')}-${String(d.getUTCHours()).padStart(2, '0')}`;
|
|
22
|
+
}
|
|
23
|
+
describe('stats-aggregator', () => {
|
|
24
|
+
let tempDir;
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
tempDir = mkdtempSync(path.join(tmpdir(), 'ccm-stats-'));
|
|
27
|
+
configureDbPath(path.join(tempDir, 'test.db'));
|
|
28
|
+
initDatabase();
|
|
29
|
+
});
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
closeDatabase();
|
|
32
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
33
|
+
});
|
|
34
|
+
it('P0: requests 源行删除后重算,冻结的 hourly 快照同步消失', () => {
|
|
35
|
+
const ts = new Date(Date.now() - 2 * 3600000).toISOString();
|
|
36
|
+
const key = hourKeyOf(ts);
|
|
37
|
+
insertRequest({ timestamp: ts, inputTokens: 100, outputTokens: 20 });
|
|
38
|
+
aggregateStats();
|
|
39
|
+
let stats = getStatsData();
|
|
40
|
+
expect(stats.hourly[key]).toBeTruthy();
|
|
41
|
+
expect(stats.hourly[key].input_tokens).toBe(100);
|
|
42
|
+
expect(stats.hourly[key].request_count).toBe(1);
|
|
43
|
+
// 模拟 cleanOldRequestsDB 把源行删掉
|
|
44
|
+
getDatabase().prepare('DELETE FROM requests').run();
|
|
45
|
+
aggregateStats();
|
|
46
|
+
stats = getStatsData();
|
|
47
|
+
expect(stats.hourly[key]).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
it('P2: closed_at 非空的中断行仍计入 aggregateStats 与 getTodayStats', () => {
|
|
50
|
+
const ts = new Date().toISOString();
|
|
51
|
+
const key = hourKeyOf(ts);
|
|
52
|
+
insertRequest({
|
|
53
|
+
timestamp: ts,
|
|
54
|
+
inputTokens: 100,
|
|
55
|
+
outputTokens: 50,
|
|
56
|
+
cacheReadTokens: 10,
|
|
57
|
+
cacheCreationTokens: 3,
|
|
58
|
+
closedAt: new Date().toISOString(),
|
|
59
|
+
model: 'claude-interrupted'
|
|
60
|
+
});
|
|
61
|
+
aggregateStats();
|
|
62
|
+
const entry = getStatsData().hourly[key];
|
|
63
|
+
expect(entry).toBeTruthy();
|
|
64
|
+
expect(entry.input_tokens).toBe(100);
|
|
65
|
+
expect(entry.output_tokens).toBe(50);
|
|
66
|
+
expect(entry.cache_read_tokens).toBe(10);
|
|
67
|
+
expect(entry.cache_creation_tokens).toBe(3);
|
|
68
|
+
expect(entry.request_count).toBe(1);
|
|
69
|
+
const since = new Date(Date.now() - 3600000).toISOString();
|
|
70
|
+
const today = getTodayStats(since);
|
|
71
|
+
expect(today.input_tokens).toBe(100);
|
|
72
|
+
expect(today.output_tokens).toBe(50);
|
|
73
|
+
expect(today.cache_read_tokens).toBe(10);
|
|
74
|
+
expect(today.request_count).toBe(1);
|
|
75
|
+
expect(today.models['claude-interrupted']).toBe(1);
|
|
76
|
+
});
|
|
77
|
+
it('P1: aggregateStats(1) 只保留 1 天窗口,无参仍为 30 天窗口', () => {
|
|
78
|
+
const nowIso = new Date().toISOString();
|
|
79
|
+
const oldIso = new Date(Date.now() - 2 * 86400000).toISOString();
|
|
80
|
+
const nowKey = hourKeyOf(nowIso);
|
|
81
|
+
const oldKey = hourKeyOf(oldIso);
|
|
82
|
+
insertRequest({ timestamp: nowIso, inputTokens: 1 });
|
|
83
|
+
insertRequest({ timestamp: oldIso, inputTokens: 2 });
|
|
84
|
+
aggregateStats(1);
|
|
85
|
+
let stats = getStatsData();
|
|
86
|
+
expect(stats.hourly[nowKey]).toBeTruthy();
|
|
87
|
+
expect(stats.hourly[oldKey]).toBeUndefined();
|
|
88
|
+
aggregateStats();
|
|
89
|
+
stats = getStatsData();
|
|
90
|
+
expect(stats.hourly[nowKey]).toBeTruthy();
|
|
91
|
+
expect(stats.hourly[oldKey]).toBeTruthy();
|
|
92
|
+
});
|
|
93
|
+
describe('P1b: aggregateStats 的 windowDays 分支(截断与回退)', () => {
|
|
94
|
+
// 31 天前落在 30 天窗口外、40 天窗口内:保留它意味着窗口被放宽到 40,
|
|
95
|
+
// 删除它才是「截断/回退为 30」;2 天前始终在窗口内。
|
|
96
|
+
it.each([
|
|
97
|
+
[40, '超过 30 截断为 30'],
|
|
98
|
+
[0, '0 回退默认 30'],
|
|
99
|
+
[-3, '负数回退默认 30'],
|
|
100
|
+
[1.5, '非整数回退默认 30']
|
|
101
|
+
])('maxDays = %s(%s):2 天前保留,31 天前删除', (maxDays, _desc) => {
|
|
102
|
+
const recentIso = new Date(Date.now() - 2 * 86400000).toISOString();
|
|
103
|
+
const oldIso = new Date(Date.now() - 31 * 86400000).toISOString();
|
|
104
|
+
const recentKey = hourKeyOf(recentIso);
|
|
105
|
+
const oldKey = hourKeyOf(oldIso);
|
|
106
|
+
insertRequest({ timestamp: recentIso, inputTokens: 2 });
|
|
107
|
+
insertRequest({ timestamp: oldIso, inputTokens: 31 });
|
|
108
|
+
aggregateStats(maxDays);
|
|
109
|
+
const stats = getStatsData();
|
|
110
|
+
expect(stats.hourly[recentKey]).toBeTruthy();
|
|
111
|
+
expect(stats.hourly[oldKey]).toBeUndefined();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// 回收空闲页。auto_vacuum 不是 INCREMENTAL 时,pragma 修改只对新建库生效,
|
|
2
|
+
// 已有库必须先 VACUUM 全库重写才能让 pragma 生效;之后例行调用走
|
|
3
|
+
// incremental_vacuum,只还页不重写,代价低。
|
|
4
|
+
export function vacuumDatabase(db) {
|
|
5
|
+
const autoVacuumBefore = db.pragma('auto_vacuum', { simple: true }) ?? 0;
|
|
6
|
+
const pagesBefore = db.pragma('page_count', { simple: true });
|
|
7
|
+
const pageSize = db.pragma('page_size', { simple: true });
|
|
8
|
+
if (autoVacuumBefore !== 2) {
|
|
9
|
+
db.pragma('auto_vacuum = INCREMENTAL');
|
|
10
|
+
// 首次:全库重写,pragma 在重写后生效
|
|
11
|
+
db.exec('VACUUM');
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
db.pragma('incremental_vacuum');
|
|
15
|
+
}
|
|
16
|
+
const pagesAfter = db.pragma('page_count', { simple: true });
|
|
17
|
+
return { autoVacuumBefore, pagesBefore, pagesAfter, pageSize };
|
|
18
|
+
}
|
package/dist/utils/db.d.ts
CHANGED
|
@@ -11,5 +11,6 @@ export declare function applySchemaV8(db: Database.Database): void;
|
|
|
11
11
|
export declare function applySchemaV9(db: Database.Database): void;
|
|
12
12
|
export declare function applySchemaV10(db: Database.Database): void;
|
|
13
13
|
export declare function applySchemaV11(db: Database.Database): void;
|
|
14
|
+
export declare function applySchemaV12(db: Database.Database): void;
|
|
14
15
|
export declare function closeDatabase(): void;
|
|
15
16
|
export declare function getDatabase(): Database.Database;
|
package/dist/utils/db.js
CHANGED
|
@@ -76,6 +76,10 @@ export function initDatabase() {
|
|
|
76
76
|
applySchemaV11(db);
|
|
77
77
|
db.prepare('INSERT OR REPLACE INTO schema_version (version) VALUES (11)').run();
|
|
78
78
|
}
|
|
79
|
+
if (currentVersion < 12) {
|
|
80
|
+
applySchemaV12(db);
|
|
81
|
+
db.prepare('INSERT OR REPLACE INTO schema_version (version) VALUES (12)').run();
|
|
82
|
+
}
|
|
79
83
|
return db;
|
|
80
84
|
}
|
|
81
85
|
export function applySchemaV2(db) {
|
|
@@ -304,6 +308,64 @@ export function applySchemaV11(db) {
|
|
|
304
308
|
ON learn_tasks(slug) WHERE status = 'running'
|
|
305
309
|
`);
|
|
306
310
|
}
|
|
311
|
+
export function applySchemaV12(db) {
|
|
312
|
+
// response_events 历史上存在 (request_id, session_id, event_index) 三元组重复行
|
|
313
|
+
// (request_id 计数器重启复用所致)和 requests 表无对应行的孤儿事件;清理后加
|
|
314
|
+
// 唯一索引硬守。生产库约 800 万行:先一次性把待删行号收集进临时表,再按主键
|
|
315
|
+
// 分批删除(每批 5000 行、独立事务),避免单个巨型事务长时间锁库。
|
|
316
|
+
//
|
|
317
|
+
// 注意:不能用 `GROUP BY ... HAVING COUNT(*) > 1 AND id != MIN(id)` 直接选行——
|
|
318
|
+
// SQLite 裸列 id 在该计划下取 MIN(id) 所在行,HAVING 恒假,实测删除 0 行。
|
|
319
|
+
const batchSize = 5000;
|
|
320
|
+
db.exec('DROP TABLE IF EXISTS temp.ids_to_purge');
|
|
321
|
+
db.exec('CREATE TEMP TABLE ids_to_purge (id INTEGER PRIMARY KEY)');
|
|
322
|
+
// a) 去重:每组 (request_id, session_id, event_index) 保留 MIN(id),其余入临时表
|
|
323
|
+
db.prepare(`
|
|
324
|
+
INSERT INTO ids_to_purge
|
|
325
|
+
SELECT id FROM response_events
|
|
326
|
+
WHERE id NOT IN (
|
|
327
|
+
SELECT MIN(id) FROM response_events
|
|
328
|
+
GROUP BY request_id, session_id, event_index
|
|
329
|
+
)
|
|
330
|
+
`).run();
|
|
331
|
+
purgeResponseEventsBatched(db, batchSize);
|
|
332
|
+
// b) 孤儿事件:requests 表中无对应 request_id
|
|
333
|
+
db.prepare(`
|
|
334
|
+
INSERT INTO ids_to_purge
|
|
335
|
+
SELECT re.id FROM response_events re
|
|
336
|
+
WHERE NOT EXISTS (
|
|
337
|
+
SELECT 1 FROM requests r WHERE r.request_id = re.request_id
|
|
338
|
+
)
|
|
339
|
+
`).run();
|
|
340
|
+
purgeResponseEventsBatched(db, batchSize);
|
|
341
|
+
db.exec('DROP TABLE IF EXISTS temp.ids_to_purge');
|
|
342
|
+
// c) 唯一索引(IF NOT EXISTS,幂等)
|
|
343
|
+
db.exec(`
|
|
344
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_response_events
|
|
345
|
+
ON response_events(request_id, session_id, event_index)
|
|
346
|
+
`);
|
|
347
|
+
}
|
|
348
|
+
// 按临时表 ids_to_purge 收集的行号分批删除 response_events;每批同时剪除临时表中
|
|
349
|
+
// 已处理的行号,整批包在一个事务里,循环直到没有可删行。
|
|
350
|
+
function purgeResponseEventsBatched(db, batchSize) {
|
|
351
|
+
const deleteEvents = db.prepare(`
|
|
352
|
+
DELETE FROM response_events
|
|
353
|
+
WHERE id IN (SELECT id FROM ids_to_purge ORDER BY id LIMIT ?)
|
|
354
|
+
`);
|
|
355
|
+
const pruneTemp = db.prepare(`
|
|
356
|
+
DELETE FROM ids_to_purge
|
|
357
|
+
WHERE id IN (SELECT id FROM ids_to_purge ORDER BY id LIMIT ?)
|
|
358
|
+
`);
|
|
359
|
+
const runBatch = db.transaction(() => {
|
|
360
|
+
const info = deleteEvents.run(batchSize);
|
|
361
|
+
pruneTemp.run(batchSize);
|
|
362
|
+
return info.changes;
|
|
363
|
+
});
|
|
364
|
+
while (true) {
|
|
365
|
+
if (runBatch() === 0)
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
307
369
|
export function closeDatabase() {
|
|
308
370
|
if (db) {
|
|
309
371
|
db.close();
|
|
@@ -37,6 +37,7 @@ export interface RequestSummary {
|
|
|
37
37
|
userAgent?: string;
|
|
38
38
|
usage?: TokenUsage;
|
|
39
39
|
closedAt?: string;
|
|
40
|
+
error?: string;
|
|
40
41
|
}
|
|
41
42
|
export interface RequestRecorder {
|
|
42
43
|
pending(): Promise<void>;
|
|
@@ -83,6 +84,20 @@ interface LoadedRequest {
|
|
|
83
84
|
error?: string;
|
|
84
85
|
};
|
|
85
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* 判断事件集合是否构成一条完整返回的流。
|
|
89
|
+
* Claude:出现 type === 'message_stop'。
|
|
90
|
+
* OpenAI 兼容:chunk 内 choices[0].finish_reason 非空
|
|
91
|
+
* ([DONE] 不入库,不能作为 DB 判定依据)。
|
|
92
|
+
*/
|
|
93
|
+
export declare function isStreamCompleteEvents(events: any[] | undefined): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* 从事件集合按字段合并重建 usage,不做整体覆盖。
|
|
96
|
+
* Claude:message_start.usage(input_tokens / cache_*)为底;
|
|
97
|
+
* message_delta.usage 的字段优先(output_tokens 为主,cache 字段也合入)。
|
|
98
|
+
* OpenAI:取带 .usage 的 chunk(prompt_tokens/completion_tokens)。
|
|
99
|
+
*/
|
|
100
|
+
export declare function reconstructUsageFromEvents(events: any[] | undefined): TokenUsage | undefined;
|
|
86
101
|
export declare function createRequestRecorder(meta: RecordedRequest): RequestRecorder;
|
|
87
102
|
export declare function recordIteration(meta: RecordedRequest & {
|
|
88
103
|
response: {
|
|
@@ -97,7 +112,14 @@ export declare function listRequestDB(limit?: number, before?: string): Promise<
|
|
|
97
112
|
hasMore: boolean;
|
|
98
113
|
}>;
|
|
99
114
|
export declare function cleanOldRequestsDB(daysToKeep?: number): Promise<void>;
|
|
100
|
-
export declare function interruptStaleRequestsOnStartup(maxAgeMs?: number):
|
|
115
|
+
export declare function interruptStaleRequestsOnStartup(maxAgeMs?: number): {
|
|
116
|
+
completed: number;
|
|
117
|
+
interrupted: number;
|
|
118
|
+
};
|
|
119
|
+
export declare function repairClosedRequests(): {
|
|
120
|
+
recovered: number;
|
|
121
|
+
tokensFilled: number;
|
|
122
|
+
};
|
|
101
123
|
export declare function loadSessionHistory(sessionId: string): {
|
|
102
124
|
messages: any[];
|
|
103
125
|
system?: any;
|