@cc-monitoring/proxy-core 0.1.5 → 0.1.7
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/docs/404.html +22 -0
- package/dist/docs/assets/app.BxFOH0P1.js +1 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.Cuu7C9vz.js +1 -0
- package/dist/docs/assets/chunks/VPLocalSearchBox.CYDhNgXW.js +9 -0
- package/dist/docs/assets/chunks/abnfDiagram-VRR7QNED.RG1C5NpS.js +1 -0
- package/dist/docs/assets/chunks/arc.C9NMazfV.js +1 -0
- package/dist/docs/assets/chunks/architectureDiagram-ZJ3FMSHR.u2IZtOa6.js +36 -0
- package/dist/docs/assets/chunks/blockDiagram-677ZJIJ3.DdHVir7t.js +132 -0
- package/dist/docs/assets/chunks/c4Diagram-LMCZKHZV.gSbgoaJo.js +10 -0
- package/dist/docs/assets/chunks/channel.Cc6zpfFP.js +1 -0
- package/dist/docs/assets/chunks/chunk-2Q5K7J3B.HRSdtksv.js +1 -0
- package/dist/docs/assets/chunks/chunk-32BRIVSS.BYzKapof.js +1 -0
- package/dist/docs/assets/chunks/chunk-5VM5RSS4.c3oeF0uS.js +15 -0
- package/dist/docs/assets/chunks/chunk-EX3LRPZG.Cz5AKl7t.js +231 -0
- package/dist/docs/assets/chunks/chunk-JWPE2WC7.DtJxsA7-.js +1 -0
- package/dist/docs/assets/chunks/chunk-MOJQB5TN.h5kO0Dtu.js +88 -0
- package/dist/docs/assets/chunks/chunk-RYQCIY6F.Ch_NfMTR.js +1 -0
- package/dist/docs/assets/chunks/chunk-V7JOEXUC.CHrHyPoW.js +206 -0
- package/dist/docs/assets/chunks/chunk-VR4S4FIN.DG8DIrH3.js +1 -0
- package/dist/docs/assets/chunks/chunk-XXDRQBXY.CSXBN12Y.js +1 -0
- package/dist/docs/assets/chunks/classDiagram-OUVF2IWQ.DV5B4KLJ.js +1 -0
- package/dist/docs/assets/chunks/classDiagram-v2-EOCWNBFH.DV5B4KLJ.js +1 -0
- package/dist/docs/assets/chunks/cose-bilkent-JH36ORCC.BeFuOTgi.js +1 -0
- package/dist/docs/assets/chunks/cynefin-VYW2F7L2.DaXIfh7Y.js +178 -0
- package/dist/docs/assets/chunks/cynefinDiagram-TSTJHNR4.BrJcHEEe.js +62 -0
- package/dist/docs/assets/chunks/cytoscape.esm.CUqq0XTU.js +331 -0
- package/dist/docs/assets/chunks/dagre-VKFMJZFB.D4vaA23X.js +4 -0
- package/dist/docs/assets/chunks/defaultLocale.DX6XiGOO.js +1 -0
- package/dist/docs/assets/chunks/diagram-FQU43EPY.uwRFLKRx.js +3 -0
- package/dist/docs/assets/chunks/diagram-G47NLZAW.Byfc9d0H.js +24 -0
- package/dist/docs/assets/chunks/diagram-NH7WQ7WH.CxjBEGZX.js +24 -0
- package/dist/docs/assets/chunks/diagram-OA4YK3LP.CU1jGXMi.js +30 -0
- package/dist/docs/assets/chunks/diagram-WEI45ONY.yXgVkURA.js +41 -0
- package/dist/docs/assets/chunks/ebnfDiagram-CCIWWBDH.UucqGQ_d.js +1 -0
- package/dist/docs/assets/chunks/erDiagram-Q63AITRT.C6qmOrvh.js +85 -0
- package/dist/docs/assets/chunks/flowDiagram-23GEKE2U.BZLu-WUl.js +156 -0
- package/dist/docs/assets/chunks/framework.D_Xv2a7c.js +19 -0
- package/dist/docs/assets/chunks/ganttDiagram-NO4QXBWP.DUzsw4Yr.js +292 -0
- package/dist/docs/assets/chunks/gitGraphDiagram-IHSO6WYX.BqLfYTI7.js +106 -0
- package/dist/docs/assets/chunks/graph.C2PyVmSI.js +1 -0
- package/dist/docs/assets/chunks/infoDiagram-FWYZ7A6U.CiKWveW9.js +2 -0
- package/dist/docs/assets/chunks/init.Gi6I4Gst.js +1 -0
- package/dist/docs/assets/chunks/ishikawaDiagram-FXEZZL3T.jerUXEXA.js +70 -0
- package/dist/docs/assets/chunks/journeyDiagram-5HDEW3XC.Cqzvo6I_.js +139 -0
- package/dist/docs/assets/chunks/kanban-definition-HUTT4EX6.BzJ4OPe5.js +89 -0
- package/dist/docs/assets/chunks/katex.C5jXJg4s.js +257 -0
- package/dist/docs/assets/chunks/layout.BG3qr61M.js +1 -0
- package/dist/docs/assets/chunks/linear.hFjgYGMN.js +1 -0
- package/dist/docs/assets/chunks/map.DwDi8XlU.js +1 -0
- package/dist/docs/assets/chunks/mermaid-client.D3gEaJ5e.js +2 -0
- package/dist/docs/assets/chunks/mermaid.core.CEHdFh45.js +314 -0
- package/dist/docs/assets/chunks/mindmap-definition-LN4V7U3C.ChC5FJW6.js +96 -0
- package/dist/docs/assets/chunks/ordinal.BYWQX77i.js +1 -0
- package/dist/docs/assets/chunks/pegDiagram-2B236MQR.ClnjsRYs.js +1 -0
- package/dist/docs/assets/chunks/pieDiagram-ENE6RG2P.Bb0MqerK.js +39 -0
- package/dist/docs/assets/chunks/quadrantDiagram-ABIIQ3AL.Do6HK_yb.js +7 -0
- package/dist/docs/assets/chunks/railroadDiagram-RFXS5EU6.D35m6hQn.js +1 -0
- package/dist/docs/assets/chunks/requirementDiagram-TGXJPOKE.DMtjRYUg.js +84 -0
- package/dist/docs/assets/chunks/sankeyDiagram-HTMAVEWB.dfXDu62B.js +40 -0
- package/dist/docs/assets/chunks/sequenceDiagram-DBY2YBRQ.CLZ0rZTY.js +162 -0
- package/dist/docs/assets/chunks/sizeCapture-X5ZJPWSS.DWvGYLUT.js +1 -0
- package/dist/docs/assets/chunks/stateDiagram-2N3HPSRC.DY8sDkI0.js +1 -0
- package/dist/docs/assets/chunks/stateDiagram-v2-6OUMAXLB.B9v-Vq5L.js +1 -0
- package/dist/docs/assets/chunks/swimlanes-5IMT3BWC.Dy9SffeT.js +2 -0
- package/dist/docs/assets/chunks/swimlanesDiagram-G3AALYLV.Kb9LnCTH.js +8 -0
- package/dist/docs/assets/chunks/theme.BLEczZWC.js +2 -0
- package/dist/docs/assets/chunks/timeline-definition-FHXFAJF6.CH6uzgIu.js +120 -0
- package/dist/docs/assets/chunks/vennDiagram-L72KCM5P.7EBCJmyP.js +34 -0
- package/dist/docs/assets/chunks/wardleyDiagram-EHGQE667.DPHAQssL.js +78 -0
- package/dist/docs/assets/chunks/xychartDiagram-FW5EYKEG.aKgxZVbK.js +7 -0
- package/dist/docs/assets/index.md.p9floL_p.js +1 -0
- package/dist/docs/assets/index.md.p9floL_p.lean.js +1 -0
- package/dist/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/dist/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/dist/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/dist/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/dist/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/dist/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/dist/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/dist/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/dist/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/dist/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/dist/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/dist/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/dist/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/dist/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/dist/docs/assets/overview.md.BKVdLT3o.js +70 -0
- package/dist/docs/assets/overview.md.BKVdLT3o.lean.js +1 -0
- package/dist/docs/assets/packages-utils.md.CvgYp5Ll.js +31 -0
- package/dist/docs/assets/packages-utils.md.CvgYp5Ll.lean.js +1 -0
- package/dist/docs/assets/proxy-admin_env-and-build.md.xkeQNYS3.js +40 -0
- package/dist/docs/assets/proxy-admin_env-and-build.md.xkeQNYS3.lean.js +1 -0
- package/dist/docs/assets/proxy-admin_index.md.DBLI0n0G.js +38 -0
- package/dist/docs/assets/proxy-admin_index.md.DBLI0n0G.lean.js +1 -0
- package/dist/docs/assets/proxy-admin_layout-and-menu.md.BKGcu49p.js +48 -0
- package/dist/docs/assets/proxy-admin_layout-and-menu.md.BKGcu49p.lean.js +1 -0
- package/dist/docs/assets/proxy-admin_pages.md.m_fGvvOH.js +51 -0
- package/dist/docs/assets/proxy-admin_pages.md.m_fGvvOH.lean.js +1 -0
- package/dist/docs/assets/proxy-admin_routing-and-auth.md.DZsdFese.js +106 -0
- package/dist/docs/assets/proxy-admin_routing-and-auth.md.DZsdFese.lean.js +1 -0
- package/dist/docs/assets/proxy-admin_services-and-mock.md.sdNPcf25.js +66 -0
- package/dist/docs/assets/proxy-admin_services-and-mock.md.sdNPcf25.lean.js +1 -0
- package/dist/docs/assets/proxy-cli_api-layer.md.GfdbtEUx.js +1 -0
- package/dist/docs/assets/proxy-cli_api-layer.md.GfdbtEUx.lean.js +1 -0
- package/dist/docs/assets/proxy-cli_app-shell.md.Do18CYhW.js +83 -0
- package/dist/docs/assets/proxy-cli_app-shell.md.Do18CYhW.lean.js +1 -0
- package/dist/docs/assets/proxy-cli_components-common.md.Dd6Yazvz.js +23 -0
- package/dist/docs/assets/proxy-cli_components-common.md.Dd6Yazvz.lean.js +1 -0
- package/dist/docs/assets/proxy-cli_components-config.md.B81Y3ta2.js +46 -0
- package/dist/docs/assets/proxy-cli_components-config.md.B81Y3ta2.lean.js +1 -0
- package/dist/docs/assets/proxy-cli_components-requests.md.vN8IrwL_.js +84 -0
- package/dist/docs/assets/proxy-cli_components-requests.md.vN8IrwL_.lean.js +1 -0
- package/dist/docs/assets/proxy-cli_components-stats.md.B90esaNa.js +14 -0
- package/dist/docs/assets/proxy-cli_components-stats.md.B90esaNa.lean.js +1 -0
- package/dist/docs/assets/proxy-cli_components-status.md.C5lAzS2c.js +42 -0
- package/dist/docs/assets/proxy-cli_components-status.md.C5lAzS2c.lean.js +1 -0
- package/dist/docs/assets/proxy-cli_data-hooks.md.w9Oe1OD3.js +87 -0
- package/dist/docs/assets/proxy-cli_data-hooks.md.w9Oe1OD3.lean.js +1 -0
- package/dist/docs/assets/proxy-cli_index.md.BA6g8QD-.js +34 -0
- package/dist/docs/assets/proxy-cli_index.md.BA6g8QD-.lean.js +1 -0
- package/dist/docs/assets/proxy-cli_utils-and-i18n.md.Ch_e-g_o.js +34 -0
- package/dist/docs/assets/proxy-cli_utils-and-i18n.md.Ch_e-g_o.lean.js +1 -0
- package/dist/docs/assets/proxy-core_chat-api.md.DHihiBxR.js +152 -0
- package/dist/docs/assets/proxy-core_chat-api.md.DHihiBxR.lean.js +1 -0
- package/dist/docs/assets/proxy-core_cli.md.DCFYX4Bd.js +37 -0
- package/dist/docs/assets/proxy-core_cli.md.DCFYX4Bd.lean.js +1 -0
- package/dist/docs/assets/proxy-core_config.md.BXSOyxYE.js +83 -0
- package/dist/docs/assets/proxy-core_config.md.BXSOyxYE.lean.js +1 -0
- package/dist/docs/assets/proxy-core_data-layer.md.Cdomb3Gz.js +85 -0
- package/dist/docs/assets/proxy-core_data-layer.md.Cdomb3Gz.lean.js +1 -0
- package/dist/docs/assets/proxy-core_entry.md.BZmIcCUM.js +90 -0
- package/dist/docs/assets/proxy-core_entry.md.BZmIcCUM.lean.js +1 -0
- package/dist/docs/assets/proxy-core_index.md.BM6VV5Qe.js +55 -0
- package/dist/docs/assets/proxy-core_index.md.BM6VV5Qe.lean.js +1 -0
- package/dist/docs/assets/proxy-core_pm2.md.DUhWEtgn.js +79 -0
- package/dist/docs/assets/proxy-core_pm2.md.DUhWEtgn.lean.js +1 -0
- package/dist/docs/assets/proxy-core_providers.md.CbDZ6b_7.js +1 -0
- package/dist/docs/assets/proxy-core_providers.md.CbDZ6b_7.lean.js +1 -0
- package/dist/docs/assets/proxy-core_proxy.md.CzQYmX6X.js +46 -0
- package/dist/docs/assets/proxy-core_proxy.md.CzQYmX6X.lean.js +1 -0
- package/dist/docs/assets/proxy-core_stats.md.BsM6wuK_.js +42 -0
- package/dist/docs/assets/proxy-core_stats.md.BsM6wuK_.lean.js +1 -0
- package/dist/docs/assets/proxy-core_tool.md.B6Le1nQr.js +100 -0
- package/dist/docs/assets/proxy-core_tool.md.B6Le1nQr.lean.js +1 -0
- package/dist/docs/assets/proxy-core_ui-routes.md.Dyqkeoiv.js +27 -0
- package/dist/docs/assets/proxy-core_ui-routes.md.Dyqkeoiv.lean.js +1 -0
- package/dist/docs/assets/proxy-core_utilities.md.C18pjnkz.js +63 -0
- package/dist/docs/assets/proxy-core_utilities.md.C18pjnkz.lean.js +1 -0
- package/dist/docs/assets/quickstart.md.Cy_FmXIY.js +38 -0
- package/dist/docs/assets/quickstart.md.Cy_FmXIY.lean.js +1 -0
- package/dist/docs/assets/request-flow.md.C1fyxdPE.js +146 -0
- package/dist/docs/assets/request-flow.md.C1fyxdPE.lean.js +1 -0
- package/dist/docs/assets/style.DU_kNMWy.css +1 -0
- package/dist/docs/hashmap.json +1 -0
- package/dist/docs/index.html +25 -0
- package/dist/docs/overview.html +94 -0
- package/dist/docs/packages-utils.html +55 -0
- package/dist/docs/proxy-admin/env-and-build.html +64 -0
- package/dist/docs/proxy-admin/index.html +62 -0
- package/dist/docs/proxy-admin/layout-and-menu.html +72 -0
- package/dist/docs/proxy-admin/pages.html +75 -0
- package/dist/docs/proxy-admin/routing-and-auth.html +130 -0
- package/dist/docs/proxy-admin/services-and-mock.html +90 -0
- package/dist/docs/proxy-cli/api-layer.html +38 -0
- package/dist/docs/proxy-cli/app-shell.html +107 -0
- package/dist/docs/proxy-cli/components-common.html +47 -0
- package/dist/docs/proxy-cli/components-config.html +70 -0
- package/dist/docs/proxy-cli/components-requests.html +108 -0
- package/dist/docs/proxy-cli/components-stats.html +38 -0
- package/dist/docs/proxy-cli/components-status.html +66 -0
- package/dist/docs/proxy-cli/data-hooks.html +111 -0
- package/dist/docs/proxy-cli/index.html +58 -0
- package/dist/docs/proxy-cli/utils-and-i18n.html +58 -0
- package/dist/docs/proxy-core/chat-api.html +176 -0
- package/dist/docs/proxy-core/cli.html +61 -0
- package/dist/docs/proxy-core/config.html +107 -0
- package/dist/docs/proxy-core/data-layer.html +109 -0
- package/dist/docs/proxy-core/entry.html +114 -0
- package/dist/docs/proxy-core/index.html +79 -0
- package/dist/docs/proxy-core/pm2.html +103 -0
- package/dist/docs/proxy-core/providers.html +39 -0
- package/dist/docs/proxy-core/proxy.html +70 -0
- package/dist/docs/proxy-core/stats.html +66 -0
- package/dist/docs/proxy-core/tool.html +124 -0
- package/dist/docs/proxy-core/ui-routes.html +51 -0
- package/dist/docs/proxy-core/utilities.html +87 -0
- package/dist/docs/quickstart.html +62 -0
- package/dist/docs/request-flow.html +170 -0
- package/dist/docs/vp-icons.css +0 -0
- package/dist/server/proxy.js +2 -1
- package/dist/ui/assets/{1c-KmWe8RWs.js → 1c-DPsrzNHI.js} +1 -1
- package/dist/ui/assets/{abnf-BKQ6qIuo.js → abnf-DNkBIQRO.js} +1 -1
- package/dist/ui/assets/{abnfDiagram-VRR7QNED-CkRuDPu1.js → abnfDiagram-VRR7QNED-BqvHvzR8.js} +1 -1
- package/dist/ui/assets/{accesslog-DD9H5Ktc.js → accesslog-D5NxQ43Q.js} +1 -1
- package/dist/ui/assets/{actionscript-CAjf2Qtd.js → actionscript-DLr-LB2m.js} +1 -1
- package/dist/ui/assets/{ada-BXkXW16O.js → ada-BRSz3tHX.js} +1 -1
- package/dist/ui/assets/{angelscript-BxT7o0Up.js → angelscript-DMtEGgIa.js} +1 -1
- package/dist/ui/assets/{apache-C5z_tmVm.js → apache-C4gn4cSd.js} +1 -1
- package/dist/ui/assets/{applescript-DdIxl8rr.js → applescript-DoodcTwg.js} +1 -1
- package/dist/ui/assets/{arc-hSdFaqKn.js → arc-HmnzaGf9.js} +1 -1
- package/dist/ui/assets/{arcade-D5m0roat.js → arcade-RJZZSHSl.js} +1 -1
- package/dist/ui/assets/{architectureDiagram-ZJ3FMSHR-DOSHSqzf.js → architectureDiagram-ZJ3FMSHR-BUfDpSIw.js} +1 -1
- package/dist/ui/assets/{arduino-FDap8kq-.js → arduino-n6Dpx6Ig.js} +1 -1
- package/dist/ui/assets/{armasm-Kzp-Jr1v.js → armasm-DzCVITGB.js} +1 -1
- package/dist/ui/assets/{asciidoc-DbGMW3EE.js → asciidoc-CaqMsApq.js} +1 -1
- package/dist/ui/assets/{aspectj-CVYMmBCO.js → aspectj-B0kIcV0v.js} +1 -1
- package/dist/ui/assets/{autohotkey-9PmBGTPd.js → autohotkey-xUEXewap.js} +1 -1
- package/dist/ui/assets/{autoit-B4F86jgh.js → autoit-D3OfN2VX.js} +1 -1
- package/dist/ui/assets/{avrasm-CSzv2eHX.js → avrasm-Cbf0F4Kf.js} +1 -1
- package/dist/ui/assets/{awk-CjsvYvZE.js → awk-Cc0rWEwe.js} +1 -1
- package/dist/ui/assets/{axapta-BVgMoNRY.js → axapta-BKEeoUEe.js} +1 -1
- package/dist/ui/assets/{bash-IeBjW3pt.js → bash-D6JH6L3m.js} +1 -1
- package/dist/ui/assets/{basic-BTFJfmV1.js → basic-ka6JXc9C.js} +1 -1
- package/dist/ui/assets/{blockDiagram-677ZJIJ3-DLw3Bbhf.js → blockDiagram-677ZJIJ3-BHXRSjnM.js} +1 -1
- package/dist/ui/assets/{bnf-rCkeRYk0.js → bnf-Bxclk5tZ.js} +1 -1
- package/dist/ui/assets/{brainfuck-DBHuB3uZ.js → brainfuck--s_Fd8b9.js} +1 -1
- package/dist/ui/assets/{coq-TwFFolHa.js → c-BsgdDna0.js} +1 -1
- package/dist/ui/assets/{c-like-CK1xGeX_.js → c-like-CNkOkbqh.js} +1 -1
- package/dist/ui/assets/{c4Diagram-LMCZKHZV-cqY51yJw.js → c4Diagram-LMCZKHZV-Bp1uSrW8.js} +1 -1
- package/dist/ui/assets/{cal-BxGPpEZK.js → cal-LVlIOy9W.js} +1 -1
- package/dist/ui/assets/{capnproto-Bo2fgXF9.js → capnproto-r9DiQ1H8.js} +1 -1
- package/dist/ui/assets/{ceylon-C4Bb4pNB.js → ceylon-CTNzyZJX.js} +1 -1
- package/dist/ui/assets/channel-_rFUXR2i.js +1 -0
- package/dist/ui/assets/{chunk-2Q5K7J3B-CGTiTBmo.js → chunk-2Q5K7J3B-nnLVtUW3.js} +1 -1
- package/dist/ui/assets/{chunk-32BRIVSS-DWagYBp9.js → chunk-32BRIVSS-BDNS6igp.js} +1 -1
- package/dist/ui/assets/{chunk-5VM5RSS4-BzSBMKJX.js → chunk-5VM5RSS4-D713xBhI.js} +1 -1
- package/dist/ui/assets/{chunk-EX3LRPZG-4-aKwCgZ.js → chunk-EX3LRPZG-DExsU6Rd.js} +1 -1
- package/dist/ui/assets/{chunk-JWPE2WC7-CPC_cBN0.js → chunk-JWPE2WC7-DcpW5RnW.js} +1 -1
- package/dist/ui/assets/{chunk-MOJQB5TN-DrDE4H9w.js → chunk-MOJQB5TN-BQiSZNTn.js} +1 -1
- package/dist/ui/assets/{chunk-RYQCIY6F-DkdXe_kU.js → chunk-RYQCIY6F-DfblgZPd.js} +1 -1
- package/dist/ui/assets/{chunk-V7JOEXUC-Da6EWcQt.js → chunk-V7JOEXUC-np--VgmN.js} +1 -1
- package/dist/ui/assets/{chunk-VR4S4FIN-nP6dstD5.js → chunk-VR4S4FIN-BG0IhjO6.js} +1 -1
- package/dist/ui/assets/{chunk-XXDRQBXY-Bi_rRAPA.js → chunk-XXDRQBXY-DHuoh-RB.js} +1 -1
- package/dist/ui/assets/classDiagram-OUVF2IWQ-DqgbMzM1.js +1 -0
- package/dist/ui/assets/classDiagram-v2-EOCWNBFH-DqgbMzM1.js +1 -0
- package/dist/ui/assets/{clean-cc1m9AxK.js → clean-CtVj0-EB.js} +1 -1
- package/dist/ui/assets/{clojure-DX5V8QA8.js → clojure-CZwle60-.js} +1 -1
- package/dist/ui/assets/{clojure-repl-FzjnVNWQ.js → clojure-repl-DJXWH00I.js} +1 -1
- package/dist/ui/assets/{cmake-COZZyrKs.js → cmake-Boxf0e7r.js} +1 -1
- package/dist/ui/assets/{coffeescript-DBsnOmC4.js → coffeescript-h85ZW4Gm.js} +1 -1
- package/dist/ui/assets/{c-C95V0zHL.js → coq-CVLui3m8.js} +1 -1
- package/dist/ui/assets/{cos-Dm5g-IQT.js → cos-CL0igafw.js} +1 -1
- package/dist/ui/assets/{cose-bilkent-JH36ORCC-B3-weOQF.js → cose-bilkent-JH36ORCC-CnXyotYB.js} +1 -1
- package/dist/ui/assets/{cpp-_RZxQjg7.js → cpp-vite1NvJ.js} +1 -1
- package/dist/ui/assets/{crmsh-DGPJ11Gt.js → crmsh-DqT9g0Iq.js} +1 -1
- package/dist/ui/assets/{crystal-BOMSPGge.js → crystal-Oans-K8G.js} +1 -1
- package/dist/ui/assets/{csharp-DZXiHH72.js → csharp-sCaDAzIB.js} +1 -1
- package/dist/ui/assets/{csp-DDSIMUoU.js → csp-rMcp93pK.js} +1 -1
- package/dist/ui/assets/{css-Bjye-ZWr.js → css-BwDLHi_h.js} +1 -1
- package/dist/ui/assets/{cynefin-VYW2F7L2-Cj5c4L3y.js → cynefin-VYW2F7L2-D8Bmecdd.js} +1 -1
- package/dist/ui/assets/{cynefinDiagram-TSTJHNR4-BYsfTeNX.js → cynefinDiagram-TSTJHNR4-BT2YFmPZ.js} +1 -1
- package/dist/ui/assets/{d-22pfurOx.js → d-BFgZm0RV.js} +1 -1
- package/dist/ui/assets/{dagre-VKFMJZFB-DiEob-by.js → dagre-VKFMJZFB-BeJU73ex.js} +1 -1
- package/dist/ui/assets/{dart-Doiu8cDW.js → dart-rIo85iL3.js} +1 -1
- package/dist/ui/assets/{delphi-CQ7epp84.js → delphi-Bi2Rfp1o.js} +1 -1
- package/dist/ui/assets/{diagram-FQU43EPY-BOPbQcL4.js → diagram-FQU43EPY-CHQiS5Fr.js} +1 -1
- package/dist/ui/assets/{diagram-G47NLZAW-C1UWDJTx.js → diagram-G47NLZAW-EVerd4V8.js} +1 -1
- package/dist/ui/assets/{diagram-NH7WQ7WH-CYsBqW3J.js → diagram-NH7WQ7WH-DMP0VOAs.js} +1 -1
- package/dist/ui/assets/{diagram-OA4YK3LP-Dnag0x_3.js → diagram-OA4YK3LP-CAur5jK9.js} +1 -1
- package/dist/ui/assets/{diagram-WEI45ONY-Bo4CsDoj.js → diagram-WEI45ONY-DvmKkPH7.js} +1 -1
- package/dist/ui/assets/{diff-Cs6VQQZt.js → diff-CZZCIG00.js} +1 -1
- package/dist/ui/assets/{django-Bo3okOrw.js → django-D88PKHy-.js} +1 -1
- package/dist/ui/assets/{dns-BpG60eIX.js → dns-DX4hzjUh.js} +1 -1
- package/dist/ui/assets/{dockerfile-BWyH8SlV.js → dockerfile-1lgzEXn1.js} +1 -1
- package/dist/ui/assets/{dos-DWlNbaB9.js → dos-CwrC1ROy.js} +1 -1
- package/dist/ui/assets/{dsconfig-D_8y8YrJ.js → dsconfig-tvVdBv4w.js} +1 -1
- package/dist/ui/assets/{dts-Cz4SyMYR.js → dts-DTgQkdRs.js} +1 -1
- package/dist/ui/assets/{dust-Cu4qLkeH.js → dust-BbLlyCpf.js} +1 -1
- package/dist/ui/assets/{ebnf-CTVHui3r.js → ebnf-D6Dc4oRr.js} +1 -1
- package/dist/ui/assets/{ebnfDiagram-CCIWWBDH-epcXQLEj.js → ebnfDiagram-CCIWWBDH-Kvilk1RF.js} +1 -1
- package/dist/ui/assets/{elixir-MAlwXhfU.js → elixir-CnMolQVs.js} +1 -1
- package/dist/ui/assets/{elm-Bdmwbvef.js → elm-BQ7VQFn7.js} +1 -1
- package/dist/ui/assets/{erDiagram-Q63AITRT-Bb60TqAM.js → erDiagram-Q63AITRT-C-S9oHZd.js} +1 -1
- package/dist/ui/assets/{erb-Ce9TpP4g.js → erb-B_1g6fXE.js} +1 -1
- package/dist/ui/assets/{erlang-CN2GEiqk.js → erlang-3b3ITXJu.js} +1 -1
- package/dist/ui/assets/{erlang-repl-D8K4O_cp.js → erlang-repl-Cy6Z3TdH.js} +1 -1
- package/dist/ui/assets/{excel-eVZU1r0f.js → excel-DtkATto8.js} +1 -1
- package/dist/ui/assets/{fix-BzKerEv0.js → fix-DsEG7TDI.js} +1 -1
- package/dist/ui/assets/{flix-B9CE3skV.js → flix-CQ-rKwGO.js} +1 -1
- package/dist/ui/assets/{flowDiagram-23GEKE2U-DzMLZIyt.js → flowDiagram-23GEKE2U-EbHDNMCC.js} +1 -1
- package/dist/ui/assets/{fortran-B4__3vos.js → fortran-AqzaRCEt.js} +1 -1
- package/dist/ui/assets/{fsharp-BJ3-ARHz.js → fsharp-CCzWGasu.js} +1 -1
- package/dist/ui/assets/{gams-Cu43oSYT.js → gams-Bqa6CB_r.js} +1 -1
- package/dist/ui/assets/{ganttDiagram-NO4QXBWP-D_aAoMRP.js → ganttDiagram-NO4QXBWP-B6jvc6O7.js} +1 -1
- package/dist/ui/assets/{gauss-T8vk_TpE.js → gauss-D4Utubeh.js} +1 -1
- package/dist/ui/assets/{gcode-9j3BicpZ.js → gcode-By3KwWjb.js} +1 -1
- package/dist/ui/assets/{gherkin-Dyh5KFG-.js → gherkin-PXOPjT7Y.js} +1 -1
- package/dist/ui/assets/{gitGraphDiagram-IHSO6WYX-CMxCHC-Z.js → gitGraphDiagram-IHSO6WYX-C5l6rHvK.js} +1 -1
- package/dist/ui/assets/{glsl-DWWjN4ts.js → glsl-KmmyBk93.js} +1 -1
- package/dist/ui/assets/{gml-BcYE-D5A.js → gml-C1Qw_Nrt.js} +1 -1
- package/dist/ui/assets/{go-DUnUzOEI.js → go-DaBQtdp1.js} +1 -1
- package/dist/ui/assets/{golo-BDoc1QQL.js → golo-D7PDzNpb.js} +1 -1
- package/dist/ui/assets/{gradle-B_WRa_bT.js → gradle-nXueCYH-.js} +1 -1
- package/dist/ui/assets/{groovy-CgjqFq1e.js → groovy-DFDsQDhl.js} +1 -1
- package/dist/ui/assets/{haml-DCqcg7Ti.js → haml-B_zxHzy_.js} +1 -1
- package/dist/ui/assets/{handlebars-DSDUQrnw.js → handlebars-BdMY-8KA.js} +1 -1
- package/dist/ui/assets/{haskell-DPVnctVi.js → haskell-CftsIPps.js} +1 -1
- package/dist/ui/assets/{haxe-BuCGXBa4.js → haxe-BFZb0OIE.js} +1 -1
- package/dist/ui/assets/{hsp-BTKpS_u0.js → hsp-EPTuqwtZ.js} +1 -1
- package/dist/ui/assets/{htmlbars-Cql3Jvy2.js → htmlbars-B7ZSC-iN.js} +1 -1
- package/dist/ui/assets/{http-BB8I9jUh.js → http-BxSd_aa1.js} +1 -1
- package/dist/ui/assets/{hy-DG7xkZNz.js → hy-D-rBN9MQ.js} +1 -1
- package/dist/ui/assets/index-CWFn1h-J.css +10 -0
- package/dist/ui/assets/{index-CfTIGTiY.js → index-DSGZBcd1.js} +166 -171
- package/dist/ui/assets/{index-DqIiCkSD.js → index-Dlp8uGHu.js} +3 -3
- package/dist/ui/assets/{infoDiagram-FWYZ7A6U-DIzEV-BJ.js → infoDiagram-FWYZ7A6U-BOF_kBqe.js} +1 -1
- package/dist/ui/assets/{inform7-BWNpVEkA.js → inform7-OjpbRXZf.js} +1 -1
- package/dist/ui/assets/{ini-BMw_C8EP.js → ini-Dc-QApXQ.js} +1 -1
- package/dist/ui/assets/{irpf90-Cy42ibFZ.js → irpf90-C4OfYiST.js} +1 -1
- package/dist/ui/assets/{isbl-CjFSc0hz.js → isbl-9km6K6Sf.js} +1 -1
- package/dist/ui/assets/{ishikawaDiagram-FXEZZL3T-BZcxp5am.js → ishikawaDiagram-FXEZZL3T-BvKSD8LY.js} +1 -1
- package/dist/ui/assets/{java-BDxe9dp1.js → java-C2mOf3QX.js} +1 -1
- package/dist/ui/assets/{javascript-CgLkkj7h.js → javascript-_i0Q5MkQ.js} +1 -1
- package/dist/ui/assets/{jboss-cli-C8biclrb.js → jboss-cli-BS5ipQOv.js} +1 -1
- package/dist/ui/assets/{journeyDiagram-5HDEW3XC-BbJu6m6Q.js → journeyDiagram-5HDEW3XC-yKdSl63P.js} +1 -1
- package/dist/ui/assets/{json-BjJKhmM6.js → json-Dci0MO4x.js} +1 -1
- package/dist/ui/assets/{julia-DvKEpg-R.js → julia-bEqo4nlV.js} +1 -1
- package/dist/ui/assets/{julia-repl-BDJWZxkU.js → julia-repl-Co6hf47q.js} +1 -1
- package/dist/ui/assets/{kanban-definition-HUTT4EX6-DeIVPlbv.js → kanban-definition-HUTT4EX6-B7BbZCd6.js} +1 -1
- package/dist/ui/assets/{kotlin-DCoXcnEb.js → kotlin-krx8WewB.js} +1 -1
- package/dist/ui/assets/{lasso-C7m0QmV2.js → lasso-d3ZTv9Tj.js} +1 -1
- package/dist/ui/assets/{latex-SHnTG9Eq.js → latex-BNWbHYxK.js} +1 -1
- package/dist/ui/assets/{ldif-_6hUnKPV.js → ldif-Bs8eb63L.js} +1 -1
- package/dist/ui/assets/{leaf-Co9qxlaF.js → leaf-BTbRDVWB.js} +1 -1
- package/dist/ui/assets/{less-CJRDPwNA.js → less-DH5cqJTD.js} +1 -1
- package/dist/ui/assets/{lisp-JEHGYbiW.js → lisp-CBUvBYtR.js} +1 -1
- package/dist/ui/assets/{livecodeserver-BSYaUj4k.js → livecodeserver-CkM7-E4a.js} +1 -1
- package/dist/ui/assets/{livescript-WQMCQeuz.js → livescript-DomXyXUO.js} +1 -1
- package/dist/ui/assets/{llvm-DnAHqj5I.js → llvm-e_-ff59-.js} +1 -1
- package/dist/ui/assets/{lsl--k-ghYia.js → lsl-BNDsGOmv.js} +1 -1
- package/dist/ui/assets/{lua-CXb23hbC.js → lua-_ZmkXwc_.js} +1 -1
- package/dist/ui/assets/{makefile-CM7o86yn.js → makefile-BhG7cThe.js} +1 -1
- package/dist/ui/assets/{markdown-HB3HvChQ.js → markdown-ClW2SRy6.js} +1 -1
- package/dist/ui/assets/{mathematica-9NTuf22N.js → mathematica-kqoCR611.js} +1 -1
- package/dist/ui/assets/{matlab-CWv-ik4r.js → matlab-DqawyK3q.js} +1 -1
- package/dist/ui/assets/{maxima-CqYiKCrw.js → maxima-B0qzCXe1.js} +1 -1
- package/dist/ui/assets/{mel-BbsRpUm5.js → mel-BZaSas3B.js} +1 -1
- package/dist/ui/assets/{mercury-Cz2j4HhU.js → mercury-ByD0wyJH.js} +1 -1
- package/dist/ui/assets/{mindmap-definition-LN4V7U3C-WyJS8oye.js → mindmap-definition-LN4V7U3C-Ce6QsaH5.js} +1 -1
- package/dist/ui/assets/{mipsasm-BqWSRbQV.js → mipsasm-B08lqZSo.js} +1 -1
- package/dist/ui/assets/{mizar-JLBfQ6vt.js → mizar-7iaUqWPq.js} +1 -1
- package/dist/ui/assets/{mojolicious-D0LbMmyk.js → mojolicious-CbH-d8Wh.js} +1 -1
- package/dist/ui/assets/{monkey-DAGL6-JN.js → monkey-D5z6-hFH.js} +1 -1
- package/dist/ui/assets/{moonscript-DzbwNSKy.js → moonscript-DuLd7pdm.js} +1 -1
- package/dist/ui/assets/{n1ql-dkgKq6Yv.js → n1ql-EbVfQMfD.js} +1 -1
- package/dist/ui/assets/{nginx-AUoaDPV4.js → nginx-D-4AxpHP.js} +1 -1
- package/dist/ui/assets/{nim-CdqYmVrl.js → nim-wPHQEX5v.js} +1 -1
- package/dist/ui/assets/{nix-CxmVjwwo.js → nix-6SVkxAXi.js} +1 -1
- package/dist/ui/assets/{node-repl-B1Z3zUAh.js → node-repl-CaFKEOyj.js} +1 -1
- package/dist/ui/assets/{nsis-CTAuPWhe.js → nsis-BmqbwVv_.js} +1 -1
- package/dist/ui/assets/{objectivec-CvbKeAPz.js → objectivec-BX0R-sI2.js} +1 -1
- package/dist/ui/assets/{ocaml-DCiyXKOH.js → ocaml-CyfdrIqo.js} +1 -1
- package/dist/ui/assets/{openscad-CPGUwmeR.js → openscad-B5qfaF1E.js} +1 -1
- package/dist/ui/assets/{oxygene-CI_bD1Gw.js → oxygene-CmlCgXLr.js} +1 -1
- package/dist/ui/assets/{parser3-C91CVs85.js → parser3-DdpPyZkn.js} +1 -1
- package/dist/ui/assets/{pegDiagram-2B236MQR-yF8AW7GR.js → pegDiagram-2B236MQR-ByyVqaXh.js} +1 -1
- package/dist/ui/assets/{perl-ChxfKl-n.js → perl-D3qc2wEe.js} +1 -1
- package/dist/ui/assets/{pf-CKCHkzIA.js → pf-Bv3zza37.js} +1 -1
- package/dist/ui/assets/{pgsql-BwOP20uH.js → pgsql-BZIppw1a.js} +1 -1
- package/dist/ui/assets/{php-B7Rs2tM5.js → php-D1ScecZC.js} +1 -1
- package/dist/ui/assets/{php-template-Cx-Fh4AR.js → php-template-sEqzzKWJ.js} +1 -1
- package/dist/ui/assets/{pieDiagram-ENE6RG2P-BS4hnGNX.js → pieDiagram-ENE6RG2P-T1OgWN3l.js} +1 -1
- package/dist/ui/assets/{plaintext-U7qkdtgS.js → plaintext-DO7Jy_T2.js} +1 -1
- package/dist/ui/assets/{pony-_fGYgePh.js → pony-CTXTp2Tb.js} +1 -1
- package/dist/ui/assets/{powershell-CFBTcUce.js → powershell-C9lzZsbj.js} +1 -1
- package/dist/ui/assets/{processing-BWHneU0K.js → processing-hzJAP_Li.js} +1 -1
- package/dist/ui/assets/{profile-BJ-HxhzS.js → profile-bFiAkh7e.js} +1 -1
- package/dist/ui/assets/{prolog-ChHo7n6L.js → prolog-CE0trHSn.js} +1 -1
- package/dist/ui/assets/{properties-e_DOYclC.js → properties-MeANnTVc.js} +1 -1
- package/dist/ui/assets/{protobuf-5aGSIv7w.js → protobuf-S-rp7SK0.js} +1 -1
- package/dist/ui/assets/{puppet-D2vQatzX.js → puppet-B5YT8Ct1.js} +1 -1
- package/dist/ui/assets/{purebasic-n6krfaeF.js → purebasic-DO-5HeDl.js} +1 -1
- package/dist/ui/assets/{python-CbIo0XxP.js → python-DVGTQbwU.js} +1 -1
- package/dist/ui/assets/{python-repl-BpIq9kyr.js → python-repl-CQ4VlKLC.js} +1 -1
- package/dist/ui/assets/{q-jntnvcCx.js → q-nS96imov.js} +1 -1
- package/dist/ui/assets/{qml-BafQlZNV.js → qml-BkzkCgUU.js} +1 -1
- package/dist/ui/assets/{quadrantDiagram-ABIIQ3AL-C3Th8mUJ.js → quadrantDiagram-ABIIQ3AL-BkaCzlXD.js} +1 -1
- package/dist/ui/assets/{r-CHgvQFLK.js → r-y4Q5t85r.js} +1 -1
- package/dist/ui/assets/{railroadDiagram-RFXS5EU6-CMQkbGJl.js → railroadDiagram-RFXS5EU6-C1lbwk19.js} +1 -1
- package/dist/ui/assets/{reasonml-DNwlPD_T.js → reasonml-Bzhzc5Ao.js} +1 -1
- package/dist/ui/assets/{requirementDiagram-TGXJPOKE-B70_oR7v.js → requirementDiagram-TGXJPOKE-C8Rw6N2I.js} +1 -1
- package/dist/ui/assets/{rib-CX4e9JSF.js → rib-BP3Y9dW3.js} +1 -1
- package/dist/ui/assets/{roboconf-ee4qsPGf.js → roboconf-87QE7cIh.js} +1 -1
- package/dist/ui/assets/{routeros-BJSwvu2L.js → routeros-BV3nt8wW.js} +1 -1
- package/dist/ui/assets/{rsl-BiKdMl_F.js → rsl-DfQf4CW_.js} +1 -1
- package/dist/ui/assets/{ruby-CAXTU5LY.js → ruby-DQxFKbgX.js} +1 -1
- package/dist/ui/assets/{ruleslanguage-C1kgDGXM.js → ruleslanguage-B8oCm_aw.js} +1 -1
- package/dist/ui/assets/{rust-DyEf_GXL.js → rust-DzZgPf9W.js} +1 -1
- package/dist/ui/assets/{sankeyDiagram-HTMAVEWB-CvUfseMg.js → sankeyDiagram-HTMAVEWB-Dxiuo8Hz.js} +1 -1
- package/dist/ui/assets/{sas-Q1rlUxPH.js → sas-CFh-Rsrv.js} +1 -1
- package/dist/ui/assets/{scala-Dg6KNmge.js → scala-3UDyJBiS.js} +1 -1
- package/dist/ui/assets/{scheme-D9iooUxI.js → scheme-KaUTge9W.js} +1 -1
- package/dist/ui/assets/{scilab-CztEJXJY.js → scilab-CLSBZzuh.js} +1 -1
- package/dist/ui/assets/{scss-BMh-JkdE.js → scss-DaEkfHWX.js} +1 -1
- package/dist/ui/assets/{sequenceDiagram-DBY2YBRQ-DB0c9kf4.js → sequenceDiagram-DBY2YBRQ-Cv__zR2v.js} +1 -1
- package/dist/ui/assets/{shell-DWo6z0MG.js → shell-B0d4cZTk.js} +1 -1
- package/dist/ui/assets/{sizeCapture-X5ZJPWSS-BcQlgjfD.js → sizeCapture-X5ZJPWSS-C4nD-ShQ.js} +1 -1
- package/dist/ui/assets/{smali-Cijeb6pL.js → smali-CLhfky4R.js} +1 -1
- package/dist/ui/assets/{smalltalk-D0lvqa_d.js → smalltalk-B4aaTMO5.js} +1 -1
- package/dist/ui/assets/{sml-B-vqWZ3b.js → sml-x4CTxWEZ.js} +1 -1
- package/dist/ui/assets/{sqf-BJ-1Q583.js → sqf-G_0vdVVs.js} +1 -1
- package/dist/ui/assets/{sql-CMGLxEYV.js → sql-C--WH0Fo.js} +1 -1
- package/dist/ui/assets/{sql_more-gsRmbNCH.js → sql_more-B4LrzCtd.js} +1 -1
- package/dist/ui/assets/{stan-D1AlTSpf.js → stan-CACDFOA0.js} +1 -1
- package/dist/ui/assets/{stata-DnEYviI0.js → stata-BP9M7sZt.js} +1 -1
- package/dist/ui/assets/{stateDiagram-2N3HPSRC-BSRgVsHO.js → stateDiagram-2N3HPSRC-z3-31Cr2.js} +1 -1
- package/dist/ui/assets/stateDiagram-v2-6OUMAXLB-BKIXFHzO.js +1 -0
- package/dist/ui/assets/{step21-DbQV9sMo.js → step21-MAOu4dNb.js} +1 -1
- package/dist/ui/assets/{stylus-Cyc7vdUc.js → stylus-CpuP3pvE.js} +1 -1
- package/dist/ui/assets/{subunit-16ipyMie.js → subunit-NWzuqFhT.js} +1 -1
- package/dist/ui/assets/{swift-DTNnw4qS.js → swift-DuFKNmKv.js} +1 -1
- package/dist/ui/assets/{swimlanes-5IMT3BWC-DgOdcc4_.js → swimlanes-5IMT3BWC-E463-rXd.js} +2 -2
- package/dist/ui/assets/swimlanesDiagram-G3AALYLV-CkQvBOYs.js +8 -0
- package/dist/ui/assets/{taggerscript-DM6pvUWE.js → taggerscript-BDnv2jvg.js} +1 -1
- package/dist/ui/assets/{tap-Ch-uyKZq.js → tap-aB2cLXdf.js} +1 -1
- package/dist/ui/assets/{tcl-D0T_V5dA.js → tcl-7WepIaIa.js} +1 -1
- package/dist/ui/assets/{thrift-D9xJjYw4.js → thrift-BLh6pdjc.js} +1 -1
- package/dist/ui/assets/{timeline-definition-FHXFAJF6-BBeDMHxb.js → timeline-definition-FHXFAJF6-FA_RNhFQ.js} +1 -1
- package/dist/ui/assets/{tp-kUrCGo9X.js → tp-D1rt5u_j.js} +1 -1
- package/dist/ui/assets/{twig-Cgub6rxA.js → twig-DgfeYg4W.js} +1 -1
- package/dist/ui/assets/{typescript-BxNP9Ap9.js → typescript-oU560Q8V.js} +1 -1
- package/dist/ui/assets/{vala-DF9ZZagU.js → vala-DlXsaz2B.js} +1 -1
- package/dist/ui/assets/{vbnet-Cf46ypDg.js → vbnet-BjpxjeMZ.js} +1 -1
- package/dist/ui/assets/{vbscript-DNs-QKLH.js → vbscript-DfNdtdO3.js} +1 -1
- package/dist/ui/assets/{vbscript-html-DyS6I5L1.js → vbscript-html-D09WPsgK.js} +1 -1
- package/dist/ui/assets/{vennDiagram-L72KCM5P-8WpWycE-.js → vennDiagram-L72KCM5P-zaCDWwTs.js} +1 -1
- package/dist/ui/assets/{verilog-CF5wC4X-.js → verilog-B9vENtLK.js} +1 -1
- package/dist/ui/assets/{vhdl-Dm0jpDMn.js → vhdl-CJLTZOu1.js} +1 -1
- package/dist/ui/assets/{vim-nyO2DRAx.js → vim-FuPhupu9.js} +1 -1
- package/dist/ui/assets/{wardleyDiagram-EHGQE667-UeIOK-jv.js → wardleyDiagram-EHGQE667-BS8ApwmV.js} +1 -1
- package/dist/ui/assets/{x86asm-BNw2-6GG.js → x86asm-kX_Cp6wB.js} +1 -1
- package/dist/ui/assets/{xl-D-YHxr6V.js → xl-MuHNmLUj.js} +1 -1
- package/dist/ui/assets/{xml-BijAlrow.js → xml-Mbn2z12n.js} +1 -1
- package/dist/ui/assets/{xquery-z0wxWBKz.js → xquery-Dy4lt-m-.js} +1 -1
- package/dist/ui/assets/{xychartDiagram-FW5EYKEG-BNzHLweN.js → xychartDiagram-FW5EYKEG-DoNZtZus.js} +1 -1
- package/dist/ui/assets/{yaml-n5TIQArR.js → yaml-CxxAisfE.js} +1 -1
- package/dist/ui/assets/{zephir-DrGhH8jG.js → zephir-WWG8qyld.js} +1 -1
- package/dist/ui/index.html +2 -2
- package/package.json +2 -2
- package/dist/ui/assets/channel-D9Y4iN1l.js +0 -1
- package/dist/ui/assets/classDiagram-OUVF2IWQ-C4UVH6RI.js +0 -1
- package/dist/ui/assets/classDiagram-v2-EOCWNBFH-C4UVH6RI.js +0 -1
- package/dist/ui/assets/index-CK_lJsTu.css +0 -10
- package/dist/ui/assets/stateDiagram-v2-6OUMAXLB-BLdfpGJO.js +0 -1
- package/dist/ui/assets/swimlanesDiagram-G3AALYLV-DRVnkg3M.js +0 -8
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import{_ as a,o as n,c as e,a2 as p}from"./chunks/framework.D_Xv2a7c.js";const m=JSON.parse('{"title":"proxy-core","description":"","frontmatter":{},"headers":[],"relativePath":"proxy-core/index.md","filePath":"proxy-core/index.md"}'),o={name:"proxy-core/index.md"};function l(i,s,t,c,d,r){return n(),e("div",null,[...s[0]||(s[0]=[p(`<h1 id="proxy-core" tabindex="-1">proxy-core <a class="header-anchor" href="#proxy-core" aria-label="Permalink to "proxy-core""></a></h1><h2 id="本文档覆盖的源文件" tabindex="-1">本文档覆盖的源文件 <a class="header-anchor" href="#本文档覆盖的源文件" aria-label="Permalink to "本文档覆盖的源文件""></a></h2><ul><li><code>apps/proxy-core/package.json</code></li><li><code>apps/proxy-core/tsconfig.json</code></li><li><code>apps/proxy-core/src/**/*.ts</code>(不含测试)</li></ul><h2 id="模块角色" tabindex="-1">模块角色 <a class="header-anchor" href="#模块角色" aria-label="Permalink to "模块角色""></a></h2><p><code>proxy-core</code> 是整个系统的后端。它承担四类职责:</p><ol><li><strong>业务代理</strong>:转发 Claude Code 的 <code>/v1/*</code> 请求到上游 AI API,同时记录完整请求与响应</li><li><strong>UI 后端</strong>:提供 REST API + 两个 WebSocket 通道给前端面板(proxy-cli)</li><li><strong>CLI 工具</strong>:<code>proxy</code> 命令行工具,用于 start/stop/status/logs/config/reload/test/ui/clean-requests</li><li><strong>静态资源托管</strong>:<code>@fastify/static</code> 挂载 <code>proxy-cli/dist</code> 到 <code>/ui/</code></li></ol><p>发布名:<code>@cc-monitoring/cli-core</code>,<code>bin/proxy</code> → <code>dist/cli/index.js</code>,<code>main</code> → <code>dist/server/index.js</code>。</p><h2 id="目录布局" tabindex="-1">目录布局 <a class="header-anchor" href="#目录布局" aria-label="Permalink to "目录布局""></a></h2><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>apps/proxy-core/</span></span>
|
|
2
|
+
<span class="line"><span>├── bin/proxy-cli.ts # 一行 bin,只 import dist/cli/index.js</span></span>
|
|
3
|
+
<span class="line"><span>├── ecosystem.config.cjs # PM2 配置(.cjs 因为 root type=module)</span></span>
|
|
4
|
+
<span class="line"><span>├── package.json</span></span>
|
|
5
|
+
<span class="line"><span>├── tsconfig.json</span></span>
|
|
6
|
+
<span class="line"><span>└── src/</span></span>
|
|
7
|
+
<span class="line"><span> ├── cli/</span></span>
|
|
8
|
+
<span class="line"><span> │ ├── index.ts # Commander 命令注册</span></span>
|
|
9
|
+
<span class="line"><span> │ └── commands/</span></span>
|
|
10
|
+
<span class="line"><span> │ ├── start.ts</span></span>
|
|
11
|
+
<span class="line"><span> │ ├── stop.ts</span></span>
|
|
12
|
+
<span class="line"><span> │ ├── status.ts</span></span>
|
|
13
|
+
<span class="line"><span> │ ├── logs.ts</span></span>
|
|
14
|
+
<span class="line"><span> │ ├── config.ts</span></span>
|
|
15
|
+
<span class="line"><span> │ ├── reload.ts</span></span>
|
|
16
|
+
<span class="line"><span> │ ├── test.ts</span></span>
|
|
17
|
+
<span class="line"><span> │ └── ui.ts</span></span>
|
|
18
|
+
<span class="line"><span> ├── server/</span></span>
|
|
19
|
+
<span class="line"><span> │ ├── index.ts # createServer / startServer / ESM+PM2 入口判断</span></span>
|
|
20
|
+
<span class="line"><span> │ ├── proxy.ts # 代理转发核心 (486 行)</span></span>
|
|
21
|
+
<span class="line"><span> │ ├── ui-route.ts # REST + 双 WebSocket + 静态挂载</span></span>
|
|
22
|
+
<span class="line"><span> │ ├── providers-route.ts</span></span>
|
|
23
|
+
<span class="line"><span> │ ├── data-push.ts # WebSocket 广播工具</span></span>
|
|
24
|
+
<span class="line"><span> │ ├── auth.ts # Bearer Token 校验</span></span>
|
|
25
|
+
<span class="line"><span> │ └── middleware/</span></span>
|
|
26
|
+
<span class="line"><span> │ └── request-id.ts # nanoid + x-request-id</span></span>
|
|
27
|
+
<span class="line"><span> ├── config/</span></span>
|
|
28
|
+
<span class="line"><span> │ ├── index.ts # load / save / reset</span></span>
|
|
29
|
+
<span class="line"><span> │ └── schema.ts # Zod 校验</span></span>
|
|
30
|
+
<span class="line"><span> ├── logger/</span></span>
|
|
31
|
+
<span class="line"><span> │ └── index.ts # Pino + file-stream-rotator</span></span>
|
|
32
|
+
<span class="line"><span> ├── utils/</span></span>
|
|
33
|
+
<span class="line"><span> │ ├── db.ts # SQLite 初始化 + 4 版 schema 迁移</span></span>
|
|
34
|
+
<span class="line"><span> │ ├── providers-db.ts # providers 表 CRUD + validate</span></span>
|
|
35
|
+
<span class="line"><span> │ ├── request-writer.ts # requests 表读写</span></span>
|
|
36
|
+
<span class="line"><span> │ ├── stats-aggregator.ts</span></span>
|
|
37
|
+
<span class="line"><span> │ ├── status-history.ts # 内存中的 CPU/内存滑动窗口</span></span>
|
|
38
|
+
<span class="line"><span> │ ├── sse-accumulator.ts</span></span>
|
|
39
|
+
<span class="line"><span> │ ├── sse-writer.ts # 合成 SSE</span></span>
|
|
40
|
+
<span class="line"><span> │ ├── tool-injector.ts</span></span>
|
|
41
|
+
<span class="line"><span> │ ├── inject-loop.ts # tool 命中后的内部循环</span></span>
|
|
42
|
+
<span class="line"><span> │ ├── pm2.ts # PM2 promise 封装</span></span>
|
|
43
|
+
<span class="line"><span> │ ├── paths.ts # ~/.cc-monitoring 锚点</span></span>
|
|
44
|
+
<span class="line"><span> │ ├── esm-dirname.ts # ESM+CJS 双模 __dirname</span></span>
|
|
45
|
+
<span class="line"><span> │ ├── net.ts # waitForPort</span></span>
|
|
46
|
+
<span class="line"><span> │ ├── version.ts # 项目 + Claude Code 版本</span></span>
|
|
47
|
+
<span class="line"><span> │ └── migrate-files-to-db.ts # 旧文件系统迁移到 SQLite(一次性脚本)</span></span>
|
|
48
|
+
<span class="line"><span> ├── tools/</span></span>
|
|
49
|
+
<span class="line"><span> │ ├── index.ts # 转发 registry 导出</span></span>
|
|
50
|
+
<span class="line"><span> │ ├── registry.ts # ALL_TOOLS + enabled/find</span></span>
|
|
51
|
+
<span class="line"><span> │ ├── types.ts # InjectableTool 接口</span></span>
|
|
52
|
+
<span class="line"><span> │ ├── curl.tool.ts</span></span>
|
|
53
|
+
<span class="line"><span> │ └── probe.tool.ts</span></span>
|
|
54
|
+
<span class="line"><span> └── types/</span></span>
|
|
55
|
+
<span class="line"><span> ├── api.ts / config.ts / log.ts / pm2.ts / index.ts</span></span></code></pre></div><h2 id="依赖速览" tabindex="-1">依赖速览 <a class="header-anchor" href="#依赖速览" aria-label="Permalink to "依赖速览""></a></h2><p><code>apps/proxy-core/package.json</code>:</p><ul><li><code>fastify</code> <code>4.26</code> + <code>@fastify/static</code> <code>7.0</code> + <code>@fastify/http-proxy</code> <code>9.5</code>(<strong>未使用</strong>,早期遗留)</li><li><code>@fastify/websocket</code> <code>10</code>(也未使用,服务器直接用原生 <code>ws</code>)</li><li><code>commander</code> <code>11.1</code>、<code>chalk</code> <code>5.6</code>、<code>ora</code> <code>9.4</code>(CLI 交互)</li><li><code>better-sqlite3</code> <code>12.11</code> + <code>@types/better-sqlite3</code></li><li><code>pino</code> <code>8.18</code> + <code>pino-pretty</code> <code>10.3</code> + <code>file-stream-rotator</code> <code>1.0</code>(日志)</li><li><code>pm2</code> <code>5.4</code></li><li><code>nanoid</code> <code>5.0</code>(request id)</li><li><code>open</code> <code>10.1</code>(<code>ui</code> 命令打开浏览器)</li><li><code>ws</code> <code>8.21</code>(原生 WebSocket)</li><li><code>zod</code> <code>3.23</code></li></ul><p>Dev:Jest 29 + ts-jest + tsx 4。</p><h2 id="阅读顺序" tabindex="-1">阅读顺序 <a class="header-anchor" href="#阅读顺序" aria-label="Permalink to "阅读顺序""></a></h2><ul><li>先看启动 → <a href="./entry.html">entry.md</a></li><li>后看代理核心 → <a href="./proxy.html">proxy.md</a></li><li>想理解数据 → <a href="./data-layer.html">data-layer.md</a> + <a href="./stats.html">stats.md</a></li><li>想扩展 tool → <a href="./tool.html">tool.md</a></li><li>想改 UI 端点 → <a href="./ui-routes.html">ui-routes.md</a></li><li>想改供应商 → <a href="./providers.html">providers.md</a></li><li>想改配置字段 → <a href="./config.html">config.md</a></li><li>打包/进程细节 → <a href="./pm2.html">pm2.md</a></li><li>底层小工具 → <a href="./utilities.html">utilities.md</a></li><li>CLI 每个命令 → <a href="./cli.html">cli.md</a></li></ul>`,15)])])}const u=a(o,[["render",l]]);export{m as __pageData,u as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as a,o as n,c as e,a2 as p}from"./chunks/framework.D_Xv2a7c.js";const m=JSON.parse('{"title":"proxy-core","description":"","frontmatter":{},"headers":[],"relativePath":"proxy-core/index.md","filePath":"proxy-core/index.md"}'),o={name:"proxy-core/index.md"};function l(i,s,t,c,d,r){return n(),e("div",null,[...s[0]||(s[0]=[p("",15)])])}const u=a(o,[["render",l]]);export{m as __pageData,u as default};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import{_ as i,o as a,c as n,a2 as t}from"./chunks/framework.D_Xv2a7c.js";const o=JSON.parse('{"title":"proxy-core / PM2 集成","description":"","frontmatter":{},"headers":[],"relativePath":"proxy-core/pm2.md","filePath":"proxy-core/pm2.md"}'),e={name:"proxy-core/pm2.md"};function l(h,s,p,k,r,d){return a(),n("div",null,[...s[0]||(s[0]=[t(`<h1 id="proxy-core-pm2-集成" tabindex="-1">proxy-core / PM2 集成 <a class="header-anchor" href="#proxy-core-pm2-集成" aria-label="Permalink to "proxy-core / PM2 集成""></a></h1><h2 id="本文档覆盖的源文件" tabindex="-1">本文档覆盖的源文件 <a class="header-anchor" href="#本文档覆盖的源文件" aria-label="Permalink to "本文档覆盖的源文件""></a></h2><ul><li><code>apps/proxy-core/ecosystem.config.cjs</code></li><li><code>apps/proxy-core/src/utils/pm2.ts</code></li><li><code>apps/proxy-core/src/utils/esm-dirname.ts</code></li><li><code>apps/proxy-core/src/server/index.ts:42-60</code>(入口守卫)</li></ul><h2 id="挑战" tabindex="-1">挑战 <a class="header-anchor" href="#挑战" aria-label="Permalink to "挑战""></a></h2><p>项目根 <code>package.json</code> 声明了 <code>"type": "module"</code>,因此所有 <code>.js</code> 文件默认按 ESM 加载。而 PM2 是 CJS 库,<code>ecosystem.config.js</code> 会被解析成 ESM,报 <code>Cannot use import statement / module.exports is not defined</code>。同时 PM2 fork 模式启动脚本时会替换 <code>process.argv[1]</code>,导致 ESM <code>import.meta.url === main module url</code> 判定失败。</p><p>三个应对:</p><ol><li><code>ecosystem.config.cjs</code> 用 <code>.cjs</code> 扩展名,绕过 <code>type: module</code> 全局设定</li><li><code>utils/pm2.ts</code> 用 <code>createRequire(import.meta.url)</code> 加载 PM2 库</li><li><code>server/index.ts</code> 用 <code>pm_id</code> / <code>NODE_APP_INSTANCE</code> 环境变量补检测</li></ol><h2 id="ecosystem-config-cjs-逐行" tabindex="-1"><code>ecosystem.config.cjs</code> 逐行 <a class="header-anchor" href="#ecosystem-config-cjs-逐行" aria-label="Permalink to "\`ecosystem.config.cjs\` 逐行""></a></h2><div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> path</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'path'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
|
2
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> os</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'os'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
|
3
|
+
<span class="line"></span>
|
|
4
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> dataDir</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> process.env.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">CC_DATA_DIR</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> path.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">join</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(os.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">homedir</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(), </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'.cc-monitoring'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
|
5
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> logsDir</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> path.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">join</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(dataDir, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'logs'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
|
6
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> configPath</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> process.env.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">CONFIG_PATH</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> path.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">join</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(dataDir, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'config.json'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
|
7
|
+
<span class="line"></span>
|
|
8
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">module</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">exports</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
9
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> apps: [{</span></span>
|
|
10
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> name: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'cc-monitoring'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
11
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> script: path.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">join</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(__dirname, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'dist/server/index.js'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">),</span></span>
|
|
12
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> cwd: dataDir,</span></span>
|
|
13
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> instances: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
14
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> exec_mode: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'fork'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
15
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> autorestart: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
16
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> watch: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
17
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> max_memory_restart: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'200M'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
18
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> env: {</span></span>
|
|
19
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> NODE_ENV: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'production'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
20
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> CONFIG_PATH: configPath</span></span>
|
|
21
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
|
|
22
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> env_development: {</span></span>
|
|
23
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> NODE_ENV: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'development'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
24
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> CONFIG_PATH: configPath</span></span>
|
|
25
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
|
|
26
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> log_date_format: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'YYYY-MM-DD HH:mm:ss Z'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
27
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> error_file: path.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">join</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(logsDir, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'error.log'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">),</span></span>
|
|
28
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> out_file: path.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">join</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(logsDir, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'out.log'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">),</span></span>
|
|
29
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> merge_logs: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span></span>
|
|
30
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }]</span></span>
|
|
31
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>关键点:</p><ul><li><code>name: 'cc-monitoring'</code>:CLI 里所有 PM2 操作都用这个进程名(<code>utils/pm2.ts</code>、<code>ui-route.ts</code> 的 <code>getProcessStatus('cc-monitoring')</code> 等)</li><li><code>script</code>:<code>path.join(__dirname, ...)</code>——<code>__dirname</code> 是 CJS 全局,在 <code>.cjs</code> 里可用。相对包内绝对路径,不依赖 <code>process.cwd()</code></li><li><code>cwd: dataDir</code>:让子进程的相对路径日志/DB 落到用户目录</li><li><code>instances: 1</code> + <code>exec_mode: 'fork'</code>:cluster 模式基于 worker 复制主进程内存,ESM 会在 worker 里报错;fork 是干净的 spawn Node</li><li><code>autorestart: true</code> + <code>max_memory_restart: '200M'</code>:内存超 200M 自动重启(应用层代理有大响应缓冲的可能)</li><li><code>watch: false</code>:不监控文件变动重启(生产模式)</li><li><code>env</code> / <code>env_development</code>:<code>NODE_ENV</code> 切换;<code>CONFIG_PATH</code> 覆盖 <code>loadConfig</code> 的读取路径</li><li><code>log_date_format</code>:PM2 自己的日志时间戳(区别于 pino 的 ISO time)</li><li><code>error_file / out_file</code>:PM2 fork 子进程的 stderr/stdout 落盘位置</li><li><code>merge_logs: true</code>:多实例合并(虽然本项目 instances=1,属默认合理设置)</li></ul><h2 id="node-侧-pm2-封装-utils-pm2-ts" tabindex="-1">Node 侧 PM2 封装 <code>utils/pm2.ts</code> <a class="header-anchor" href="#node-侧-pm2-封装-utils-pm2-ts" aria-label="Permalink to "Node 侧 PM2 封装 \`utils/pm2.ts\`""></a></h2><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> requireCjs</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> NodeRequire</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
32
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> try</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> createRequire</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">getCurrentModuleUrl</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()) } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">catch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> require }</span></span>
|
|
33
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})()</span></span>
|
|
34
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> pm2</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> requireCjs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'pm2'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">as</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> any</span></span></code></pre></div><ul><li><code>createRequire(getCurrentModuleUrl())</code>:ESM 环境下让 CJS <code>require</code> 可用</li><li><code>getCurrentModuleUrl()</code>(<code>esm-dirname.ts:52</code>)包装 <code>import.meta.url</code>——<code>__mocks__/esm-dirname.ts</code> 里被 Jest 覆盖成静态字符串,因为 ts-jest 编译 CJS 时无法解析 <code>import.meta</code></li><li>若 <code>createRequire</code> 也不可用(比如某种测试环境),fallback 到全局 <code>require</code>(此时应用其实已在 CJS 环境)</li></ul><p>内部 promisify 六个 PM2 API:</p><table tabindex="0"><thead><tr><th>内部</th><th>说明</th></tr></thead><tbody><tr><td><code>pm2Connect()</code></td><td>建立与 PM2 daemon 的 IPC</td></tr><tr><td><code>pm2Disconnect()</code></td><td>释放(<strong>必须调</strong>否则 daemon 认为客户端仍连着,其它 CLI 会等待)</td></tr><tr><td><code>pm2Start(options)</code></td><td>options 可以是 ecosystem 路径字符串或对象</td></tr><tr><td><code>pm2Stop(name)</code></td><td></td></tr><tr><td><code>pm2Reload(name)</code></td><td>优雅(零停机)重启</td></tr><tr><td><code>pm2List()</code></td><td>返回所有进程数组</td></tr></tbody></table><p>对外 5 个函数:</p><ul><li><code>startProcess(ecosystemPath)</code> = connect + start + disconnect</li><li><code>stopProcess(name)</code> = 同上</li><li><code>reloadProcess(name)</code> = 同上</li><li><code>waitForOnline(name, timeoutMs=8000, intervalMs=300)</code>:单次 connect + 轮询 list + disconnect(finally 保证释放)</li><li><code>getProcessStatus(name)</code>:单次 connect + list + disconnect,映射到 <code>Pm2ProcessStatus</code> 简洁形状</li></ul><p><strong>pm2Start 的错误可能是数组</strong>:PM2 有时候多个 app 同时失败,回调 <code>err</code> 是 <code>Error[]</code>:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> error</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Array.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">isArray</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(err)</span></span>
|
|
35
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> ?</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> Error</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(err.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">map</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">e</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> e.message).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">join</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'; '</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">))</span></span>
|
|
36
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> :</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> err</span></span></code></pre></div><p>合成成单个 Error 抛出。</p><h2 id="双入口守卫-server-index-ts" tabindex="-1">双入口守卫 <code>server/index.ts</code> <a class="header-anchor" href="#双入口守卫-server-index-ts" aria-label="Permalink to "双入口守卫 \`server/index.ts\`""></a></h2><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> _isPm2</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> process.env.pm_id </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">!==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> undefined</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> process.env.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">NODE_APP_INSTANCE</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> !==</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> undefined</span></span>
|
|
37
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> _currentModuleUrl</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> getCurrentModuleUrl</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()</span></span>
|
|
38
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> _mainModulePath</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> process.argv[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">?</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> path.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">resolve</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(process.argv[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> ''</span></span>
|
|
39
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> _mainModuleUrl</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> _mainModulePath </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">?</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> \`file://\${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">_mainModulePath</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">}\`</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> :</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> ''</span></span>
|
|
40
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> _isDirectExecution</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> _currentModuleUrl </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">===</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> _mainModuleUrl</span></span>
|
|
41
|
+
<span class="line"></span>
|
|
42
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (_isPm2 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> _isDirectExecution) {</span></span>
|
|
43
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> startServer</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">catch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">err</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">error</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">); process.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">exit</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) })</span></span>
|
|
44
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><ul><li><strong>PM2 fork 特殊性</strong>:PM2 用 <code>child_process.fork(ProcessContainerFork.js)</code> 拉起进程,<code>process.argv[1]</code> 是 <code>ProcessContainerFork.js</code> 而非 <code>dist/server/index.js</code>。所以 <code>_isDirectExecution</code> 判定为 false。用 <code>pm_id</code> env 补检测</li><li><strong>直接执行</strong>:<code>tsx src/server/index.ts</code> 或 <code>node dist/server/index.js</code>,<code>argv[1]</code> 就是本文件,URL 相等</li><li><strong>测试 / import</strong>:既不是 PM2 也不是直接执行时(例如从测试文件 import),两个判定都是 false,<code>startServer</code> 不自动跑,测试可以控制生命周期</li></ul><p><strong>注释里说明</strong>:<code>server/index.ts:42-45</code> 明确指出为什么不能用 <code>require.main === module</code>——那是 CJS 模式的判定,ESM 里 <code>require</code> 不存在。</p><h2 id="esm-dirname-替代-esm-dirname-ts" tabindex="-1">ESM __dirname 替代 <code>esm-dirname.ts</code> <a class="header-anchor" href="#esm-dirname-替代-esm-dirname-ts" aria-label="Permalink to "ESM __dirname 替代 \`esm-dirname.ts\`""></a></h2><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> thisDirname</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> string</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
45
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> try</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> path.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">dirname</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> URL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.url).pathname) }</span></span>
|
|
46
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> catch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">return</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> typeof</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> __dirname </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">!==</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> 'undefined'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> ?</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> __dirname </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> process.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">cwd</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">() }</span></span>
|
|
47
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})()</span></span>
|
|
48
|
+
<span class="line"></span>
|
|
49
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> getCallerDirname</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">relativePath</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
50
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> path.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">resolve</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(thisDirname, relativePath)</span></span>
|
|
51
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span>
|
|
52
|
+
<span class="line"></span>
|
|
53
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> getCurrentModuleUrl</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
54
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.url</span></span>
|
|
55
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><ul><li><code>thisDirname</code>:本模块自身的 __dirname 等价物(编译后位于 <code>dist/utils/</code>)</li><li><code>getCallerDirname('../server')</code>:调用方直接算出 <code>dist/server</code> 目录,不用自己 fiddle import.meta.url。多处使用(<code>ui-route.ts:396</code> 静态资源、<code>start.ts:32</code> ecosystem 路径、<code>ui.ts:9</code> UI dist 路径、<code>utils/version.ts:14</code> 版本文件)</li><li>三重 fallback(ESM → CJS <code>__dirname</code> → <code>process.cwd()</code>)覆盖所有环境</li></ul><p><strong>注释里明确</strong>:<code>getCurrentModuleUrl</code> 是包装函数而非直接内联 <code>import.meta.url</code>——因为 ts-jest 编译 CJS 时会遇到语法错误,<code>__mocks__/esm-dirname.ts</code> 可以在测试里替换整个模块返回固定值。</p><h2 id="冷启动全流程" tabindex="-1">冷启动全流程 <a class="header-anchor" href="#冷启动全流程" aria-label="Permalink to "冷启动全流程""></a></h2><div class="language-mermaid vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">mermaid</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">sequenceDiagram</span></span>
|
|
56
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> actor U as 用户</span></span>
|
|
57
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> participant CLI as bin/proxy-cli.ts (ESM)</span></span>
|
|
58
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> participant CI as cli/index.ts</span></span>
|
|
59
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> participant SC as commands/start.ts</span></span>
|
|
60
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> participant PM as utils/pm2.ts</span></span>
|
|
61
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> participant D as PM2 daemon</span></span>
|
|
62
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> participant PC as ProcessContainerFork.js (CJS wrapper)</span></span>
|
|
63
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> participant S as dist/server/index.js (ESM)</span></span>
|
|
64
|
+
<span class="line"></span>
|
|
65
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> U->>CLI: proxy start</span></span>
|
|
66
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> CLI->>CI: import</span></span>
|
|
67
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> CI->>SC: startCommand</span></span>
|
|
68
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> SC->>PM: startProcess(ecosystemPath)</span></span>
|
|
69
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> PM->>PM: createRequire(import.meta.url)('pm2')</span></span>
|
|
70
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> PM->>D: pm2.connect + pm2.start(ecosystem.config.cjs)</span></span>
|
|
71
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> D->>PC: fork</span></span>
|
|
72
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> PC->>S: 加载 script</span></span>
|
|
73
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> S->>S: 检测 pm_id → startServer()</span></span>
|
|
74
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> S->>S: loadConfig → configureDbPath → createLogger → Fastify → setupUi → setupProxy → listen</span></span>
|
|
75
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> PM->>D: pm2.disconnect</span></span>
|
|
76
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> PM-->>SC: return</span></span>
|
|
77
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> SC->>SC: waitForOnline (再 connect/disconnect)</span></span>
|
|
78
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> SC->>SC: waitForPort (递增退避)</span></span>
|
|
79
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> SC-->>U: 打印 summary</span></span></code></pre></div><h2 id="相关文档" tabindex="-1">相关文档 <a class="header-anchor" href="#相关文档" aria-label="Permalink to "相关文档""></a></h2><ul><li>CLI 命令细节 → <a href="./cli.html">cli.md</a></li><li>启动链 → <a href="./entry.html">entry.md</a></li></ul>`,33)])])}const c=i(e,[["render",l]]);export{o as __pageData,c as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as i,o as a,c as n,a2 as t}from"./chunks/framework.D_Xv2a7c.js";const o=JSON.parse('{"title":"proxy-core / PM2 集成","description":"","frontmatter":{},"headers":[],"relativePath":"proxy-core/pm2.md","filePath":"proxy-core/pm2.md"}'),e={name:"proxy-core/pm2.md"};function l(h,s,p,k,r,d){return a(),n("div",null,[...s[0]||(s[0]=[t("",33)])])}const c=i(e,[["render",l]]);export{o as __pageData,c as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as i,o as s,c as a,a2 as d}from"./chunks/framework.D_Xv2a7c.js";const k=JSON.parse('{"title":"proxy-core / 供应商管理","description":"","frontmatter":{},"headers":[],"relativePath":"proxy-core/providers.md","filePath":"proxy-core/providers.md"}'),t={name:"proxy-core/providers.md"};function o(r,e,l,p,n,h){return s(),a("div",null,[...e[0]||(e[0]=[d('<h1 id="proxy-core-供应商管理" tabindex="-1">proxy-core / 供应商管理 <a class="header-anchor" href="#proxy-core-供应商管理" aria-label="Permalink to "proxy-core / 供应商管理""></a></h1><h2 id="本文档覆盖的源文件" tabindex="-1">本文档覆盖的源文件 <a class="header-anchor" href="#本文档覆盖的源文件" aria-label="Permalink to "本文档覆盖的源文件""></a></h2><ul><li><code>apps/proxy-core/src/server/providers-route.ts</code></li><li><code>apps/proxy-core/src/utils/providers-db.ts</code></li><li><code>apps/proxy-core/src/server/proxy.ts:40-88</code>(<code>resolveProxyTarget</code>)</li><li><code>apps/proxy-core/src/server/proxy.ts:98-115</code>(<code>getProxyModel</code>)</li><li><code>apps/proxy-core/src/utils/db.ts:68-131</code>(v2/v3/v4 schema)</li></ul><h2 id="用途" tabindex="-1">用途 <a class="header-anchor" href="#用途" aria-label="Permalink to "用途""></a></h2><p>「供应商」= 一组可切换的上游 AI API 配置(base_url + api_key + 多档模型)。用户可以在 UI 里维护多个 provider,激活其中一个作为「当前」,proxy 转发时会按激活项覆盖 <code>config.json</code> 的对应字段。</p><h2 id="数据模型" tabindex="-1">数据模型 <a class="header-anchor" href="#数据模型" aria-label="Permalink to "数据模型""></a></h2><p>SQLite <code>providers</code> 表(<code>db.ts:68-131</code>,v2 建表、v3 加 <code>is_default</code>、v4 放开 NOT NULL):</p><table tabindex="0"><thead><tr><th>字段</th><th>类型</th><th>备注</th></tr></thead><tbody><tr><td><code>id</code></td><td>INTEGER PK AUTOINCREMENT</td><td></td></tr><tr><td><code>name</code></td><td>TEXT UNIQUE NOT NULL</td><td>主键之外的唯一键</td></tr><tr><td><code>base_url</code></td><td>TEXT (nullable)</td><td>完整 URL,含 API 前缀</td></tr><tr><td><code>api_key</code></td><td>TEXT (nullable)</td><td></td></tr><tr><td><code>model</code></td><td>TEXT (nullable)</td><td>主 model 覆盖</td></tr><tr><td><code>haiku_model</code></td><td>TEXT (nullable)</td><td>Anthropic 分档预留</td></tr><tr><td><code>sonnet_model</code></td><td>TEXT (nullable)</td><td></td></tr><tr><td><code>opus_model</code></td><td>TEXT (nullable)</td><td></td></tr><tr><td><code>subagent_model</code></td><td>TEXT (nullable)</td><td>子 agent 单独 model 预留</td></tr><tr><td><code>provider</code></td><td>TEXT NOT NULL</td><td>分类标签("anthropic"/"aliyun"/"volces"...)</td></tr><tr><td><code>is_default</code></td><td>INTEGER NOT NULL DEFAULT 0</td><td>激活标记,全表最多 1 个为 1</td></tr><tr><td><code>created_at</code></td><td>TEXT DEFAULT CURRENT_TIMESTAMP</td><td></td></tr><tr><td><code>updated_at</code></td><td>TEXT DEFAULT CURRENT_TIMESTAMP</td><td></td></tr></tbody></table><p>索引:<code>idx_providers_name</code> UNIQUE。</p><p><code>haiku_model / sonnet_model / opus_model</code> <strong>现已参与转发</strong>:<code>resolveProxyTarget</code> 读取 tier models 并返回,由 <code>getProxyModel</code> 根据请求的原始 model 按 tier 映射替换。<code>subagent_model</code> 目前仅存储,不参与转发——为将来子 agent 单独 model 预留。</p><h2 id="schema-迁移的三步" tabindex="-1">Schema 迁移的三步 <a class="header-anchor" href="#schema-迁移的三步" aria-label="Permalink to "Schema 迁移的三步""></a></h2><p><code>applySchemaV2</code>:建表 + 唯一索引,NOT NULL 约束加在 <code>base_url / api_key / model</code>(<code>db.ts:68-86</code>)。</p><p><code>applySchemaV3</code>:<code>PRAGMA table_info</code> 检测 <code>is_default</code> 是否存在,缺失则 <code>ALTER TABLE ADD COLUMN is_default INTEGER NOT NULL DEFAULT 0</code>(<code>db.ts:88-95</code>)。ADD COLUMN 是 SQLite 支持的少数原地变更之一,且带 DEFAULT 时不阻塞。</p><p><code>applySchemaV4</code>:SQLite 不能直接 <code>ALTER COLUMN</code> 去除 NOT NULL,只能<strong>重建表</strong>:新建 <code>providers_new</code>(无 NOT NULL) → <code>INSERT INTO providers_new SELECT ... FROM providers</code> → <code>DROP TABLE providers</code> → <code>ALTER TABLE providers_new RENAME TO providers</code> → 重建索引(<code>db.ts:97-132</code>)。整个过程带条件(仅当检测到 NOT NULL 列时才触发),idempotent-safe。</p><p>放开 NOT NULL 是为支持「部分覆盖」:一个 provider 只需要指定要替换的字段,其它字段留 NULL 表示「用 config.json 默认」。</p><h2 id="resolveproxytarget" tabindex="-1"><code>resolveProxyTarget</code> <a class="header-anchor" href="#resolveproxytarget" aria-label="Permalink to "`resolveProxyTarget`""></a></h2><p><code>apps/proxy-core/src/server/proxy.ts:40-88</code>。每次业务请求进入 handler 都同步跑一次:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> active</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> db.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">prepare</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span></span>\n<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> 'SELECT base_url, api_key, model, haiku_model, sonnet_model, opus_model FROM providers WHERE is_default = 1 LIMIT 1'</span></span>\n<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">get</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()</span></span>\n<span class="line"></span>\n<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (active) {</span></span>\n<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (active.base_url?.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">trim</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()) { </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">/* 覆盖 baseUrl/targetPath */</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>\n<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (active.api_key?.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">trim</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()) { apiKey </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> active.api_key }</span></span>\n<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (active.model?.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">trim</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()) { model </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> active.model }</span></span>\n<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (active.haiku_model?.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">trim</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()) { haiku_model </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> active.haiku_model }</span></span>\n<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (active.sonnet_model?.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">trim</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()) { sonnet_model </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> active.sonnet_model }</span></span>\n<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (active.opus_model?.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">trim</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()) { opus_model </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> active.opus_model }</span></span>\n<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>返回 <code>{ baseUrl, targetPath, apiKey?, model?, haiku_model?, sonnet_model?, opus_model? }</code>。</p><p><strong>部分覆盖语义</strong>:</p><ul><li>只填 <code>base_url</code> → 换目标但复用 config.json 的 key(多数场景:切上游 endpoint)</li><li>只填 <code>api_key</code> → 换 key 但复用 target(多账户轮换)</li><li>只填 <code>model</code> → 强制所有请求跑同一 model(灰度、成本控制)</li><li>填 tier models(如 <code>sonnet_model</code>)→ 按 <code>getProxyModel</code> 映射,haiku/sonnet/opus 分别走不同模型</li><li>三者组合 → 完全独立的 provider</li></ul><h2 id="getproxymodel" tabindex="-1"><code>getProxyModel</code> <a class="header-anchor" href="#getproxymodel" aria-label="Permalink to "`getProxyModel`""></a></h2><p><code>apps/proxy-core/src/server/proxy.ts:98-115</code>。根据请求的原始 model 和 provider tier 配置选择替换 model:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">claude</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">haiku</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> → </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">haiku_model</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (fallback: model → </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">undefined</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>\n<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">claude</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">sonnet</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> → </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">sonnet_model</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (fallback: model → </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">undefined</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>\n<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">claude</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">opus</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> → </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">opus_model</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (fallback: model → </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">undefined</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>\n<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">其它 → </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">model</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (fallback: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">undefined</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span></code></pre></div><p><strong>fallback 链</strong>:tier 专属 model → 通用 <code>model</code> → undefined(不替换)。这让一个 provider 可以只配一个通用 model(所有 tier 都用它),也可以精细到每个 tier 不同 model。</p><p><strong>使用场景</strong>:国内模型通常不区分 tier,一个 <code>model</code> 字段就够了。但如果用 Anthropic 直连或不同 tier 跑不同供应商,<code>haiku_model / sonnet_model / opus_model</code> 让一次切换覆盖所有 tier。</p><p><strong>同步查询开销</strong>:better-sqlite3 是同步 API,单行 UNIQUE 索引 SELECT 微秒级,无缓存。热切换时下一次请求立即生效,不需要重启进程。</p><p><strong>异常兜底</strong>:<code>try/catch</code> 内部若 DB 未 initDatabase / URL 解析失败 / 表不存在,直接 fallback 到 <code>config.proxy.target</code> 的解析结果。</p><h2 id="node-层-utils-providers-db-ts" tabindex="-1">Node 层:<code>utils/providers-db.ts</code> <a class="header-anchor" href="#node-层-utils-providers-db-ts" aria-label="Permalink to "Node 层:`utils/providers-db.ts`""></a></h2><p>三类函数:</p><h3 id="listproviders-db" tabindex="-1"><code>listProviders(db)</code> <a class="header-anchor" href="#listproviders-db" aria-label="Permalink to "`listProviders(db)`""></a></h3><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">SELECT</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> *</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> FROM</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> providers </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">ORDER</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> BY</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> created_at </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">DESC</span></span></code></pre></div><p>每行把 <code>is_default: number</code> 转为 <code>default: boolean</code> 后返回。</p><h3 id="saveproviders-db-providers" tabindex="-1"><code>saveProviders(db, providers[])</code> <a class="header-anchor" href="#saveproviders-db-providers" aria-label="Permalink to "`saveProviders(db, providers[])`""></a></h3><p><strong>Destructive full replacement</strong>:先 <code>DELETE FROM providers</code>,再逐行 INSERT,全部在一个 SQLite transaction 内。空数组 → 清空全表。</p><p><code>INSERT</code> 中每个可选字段:<code>p.base_url?.trim() ? p.base_url : null</code> — 空串统一转 NULL,避免 UNIQUE / 逻辑判断上「非空空串」和「NULL」混淆。</p><p><code>is_default</code> 从 boolean 转 <code>1 | 0</code>。</p><p><strong>为什么全量替换而不是 upsert</strong>:前端 UI 是「一张表格 → 保存整表」的模式,实现上更简单,避免局部同步的边界(同一 id 改名、id 冲突等)。</p><p><strong>开销</strong>:只在用户点「保存」时触发(低频运维操作),准备语句每次 new 也无性能问题。</p><h3 id="validateprovider-validateproviders" tabindex="-1"><code>validateProvider</code> / <code>validateProviders</code> <a class="header-anchor" href="#validateprovider-validateproviders" aria-label="Permalink to "`validateProvider` / `validateProviders`""></a></h3><p><strong>单个校验</strong>(<code>providers-db.ts:85-126</code>):</p><ul><li>必填字段:<code>name</code>、<code>provider</code>(trim 后非空)</li><li>「至少填一个可选字段」:<code>base_url / api_key / model / haiku_model / sonnet_model / opus_model / subagent_model</code> 至少有一个非空——防止用户建一个只有名字的空 provider</li><li><code>base_url</code> 若填了必须是 <code>http:</code> 或 <code>https:</code> 协议的合法 URL</li></ul><p><strong>批量校验</strong>(<code>providers-db.ts:128-156</code>):</p><ul><li>逐个跑 <code>validateProvider</code>,错误项挂 <code>index</code></li><li>检查 <code>name</code> 重复</li><li>检查 <code>default: true</code> 最多 1 个</li></ul><p>任何一条不通过就整批拒绝,返回 <code>ProviderValidationError[]</code>。</p><h2 id="rest-端点" tabindex="-1">REST 端点 <a class="header-anchor" href="#rest-端点" aria-label="Permalink to "REST 端点""></a></h2><p><code>apps/proxy-core/src/server/providers-route.ts</code>:</p><h3 id="get-ui-api-providers" tabindex="-1">GET /ui/api/providers <a class="header-anchor" href="#get-ui-api-providers" aria-label="Permalink to "GET /ui/api/providers""></a></h3><p>无认证。<code>listProviders</code> 结果封装到 <code>{ providers }</code>。异常返回 500。</p><h3 id="post-ui-api-providers" tabindex="-1">POST /ui/api/providers <a class="header-anchor" href="#post-ui-api-providers" aria-label="Permalink to "POST /ui/api/providers""></a></h3><p><strong>认证</strong>(<code>checkAuth</code>)。请求体:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{ </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">providers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: ProviderInput[] }</span></span></code></pre></div><p>流程:</p><ol><li>Bearer 认证</li><li>请求体存在 + <code>providers</code> 是数组</li><li><code>validateProviders(providers)</code> → 错误列表非空 → 400 + <code>{ errors }</code></li><li><code>saveProviders(db, providers)</code>(事务)</li><li><code>{ success: true }</code></li></ol><h2 id="前端交互" tabindex="-1">前端交互 <a class="header-anchor" href="#前端交互" aria-label="Permalink to "前端交互""></a></h2><p><code>ProviderManagerModal</code>(<code>apps/proxy-cli/src/components/ProviderManagerModal.tsx</code>):</p><ul><li>打开时 <code>fetchProviders()</code> 装填表格</li><li>每行 9 个字段 + 「激活 / 删除」按钮;「激活」把该行 <code>default = true</code>、其它行 <code>default = false</code></li><li>「保存」调 <code>saveProviders(rows)</code>:全量提交</li><li>错误分两种:<code>errors[]</code>(字段级)分行显示;<code>error</code>(顶层字符串)单条显示</li></ul><p>详见 <a href="./../proxy-cli/components-config.html">../proxy-cli/components-config</a>。</p><h2 id="切换与广播" tabindex="-1">切换与广播 <a class="header-anchor" href="#切换与广播" aria-label="Permalink to "切换与广播""></a></h2><p>供应商切换<strong>不广播事件</strong>。前端保存成功后无 WebSocket 通知,其它连接需要手动刷新 <code>ProviderManagerModal</code>。业务请求端不受影响:<code>resolveProxyTarget</code> 下次 SELECT 就拿到新的激活项。</p><h2 id="相关文档" tabindex="-1">相关文档 <a class="header-anchor" href="#相关文档" aria-label="Permalink to "相关文档""></a></h2><ul><li>Schema 迁移全景 → <a href="./data-layer.html">data-layer.md</a></li><li>前端管理界面 → <a href="./../proxy-cli/components-config.html">../proxy-cli/components-config</a></li><li><code>resolveProxyTarget</code> 在 handler 中的位置 → <a href="./proxy.html#公用步骤">proxy.md</a></li></ul>',62)])])}const E=i(t,[["render",o]]);export{k as __pageData,E as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as i,o as s,c as a,a2 as d}from"./chunks/framework.D_Xv2a7c.js";const k=JSON.parse('{"title":"proxy-core / 供应商管理","description":"","frontmatter":{},"headers":[],"relativePath":"proxy-core/providers.md","filePath":"proxy-core/providers.md"}'),t={name:"proxy-core/providers.md"};function o(r,e,l,p,n,h){return s(),a("div",null,[...e[0]||(e[0]=[d("",62)])])}const E=i(t,[["render",o]]);export{k as __pageData,E as default};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import{_ as e,o as i,c as a,a2 as t}from"./chunks/framework.D_Xv2a7c.js";const c=JSON.parse('{"title":"proxy-core / 代理转发(/v1/* 纯代理路径)","description":"","frontmatter":{},"headers":[],"relativePath":"proxy-core/proxy.md","filePath":"proxy-core/proxy.md"}'),l={name:"proxy-core/proxy.md"};function n(d,s,o,h,p,r){return i(),a("div",null,[...s[0]||(s[0]=[t(`<h1 id="proxy-core-代理转发-v1-纯代理路径" tabindex="-1">proxy-core / 代理转发(<code>/v1/*</code> 纯代理路径) <a class="header-anchor" href="#proxy-core-代理转发-v1-纯代理路径" aria-label="Permalink to "proxy-core / 代理转发(\`/v1/*\` 纯代理路径)""></a></h1><h2 id="本文档覆盖的源文件" tabindex="-1">本文档覆盖的源文件 <a class="header-anchor" href="#本文档覆盖的源文件" aria-label="Permalink to "本文档覆盖的源文件""></a></h2><ul><li><code>apps/proxy-core/src/server/proxy.ts:1-457</code></li></ul><blockquote><p><strong>注意</strong>:<code>/v1/*</code> 现为纯代理路径,不做请求体修改(除 model tier rewrite)、不做 tool 注入、不做 mock 拦截。增强型代理路径 <code>/api/chat</code> 见 <a href="./chat-api.html">chat-api.md</a>。</p></blockquote><h2 id="与-api-chat-的分工" tabindex="-1">与 <code>/api/chat</code> 的分工 <a class="header-anchor" href="#与-api-chat-的分工" aria-label="Permalink to "与 \`/api/chat\` 的分工""></a></h2><table tabindex="0"><thead><tr><th>能力</th><th><code>/v1/*</code></th><th><code>/api/chat</code></th></tr></thead><tbody><tr><td>请求体修改</td><td>仅 model tier rewrite</td><td>model rewrite + MCP tool 注入</td></tr><tr><td>响应处理</td><td>透传原样(JSON/SSE pipe)</td><td>SSE 流式转发 + inject loop 续接</td></tr><tr><td>Session 管理</td><td>从 header 读 <code>x-claude-code-session-id</code></td><td>生成/复用 <code>x-chat-session-id</code>,支持 resume</td></tr><tr><td>Tool 注入</td><td>无</td><td><code>injectMcpTools</code>(MCP 动态 tool only)</td></tr><tr><td>Mock 拦截</td><td>无</td><td><code>shouldMock</code>(PoC 测试)</td></tr><tr><td>DB 记录</td><td>单条(原始请求)</td><td>多条(原始 + 每次 loop 迭代独立记录)</td></tr></tbody></table><h2 id="顶层结构" tabindex="-1">顶层结构 <a class="header-anchor" href="#顶层结构" aria-label="Permalink to "顶层结构""></a></h2><p><code>setupProxy(fastify, proxyConfig)</code>(<code>proxy.ts:186-228</code>)注册一个 <code>fastify.all('/v1/*', ...)</code>。函数在服务启动时执行一次,主要做五件事:</p><ol><li><code>initDatabase()</code></li><li><code>mcpManager.init()</code>:初始化 MCP server 连接(seed config + connect enabled servers)</li><li><code>preloadMachineId()</code>:预加载 machine ID</li><li>启动 <code>aggregateStats</code> 定时器(5 分钟一次;启动时立即跑一次)</li><li><code>fastify.addHook('onClose', ...)</code>:关服时 <code>clearInterval</code> + <code>mcpManager.close()</code> + <code>closeDatabase()</code></li><li>注册请求 handler</li></ol><p>请求 handler 是本文的核心。</p><h2 id="为什么不用-fastify-http-proxy" tabindex="-1">为什么不用 <code>@fastify/http-proxy</code> <a class="header-anchor" href="#为什么不用-fastify-http-proxy" aria-label="Permalink to "为什么不用 \`@fastify/http-proxy\`""></a></h2><p>Fastify 生态里有透明代理插件,但项目最早使用后遇到了矛盾:透明代理内部完全接管 request stream,任何试图读 body 的操作都会消耗 stream。而本项目要同时满足:</p><ul><li>记录完整 payload(model / messages / tools / MCP 参数)</li><li>原样转发 SSE 流</li><li>未来扩展 Token 统计、鉴权、审计</li></ul><p>HTTP stream 只能被消费一次,于是切到「应用层转发」:Fastify 让 body 先被解析成 JSON,业务代码用 <code>fetch()</code> 主动发起上游请求,响应体用 <code>Readable.fromWeb().pipe(reply.raw)</code> 转发。</p><h2 id="handler-三分支-纯代理" tabindex="-1">Handler 三分支(纯代理) <a class="header-anchor" href="#handler-三分支-纯代理" aria-label="Permalink to "Handler 三分支(纯代理)""></a></h2><p><code>/v1/*</code> 不注入 tool、不做 mock、不进入 inject loop——只做纯转发 + 记录。</p><div class="language-mermaid vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">mermaid</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">flowchart TD</span></span>
|
|
2
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> START([POST/GET/... /v1/*]) --> RESOLVE[resolveProxyTarget]</span></span>
|
|
3
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> RESOLVE --> MODEL[getProxyModel tier rewrite]</span></span>
|
|
4
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> MODEL --> HEADER[extractSafeHeaders → filterForwardHeaders]</span></span>
|
|
5
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> HEADER --> DATA[组装 requestData + machineId]</span></span>
|
|
6
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> DATA --> LOG[cleanOldRequestsDB]</span></span>
|
|
7
|
+
<span class="line"></span>
|
|
8
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> LOG --> FETCH[fetch targetUrl]</span></span>
|
|
9
|
+
<span class="line"></span>
|
|
10
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> FETCH --> CT{content-type}</span></span>
|
|
11
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> CT -- application/json --> BR1["JSON 分支<br/>response.text() + reply.code().send()"]</span></span>
|
|
12
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> CT -- text/event-stream --> BR2["SSE pipe 分支<br/>Readable.fromWeb.pipe(reply.raw)<br/>边转发边解析 data: 行"]</span></span>
|
|
13
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> FETCH --> CATCH[catch → error 分支]</span></span>
|
|
14
|
+
<span class="line"></span>
|
|
15
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> BR1 --> SAVE[saveRequestDB + pushNewRequest]</span></span>
|
|
16
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> BR2 --> STREAM_END[on 'end' → save + push]</span></span>
|
|
17
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> CATCH --> PUSH_ERR[pushNewRequest(error record)]</span></span>
|
|
18
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> SAVE --> END([返回])</span></span>
|
|
19
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> STREAM_END --> END</span></span>
|
|
20
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> PUSH_ERR --> END_ERR([返回错误 JSON])</span></span></code></pre></div><p>公用部分见下面 <a href="#公用步骤">公用步骤</a>,三个分支分别见后续小节。</p><h2 id="公用步骤" tabindex="-1">公用步骤 <a class="header-anchor" href="#公用步骤" aria-label="Permalink to "公用步骤""></a></h2><h3 id="resolveproxytarget-proxyconfig" tabindex="-1"><code>resolveProxyTarget(proxyConfig)</code> <a class="header-anchor" href="#resolveproxytarget-proxyconfig" aria-label="Permalink to "\`resolveProxyTarget(proxyConfig)\`""></a></h3><p><code>proxy.ts:40-88</code>。每次请求同步查一次 <code>providers WHERE is_default = 1 LIMIT 1</code>。better-sqlite3 是同步 API,单行 SELECT 微秒级,无需缓存。</p><p>策略:</p><ul><li>起始值 = 从 <code>config.proxy.target</code> 解析出 <code>baseUrl</code> + <code>targetPath</code>,<code>apiKey = config.proxy.apiKey</code></li><li>若激活的 provider 有 <code>base_url</code> 且非空 → 覆盖 baseUrl/targetPath</li><li>若 <code>api_key</code> 非空 → 覆盖 apiKey</li><li>若 <code>model / haiku_model / sonnet_model / opus_model</code> 非空 → 记入返回值(供 <code>getProxyModel</code> 按 tier 替换)</li><li>任何异常(DB 未初始化 / URL 解析失败)→ fallback 只用 config</li></ul><p>返回 <code>{ baseUrl, targetPath, apiKey?, model?, haiku_model?, sonnet_model?, opus_model? }</code>。</p><h3 id="getproxymodel-originalmodel-tiermodels" tabindex="-1"><code>getProxyModel(originalModel, tierModels)</code> <a class="header-anchor" href="#getproxymodel-originalmodel-tiermodels" aria-label="Permalink to "\`getProxyModel(originalModel, tierModels)\`""></a></h3><p><code>proxy.ts:98-115</code>。根据请求的原始 model 和 provider tier 配置选择替换 model:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">claude</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">haiku</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> → </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">haiku_model</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (fallback: model)</span></span>
|
|
21
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">claude</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">sonnet</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> → </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">sonnet_model</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (fallback: model)</span></span>
|
|
22
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">claude</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">opus</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">*</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> → </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">opus_model</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (fallback: model)</span></span>
|
|
23
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">其它 → model</span></span>
|
|
24
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">所有为空 → </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">undefined</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (不替换)</span></span></code></pre></div><p>例如:provider <code>sonnet_model = "glm-5.1"</code> → 请求 <code>model = "claude-sonnet-4-6"</code> 被改写为 <code>glm-5.1</code>。</p><h3 id="extractsafeheaders-headers" tabindex="-1"><code>extractSafeHeaders(headers)</code> <a class="header-anchor" href="#extractsafeheaders-headers" aria-label="Permalink to "\`extractSafeHeaders(headers)\`""></a></h3><p><code>proxy.ts:117-128</code>。Fastify 的 <code>request.headers</code> 是 <code>Record<string, unknown></code>(值可能是 string[]),直接传给 <code>filterForwardHeaders</code> 会产生 circular reference 或类型问题。<code>extractSafeHeaders</code> 把所有值转为 <code>string</code>(数组 join <code>, </code>),生成干净的 <code>Record<string, string></code>。</p><h3 id="请求元数据组装" tabindex="-1">请求元数据组装 <a class="header-anchor" href="#请求元数据组装" aria-label="Permalink to "请求元数据组装""></a></h3><p><code>proxy.ts:234-290</code>:</p><ul><li><code>sessionId = headers['x-claude-code-session-id'] || 'unknown-session'</code></li><li><code>getProxyModel(originalModel, resolved)</code> 替换 model(原地修改 <code>request.body.model</code>)</li><li><code>extractSafeHeaders(request.headers)</code> → <code>filterForwardHeaders(safeHeaders, resolved.apiKey)</code>:删 host/content-length/x-req-key;有 provider key 则覆盖 <code>x-api-key</code> 和 <code>authorization</code></li><li>从 Authorization / x-api-key 头提取客户端原 apiKey;从 filtered 后的 headers 提取实际发给上游的 proxyApiKey</li><li><code>machineId = getMachineIdSync() ?? undefined</code></li><li>组装 <code>RequestData</code>:request 部分立即填齐(含 machineId, baseUrl),response 部分先占位(<code>statusCode: 0, events: []</code>)</li></ul><h3 id="记录与清理" tabindex="-1">记录与清理 <a class="header-anchor" href="#记录与清理" aria-label="Permalink to "记录与清理""></a></h3><ul><li><code>cleanOldRequestsDB(maxDays)</code>:每次请求都跑一次 DELETE(<code>maxDays=0</code> 时函数直接 return)</li><li><code>request.log.info(...)</code> 打印请求摘要</li></ul><h2 id="分支-1-json-响应" tabindex="-1">分支 1:JSON 响应 <a class="header-anchor" href="#分支-1-json-响应" aria-label="Permalink to "分支 1:JSON 响应""></a></h2><p><code>proxy.ts:324-362</code>。当上游返回 <code>content-type: application/json</code>(通常是错误响应或非流式):</p><ul><li><code>const responseBody = await response.text()</code></li><li>尝试 <code>JSON.parse(responseBody)</code>;成功则把整个 body 作为单一 event <code>push</code> 到 <code>events</code> 数组;失败则 push <code>{ raw: responseBody }</code></li><li>更新 <code>requestData.response.statusCode / headers / completedAt</code></li><li><code>saveRequestDB</code> 写库</li><li><code>extractTokenUsage</code> 提取 usage(从 events 中找 <code>type === 'message_delta'</code>)</li><li><code>pushNewRequest</code> 广播(含 machineId, baseUrl)</li><li><code>reply.code(response.status).headers(...).send(responseBody)</code> 原样返回给客户端</li></ul><p>优点:完整错误信息进库,方便调试云端 429/401/500。</p><h2 id="分支-2-sse-pipe-默认路径" tabindex="-1">分支 2:SSE pipe(默认路径) <a class="header-anchor" href="#分支-2-sse-pipe-默认路径" aria-label="Permalink to "分支 2:SSE pipe(默认路径)""></a></h2><p><code>proxy.ts:364-426</code>。零缓冲,纯转发:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">reply.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">hijack</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()</span></span>
|
|
25
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">reply.raw.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">writeHead</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(response.status, responseHeaders)</span></span>
|
|
26
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> nodeStream</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Readable.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">fromWeb</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(response.body </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">as</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> any</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
|
27
|
+
<span class="line"></span>
|
|
28
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">nodeStream.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'data'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">chunk</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
29
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 边转发边解析 data: 行到 requestData.response.events</span></span>
|
|
30
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 管道转发:chunk → reply.raw(客户端实时收到)</span></span>
|
|
31
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 解析:提取 data: 行 JSON → push events[]</span></span>
|
|
32
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span>
|
|
33
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">nodeStream.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'end'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">async</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
34
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> requestData.response.completedAt </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> Date</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">toISOString</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()</span></span>
|
|
35
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> await</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> saveRequestDB</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
|
36
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> pushNewRequest</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// 含 machineId, baseUrl</span></span>
|
|
37
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span>
|
|
38
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">nodeStream.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">pipe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(reply.raw)</span></span></code></pre></div><ul><li><strong>Web ReadableStream → Node Readable</strong>:<code>Readable.fromWeb()</code>(Node 18+ 提供)</li><li><strong>数据事件双职责</strong>:pipe 会把 chunk 转发出去,同时 handler 里手动解析 <code>data:</code> 前缀行提取 event(含 Anthropic 双行 <code>event:xxx\\ndata:{...}</code> 格式)</li><li><strong>保留原响应头</strong>:不覆盖 content-type,客户端拿到的响应长得跟直连上游一样</li></ul><h3 id="sse-解析容错" tabindex="-1">SSE 解析容错 <a class="header-anchor" href="#sse-解析容错" aria-label="Permalink to "SSE 解析容错""></a></h3><p><code>proxy.ts:412-427</code>:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> lines</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> text.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">split</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">\\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
|
39
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> line</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> of</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> lines) {</span></span>
|
|
40
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (line.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">startsWith</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'data:'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)) {</span></span>
|
|
41
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> eventData</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> line.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">slice</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">trim</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()</span></span>
|
|
42
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (eventData </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&&</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> eventData </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">!==</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> '[DONE]'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) {</span></span>
|
|
43
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> try</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { requestData.response.events.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">push</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">JSON</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">parse</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(eventData)) } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">catch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {}</span></span>
|
|
44
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
|
45
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
|
46
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>同时兼容标准 <code>data: {...}</code> 和阿里云的 <code>event:xxx\\ndata:{...}</code> 双行格式(<code>event:</code> 行被忽略)。<code>[DONE]</code> 也跳过。</p><h3 id="chunk-边界问题" tabindex="-1">chunk 边界问题 <a class="header-anchor" href="#chunk-边界问题" aria-label="Permalink to "chunk 边界问题""></a></h3><p><strong>已知边界</strong>:如果一个 <code>data: {...}</code> 事件跨 chunk 被截断,当前实现会解析失败(<code>JSON.parse</code> 抛错被 catch 吞掉)。数据库里丢一条 event 但不影响客户端流转发。项目里另有 <code>SSEAccumulator</code>(<code>utils/sse-accumulator.ts</code>)是完整的 Transform 流,但在 <code>proxy.ts</code> 当前路径中<strong>未启用</strong>。</p><h2 id="分支-3-error" tabindex="-1">分支 3:Error <a class="header-anchor" href="#分支-3-error" aria-label="Permalink to "分支 3:Error""></a></h2><p><code>proxy.ts:428-456</code>:</p><ul><li>上游 <code>fetch</code> 抛错时命中</li><li><code>getErrorStatusCode</code>:<code>ECONNREFUSED</code> / <code>ENOTFOUND</code> → 502;<code>ETIMEDOUT</code> → 504;其它 → 500</li><li><code>getErrorMessage</code>:三选一或 <code>Proxy error</code></li><li>仍然 <code>pushNewRequest</code> 一条含错误 statusCode 的记录(供面板显示红色徽章)</li><li>响应体:<code>{ error, requestId }</code> JSON</li></ul><h2 id="关键决策" tabindex="-1">关键决策 <a class="header-anchor" href="#关键决策" aria-label="Permalink to "关键决策""></a></h2><ul><li><strong>纯代理设计</strong>:<code>/v1/*</code> 不做任何请求体修改(除 model tier rewrite)、不注入 tool、不做 mock。所有增强逻辑放在 <code>/api/chat</code>。这让 <code>/v1/*</code> 成为零风险路径——客户端用它时 proxy 完全透明</li><li><strong><code>reply.hijack()</code> 的语义</strong>:Fastify 停止管你,包括路由响应、日志钩子、<code>onSend</code> hook。之后你必须自己 <code>reply.raw.end()</code>,否则连接挂着</li><li><strong>重新序列化 body</strong>:<code>fetch</code> 的 body 用 <code>JSON.stringify(request.body)</code>。这意味着如果客户端发送的 JSON 有非语义变化(空格、字段顺序),代理会「格式化」它。对语义无影响,但会影响某些希望做 body hash 的场景</li><li><strong>URL 拼接</strong>:<code>targetUrlStr = baseUrl + targetPath + request.url</code>。例如 <code>baseUrl='https://ark.cn-beijing.volces.com'</code> + <code>targetPath='/api/coding'</code> + <code>request.url='/v1/messages'</code> = <code>https://ark.cn-beijing.volces.com/api/coding/v1/messages</code>。因此 provider 的 base_url 应写成含 API 前缀的形式</li><li><strong>一次请求两次 <code>Date.now()</code></strong>:<code>startTime</code> 在 handler 入口;SSE 分支的 <code>elapsed</code> 在 <code>end</code> 事件;同步分支(JSON)在 <code>send</code> 前。都体现 handler 视角的耗时</li><li><strong>Tier model fallback 链</strong>:<code>getProxyModel</code> 按 tier 映射,每个 tier 优先用专属 model(<code>haiku_model / sonnet_model / opus_model</code>),无专属 model 时 fallback 到通用 <code>model</code>,再 fallback 到不替换</li></ul>`,54)])])}const E=e(l,[["render",n]]);export{c as __pageData,E as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as e,o as i,c as a,a2 as t}from"./chunks/framework.D_Xv2a7c.js";const c=JSON.parse('{"title":"proxy-core / 代理转发(/v1/* 纯代理路径)","description":"","frontmatter":{},"headers":[],"relativePath":"proxy-core/proxy.md","filePath":"proxy-core/proxy.md"}'),l={name:"proxy-core/proxy.md"};function n(d,s,o,h,p,r){return i(),a("div",null,[...s[0]||(s[0]=[t("",54)])])}const E=e(l,[["render",n]]);export{c as __pageData,E as default};
|