@cc-monitoring/proxy-core 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -35
- package/dist/cli/commands/doc.d.ts +4 -0
- package/dist/cli/commands/doc.js +31 -0
- package/dist/cli/index.js +7 -0
- package/dist/config/index.js +2 -1
- package/dist/config/schema.d.ts +48 -0
- package/dist/config/schema.js +11 -1
- package/dist/parsers/docx-loader.d.ts +5 -0
- package/dist/parsers/docx-loader.js +15 -0
- package/dist/parsers/index.d.ts +3 -0
- package/dist/parsers/index.js +10 -0
- package/dist/parsers/md-loader.d.ts +5 -0
- package/dist/parsers/md-loader.js +13 -0
- package/dist/parsers/registry.d.ts +8 -0
- package/dist/parsers/registry.js +32 -0
- package/dist/parsers/types.d.ts +4 -0
- package/dist/parsers/types.js +1 -0
- package/dist/parsers/xlsx-loader.d.ts +5 -0
- package/dist/parsers/xlsx-loader.js +34 -0
- package/dist/server/__tests__/providers-route.test.js +4 -3
- package/dist/server/__tests__/proxy.test.js +11 -1
- package/dist/server/chat-route.d.ts +3 -0
- package/dist/server/chat-route.js +401 -0
- package/dist/server/claude-settings-route.d.ts +2 -0
- package/dist/server/claude-settings-route.js +76 -0
- package/dist/server/data-push.d.ts +3 -1
- package/dist/server/data-push.js +10 -3
- package/dist/server/docs-route.d.ts +2 -0
- package/dist/server/docs-route.js +63 -0
- package/dist/server/index.js +4 -0
- package/dist/server/mcp-route.d.ts +2 -0
- package/dist/server/mcp-route.js +161 -0
- package/dist/server/proxy.d.ts +4 -1
- package/dist/server/proxy.js +289 -128
- package/dist/server/ui-route.js +123 -46
- package/dist/tools/mcp-bridge.d.ts +13 -0
- package/dist/tools/mcp-bridge.js +36 -0
- package/dist/tools/registry.js +10 -4
- package/dist/ui/assets/{1c-CgVqmGt6.js → 1c-KmWe8RWs.js} +1 -1
- package/dist/ui/assets/{abnf-JPM4b6fj.js → abnf-BKQ6qIuo.js} +1 -1
- package/dist/ui/assets/{abnfDiagram-VRR7QNED-DJkd8PHl.js → abnfDiagram-VRR7QNED-CkRuDPu1.js} +1 -1
- package/dist/ui/assets/{accesslog-CLIMhXWi.js → accesslog-DD9H5Ktc.js} +1 -1
- package/dist/ui/assets/{actionscript-DqniQGld.js → actionscript-CAjf2Qtd.js} +1 -1
- package/dist/ui/assets/{ada-2BFfY7fF.js → ada-BXkXW16O.js} +1 -1
- package/dist/ui/assets/{angelscript-C47qRqcs.js → angelscript-BxT7o0Up.js} +1 -1
- package/dist/ui/assets/{apache-DP-LC5-h.js → apache-C5z_tmVm.js} +1 -1
- package/dist/ui/assets/{applescript-BB0gk3HQ.js → applescript-DdIxl8rr.js} +1 -1
- package/dist/ui/assets/{arc-D8wRCC4D.js → arc-hSdFaqKn.js} +1 -1
- package/dist/ui/assets/{arcade-1R6g1VPm.js → arcade-D5m0roat.js} +1 -1
- package/dist/ui/assets/{architectureDiagram-ZJ3FMSHR-CbHhHV9v.js → architectureDiagram-ZJ3FMSHR-DOSHSqzf.js} +1 -1
- package/dist/ui/assets/{arduino-B8YryK0R.js → arduino-FDap8kq-.js} +1 -1
- package/dist/ui/assets/{armasm-CYdj7mzR.js → armasm-Kzp-Jr1v.js} +1 -1
- package/dist/ui/assets/{asciidoc-bZ11zz6i.js → asciidoc-DbGMW3EE.js} +1 -1
- package/dist/ui/assets/{aspectj-D5WPU-RQ.js → aspectj-CVYMmBCO.js} +1 -1
- package/dist/ui/assets/{autohotkey-70tZvCLL.js → autohotkey-9PmBGTPd.js} +1 -1
- package/dist/ui/assets/{autoit-CX2GaaHP.js → autoit-B4F86jgh.js} +1 -1
- package/dist/ui/assets/{avrasm-C154El0d.js → avrasm-CSzv2eHX.js} +1 -1
- package/dist/ui/assets/{awk-DdXKOeux.js → awk-CjsvYvZE.js} +1 -1
- package/dist/ui/assets/{axapta-CwLwkXST.js → axapta-BVgMoNRY.js} +1 -1
- package/dist/ui/assets/{bash-Uu61S1nB.js → bash-IeBjW3pt.js} +1 -1
- package/dist/ui/assets/{basic-CeiAWoiF.js → basic-BTFJfmV1.js} +1 -1
- package/dist/ui/assets/{blockDiagram-677ZJIJ3-DcvhphUd.js → blockDiagram-677ZJIJ3-DLw3Bbhf.js} +1 -1
- package/dist/ui/assets/{bnf-B5KF61QW.js → bnf-rCkeRYk0.js} +1 -1
- package/dist/ui/assets/{brainfuck-CP0Y7_VW.js → brainfuck-DBHuB3uZ.js} +1 -1
- package/dist/ui/assets/{coq-D-WTlq_d.js → c-C95V0zHL.js} +1 -1
- package/dist/ui/assets/{c-like-DXr53gS6.js → c-like-CK1xGeX_.js} +1 -1
- package/dist/ui/assets/{c4Diagram-LMCZKHZV-Cn017lmJ.js → c4Diagram-LMCZKHZV-cqY51yJw.js} +1 -1
- package/dist/ui/assets/{cal-7ld1j-Wh.js → cal-BxGPpEZK.js} +1 -1
- package/dist/ui/assets/{capnproto-DrddNBCZ.js → capnproto-Bo2fgXF9.js} +1 -1
- package/dist/ui/assets/{ceylon-XPuE6IIL.js → ceylon-C4Bb4pNB.js} +1 -1
- package/dist/ui/assets/channel-D9Y4iN1l.js +1 -0
- package/dist/ui/assets/{chunk-2Q5K7J3B-BoX3IESS.js → chunk-2Q5K7J3B-CGTiTBmo.js} +1 -1
- package/dist/ui/assets/{chunk-32BRIVSS-C300ACzq.js → chunk-32BRIVSS-DWagYBp9.js} +1 -1
- package/dist/ui/assets/{chunk-5VM5RSS4-MtSkFsde.js → chunk-5VM5RSS4-BzSBMKJX.js} +1 -1
- package/dist/ui/assets/{chunk-EX3LRPZG-CEeHrGNV.js → chunk-EX3LRPZG-4-aKwCgZ.js} +1 -1
- package/dist/ui/assets/{chunk-JWPE2WC7-DTRgpK0R.js → chunk-JWPE2WC7-CPC_cBN0.js} +1 -1
- package/dist/ui/assets/{chunk-MOJQB5TN-DiOl0HJx.js → chunk-MOJQB5TN-DrDE4H9w.js} +1 -1
- package/dist/ui/assets/{chunk-RYQCIY6F-CVC3opqY.js → chunk-RYQCIY6F-DkdXe_kU.js} +1 -1
- package/dist/ui/assets/{chunk-V7JOEXUC-yQYlqQCC.js → chunk-V7JOEXUC-Da6EWcQt.js} +1 -1
- package/dist/ui/assets/{chunk-VR4S4FIN-iAA8ZgkK.js → chunk-VR4S4FIN-nP6dstD5.js} +1 -1
- package/dist/ui/assets/{chunk-XXDRQBXY-B303eieF.js → chunk-XXDRQBXY-Bi_rRAPA.js} +1 -1
- package/dist/ui/assets/classDiagram-OUVF2IWQ-C4UVH6RI.js +1 -0
- package/dist/ui/assets/classDiagram-v2-EOCWNBFH-C4UVH6RI.js +1 -0
- package/dist/ui/assets/{clean-MfHaW_uS.js → clean-cc1m9AxK.js} +1 -1
- package/dist/ui/assets/{clojure-9L8TfiWA.js → clojure-DX5V8QA8.js} +1 -1
- package/dist/ui/assets/{clojure-repl-CjsH9sxr.js → clojure-repl-FzjnVNWQ.js} +1 -1
- package/dist/ui/assets/{cmake-D0D1Q30G.js → cmake-COZZyrKs.js} +1 -1
- package/dist/ui/assets/{coffeescript-BI4jWZnB.js → coffeescript-DBsnOmC4.js} +1 -1
- package/dist/ui/assets/{c-BgqCrSpv.js → coq-TwFFolHa.js} +1 -1
- package/dist/ui/assets/{cos-DHax5LIJ.js → cos-Dm5g-IQT.js} +1 -1
- package/dist/ui/assets/{cose-bilkent-JH36ORCC-BzrKmTAZ.js → cose-bilkent-JH36ORCC-B3-weOQF.js} +1 -1
- package/dist/ui/assets/{cpp-M4aMPUas.js → cpp-_RZxQjg7.js} +1 -1
- package/dist/ui/assets/{crmsh-DAAczfR0.js → crmsh-DGPJ11Gt.js} +1 -1
- package/dist/ui/assets/{crystal-7g2CpWm4.js → crystal-BOMSPGge.js} +1 -1
- package/dist/ui/assets/{csharp-DW5GdKKf.js → csharp-DZXiHH72.js} +1 -1
- package/dist/ui/assets/{csp-Bq74fG7-.js → csp-DDSIMUoU.js} +1 -1
- package/dist/ui/assets/{css-DHyeLXff.js → css-Bjye-ZWr.js} +1 -1
- package/dist/ui/assets/{cynefin-VYW2F7L2-DwV1t9iV.js → cynefin-VYW2F7L2-Cj5c4L3y.js} +1 -1
- package/dist/ui/assets/{cynefinDiagram-TSTJHNR4-kBCFRYPz.js → cynefinDiagram-TSTJHNR4-BYsfTeNX.js} +1 -1
- package/dist/ui/assets/{d-yey2lm3v.js → d-22pfurOx.js} +1 -1
- package/dist/ui/assets/{dagre-VKFMJZFB-CErVJ1s0.js → dagre-VKFMJZFB-DiEob-by.js} +1 -1
- package/dist/ui/assets/{dart-BadDxflP.js → dart-Doiu8cDW.js} +1 -1
- package/dist/ui/assets/{delphi-CqzVqtDp.js → delphi-CQ7epp84.js} +1 -1
- package/dist/ui/assets/{diagram-FQU43EPY-C7zAW2DU.js → diagram-FQU43EPY-BOPbQcL4.js} +1 -1
- package/dist/ui/assets/{diagram-G47NLZAW-CbUb6BZI.js → diagram-G47NLZAW-C1UWDJTx.js} +1 -1
- package/dist/ui/assets/{diagram-NH7WQ7WH-C5mgi3dn.js → diagram-NH7WQ7WH-CYsBqW3J.js} +1 -1
- package/dist/ui/assets/{diagram-OA4YK3LP-Cok2YNfS.js → diagram-OA4YK3LP-Dnag0x_3.js} +1 -1
- package/dist/ui/assets/{diagram-WEI45ONY-BODuP2EB.js → diagram-WEI45ONY-Bo4CsDoj.js} +1 -1
- package/dist/ui/assets/{diff-EVigJ-h2.js → diff-Cs6VQQZt.js} +1 -1
- package/dist/ui/assets/{django-BH2EEal6.js → django-Bo3okOrw.js} +1 -1
- package/dist/ui/assets/{dns-BSc8VTm8.js → dns-BpG60eIX.js} +1 -1
- package/dist/ui/assets/{dockerfile-Br_pZjru.js → dockerfile-BWyH8SlV.js} +1 -1
- package/dist/ui/assets/{dos-Bx3ludet.js → dos-DWlNbaB9.js} +1 -1
- package/dist/ui/assets/{dsconfig-DSI-ePne.js → dsconfig-D_8y8YrJ.js} +1 -1
- package/dist/ui/assets/{dts-ClCuE1Xq.js → dts-Cz4SyMYR.js} +1 -1
- package/dist/ui/assets/{dust-CC9Z8iuW.js → dust-Cu4qLkeH.js} +1 -1
- package/dist/ui/assets/{ebnf-DI0w_fbi.js → ebnf-CTVHui3r.js} +1 -1
- package/dist/ui/assets/{ebnfDiagram-CCIWWBDH-CgsnO8Qm.js → ebnfDiagram-CCIWWBDH-epcXQLEj.js} +1 -1
- package/dist/ui/assets/{elixir-hdzZik8p.js → elixir-MAlwXhfU.js} +1 -1
- package/dist/ui/assets/{elm-DJAgqt5I.js → elm-Bdmwbvef.js} +1 -1
- package/dist/ui/assets/{erDiagram-Q63AITRT-K4zJcEKd.js → erDiagram-Q63AITRT-Bb60TqAM.js} +1 -1
- package/dist/ui/assets/{erb-Cq4OQqkl.js → erb-Ce9TpP4g.js} +1 -1
- package/dist/ui/assets/{erlang-CHoXLODu.js → erlang-CN2GEiqk.js} +1 -1
- package/dist/ui/assets/{erlang-repl-Brp70Zip.js → erlang-repl-D8K4O_cp.js} +1 -1
- package/dist/ui/assets/{excel-DjS-QR9P.js → excel-eVZU1r0f.js} +1 -1
- package/dist/ui/assets/{fix-BsO9Oq6H.js → fix-BzKerEv0.js} +1 -1
- package/dist/ui/assets/{flix-2A7CKozm.js → flix-B9CE3skV.js} +1 -1
- package/dist/ui/assets/{flowDiagram-23GEKE2U-Ck4tfxWX.js → flowDiagram-23GEKE2U-DzMLZIyt.js} +1 -1
- package/dist/ui/assets/{fortran-BQu3wORI.js → fortran-B4__3vos.js} +1 -1
- package/dist/ui/assets/{fsharp-D439S6PZ.js → fsharp-BJ3-ARHz.js} +1 -1
- package/dist/ui/assets/{gams-CPRxKfRH.js → gams-Cu43oSYT.js} +1 -1
- package/dist/ui/assets/{ganttDiagram-NO4QXBWP-QTPyXXxa.js → ganttDiagram-NO4QXBWP-D_aAoMRP.js} +1 -1
- package/dist/ui/assets/{gauss-GJFW9Vod.js → gauss-T8vk_TpE.js} +1 -1
- package/dist/ui/assets/{gcode-CF6woDAl.js → gcode-9j3BicpZ.js} +1 -1
- package/dist/ui/assets/{gherkin-Co-YjHfW.js → gherkin-Dyh5KFG-.js} +1 -1
- package/dist/ui/assets/{gitGraphDiagram-IHSO6WYX-Do9VfPHL.js → gitGraphDiagram-IHSO6WYX-CMxCHC-Z.js} +1 -1
- package/dist/ui/assets/{glsl-D3lldBTJ.js → glsl-DWWjN4ts.js} +1 -1
- package/dist/ui/assets/{gml-DyoCibnw.js → gml-BcYE-D5A.js} +1 -1
- package/dist/ui/assets/{go-BNeAS1jC.js → go-DUnUzOEI.js} +1 -1
- package/dist/ui/assets/{golo-BuXecWAH.js → golo-BDoc1QQL.js} +1 -1
- package/dist/ui/assets/{gradle-BP7ToLe9.js → gradle-B_WRa_bT.js} +1 -1
- package/dist/ui/assets/{groovy-GqWVT09g.js → groovy-CgjqFq1e.js} +1 -1
- package/dist/ui/assets/{haml-SLbvCHsC.js → haml-DCqcg7Ti.js} +1 -1
- package/dist/ui/assets/{handlebars-BnaCJTc5.js → handlebars-DSDUQrnw.js} +1 -1
- package/dist/ui/assets/{haskell-CrX9ftWp.js → haskell-DPVnctVi.js} +1 -1
- package/dist/ui/assets/{haxe-DNoaIxsE.js → haxe-BuCGXBa4.js} +1 -1
- package/dist/ui/assets/{hsp-CLjy3djj.js → hsp-BTKpS_u0.js} +1 -1
- package/dist/ui/assets/{htmlbars-w96Kz5NL.js → htmlbars-Cql3Jvy2.js} +1 -1
- package/dist/ui/assets/{http-VIMXIxM8.js → http-BB8I9jUh.js} +1 -1
- package/dist/ui/assets/{hy-_qE83_aW.js → hy-DG7xkZNz.js} +1 -1
- package/dist/ui/assets/index-CK_lJsTu.css +10 -0
- package/dist/ui/assets/index-CfTIGTiY.js +2677 -0
- package/dist/ui/assets/{index-DpcPfZl3.js → index-DqIiCkSD.js} +3 -3
- package/dist/ui/assets/{infoDiagram-FWYZ7A6U-D9O7SZ2X.js → infoDiagram-FWYZ7A6U-DIzEV-BJ.js} +1 -1
- package/dist/ui/assets/{inform7-CnyW73C1.js → inform7-BWNpVEkA.js} +1 -1
- package/dist/ui/assets/{ini-GFMFAjln.js → ini-BMw_C8EP.js} +1 -1
- package/dist/ui/assets/{irpf90-BPmSYATn.js → irpf90-Cy42ibFZ.js} +1 -1
- package/dist/ui/assets/{isbl-BnStGiBQ.js → isbl-CjFSc0hz.js} +1 -1
- package/dist/ui/assets/{ishikawaDiagram-FXEZZL3T-DgcUDM9X.js → ishikawaDiagram-FXEZZL3T-BZcxp5am.js} +1 -1
- package/dist/ui/assets/{java-CVORcFd8.js → java-BDxe9dp1.js} +1 -1
- package/dist/ui/assets/{javascript-B2pFzODm.js → javascript-CgLkkj7h.js} +1 -1
- package/dist/ui/assets/{jboss-cli-B9cQeKvZ.js → jboss-cli-C8biclrb.js} +1 -1
- package/dist/ui/assets/{journeyDiagram-5HDEW3XC-Bivjw4h7.js → journeyDiagram-5HDEW3XC-BbJu6m6Q.js} +1 -1
- package/dist/ui/assets/{json-BJof8L10.js → json-BjJKhmM6.js} +1 -1
- package/dist/ui/assets/{julia-D_dGrCpZ.js → julia-DvKEpg-R.js} +1 -1
- package/dist/ui/assets/{julia-repl-Dsw0K1B1.js → julia-repl-BDJWZxkU.js} +1 -1
- package/dist/ui/assets/{kanban-definition-HUTT4EX6-Dg7NbKta.js → kanban-definition-HUTT4EX6-DeIVPlbv.js} +1 -1
- package/dist/ui/assets/{kotlin-8oetHRtX.js → kotlin-DCoXcnEb.js} +1 -1
- package/dist/ui/assets/{lasso-e37r_NX1.js → lasso-C7m0QmV2.js} +1 -1
- package/dist/ui/assets/{latex-DAmwzl3n.js → latex-SHnTG9Eq.js} +1 -1
- package/dist/ui/assets/{ldif-CD0mgmt4.js → ldif-_6hUnKPV.js} +1 -1
- package/dist/ui/assets/{leaf-GfPKlp7a.js → leaf-Co9qxlaF.js} +1 -1
- package/dist/ui/assets/{less-PndaZJ6n.js → less-CJRDPwNA.js} +1 -1
- package/dist/ui/assets/{lisp-CNaspu3A.js → lisp-JEHGYbiW.js} +1 -1
- package/dist/ui/assets/{livecodeserver-DEKsXiP3.js → livecodeserver-BSYaUj4k.js} +1 -1
- package/dist/ui/assets/{livescript-DM7jasJn.js → livescript-WQMCQeuz.js} +1 -1
- package/dist/ui/assets/{llvm-D_29j9MV.js → llvm-DnAHqj5I.js} +1 -1
- package/dist/ui/assets/{lsl-DY0WRYhg.js → lsl--k-ghYia.js} +1 -1
- package/dist/ui/assets/{lua-DZ_v4xaE.js → lua-CXb23hbC.js} +1 -1
- package/dist/ui/assets/{makefile-VOjcUGXf.js → makefile-CM7o86yn.js} +1 -1
- package/dist/ui/assets/{markdown-yUEmZWvu.js → markdown-HB3HvChQ.js} +1 -1
- package/dist/ui/assets/{mathematica-BLXYGKkd.js → mathematica-9NTuf22N.js} +1 -1
- package/dist/ui/assets/{matlab-B7SkIpvh.js → matlab-CWv-ik4r.js} +1 -1
- package/dist/ui/assets/{maxima-C7Fw8Vyt.js → maxima-CqYiKCrw.js} +1 -1
- package/dist/ui/assets/{mel-C_Znst2Y.js → mel-BbsRpUm5.js} +1 -1
- package/dist/ui/assets/{mercury-DjUnQNsq.js → mercury-Cz2j4HhU.js} +1 -1
- package/dist/ui/assets/{mindmap-definition-LN4V7U3C-Dp6mEy6L.js → mindmap-definition-LN4V7U3C-WyJS8oye.js} +1 -1
- package/dist/ui/assets/{mipsasm-DaGHhR8c.js → mipsasm-BqWSRbQV.js} +1 -1
- package/dist/ui/assets/{mizar-CZnct-_Z.js → mizar-JLBfQ6vt.js} +1 -1
- package/dist/ui/assets/{mojolicious-BdWFK35j.js → mojolicious-D0LbMmyk.js} +1 -1
- package/dist/ui/assets/{monkey-ta_jgafo.js → monkey-DAGL6-JN.js} +1 -1
- package/dist/ui/assets/{moonscript-B9qovjV6.js → moonscript-DzbwNSKy.js} +1 -1
- package/dist/ui/assets/{n1ql-BciXbxiw.js → n1ql-dkgKq6Yv.js} +1 -1
- package/dist/ui/assets/{nginx-DRCZYbxj.js → nginx-AUoaDPV4.js} +1 -1
- package/dist/ui/assets/{nim-DTMCxPJt.js → nim-CdqYmVrl.js} +1 -1
- package/dist/ui/assets/{nix-D39yZxF9.js → nix-CxmVjwwo.js} +1 -1
- package/dist/ui/assets/{node-repl-vAEXdnYM.js → node-repl-B1Z3zUAh.js} +1 -1
- package/dist/ui/assets/{nsis-CCMP8mq3.js → nsis-CTAuPWhe.js} +1 -1
- package/dist/ui/assets/{objectivec-CZx4kSLP.js → objectivec-CvbKeAPz.js} +1 -1
- package/dist/ui/assets/{ocaml-DBnEo9uR.js → ocaml-DCiyXKOH.js} +1 -1
- package/dist/ui/assets/{openscad-BKc7u4Bl.js → openscad-CPGUwmeR.js} +1 -1
- package/dist/ui/assets/{oxygene-M_Z4lQ6l.js → oxygene-CI_bD1Gw.js} +1 -1
- package/dist/ui/assets/{parser3-DS9UKiKD.js → parser3-C91CVs85.js} +1 -1
- package/dist/ui/assets/{pegDiagram-2B236MQR-BBEXn76M.js → pegDiagram-2B236MQR-yF8AW7GR.js} +1 -1
- package/dist/ui/assets/{perl-D6iEALGE.js → perl-ChxfKl-n.js} +1 -1
- package/dist/ui/assets/{pf-JQL0ClGi.js → pf-CKCHkzIA.js} +1 -1
- package/dist/ui/assets/{pgsql-DEmCURBr.js → pgsql-BwOP20uH.js} +1 -1
- package/dist/ui/assets/{php-DVm-0PY5.js → php-B7Rs2tM5.js} +1 -1
- package/dist/ui/assets/{php-template-DfWbDpnE.js → php-template-Cx-Fh4AR.js} +1 -1
- package/dist/ui/assets/{pieDiagram-ENE6RG2P-D5PZFiDj.js → pieDiagram-ENE6RG2P-BS4hnGNX.js} +1 -1
- package/dist/ui/assets/{plaintext-BahVkGlu.js → plaintext-U7qkdtgS.js} +1 -1
- package/dist/ui/assets/{pony-wVig0caC.js → pony-_fGYgePh.js} +1 -1
- package/dist/ui/assets/{powershell-COjMBUit.js → powershell-CFBTcUce.js} +1 -1
- package/dist/ui/assets/{processing-DnUzvFZx.js → processing-BWHneU0K.js} +1 -1
- package/dist/ui/assets/{profile-mDlbwJeL.js → profile-BJ-HxhzS.js} +1 -1
- package/dist/ui/assets/{prolog-x2hG6DnA.js → prolog-ChHo7n6L.js} +1 -1
- package/dist/ui/assets/{properties-DzZyyivC.js → properties-e_DOYclC.js} +1 -1
- package/dist/ui/assets/{protobuf-a5NwbYD5.js → protobuf-5aGSIv7w.js} +1 -1
- package/dist/ui/assets/{puppet-CCWyPVuC.js → puppet-D2vQatzX.js} +1 -1
- package/dist/ui/assets/{purebasic-CXNdqtD4.js → purebasic-n6krfaeF.js} +1 -1
- package/dist/ui/assets/{python-iSR9YlDJ.js → python-CbIo0XxP.js} +1 -1
- package/dist/ui/assets/{python-repl-CCmKzWru.js → python-repl-BpIq9kyr.js} +1 -1
- package/dist/ui/assets/{q-B30ZmDuD.js → q-jntnvcCx.js} +1 -1
- package/dist/ui/assets/{qml-BGdPdB4X.js → qml-BafQlZNV.js} +1 -1
- package/dist/ui/assets/{quadrantDiagram-ABIIQ3AL-BBF1rAvI.js → quadrantDiagram-ABIIQ3AL-C3Th8mUJ.js} +1 -1
- package/dist/ui/assets/{r-B8ASdWL1.js → r-CHgvQFLK.js} +1 -1
- package/dist/ui/assets/{railroadDiagram-RFXS5EU6-DQFUXGVD.js → railroadDiagram-RFXS5EU6-CMQkbGJl.js} +1 -1
- package/dist/ui/assets/{reasonml-Dw4Bi5B5.js → reasonml-DNwlPD_T.js} +1 -1
- package/dist/ui/assets/{requirementDiagram-TGXJPOKE-DCgrs7-P.js → requirementDiagram-TGXJPOKE-B70_oR7v.js} +1 -1
- package/dist/ui/assets/{rib-CgUGGGZ9.js → rib-CX4e9JSF.js} +1 -1
- package/dist/ui/assets/{roboconf-CCZNQvSP.js → roboconf-ee4qsPGf.js} +1 -1
- package/dist/ui/assets/{routeros-DVT5M6iV.js → routeros-BJSwvu2L.js} +1 -1
- package/dist/ui/assets/{rsl-BY-LhdKq.js → rsl-BiKdMl_F.js} +1 -1
- package/dist/ui/assets/{ruby-CZW4dclw.js → ruby-CAXTU5LY.js} +1 -1
- package/dist/ui/assets/{ruleslanguage-CZdjv1of.js → ruleslanguage-C1kgDGXM.js} +1 -1
- package/dist/ui/assets/{rust-D5Xa_mSD.js → rust-DyEf_GXL.js} +1 -1
- package/dist/ui/assets/{sankeyDiagram-HTMAVEWB-BXeEgUB7.js → sankeyDiagram-HTMAVEWB-CvUfseMg.js} +1 -1
- package/dist/ui/assets/{sas-B_UJ-HCZ.js → sas-Q1rlUxPH.js} +1 -1
- package/dist/ui/assets/{scala-B8wSdZik.js → scala-Dg6KNmge.js} +1 -1
- package/dist/ui/assets/{scheme-DD7U81eh.js → scheme-D9iooUxI.js} +1 -1
- package/dist/ui/assets/{scilab-aJGpbkKs.js → scilab-CztEJXJY.js} +1 -1
- package/dist/ui/assets/{scss-BBqyvWYh.js → scss-BMh-JkdE.js} +1 -1
- package/dist/ui/assets/{sequenceDiagram-DBY2YBRQ-CnDTZN18.js → sequenceDiagram-DBY2YBRQ-DB0c9kf4.js} +1 -1
- package/dist/ui/assets/{shell-CR23jqfz.js → shell-DWo6z0MG.js} +1 -1
- package/dist/ui/assets/{sizeCapture-X5ZJPWSS-joZ477RB.js → sizeCapture-X5ZJPWSS-BcQlgjfD.js} +1 -1
- package/dist/ui/assets/{smali-ZmLyRCrQ.js → smali-Cijeb6pL.js} +1 -1
- package/dist/ui/assets/{smalltalk-CnADYfPr.js → smalltalk-D0lvqa_d.js} +1 -1
- package/dist/ui/assets/{sml-zj1KD04_.js → sml-B-vqWZ3b.js} +1 -1
- package/dist/ui/assets/{sqf-DCCaZ3e3.js → sqf-BJ-1Q583.js} +1 -1
- package/dist/ui/assets/{sql-CHGw2SCR.js → sql-CMGLxEYV.js} +1 -1
- package/dist/ui/assets/{sql_more-DsnvjMwh.js → sql_more-gsRmbNCH.js} +1 -1
- package/dist/ui/assets/{stan-ChxdV8f9.js → stan-D1AlTSpf.js} +1 -1
- package/dist/ui/assets/{stata-CgFw45qg.js → stata-DnEYviI0.js} +1 -1
- package/dist/ui/assets/{stateDiagram-2N3HPSRC-CzLIdKbt.js → stateDiagram-2N3HPSRC-BSRgVsHO.js} +1 -1
- package/dist/ui/assets/stateDiagram-v2-6OUMAXLB-BLdfpGJO.js +1 -0
- package/dist/ui/assets/{step21-DaraQMOk.js → step21-DbQV9sMo.js} +1 -1
- package/dist/ui/assets/{stylus-BZpiJEzN.js → stylus-Cyc7vdUc.js} +1 -1
- package/dist/ui/assets/{subunit-B6Gp0n7h.js → subunit-16ipyMie.js} +1 -1
- package/dist/ui/assets/{swift-DQzoz60p.js → swift-DTNnw4qS.js} +1 -1
- package/dist/ui/assets/{swimlanes-5IMT3BWC-DOMOhwX0.js → swimlanes-5IMT3BWC-DgOdcc4_.js} +2 -2
- package/dist/ui/assets/swimlanesDiagram-G3AALYLV-DRVnkg3M.js +8 -0
- package/dist/ui/assets/{taggerscript-BMLkfiab.js → taggerscript-DM6pvUWE.js} +1 -1
- package/dist/ui/assets/{tap-PUNWTFgu.js → tap-Ch-uyKZq.js} +1 -1
- package/dist/ui/assets/{tcl-BODH5qJa.js → tcl-D0T_V5dA.js} +1 -1
- package/dist/ui/assets/{thrift-BQ-W_YlC.js → thrift-D9xJjYw4.js} +1 -1
- package/dist/ui/assets/{timeline-definition-FHXFAJF6-DQBDeTzH.js → timeline-definition-FHXFAJF6-BBeDMHxb.js} +1 -1
- package/dist/ui/assets/{tp-Dt3WwOGF.js → tp-kUrCGo9X.js} +1 -1
- package/dist/ui/assets/{twig-44RQtw7Z.js → twig-Cgub6rxA.js} +1 -1
- package/dist/ui/assets/{typescript-BsouHXHR.js → typescript-BxNP9Ap9.js} +1 -1
- package/dist/ui/assets/{vala-DqJj6fPT.js → vala-DF9ZZagU.js} +1 -1
- package/dist/ui/assets/{vbnet-CuqWYg5r.js → vbnet-Cf46ypDg.js} +1 -1
- package/dist/ui/assets/{vbscript-w20MQMYP.js → vbscript-DNs-QKLH.js} +1 -1
- package/dist/ui/assets/{vbscript-html-BP0D0WEV.js → vbscript-html-DyS6I5L1.js} +1 -1
- package/dist/ui/assets/{vennDiagram-L72KCM5P-DwNnj3Kc.js → vennDiagram-L72KCM5P-8WpWycE-.js} +1 -1
- package/dist/ui/assets/{verilog-DIpBpJAe.js → verilog-CF5wC4X-.js} +1 -1
- package/dist/ui/assets/{vhdl-D5nCxDbL.js → vhdl-Dm0jpDMn.js} +1 -1
- package/dist/ui/assets/{vim-CPfbBM_x.js → vim-nyO2DRAx.js} +1 -1
- package/dist/ui/assets/{wardleyDiagram-EHGQE667-WxVQHmM7.js → wardleyDiagram-EHGQE667-UeIOK-jv.js} +1 -1
- package/dist/ui/assets/{x86asm-D_t4Z_eq.js → x86asm-BNw2-6GG.js} +1 -1
- package/dist/ui/assets/{xl-DQf5IG_W.js → xl-D-YHxr6V.js} +1 -1
- package/dist/ui/assets/{xml-D-Bn0NMR.js → xml-BijAlrow.js} +1 -1
- package/dist/ui/assets/{xquery-DqushYX1.js → xquery-z0wxWBKz.js} +1 -1
- package/dist/ui/assets/{xychartDiagram-FW5EYKEG-B6D8mgs8.js → xychartDiagram-FW5EYKEG-BNzHLweN.js} +1 -1
- package/dist/ui/assets/{yaml-B2GFZenc.js → yaml-n5TIQArR.js} +1 -1
- package/dist/ui/assets/{zephir-SiNGockR.js → zephir-DrGhH8jG.js} +1 -1
- package/dist/ui/index.html +2 -2
- package/dist/utils/__tests__/providers-db.test.js +2 -1
- package/dist/utils/__tests__/tool-injector.test.js +32 -35
- package/dist/utils/db.d.ts +3 -0
- package/dist/utils/db.js +54 -0
- package/dist/utils/inject-loop.d.ts +52 -1
- package/dist/utils/inject-loop.js +70 -2
- package/dist/utils/mcp-db.d.ts +35 -0
- package/dist/utils/mcp-db.js +79 -0
- package/dist/utils/mcp-manager.d.ts +36 -0
- package/dist/utils/mcp-manager.js +215 -0
- package/dist/utils/openai-converter.d.ts +99 -0
- package/dist/utils/openai-converter.js +293 -0
- package/dist/utils/providers-db.d.ts +2 -0
- package/dist/utils/providers-db.js +19 -7
- package/dist/utils/request-writer.d.ts +20 -0
- package/dist/utils/request-writer.js +184 -4
- package/dist/utils/sse-stream-processor.d.ts +27 -0
- package/dist/utils/sse-stream-processor.js +144 -0
- package/dist/utils/stats-aggregator.js +2 -0
- package/dist/utils/status-history.js +3 -3
- package/dist/utils/tool-injector.d.ts +6 -9
- package/dist/utils/tool-injector.js +13 -14
- package/dist/utils/version.d.ts +6 -0
- package/dist/utils/version.js +11 -1
- package/package.json +14 -12
- package/dist/ui/assets/channel-C5JlZ38H.js +0 -1
- package/dist/ui/assets/classDiagram-OUVF2IWQ-j5FVBKaD.js +0 -1
- package/dist/ui/assets/classDiagram-v2-EOCWNBFH-j5FVBKaD.js +0 -1
- package/dist/ui/assets/index-Baqd6LlK.js +0 -2625
- package/dist/ui/assets/index-fHtNI_e8.css +0 -1
- package/dist/ui/assets/stateDiagram-v2-6OUMAXLB-Bg4GAkB5.js +0 -1
- package/dist/ui/assets/swimlanesDiagram-G3AALYLV-99wEK8yq.js +0 -8
package/dist/server/proxy.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import { Readable } from 'stream';
|
|
1
|
+
import { Readable, finished } from 'stream';
|
|
2
|
+
import { convertClaudeToOpenAIRequest, convertOpenAIToClaudeResponse, createStreamTransformer, buildOpenAIHeaders } from '../utils/openai-converter.js';
|
|
2
3
|
import { loadConfig } from '../config/index.js';
|
|
3
|
-
import { saveRequestDB, extractTokenUsage } from '../utils/request-writer.js';
|
|
4
|
+
import { saveRequestDB, extractTokenUsage, flushEventBatch, deleteResponseEvents, deleteRequestRow, updateRequestTokens, EVENT_BATCH_SIZE } from '../utils/request-writer.js';
|
|
4
5
|
import { cleanOldRequestsDB } from '../utils/request-writer.js';
|
|
5
6
|
import { pushNewRequest } from './data-push.js';
|
|
6
7
|
import { initDatabase, closeDatabase, getDatabase } from '../utils/db.js';
|
|
7
8
|
import { aggregateStats } from '../utils/stats-aggregator.js';
|
|
8
|
-
import {
|
|
9
|
-
import { injectTools } from '../utils/tool-injector.js';
|
|
10
|
-
import { parseSSEText, parseUpstreamEvents, findInjectedCall, runInjectLoop } from '../utils/inject-loop.js';
|
|
11
|
-
import { hasEnabledTools } from '../tools/index.js';
|
|
9
|
+
import { mcpManager } from '../utils/mcp-manager.js';
|
|
12
10
|
import { getMachineIdSync, preloadMachineId } from '../utils/machine-id.js';
|
|
11
|
+
// Cached prepared statement for active provider lookup (avoids per-request allocation)
|
|
12
|
+
let activeProviderStmt = null;
|
|
13
|
+
let cachedDbInstance = null;
|
|
13
14
|
/**
|
|
14
15
|
* Resolves the proxy target per request:
|
|
15
16
|
* - If a provider with is_default=1 exists in DB, use its base_url + api_key + tier models (partial replacement)
|
|
16
17
|
* - Otherwise fall back to config.json proxy.target + proxy.apiKey
|
|
17
18
|
*
|
|
18
19
|
* Called per request for hot-swap support (no server restart needed on activation change).
|
|
19
|
-
* better-sqlite3 is synchronous and ~us-level for a single-row SELECT;
|
|
20
|
+
* better-sqlite3 is synchronous and ~us-level for a single-row SELECT; cached statement avoids repeated allocation.
|
|
20
21
|
*/
|
|
21
22
|
export function resolveProxyTarget(proxyConfig) {
|
|
22
23
|
// Default to config values
|
|
@@ -27,13 +28,22 @@ export function resolveProxyTarget(proxyConfig) {
|
|
|
27
28
|
let haiku_model;
|
|
28
29
|
let sonnet_model;
|
|
29
30
|
let opus_model;
|
|
31
|
+
let is_openai_compatible;
|
|
30
32
|
try {
|
|
31
33
|
// Parse config target as base
|
|
32
34
|
const configUrl = new URL(proxyConfig.target);
|
|
33
35
|
baseUrl = configUrl.origin;
|
|
34
36
|
targetPath = configUrl.pathname.replace(/\/$/, '');
|
|
35
37
|
const db = getDatabase();
|
|
36
|
-
|
|
38
|
+
// Invalidate cached stmt if DB instance changed (happens in tests with fresh DB per test)
|
|
39
|
+
if (db !== cachedDbInstance) {
|
|
40
|
+
activeProviderStmt = null;
|
|
41
|
+
cachedDbInstance = db;
|
|
42
|
+
}
|
|
43
|
+
if (!activeProviderStmt) {
|
|
44
|
+
activeProviderStmt = db.prepare('SELECT base_url, api_key, model, haiku_model, sonnet_model, opus_model, is_openai_compatible FROM providers WHERE is_default = 1 LIMIT 1');
|
|
45
|
+
}
|
|
46
|
+
const active = activeProviderStmt.get();
|
|
37
47
|
if (active) {
|
|
38
48
|
// Replace base_url only if provided
|
|
39
49
|
if (active.base_url && active.base_url.trim() !== '') {
|
|
@@ -54,6 +64,8 @@ export function resolveProxyTarget(proxyConfig) {
|
|
|
54
64
|
sonnet_model = active.sonnet_model;
|
|
55
65
|
if (active.opus_model && active.opus_model.trim() !== '')
|
|
56
66
|
opus_model = active.opus_model;
|
|
67
|
+
// Populate is_openai_compatible
|
|
68
|
+
is_openai_compatible = !!active.is_openai_compatible;
|
|
57
69
|
}
|
|
58
70
|
}
|
|
59
71
|
catch {
|
|
@@ -62,7 +74,7 @@ export function resolveProxyTarget(proxyConfig) {
|
|
|
62
74
|
baseUrl = configUrl.origin;
|
|
63
75
|
targetPath = configUrl.pathname.replace(/\/$/, '');
|
|
64
76
|
}
|
|
65
|
-
return { baseUrl, targetPath, apiKey, model, haiku_model, sonnet_model, opus_model };
|
|
77
|
+
return { baseUrl, targetPath, apiKey, model, haiku_model, sonnet_model, opus_model, is_openai_compatible };
|
|
66
78
|
}
|
|
67
79
|
/**
|
|
68
80
|
* Selects the proxy model based on the original request model and provider tier config.
|
|
@@ -87,7 +99,7 @@ export function getProxyModel(originalModel, tierModels) {
|
|
|
87
99
|
return tierModels.model || undefined;
|
|
88
100
|
}
|
|
89
101
|
// Extract safe headers (avoid circular reference)
|
|
90
|
-
function extractSafeHeaders(headers) {
|
|
102
|
+
export function extractSafeHeaders(headers) {
|
|
91
103
|
const safeHeaders = {};
|
|
92
104
|
for (const [key, value] of Object.entries(headers)) {
|
|
93
105
|
if (typeof value === 'string' || typeof value === 'number') {
|
|
@@ -100,23 +112,29 @@ function extractSafeHeaders(headers) {
|
|
|
100
112
|
return safeHeaders;
|
|
101
113
|
}
|
|
102
114
|
// Filter headers for forwarding (remove problematic ones, replace API key if configured)
|
|
103
|
-
function filterForwardHeaders(headers, proxyApiKey) {
|
|
115
|
+
export function filterForwardHeaders(headers, proxyApiKey) {
|
|
104
116
|
const filtered = { ...headers };
|
|
105
117
|
// Remove headers that cause issues
|
|
106
118
|
delete filtered.host;
|
|
107
119
|
delete filtered['content-length'];
|
|
108
120
|
delete filtered['x-req-key'];
|
|
109
|
-
//
|
|
121
|
+
// Inject or replace API key when proxy key is available
|
|
110
122
|
if (proxyApiKey) {
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
const hasApiKey = 'x-api-key' in filtered;
|
|
124
|
+
const hasAuth = 'authorization' in filtered || 'Authorization' in filtered;
|
|
125
|
+
if (hasApiKey) {
|
|
126
|
+
// Replace existing x-api-key
|
|
113
127
|
filtered['x-api-key'] = proxyApiKey;
|
|
114
128
|
}
|
|
115
|
-
if (
|
|
116
|
-
//
|
|
129
|
+
else if (hasAuth) {
|
|
130
|
+
// Replace existing authorization
|
|
117
131
|
const key = 'authorization' in filtered ? 'authorization' : 'Authorization';
|
|
118
132
|
filtered[key] = `Bearer ${proxyApiKey}`;
|
|
119
133
|
}
|
|
134
|
+
else {
|
|
135
|
+
// No auth header in request → inject x-api-key from provider
|
|
136
|
+
filtered['x-api-key'] = proxyApiKey;
|
|
137
|
+
}
|
|
120
138
|
}
|
|
121
139
|
return filtered;
|
|
122
140
|
}
|
|
@@ -129,6 +147,56 @@ export function getErrorStatusCode(error) {
|
|
|
129
147
|
}
|
|
130
148
|
return 500;
|
|
131
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Push a parsed SSE event into batch/tail buffers.
|
|
152
|
+
* Flushes batch to DB when it reaches EVENT_BATCH_SIZE (10).
|
|
153
|
+
* Only memory held: current batch (≤10 events) + tail (≤5 events).
|
|
154
|
+
*/
|
|
155
|
+
function pushEventToBuffers(event, sessionId, requestId, eventBatch, tailBuffer, eventIndexRef) {
|
|
156
|
+
const entry = { index: eventIndexRef.value, type: event?.type, data: event };
|
|
157
|
+
eventBatch.push(entry);
|
|
158
|
+
// Maintain tail buffer (last 5 events for usage extraction)
|
|
159
|
+
tailBuffer.push(event);
|
|
160
|
+
if (tailBuffer.length > 5) {
|
|
161
|
+
tailBuffer.shift();
|
|
162
|
+
}
|
|
163
|
+
// Flush batch to DB when threshold reached
|
|
164
|
+
if (eventBatch.length >= EVENT_BATCH_SIZE) {
|
|
165
|
+
flushEventBatch(sessionId, requestId, eventBatch, eventIndexRef.value - eventBatch.length + 1);
|
|
166
|
+
eventBatch.length = 0; // Reset batch (memory freed)
|
|
167
|
+
}
|
|
168
|
+
eventIndexRef.value++;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Flush remaining batch + update request completion in DB.
|
|
172
|
+
* Extracts token usage from tail buffer (no need for full events array).
|
|
173
|
+
*/
|
|
174
|
+
async function completeStreamSave(sessionId, requestId, requestData, eventBatch, tailBuffer, eventIndexRef, startTime, statusCode, resolved, forwardedModel, originalModel, machineId, request) {
|
|
175
|
+
// Flush remaining events in batch
|
|
176
|
+
if (eventBatch.length > 0) {
|
|
177
|
+
flushEventBatch(sessionId, requestId, eventBatch, eventIndexRef.value - eventBatch.length);
|
|
178
|
+
eventBatch.length = 0;
|
|
179
|
+
}
|
|
180
|
+
requestData.response.completedAt = new Date().toISOString();
|
|
181
|
+
requestData.response.events = []; // Not needed for streaming, clear to avoid serialization
|
|
182
|
+
await saveRequestDB(sessionId, requestId, requestData);
|
|
183
|
+
const usage = extractTokenUsage(tailBuffer);
|
|
184
|
+
updateRequestTokens(requestId, sessionId, usage);
|
|
185
|
+
pushNewRequest({
|
|
186
|
+
requestId,
|
|
187
|
+
sessionId,
|
|
188
|
+
compositeId: `${sessionId}:${requestId}`,
|
|
189
|
+
method: request.method,
|
|
190
|
+
path: request.url,
|
|
191
|
+
timestamp: requestData.timestamp,
|
|
192
|
+
statusCode,
|
|
193
|
+
responseTime: Date.now() - startTime,
|
|
194
|
+
model: forwardedModel || originalModel,
|
|
195
|
+
machineId,
|
|
196
|
+
baseUrl: resolved.baseUrl,
|
|
197
|
+
usage
|
|
198
|
+
}, 'completed');
|
|
199
|
+
}
|
|
132
200
|
export function getErrorMessage(error) {
|
|
133
201
|
if (error.code === 'ECONNREFUSED') {
|
|
134
202
|
return 'Target server unreachable';
|
|
@@ -147,6 +215,13 @@ export async function setupProxy(fastify, proxyConfig) {
|
|
|
147
215
|
const maxDays = fullConfig.storage?.maxDays ?? 7;
|
|
148
216
|
// Initialize SQLite database
|
|
149
217
|
initDatabase();
|
|
218
|
+
// Initialize MCP connections (after DB is ready)
|
|
219
|
+
try {
|
|
220
|
+
await mcpManager.init();
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
console.error('MCP manager init failed:', err);
|
|
224
|
+
}
|
|
150
225
|
// Preload machine ID
|
|
151
226
|
await preloadMachineId();
|
|
152
227
|
// Start stats aggregation timer (every 5 minutes)
|
|
@@ -158,6 +233,15 @@ export async function setupProxy(fastify, proxyConfig) {
|
|
|
158
233
|
console.error('Stats aggregation failed:', err);
|
|
159
234
|
}
|
|
160
235
|
}, 300000); // 5 minutes
|
|
236
|
+
// Periodic cleanup timer (every 30 minutes, not per-request)
|
|
237
|
+
const cleanupTimer = setInterval(() => {
|
|
238
|
+
try {
|
|
239
|
+
cleanOldRequestsDB(maxDays);
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
console.error('Cleanup failed:', err);
|
|
243
|
+
}
|
|
244
|
+
}, 1800000); // 30 minutes
|
|
161
245
|
// Initial aggregation on startup
|
|
162
246
|
try {
|
|
163
247
|
aggregateStats();
|
|
@@ -165,9 +249,18 @@ export async function setupProxy(fastify, proxyConfig) {
|
|
|
165
249
|
catch (err) {
|
|
166
250
|
console.error('Initial stats aggregation failed:', err);
|
|
167
251
|
}
|
|
168
|
-
//
|
|
252
|
+
// Initial cleanup on startup
|
|
253
|
+
try {
|
|
254
|
+
cleanOldRequestsDB(maxDays);
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
console.error('Initial cleanup failed:', err);
|
|
258
|
+
}
|
|
259
|
+
// Cleanup database, MCP connections, and timer on server close
|
|
169
260
|
fastify.addHook('onClose', async () => {
|
|
170
261
|
clearInterval(statsTimer);
|
|
262
|
+
clearInterval(cleanupTimer);
|
|
263
|
+
await mcpManager.close();
|
|
171
264
|
closeDatabase();
|
|
172
265
|
});
|
|
173
266
|
// Application-layer forwarding (Controller + Fetch + SSE Pipe)
|
|
@@ -218,38 +311,166 @@ export async function setupProxy(fastify, proxyConfig) {
|
|
|
218
311
|
},
|
|
219
312
|
response: {
|
|
220
313
|
statusCode: 0, // Will be updated after fetch
|
|
221
|
-
events: [] //
|
|
314
|
+
events: [] // Only used for JSON responses; streaming uses batch buffer
|
|
222
315
|
}
|
|
223
316
|
};
|
|
224
|
-
|
|
317
|
+
// SSE event batch buffer: flush to DB every ~10 events to avoid in-memory accumulation
|
|
318
|
+
// Tail buffer: keep last 5 events for token usage extraction at stream end
|
|
319
|
+
const eventBatch = [];
|
|
320
|
+
const tailBuffer = [];
|
|
321
|
+
const eventIndexRef = { value: 0 };
|
|
225
322
|
request.log.info(`request logged: ${request.id} ${request.method} ${request.url}`);
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
323
|
+
// Save initial record + push pending status to frontend
|
|
324
|
+
await saveRequestDB(sessionId, request.id, requestData);
|
|
325
|
+
pushNewRequest({
|
|
326
|
+
requestId: request.id,
|
|
327
|
+
sessionId,
|
|
328
|
+
compositeId: `${sessionId}:${request.id}`,
|
|
329
|
+
method: request.method,
|
|
330
|
+
path: request.url,
|
|
331
|
+
timestamp: requestData.timestamp,
|
|
332
|
+
statusCode: 0,
|
|
333
|
+
responseTime: 0,
|
|
334
|
+
model: forwardedModel,
|
|
335
|
+
machineId,
|
|
336
|
+
baseUrl: resolved.baseUrl
|
|
337
|
+
}, 'pending');
|
|
338
|
+
// --- OpenAI Compatible Mode ---
|
|
339
|
+
if (resolved.is_openai_compatible) {
|
|
340
|
+
const openaiRequest = convertClaudeToOpenAIRequest(request.body, forwardedModel);
|
|
341
|
+
const openaiHeaders = buildOpenAIHeaders(resolved.apiKey || '');
|
|
342
|
+
// Build target URL: /chat/completions for OpenAI format
|
|
343
|
+
const targetUrlStr = `${resolved.baseUrl}${resolved.targetPath}/chat/completions`;
|
|
344
|
+
request.log.info(`forwarding (openai mode) to: ${targetUrlStr}`);
|
|
345
|
+
// Forward converted request to OpenAI-compatible API
|
|
346
|
+
const response = await fetch(targetUrlStr, {
|
|
347
|
+
method: request.method,
|
|
348
|
+
headers: openaiHeaders,
|
|
349
|
+
body: JSON.stringify(openaiRequest)
|
|
234
350
|
});
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
351
|
+
request.log.info(`response status: ${response.status}`);
|
|
352
|
+
// Extract cloud request ID
|
|
353
|
+
const cloudRequestId = response.headers.get('x-request-id') || request.id;
|
|
354
|
+
requestData.requestId = cloudRequestId;
|
|
355
|
+
requestData.response.statusCode = response.status;
|
|
356
|
+
requestData.response.headers = Object.fromEntries(response.headers);
|
|
357
|
+
// Check response type
|
|
358
|
+
const contentType = response.headers.get('content-type') || '';
|
|
359
|
+
const isSSE = contentType.includes('text/event-stream');
|
|
360
|
+
const isJSON = contentType.includes('application/json');
|
|
361
|
+
// Handle non-streaming JSON response
|
|
362
|
+
if (isJSON && !isSSE && response.body) {
|
|
363
|
+
const responseBody = await response.text();
|
|
364
|
+
let claudeResponseBody;
|
|
365
|
+
try {
|
|
366
|
+
const openaiRes = JSON.parse(responseBody);
|
|
367
|
+
const claudeRes = convertOpenAIToClaudeResponse(openaiRes, forwardedModel || originalModel);
|
|
368
|
+
claudeResponseBody = JSON.stringify(claudeRes);
|
|
369
|
+
requestData.response.events.push(claudeRes);
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
claudeResponseBody = responseBody;
|
|
373
|
+
try {
|
|
374
|
+
requestData.response.events.push(JSON.parse(responseBody));
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
requestData.response.events.push({ raw: responseBody });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
requestData.response.completedAt = new Date().toISOString();
|
|
381
|
+
if (cloudRequestId !== request.id) {
|
|
382
|
+
deleteResponseEvents(request.id, sessionId);
|
|
383
|
+
deleteRequestRow(request.id, sessionId);
|
|
384
|
+
}
|
|
385
|
+
await saveRequestDB(sessionId, cloudRequestId, requestData);
|
|
386
|
+
const usage = extractTokenUsage(requestData.response.events);
|
|
387
|
+
pushNewRequest({
|
|
388
|
+
requestId: cloudRequestId,
|
|
389
|
+
sessionId,
|
|
390
|
+
compositeId: `${sessionId}:${cloudRequestId}`,
|
|
391
|
+
method: request.method,
|
|
392
|
+
path: request.url,
|
|
393
|
+
timestamp: requestData.timestamp,
|
|
394
|
+
statusCode: response.status,
|
|
395
|
+
responseTime: Date.now() - startTime,
|
|
396
|
+
model: forwardedModel,
|
|
397
|
+
machineId,
|
|
398
|
+
baseUrl: resolved.baseUrl,
|
|
399
|
+
usage,
|
|
400
|
+
pendingId: cloudRequestId !== request.id ? `${sessionId}:${request.id}` : undefined
|
|
401
|
+
}, 'completed');
|
|
402
|
+
const elapsed = Date.now() - startTime;
|
|
403
|
+
request.log.info(`json response (openai): ${cloudRequestId} ${response.status} (${elapsed}ms)`);
|
|
404
|
+
reply.code(response.status).headers({ 'Content-Type': 'application/json' }).send(claudeResponseBody);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
// Handle SSE streaming response
|
|
408
|
+
reply.hijack();
|
|
409
|
+
const responseHeaders = Object.fromEntries(response.headers);
|
|
410
|
+
// Replace content-type with Claude SSE format
|
|
411
|
+
responseHeaders['content-type'] = 'text/event-stream';
|
|
412
|
+
reply.raw.writeHead(response.status, responseHeaders);
|
|
413
|
+
if (response.body) {
|
|
414
|
+
const nodeStream = Readable.fromWeb(response.body);
|
|
415
|
+
const transformStream = createStreamTransformer(forwardedModel || originalModel);
|
|
416
|
+
// Record SSE events via batch buffer (memory: ≤10 batch + ≤5 tail)
|
|
417
|
+
nodeStream.on('data', (chunk) => {
|
|
418
|
+
const text = chunk.toString();
|
|
419
|
+
const lines = text.split('\n');
|
|
420
|
+
for (const line of lines) {
|
|
421
|
+
if (line.startsWith('data:')) {
|
|
422
|
+
const eventData = line.slice(5).trim();
|
|
423
|
+
if (eventData && eventData !== '[DONE]') {
|
|
424
|
+
try {
|
|
425
|
+
const event = JSON.parse(eventData);
|
|
426
|
+
pushEventToBuffers(event, sessionId, cloudRequestId, eventBatch, tailBuffer, eventIndexRef);
|
|
427
|
+
}
|
|
428
|
+
catch {
|
|
429
|
+
// Not valid JSON, skip
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
// Use finished() for reliable stream completion detection
|
|
436
|
+
finished(nodeStream, async (err) => {
|
|
437
|
+
if (err) {
|
|
438
|
+
request.log.error(`openai stream error: ${err.message}`);
|
|
439
|
+
}
|
|
440
|
+
// Convert OpenAI stream usage to Claude format for extractTokenUsage compatibility
|
|
441
|
+
const lastChunk = tailBuffer.find((e) => e?.usage);
|
|
442
|
+
if (lastChunk?.usage) {
|
|
443
|
+
const syntheticEvent = {
|
|
444
|
+
type: 'message_delta',
|
|
445
|
+
usage: {
|
|
446
|
+
output_tokens: lastChunk.usage.completion_tokens || 0
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
pushEventToBuffers(syntheticEvent, sessionId, cloudRequestId, eventBatch, tailBuffer, eventIndexRef);
|
|
450
|
+
}
|
|
451
|
+
if (cloudRequestId !== request.id) {
|
|
452
|
+
deleteResponseEvents(request.id, sessionId);
|
|
453
|
+
deleteRequestRow(request.id, sessionId);
|
|
454
|
+
}
|
|
455
|
+
// Flush remaining batch + complete request
|
|
456
|
+
try {
|
|
457
|
+
await completeStreamSave(sessionId, cloudRequestId, requestData, eventBatch, tailBuffer, eventIndexRef, startTime, response.status, resolved, forwardedModel, originalModel, machineId, request);
|
|
458
|
+
const elapsed = Date.now() - startTime;
|
|
459
|
+
request.log.info(`stream completed (openai): ${cloudRequestId} ${response.status} (${elapsed}ms, ${eventIndexRef.value} events)`);
|
|
460
|
+
}
|
|
461
|
+
catch (dbErr) {
|
|
462
|
+
request.log.error(`openai stream save failed: ${dbErr.message}`);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
// Pipe: OpenAI SSE → Transform → Claude SSE → Client
|
|
466
|
+
nodeStream.pipe(transformStream).pipe(reply.raw);
|
|
467
|
+
}
|
|
238
468
|
return;
|
|
239
469
|
}
|
|
470
|
+
// --- Claude Mode (existing direct forwarding) ---
|
|
240
471
|
// Build target URL correctly
|
|
241
472
|
// request.url = '/v1/messages' -> should become '/apps/anthropic/v1/messages'
|
|
242
473
|
const targetUrlStr = `${resolved.baseUrl}${resolved.targetPath}${request.url}`;
|
|
243
|
-
// Tool 注入:存在启用 tool 时,向主对话请求的 tools 末尾注入自定义 tool。
|
|
244
|
-
// 无启用 tool 时完全不进此分支,走下方原 pipe 路径(零影响)。
|
|
245
|
-
let injectedNames = [];
|
|
246
|
-
if (hasEnabledTools()) {
|
|
247
|
-
injectedNames = injectTools(request.body);
|
|
248
|
-
if (injectedNames.length) {
|
|
249
|
-
request.log.info(`[inject] tools injected: ${injectedNames.join(',')} (${request.id})`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
const injected = injectedNames.length > 0;
|
|
253
474
|
request.log.info(`forwarding to: ${targetUrlStr}`);
|
|
254
475
|
// Forward request to Aliyun with filtered headers
|
|
255
476
|
const response = await fetch(targetUrlStr, {
|
|
@@ -277,10 +498,14 @@ export async function setupProxy(fastify, proxyConfig) {
|
|
|
277
498
|
requestData.response.events.push(jsonBody);
|
|
278
499
|
}
|
|
279
500
|
catch {
|
|
280
|
-
// Not valid JSON, record as raw text
|
|
281
501
|
requestData.response.events.push({ raw: responseBody });
|
|
282
502
|
}
|
|
283
503
|
requestData.response.completedAt = new Date().toISOString();
|
|
504
|
+
// Remove pending row if cloudRequestId differs from request.id
|
|
505
|
+
if (cloudRequestId !== request.id) {
|
|
506
|
+
deleteResponseEvents(request.id, sessionId);
|
|
507
|
+
deleteRequestRow(request.id, sessionId);
|
|
508
|
+
}
|
|
284
509
|
await saveRequestDB(sessionId, cloudRequestId, requestData);
|
|
285
510
|
const usage = extractTokenUsage(requestData.response.events);
|
|
286
511
|
pushNewRequest({
|
|
@@ -295,74 +520,16 @@ export async function setupProxy(fastify, proxyConfig) {
|
|
|
295
520
|
model: forwardedModel,
|
|
296
521
|
machineId,
|
|
297
522
|
baseUrl: resolved.baseUrl,
|
|
298
|
-
usage
|
|
299
|
-
|
|
523
|
+
usage,
|
|
524
|
+
pendingId: cloudRequestId !== request.id ? `${sessionId}:${request.id}` : undefined
|
|
525
|
+
}, 'completed');
|
|
300
526
|
const elapsed = Date.now() - startTime;
|
|
301
527
|
request.log.info(`json response: ${cloudRequestId} ${response.status} (${elapsed}ms)`);
|
|
302
|
-
// Send response to client
|
|
303
528
|
reply.code(response.status);
|
|
304
529
|
reply.headers(Object.fromEntries(response.headers));
|
|
305
530
|
reply.send(responseBody);
|
|
306
531
|
return;
|
|
307
532
|
}
|
|
308
|
-
// 条件缓冲:注入生效且为 SSE 时,收完整流检测是否命中已启用 tool。
|
|
309
|
-
// 命中 -> 内部循环 -> 合成 SSE 回写;未命中 -> 原样回放缓冲字节。
|
|
310
|
-
// 未注入时完全不进此分支,走下方原有 pipe 路径(零影响)。
|
|
311
|
-
if (injected && isSSE && response.body) {
|
|
312
|
-
const fullText = await response.text();
|
|
313
|
-
const events = parseSSEText(fullText);
|
|
314
|
-
requestData.response.events = events;
|
|
315
|
-
const parsed = parseUpstreamEvents(events);
|
|
316
|
-
const hitInjected = findInjectedCall(parsed) && parsed.stopReason === 'tool_use';
|
|
317
|
-
reply.hijack();
|
|
318
|
-
if (!hitInjected) {
|
|
319
|
-
// 未命中:原样回放上游缓冲字节,客户端无感知
|
|
320
|
-
reply.raw.writeHead(response.status, Object.fromEntries(response.headers));
|
|
321
|
-
reply.raw.end(fullText);
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
// 命中:进入内部循环,代行「执行 tool -> 追加对话 -> 再调上游」
|
|
325
|
-
request.log.info(`[inject] tool hit, entering loop: ${cloudRequestId}`);
|
|
326
|
-
const loop = await runInjectLoop({
|
|
327
|
-
events,
|
|
328
|
-
body: request.body,
|
|
329
|
-
callUpstream: async (b) => {
|
|
330
|
-
const r = await fetch(targetUrlStr, {
|
|
331
|
-
method: request.method,
|
|
332
|
-
headers: forwardHeaders,
|
|
333
|
-
body: JSON.stringify(b)
|
|
334
|
-
});
|
|
335
|
-
return parseSSEText(await r.text());
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
reply.raw.writeHead(200, {
|
|
339
|
-
'content-type': 'text/event-stream',
|
|
340
|
-
'cache-control': 'no-cache',
|
|
341
|
-
'connection': 'keep-alive'
|
|
342
|
-
});
|
|
343
|
-
writeTextAsSSE(reply.raw, loop.text, `msg_inject_${cloudRequestId}`);
|
|
344
|
-
reply.raw.end();
|
|
345
|
-
request.log.info(`[inject] loop done: ${cloudRequestId} (${loop.iterations} iter, limit=${loop.stoppedByLimit})`);
|
|
346
|
-
}
|
|
347
|
-
requestData.response.completedAt = new Date().toISOString();
|
|
348
|
-
await saveRequestDB(sessionId, cloudRequestId, requestData);
|
|
349
|
-
const usage = extractTokenUsage(requestData.response.events);
|
|
350
|
-
pushNewRequest({
|
|
351
|
-
requestId: cloudRequestId,
|
|
352
|
-
sessionId,
|
|
353
|
-
compositeId: `${sessionId}:${cloudRequestId}`,
|
|
354
|
-
method: request.method,
|
|
355
|
-
path: request.url,
|
|
356
|
-
timestamp: requestData.timestamp,
|
|
357
|
-
statusCode: response.status,
|
|
358
|
-
responseTime: Date.now() - startTime,
|
|
359
|
-
model: forwardedModel,
|
|
360
|
-
machineId,
|
|
361
|
-
baseUrl: resolved.baseUrl,
|
|
362
|
-
usage
|
|
363
|
-
});
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
533
|
// Hijack reply to take full control of SSE stream
|
|
367
534
|
reply.hijack();
|
|
368
535
|
// Write response headers (preserve original from Aliyun)
|
|
@@ -372,20 +539,17 @@ export async function setupProxy(fastify, proxyConfig) {
|
|
|
372
539
|
if (response.body) {
|
|
373
540
|
// Convert Web ReadableStream to Node Readable stream
|
|
374
541
|
const nodeStream = Readable.fromWeb(response.body);
|
|
375
|
-
// Record SSE events
|
|
542
|
+
// Record SSE events via batch buffer (memory: ≤10 batch + ≤5 tail)
|
|
376
543
|
nodeStream.on('data', (chunk) => {
|
|
377
544
|
const text = chunk.toString();
|
|
378
545
|
const lines = text.split('\n');
|
|
379
546
|
for (const line of lines) {
|
|
380
|
-
// Handle both standard SSE and Aliyun format
|
|
381
|
-
// Standard: data: {...}
|
|
382
|
-
// Aliyun: event:xxx\ndata:{...}
|
|
383
547
|
if (line.startsWith('data:')) {
|
|
384
|
-
const eventData = line.slice(5).trim();
|
|
548
|
+
const eventData = line.slice(5).trim();
|
|
385
549
|
if (eventData && eventData !== '[DONE]') {
|
|
386
550
|
try {
|
|
387
551
|
const event = JSON.parse(eventData);
|
|
388
|
-
|
|
552
|
+
pushEventToBuffers(event, sessionId, cloudRequestId, eventBatch, tailBuffer, eventIndexRef);
|
|
389
553
|
}
|
|
390
554
|
catch {
|
|
391
555
|
// Not valid JSON, skip
|
|
@@ -395,25 +559,22 @@ export async function setupProxy(fastify, proxyConfig) {
|
|
|
395
559
|
}
|
|
396
560
|
});
|
|
397
561
|
// Save request data when stream completes
|
|
398
|
-
nodeStream
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
usage
|
|
415
|
-
});
|
|
416
|
-
request.log.info(`stream completed: ${cloudRequestId} ${response.status} (${elapsed}ms, ${requestData.response.events.length} events)`);
|
|
562
|
+
finished(nodeStream, async (err) => {
|
|
563
|
+
if (err) {
|
|
564
|
+
request.log.error(`claude stream error: ${err.message}`);
|
|
565
|
+
}
|
|
566
|
+
if (cloudRequestId !== request.id) {
|
|
567
|
+
deleteResponseEvents(request.id, sessionId);
|
|
568
|
+
deleteRequestRow(request.id, sessionId);
|
|
569
|
+
}
|
|
570
|
+
try {
|
|
571
|
+
await completeStreamSave(sessionId, cloudRequestId, requestData, eventBatch, tailBuffer, eventIndexRef, startTime, response.status, resolved, forwardedModel, originalModel, machineId, request);
|
|
572
|
+
const elapsed = Date.now() - startTime;
|
|
573
|
+
request.log.info(`stream completed: ${cloudRequestId} ${response.status} (${elapsed}ms, ${eventIndexRef.value} events)`);
|
|
574
|
+
}
|
|
575
|
+
catch (dbErr) {
|
|
576
|
+
request.log.error(`stream save failed: ${dbErr.message}`);
|
|
577
|
+
}
|
|
417
578
|
});
|
|
418
579
|
// Pipe: Aliyun -> Claude Code
|
|
419
580
|
nodeStream.pipe(reply.raw);
|
|
@@ -439,7 +600,7 @@ export async function setupProxy(fastify, proxyConfig) {
|
|
|
439
600
|
model: originalModel,
|
|
440
601
|
machineId: getMachineIdSync() ?? undefined,
|
|
441
602
|
baseUrl: resolved.baseUrl
|
|
442
|
-
});
|
|
603
|
+
}, 'completed');
|
|
443
604
|
const statusCode = getErrorStatusCode(error);
|
|
444
605
|
reply.code(statusCode).send({
|
|
445
606
|
error: getErrorMessage(error),
|