@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,124 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN" dir="ltr">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>proxy-core / Tool 注入 + MCP 动态 Tool | CC Monitoring</title>
|
|
7
|
+
<meta name="description" content="Claude Code 代理 + 监控系统文档">
|
|
8
|
+
<meta name="generator" content="VitePress v1.6.4">
|
|
9
|
+
<link rel="preload stylesheet" href="/docs/assets/style.DU_kNMWy.css" as="style">
|
|
10
|
+
<link rel="preload stylesheet" href="/docs/vp-icons.css" as="style">
|
|
11
|
+
|
|
12
|
+
<script type="module" src="/docs/assets/app.BxFOH0P1.js"></script>
|
|
13
|
+
<link rel="preload" href="/docs/assets/inter-roman-latin.Di8DUHzh.woff2" as="font" type="font/woff2" crossorigin="">
|
|
14
|
+
<link rel="modulepreload" href="/docs/assets/chunks/theme.BLEczZWC.js">
|
|
15
|
+
<link rel="modulepreload" href="/docs/assets/chunks/framework.D_Xv2a7c.js">
|
|
16
|
+
<link rel="modulepreload" href="/docs/assets/proxy-core_tool.md.B6Le1nQr.lean.js">
|
|
17
|
+
<script id="check-dark-mode">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"auto",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
|
|
18
|
+
<script id="check-mac-os">document.documentElement.classList.toggle("mac",/Mac|iPhone|iPod|iPad/i.test(navigator.platform));</script>
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
<div id="app"><div class="Layout" data-v-0fd5f79a><!--[--><!--]--><!--[--><span tabindex="-1" data-v-13bcab17></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-13bcab17>Skip to content</a><!--]--><!----><header class="VPNav" data-v-0fd5f79a data-v-221ddc7b><div class="VPNavBar" data-v-221ddc7b data-v-6a82b350><div class="wrapper" data-v-6a82b350><div class="container" data-v-6a82b350><div class="title" data-v-6a82b350><div class="VPNavBarTitle has-sidebar" data-v-6a82b350 data-v-98825d79><a class="title" href="/docs/" data-v-98825d79><!--[--><!--]--><!----><span data-v-98825d79>CC Monitoring</span><!--[--><!--]--></a></div></div><div class="content" data-v-6a82b350><div class="content-body" data-v-6a82b350><!--[--><!--]--><div class="VPNavBarSearch search" data-v-6a82b350><!--[--><!----><div id="local-search"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><span class="vp-icon DocSearch-Search-Icon"></span><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-6a82b350 data-v-2fa375b4><span id="main-nav-aria-label" class="visually-hidden" data-v-2fa375b4> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/docs/overview.html" tabindex="0" data-v-2fa375b4 data-v-88169469><!--[--><span data-v-88169469>概览</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/docs/quickstart.html" tabindex="0" data-v-2fa375b4 data-v-88169469><!--[--><span data-v-88169469>快速开始</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/docs/proxy-core/" tabindex="0" data-v-2fa375b4 data-v-88169469><!--[--><span data-v-88169469>Proxy Core</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/docs/proxy-cli/" tabindex="0" data-v-2fa375b4 data-v-88169469><!--[--><span data-v-88169469>Proxy CLI</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/docs/proxy-admin/" tabindex="0" data-v-2fa375b4 data-v-88169469><!--[--><span data-v-88169469>Proxy Admin</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-6a82b350 data-v-c7859fea><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-c7859fea data-v-458e2939 data-v-2fadb15f><span class="check" data-v-2fadb15f><span class="icon" data-v-2fadb15f><!--[--><span class="vpi-sun sun" data-v-458e2939></span><span class="vpi-moon moon" data-v-458e2939></span><!--]--></span></span></button></div><!----><div class="VPFlyout VPNavBarExtra extra" data-v-6a82b350 data-v-4a332738 data-v-3416cc30><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-3416cc30><span class="vpi-more-horizontal icon" data-v-3416cc30></span></button><div class="menu" data-v-3416cc30><div class="VPMenu" data-v-3416cc30 data-v-70b241a4><!----><!--[--><!--[--><!----><div class="group" data-v-4a332738><div class="item appearance" data-v-4a332738><p class="label" data-v-4a332738>Appearance</p><div class="appearance-action" data-v-4a332738><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-4a332738 data-v-458e2939 data-v-2fadb15f><span class="check" data-v-2fadb15f><span class="icon" data-v-2fadb15f><!--[--><span class="vpi-sun sun" data-v-458e2939></span><span class="vpi-moon moon" data-v-458e2939></span><!--]--></span></span></button></div></div></div><!----><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-6a82b350 data-v-2b9279da><span class="container" data-v-2b9279da><span class="top" data-v-2b9279da></span><span class="middle" data-v-2b9279da></span><span class="bottom" data-v-2b9279da></span></span></button></div></div></div></div><div class="divider" data-v-6a82b350><div class="divider-line" data-v-6a82b350></div></div></div><!----></header><div class="VPLocalNav has-sidebar empty" data-v-0fd5f79a data-v-751e8533><div class="container" data-v-751e8533><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-751e8533><span class="vpi-align-left menu-icon" data-v-751e8533></span><span class="menu-text" data-v-751e8533>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-751e8533 data-v-afd8288e><button data-v-afd8288e>Return to top</button><!----></div></div></div><aside class="VPSidebar" data-v-0fd5f79a data-v-711af969><div class="curtain" data-v-711af969></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-711af969><span class="visually-hidden" id="sidebar-aria-label" data-v-711af969> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="no-transition group" data-v-4ebb8294><section class="VPSidebarItem level-0 has-active" data-v-4ebb8294 data-v-04203d25><div class="item" role="button" tabindex="0" data-v-04203d25><div class="indicator" data-v-04203d25></div><h2 class="text" data-v-04203d25>Proxy Core</h2><!----></div><div class="items" data-v-04203d25><!--[--><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/" data-v-04203d25><!--[--><p class="text" data-v-04203d25>模块概览</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/entry.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>入口与启动</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/cli.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>CLI 命令</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/proxy.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>代理转发</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/ui-routes.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>UI 路由</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/providers.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>供应商管理</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/config.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>配置与日志</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/data-layer.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>数据层</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/stats.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>统计与历史</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/tool.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>工具注入</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/pm2.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>PM2 集成</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/utilities.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>工具函数</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/chat-api.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>Chat API</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-0fd5f79a data-v-3aa167b9><div class="VPDoc has-sidebar has-aside" data-v-3aa167b9 data-v-b2c5ca34><!--[--><!--]--><div class="container" data-v-b2c5ca34><div class="aside" data-v-b2c5ca34><div class="aside-curtain" data-v-b2c5ca34></div><div class="aside-container" data-v-b2c5ca34><div class="aside-content" data-v-b2c5ca34><div class="VPDocAside" data-v-b2c5ca34 data-v-af7efbb6><!--[--><!--]--><!--[--><!--]--><nav aria-labelledby="doc-outline-aria-label" class="VPDocAsideOutline" data-v-af7efbb6 data-v-db542fa8><div class="content" data-v-db542fa8><div class="outline-marker" data-v-db542fa8></div><div aria-level="2" class="outline-title" id="doc-outline-aria-label" role="heading" data-v-db542fa8>On this page</div><ul class="VPDocOutlineItem root" data-v-db542fa8 data-v-1d036821><!--[--><!--]--></ul></div></nav><!--[--><!--]--><div class="spacer" data-v-af7efbb6></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-b2c5ca34><div class="content-container" data-v-b2c5ca34><!--[--><!--]--><main class="main" data-v-b2c5ca34><div style="position:relative;" class="vp-doc _docs_proxy-core_tool" data-v-b2c5ca34><div><h1 id="proxy-core-tool-注入-mcp-动态-tool" tabindex="-1">proxy-core / Tool 注入 + MCP 动态 Tool <a class="header-anchor" href="#proxy-core-tool-注入-mcp-动态-tool" aria-label="Permalink to "proxy-core / Tool 注入 + MCP 动态 Tool""></a></h1><h2 id="本文档覆盖的源文件" tabindex="-1">本文档覆盖的源文件 <a class="header-anchor" href="#本文档覆盖的源文件" aria-label="Permalink to "本文档覆盖的源文件""></a></h2><ul><li><code>apps/proxy-core/src/tools/index.ts</code></li><li><code>apps/proxy-core/src/tools/types.ts</code></li><li><code>apps/proxy-core/src/tools/registry.ts</code></li><li><code>apps/proxy-core/src/tools/curl.tool.ts</code></li><li><code>apps/proxy-core/src/tools/probe.tool.ts</code></li><li><code>apps/proxy-core/src/tools/mcp-bridge.ts</code></li><li><code>apps/proxy-core/src/utils/tool-injector.ts</code></li><li><code>apps/proxy-core/src/utils/inject-loop.ts</code></li><li><code>apps/proxy-core/src/utils/sse-stream-processor.ts</code></li><li><code>apps/proxy-core/src/utils/sse-writer.ts</code></li><li><code>apps/proxy-core/src/utils/mcp-manager.ts</code></li><li><code>apps/proxy-core/src/utils/mcp-db.ts</code></li><li><code>apps/proxy-core/src/server/chat-route.ts</code></li></ul><h2 id="概念" tabindex="-1">概念 <a class="header-anchor" href="#概念" aria-label="Permalink to "概念""></a></h2><p>「Tool 注入」= 在 proxy 转发前把一些 <strong>tool</strong> 追加到请求体的 <code>tools</code> 数组末尾。上游模型可能调用这些 tool;proxy 检测到命中后<strong>在后端本地执行 exec</strong>,把结果作为 <code>tool_result</code> 追加到对话,然后再次调用上游,直到模型给出最终文本。</p><p><strong>注入路径分工</strong>:</p><table tabindex="0"><thead><tr><th>路径</th><th>注入方式</th><th>说明</th></tr></thead><tbody><tr><td><code>/v1/*</code></td><td>无注入</td><td>纯代理,不做任何请求体修改</td></tr><tr><td><code>/api/chat</code></td><td><code>injectMcpTools</code></td><td>仅注入 MCP 动态 tool(不含 probe/curl)</td></tr></tbody></table><p>对客户端而言,<code>/api/chat</code> 的 SSE 响应看起来像一次正常的流式响应;实际上 proxy 可能已经代替客户端完成了若干轮 MCP tool 调用,续接到同一 SSE 连接上。</p><p>设计目标见 <code>docs/thinks/tool-plugin-injection-plan.md</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>mcp-manager + mcp-db → MCP server 连接管理(发现 tool)</span></span>
|
|
22
|
+
<span class="line"><span>mcp-bridge → MCP tool → InjectableTool 转换(name prefix + exec 代理)</span></span>
|
|
23
|
+
<span class="line"><span>tools/registry.ts → 注册表 ALL_TOOLS(静态) + mcpManager.getMcpTools()(动态)</span></span>
|
|
24
|
+
<span class="line"><span>tools/*.tool.ts → 单个 tool 定义(name + description + input_schema + enabled + exec)</span></span>
|
|
25
|
+
<span class="line"><span>utils/tool-injector → injectMcpTools:在 /api/chat 发送前追加 MCP tools</span></span>
|
|
26
|
+
<span class="line"><span>utils/inject-loop → 命中后的内部循环(3 种:SSE buffered / SSE streaming / JSON)</span></span>
|
|
27
|
+
<span class="line"><span>sse-stream-processor → 流式 SSE 增量解析 + 转发</span></span>
|
|
28
|
+
<span class="line"><span>utils/sse-writer → 合成 SSE 回写(mock / 旧版 buffered loop)</span></span></code></pre></div><h2 id="injectabletool-接口" tabindex="-1"><code>InjectableTool</code> 接口 <a class="header-anchor" href="#injectabletool-接口" aria-label="Permalink to "`InjectableTool` 接口""></a></h2><p><code>tools/types.ts:16-30</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;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> interface</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> InjectableTool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
29
|
+
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> name</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> string</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 唯一,注入 + 命中检测 + tool_result 配对</span></span>
|
|
30
|
+
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> description</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> string</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 给模型看(决定何时调用)</span></span>
|
|
31
|
+
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> input_schema</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> ToolInputSchema</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // JSON Schema,type=object</span></span>
|
|
32
|
+
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> enabled</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> boolean</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 是否启用</span></span>
|
|
33
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> exec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">input</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> any</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:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> Promise</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"><</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">> </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// 本地执行,返回字符串给上游</span></span>
|
|
34
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>三件套 <code>name / description / input_schema</code> 会随 <code>injectTools</code> 写进请求体的 <code>tools</code> 数组;<code>enabled</code> 和 <code>exec</code> 是本地元数据,<strong>不进入请求体</strong>。</p><h2 id="已内置的-2-个-tool" tabindex="-1">已内置的 2 个 tool <a class="header-anchor" href="#已内置的-2-个-tool" aria-label="Permalink to "已内置的 2 个 tool""></a></h2><h3 id="probetool-tools-probe-tool-ts" tabindex="-1"><code>probeTool</code>(<code>tools/probe.tool.ts</code>) <a class="header-anchor" href="#probetool-tools-probe-tool-ts" aria-label="Permalink to "`probeTool`(`tools/probe.tool.ts`)""></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:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
|
|
35
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'__inject_probe'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
36
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> description</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'当 user 消息含 __PROBE__ 时必须调用此 tool 回显请求'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
37
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> input_schema</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: { </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">echo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: string, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">required</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'echo'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">] },</span></span>
|
|
38
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> enabled</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// 默认关</span></span>
|
|
39
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> exec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(input) { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">return</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> 'Probe executed. Received echo: "<...>". Now answer directly; do NOT call __inject_probe again.'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
|
40
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>PoC 验证工具。把 <code>enabled</code> 改成 <code>true</code> 后端到端跑一遍:</p><ul><li>Claude Code 发含 <code>__PROBE__</code> 的 user message</li><li>proxy 把 <code>__inject_probe</code> 追加到 tools</li><li>上游模型看到描述后决定调用它</li><li>proxy 捕获 tool_use → 本地跑 exec → 生成 tool_result → 再调上游</li><li>上游模型此时应给出正式文本回答(描述里明确要求「不要再次调用」)</li><li>proxy 合成 SSE 把文本回给客户端</li></ul><h3 id="curltool-tools-curl-tool-ts" tabindex="-1"><code>curlTool</code>(<code>tools/curl.tool.ts</code>) <a class="header-anchor" href="#curltool-tools-curl-tool-ts" aria-label="Permalink to "`curlTool`(`tools/curl.tool.ts`)""></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:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
|
|
41
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'curl_proxy'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
42
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> description</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'Use this tool to fetch data from a local service or API...'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
43
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> input_schema</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: { </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">url</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: string, method</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">?:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> 'GET'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">|</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'POST'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, body</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">?:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> string, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">required</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'url'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">] },</span></span>
|
|
44
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> enabled</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// 默认开</span></span>
|
|
45
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> async </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">exec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(input) {</span></span>
|
|
46
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> response</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> await</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> fetch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(url, { method, headers: { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'Content-Type'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'application/json'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }, body })</span></span>
|
|
47
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> data</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> await</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> response.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()</span></span>
|
|
48
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> `Successfully fetched from ${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">url</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">}:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">\n\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">data</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">substring</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">2000</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">}`</span></span>
|
|
49
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
|
50
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>给模型一个能拉本地服务的能力(本机 API 探测、开发调试)。响应文本截断到 2000 字符,避免灌爆上下文。</p><h2 id="注册表-registry-ts-—-静态-动态" tabindex="-1">注册表 <code>registry.ts</code> — 静态 + 动态 <a class="header-anchor" href="#注册表-registry-ts-—-静态-动态" aria-label="Permalink to "注册表 `registry.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;"> ALL_TOOLS</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> InjectableTool</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;"> [probeTool, curlTool] </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// 静态本地 tool</span></span>
|
|
51
|
+
<span class="line"></span>
|
|
52
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> allKnownTools</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> InjectableTool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">[] {</span></span>
|
|
53
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</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;">ALL_TOOLS</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;">mcpManager.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">getMcpTools</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()] </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// 动态合并 MCP tools</span></span>
|
|
54
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span>
|
|
55
|
+
<span class="line"></span>
|
|
56
|
+
<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;"> enabledTools</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> InjectableTool</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;"> allKnownTools</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">filter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> t.enabled) }</span></span>
|
|
57
|
+
<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;"> hasEnabledTools</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;"> boolean</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;"> allKnownTools</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">().</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">some</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> t.enabled) }</span></span>
|
|
58
|
+
<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;"> findEnabledTool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> InjectableTool</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> |</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> undefined</span></span></code></pre></div><p>新增本地 tool:<code>tools/xxx.tool.ts</code> 导出 <code>InjectableTool</code> → 在 <code>ALL_TOOLS</code> 添一行 → 决定 <code>enabled</code> 初值。</p><p>MCP tool 通过 <code>mcpManager.getMcpTools()</code> 动态合并——连接时发现,断开时消失。每次 <code>enabledTools()</code> / <code>hasEnabledTools()</code> / <code>findEnabledTool()</code> 调用都含当前已连接 MCP server 的 tool。</p><p><code>tools/index.ts</code> 是 barrel:<code>export { enabledTools, hasEnabledTools, findEnabledTool } from './registry.js'</code> + <code>export type { InjectableTool, ToolInputSchema } from './types.js'</code>。</p><h2 id="mcp-tool-bridge-mcp-bridge-ts" tabindex="-1">MCP Tool Bridge (<code>mcp-bridge.ts</code>) <a class="header-anchor" href="#mcp-tool-bridge-mcp-bridge-ts" aria-label="Permalink to "MCP Tool Bridge (`mcp-bridge.ts`)""></a></h2><p><code>mcpToolToInjectable(mcpTool, serverName, client)</code> 把 MCP SDK 发现的 tool 转为 <code>InjectableTool</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;"> prefixedName</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> `mcp__${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">serverName</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">}__${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">mcpTool</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">}`</span></span>
|
|
59
|
+
<span class="line"></span>
|
|
60
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
61
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> name: prefixedName,</span></span>
|
|
62
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> description: mcpTool.description </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">||</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> ''</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
63
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> input_schema: mcpTool.inputSchema,</span></span>
|
|
64
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> enabled: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
65
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> async</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> exec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">input</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) {</span></span>
|
|
66
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> result</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> await</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> client.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">callTool</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({ name: mcpTool.name, arguments: input }) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// 原始 name,无 prefix</span></span>
|
|
67
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 从 result.content[] 提取 text 块,join 为字符串返回</span></span>
|
|
68
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
|
69
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p><strong>Name prefix 设计</strong>:<code>mcp__<serverName>__<toolName></code> 避免 MCP tool 与本地 tool(probe/curl)name 冲突。<code>exec()</code> 调 <code>client.callTool</code> 时用<strong>原始 name</strong>(无 prefix),因为 MCP server 只认自己的 name。</p><p><strong>exec 代理</strong>:不是本地执行,而是远程调 MCP server。MCP server 返回 <code>content[]</code>(text 块 + 其它块),bridge 把 text 块 join 成字符串返回给 inject loop。</p><h2 id="injectmcptools-body-—-mcp-only-注入" tabindex="-1"><code>injectMcpTools(body)</code> — MCP-only 注入 <a class="header-anchor" href="#injectmcptools-body-—-mcp-only-注入" aria-label="Permalink to "`injectMcpTools(body)` — MCP-only 注入""></a></h2><p><code>utils/tool-injector.ts:14-31</code>。仅用于 <code>/api/chat</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;">if</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;">Array.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">isArray</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(body?.tools)) {</span></span>
|
|
70
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> body.tools </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;">// 裸请求也能用 MCP tool — 自动创建 tools 数组</span></span>
|
|
71
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span>
|
|
72
|
+
<span class="line"></span>
|
|
73
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> injected</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:#24292E;--shiki-dark:#E1E4E8;"> []</span></span>
|
|
74
|
+
<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;"> tool</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> of</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> mcpManager.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">getMcpTools</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()) { </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// 仅 MCP tools,不含 probe/curl</span></span>
|
|
75
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</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;">tool.enabled) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">continue</span></span>
|
|
76
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (body.tools.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">some</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> t?.name </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">===</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> tool.name)) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">continue</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 幂等</span></span>
|
|
77
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> body.tools.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">push</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({ name: tool.name, description: tool.description, input_schema: tool.input_schema })</span></span>
|
|
78
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> injected.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">push</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(tool.name)</span></span>
|
|
79
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span>
|
|
80
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> injected</span></span></code></pre></div><h3 id="关键约束" tabindex="-1">关键约束 <a class="header-anchor" href="#关键约束" aria-label="Permalink to "关键约束""></a></h3><ul><li><strong>仅注入 MCP tools</strong>:<code>mcpManager.getMcpTools()</code> 只返回当前已连接 MCP server 的 tool,不含 probe/curl。这是 <code>/api/chat</code> 的设计决策——MCP tool 是用户自配置的业务 tool,probe/curl 是开发调试 tool</li><li><strong>裸请求也能注入</strong>:<code>body.tools</code> 不存在时自动创建空数组,让没有 tools 字段的请求也能使用 MCP tool(<code>/v1/*</code> 的旧 <code>injectTools</code> 要求 <code>body.tools</code> 非空才注入)</li><li><strong>追加到末尾</strong>:Anthropic 按 <code>tools → system → messages</code> 顺序算前缀哈希,追加末尾只让 tools 段末端缓存失效</li><li><strong>幂等</strong>:已存在同名 tool 就跳过</li><li><strong>仅写三件套</strong>:exec/enabled 是本地状态,模型不需要知道</li></ul><h2 id="hasenabledtools-是-v1-的零影响保障" tabindex="-1"><code>hasEnabledTools()</code> 是 <code>/v1/*</code> 的零影响保障 <a class="header-anchor" href="#hasenabledtools-是-v1-的零影响保障" aria-label="Permalink to "`hasEnabledTools()` 是 `/v1/*` 的零影响保障""></a></h2><p>若 registry 中所有 tool 的 <code>enabled: false</code>(例如 probe 默认关、MCP server 断连),<code>hasEnabledTools()</code> return false,<code>/v1/*</code> 的转发路径完全等同纯代理——<strong>零性能影响</strong>。</p><p>但 <code>/api/chat</code> <strong>始终调用 <code>injectMcpTools</code></strong>,不依赖 <code>hasEnabledTools()</code> 判断。即使当前无 MCP tool 连接,<code>injectMcpTools</code> 也只是返回空数组(<code>injected = false</code>),代码正常执行。</p><h2 id="三种内部循环" tabindex="-1">三种内部循环 <a class="header-anchor" href="#三种内部循环" aria-label="Permalink to "三种内部循环""></a></h2><h3 id="runinjectloop-sse-buffered-—-旧版" tabindex="-1"><code>runInjectLoop</code>(SSE buffered — 旧版) <a class="header-anchor" href="#runinjectloop-sse-buffered-—-旧版" aria-label="Permalink to "`runInjectLoop`(SSE buffered — 旧版)""></a></h3><p><code>inject-loop.ts:129-159</code>。用于旧版 <code>/v1/*</code> 注入路径(现已移除)。保留供参考。</p><p>流程:缓冲上游 SSE 全文 → 解析 → 检测命中 → 循环(exec tool → 追加对话 → callUpstream)→ 合成 SSE 回写。</p><p>返回 <code>{ text, iterations, stoppedByLimit }</code>。<code>text</code> 是最终模型输出,由 <code>writeTextAsSSE</code> 合成到客户端。</p><h3 id="runstreaminginjectloop-sse-streaming-—-api-chat-用" tabindex="-1"><code>runStreamingInjectLoop</code>(SSE streaming — <code>/api/chat</code> 用) <a class="header-anchor" href="#runstreaminginjectloop-sse-streaming-—-api-chat-用" aria-label="Permalink to "`runStreamingInjectLoop`(SSE streaming — `/api/chat` 用)""></a></h3><p><code>inject-loop.ts:175-227</code>。<strong>核心路径</strong>——不缓冲,直接流式转发到同一 <code>reply.raw</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;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> async</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> runStreamingInjectLoop</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">opts</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
81
|
+
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> initialParsed</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> ParsedUpstream</span></span>
|
|
82
|
+
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> body</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> any</span></span>
|
|
83
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> callUpstream</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">body</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> Promise</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"><{ </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">response</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> Response</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">requestId</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>
|
|
84
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> streamAndParse</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">response</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> Promise</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"><{ </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">processor</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> SSEStreamProcessor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">statusCode</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> number</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }></span></span>
|
|
85
|
+
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> sessionId</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> string</span></span>
|
|
86
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> saveIterationDB</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">requestId</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">processor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">statusCode</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">requestBody</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> Promise</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"><</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">void</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">></span></span>
|
|
87
|
+
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> raw</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> ServerResponse</span></span>
|
|
88
|
+
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> maxIterations</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">?:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> number</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 默认 3</span></span>
|
|
89
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> Promise</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"><</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">StreamingInjectLoopResult</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">></span></span></code></pre></div><p>流程:</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;">stateDiagram-v2</span></span>
|
|
90
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [*] --> Check: findInjectedCall + stop_reason=tool_use?</span></span>
|
|
91
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Check --> Exec: 命中 & raw 未 destroy</span></span>
|
|
92
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Check --> Return: 未命中或 end_turn → {iterations}</span></span>
|
|
93
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Exec --> Append: body.messages.push(assistant + tool_result)</span></span>
|
|
94
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Append --> Upstream: callUpstream(body) → {response, requestId}</span></span>
|
|
95
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Upstream --> StreamParse: streamAndParse → SSEStreamProcessor<br/>流式转发到 raw</span></span>
|
|
96
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> StreamParse --> Save: saveIterationDB → 独立 DB 记录</span></span>
|
|
97
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Save --> Check</span></span>
|
|
98
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Return --> [*]</span></span></code></pre></div><p><strong>与旧版 <code>runInjectLoop</code> 的核心区别</strong>:</p><table tabindex="0"><thead><tr><th></th><th>runInjectLoop(旧)</th><th>runStreamingInjectLoop(新)</th></tr></thead><tbody><tr><td>上游响应处理</td><td><code>await response.text()</code> 全量缓冲</td><td><code>SSEStreamProcessor</code> 流式转发</td></tr><tr><td>客户端看到</td><td>合成 SSE(仅 text,<code>writeTextAsSSE</code>)</td><td>原始 SSE(thinking + text + tool_use)</td></tr><tr><td>DB 记录</td><td>原始 events 存第 1 条,loop 结果合并</td><td>原始 + 每次迭代独立记录</td></tr><tr><td>返回值</td><td><code>{ text, iterations }</code></td><td><code>{ iterations }</code>(text 已实时流到客户端)</td></tr><tr><td>连接模式</td><td>缓冲完毕后一次性 <code>reply.raw.end()</code></td><td>同连接续接,<code>reply.raw.end()</code> 在最后</td></tr><tr><td>客户端断连检测</td><td>无</td><td><code>raw.destroyed</code> 每次迭代前检查</td></tr></tbody></table><h3 id="runjsoninjectloop-json-—-api-chat-用" tabindex="-1"><code>runJsonInjectLoop</code>(JSON — <code>/api/chat</code> 用) <a class="header-anchor" href="#runjsoninjectloop-json-—-api-chat-用" aria-label="Permalink to "`runJsonInjectLoop`(JSON — `/api/chat` 用)""></a></h3><p><code>inject-loop.ts:244-277</code>。处理非流式 JSON 响应中的 tool_use。</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;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> async</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> runJsonInjectLoop</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">opts</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
99
|
+
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> initialBody</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> any</span></span>
|
|
100
|
+
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> requestBody</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> any</span></span>
|
|
101
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> callUpstream</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">body</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> Promise</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"><</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">any</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">> </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// 返回 parsed JSON body</span></span>
|
|
102
|
+
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> maxIterations</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">?:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> number</span></span>
|
|
103
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> Promise</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"><</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">JsonInjectLoopResult</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">></span></span></code></pre></div><p>与 SSE streaming 版对称,但处理 parsed JSON response body 而非 SSE events。返回 <code>{ finalBody, iterations, stoppedByLimit }</code>。</p><h3 id="blockstoassistantcontent" tabindex="-1"><code>blocksToAssistantContent</code> <a class="header-anchor" href="#blockstoassistantcontent" aria-label="Permalink to "`blocksToAssistantContent`""></a></h3><p><code>inject-loop.ts:99-106</code>。三种 loop 共用——只把 <code>text</code> 和 <code>tool_use</code> 块转成 API 格式,<strong>跳过 thinking 块</strong>(Anthropic extended thinking 有 signature 校验)。</p><h3 id="maxiterations-3" tabindex="-1"><code>maxIterations = 3</code> <a class="header-anchor" href="#maxiterations-3" aria-label="Permalink to "`maxIterations = 3`""></a></h3><p>三种 loop 共用护栏:tool 描述可能诱导模型反复调用同一 tool。3 轮后强制退出。</p><h2 id="缓冲检测-→-流式检测-api-chat-用-ssestreamprocessor" tabindex="-1">缓冲检测 → 流式检测(<code>/api/chat</code> 用 SSEStreamProcessor) <a class="header-anchor" href="#缓冲检测-→-流式检测-api-chat-用-ssestreamprocessor" aria-label="Permalink to "缓冲检测 → 流式检测(`/api/chat` 用 SSEStreamProcessor)""></a></h2><p><code>/v1/*</code> 不再有缓冲检测(纯代理)。<code>/api/chat</code> 的 SSE 路径统一处理:</p><ol><li><strong>Phase 1</strong>:<code>SSEStreamProcessor</code> 边转发原始 SSE 边增量解析,客户端实时收到与上游完全一致的流</li><li><strong>Stream end</strong>:<code>processor.hasInjectedToolUse()</code> 检测命中(<code>stopReason === 'tool_use'</code> + <code>findInjectedCall</code> 找到注入 tool)</li><li><strong>Phase 2</strong>(命中时):发 <code>proxy_status</code> SSE 事件 + <code>runStreamingInjectLoop</code> 流式续接</li></ol><p><strong>不再缓冲上游响应</strong>——这是与旧版的关键区别。旧版用 <code>await response.text()</code> 全量缓冲再检测;新版用 <code>SSEStreamProcessor</code> 实时解析,stream end 后检测。客户端始终收到原始 SSE(零延迟)。</p><h3 id="ssestreamprocessor" tabindex="-1">SSEStreamProcessor <a class="header-anchor" href="#ssestreamprocessor" aria-label="Permalink to "SSEStreamProcessor""></a></h3><p><code>sse-stream-processor.ts</code>:</p><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>SSEStreamProcessor</span></span>
|
|
104
|
+
<span class="line"><span>├── raw: ServerResponse // reply.raw,用于转发 chunk</span></span>
|
|
105
|
+
<span class="line"><span>├── events: any[] // 解析出的 JSON 事件(供 DB 记录)</span></span>
|
|
106
|
+
<span class="line"><span>├── contentBlocks: Map<number, ParsedBlock> // 增量累积的 content block 状态</span></span>
|
|
107
|
+
<span class="line"><span>├── stopReason: string // 从 message_delta 捕获</span></span>
|
|
108
|
+
<span class="line"><span>├── usage: any // 从 message_delta 捕获</span></span>
|
|
109
|
+
<span class="line"><span>├── leftover: string // 跨 chunk 的不完整行缓冲</span></span>
|
|
110
|
+
<span class="line"><span>│</span></span>
|
|
111
|
+
<span class="line"><span>├── processChunk(chunk) // raw.write(chunk) + 解析 data: 行 + processEvent</span></span>
|
|
112
|
+
<span class="line"><span>├── flushLeftover() // stream end 后处理残留行</span></span>
|
|
113
|
+
<span class="line"><span>├── processEvent(e) // 增量更新 contentBlocks/stopReason</span></span>
|
|
114
|
+
<span class="line"><span>├── getParsedUpstream() // 从累积状态构建 ParsedUpstream</span></span>
|
|
115
|
+
<span class="line"><span>└── hasInjectedToolUse() // stopReason=tool_use + findInjectedCall</span></span></code></pre></div><p><code>processEvent</code> 逻辑与 <code>parseUpstreamEvents</code> 完全一致,改为逐事件调用。跨 chunk 行边界通过 <code>leftover</code> 缓冲处理。</p><h2 id="合成-sse-sse-writer-ts" tabindex="-1">合成 SSE <code>sse-writer.ts</code> <a class="header-anchor" href="#合成-sse-sse-writer-ts" aria-label="Permalink to "合成 SSE `sse-writer.ts`""></a></h2><p><code>writeSSEEvent(raw, event)</code> 按 Anthropic 双行格式写:<code>event: <type>\ndata: <json>\n\n</code>。</p><p><code>writeTextAsSSE(raw, text, messageId)</code> 生成一次完整的合法事件流:</p><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>message_start (usage 全 0,model 'auto')</span></span>
|
|
116
|
+
<span class="line"><span>content_block_start (text 块 0)</span></span>
|
|
117
|
+
<span class="line"><span>content_block_delta (text_delta → 一次性把全部文本吐出)</span></span>
|
|
118
|
+
<span class="line"><span>content_block_stop</span></span>
|
|
119
|
+
<span class="line"><span>message_delta (stop_reason: end_turn)</span></span>
|
|
120
|
+
<span class="line"><span>message_stop</span></span></code></pre></div><p><code>messageId</code> 由 proxy 自己生成(如 <code>msg_inject_<cloudRequestId></code> / <code>msg_poc_<requestId></code>):合成流没有上游 id,全程用一致的假 id。</p><p><code>shouldMock(body)</code>:<code>__MOCK__</code> PoC 触发词(详见 <a href="./proxy.html#分支-1mock">proxy.md</a>)。</p><h2 id="与前端的关系" tabindex="-1">与前端的关系 <a class="header-anchor" href="#与前端的关系" aria-label="Permalink to "与前端的关系""></a></h2><p>前端<strong>不知道</strong> <code>/api/chat</code> 的 SSE 响应经历了几轮 tool 调用。请求历史面板里看到的是:</p><ul><li><code>/v1/*</code> 请求:单条记录(原始请求 + events)</li><li><code>/api/chat</code> 请求:可能 1~N 条记录(第 1 条 = 原始 SSE,第 2~N 条 = inject loop 迭代)</li><li>所有记录共享同一 <code>sessionId</code>,通过 <code>compositeId = sessionId:requestId</code> 关联</li><li><code>response_events</code> 与客户端实际收到的一致(原始 SSE 透传)</li></ul><p><code>/api/chat</code> 的 inject loop 迭代独立记录到 DB——与旧版(合并到第 1 条)不同,面板可以看到每次 tool 调用的详情。</p><h2 id="相关文档" tabindex="-1">相关文档 <a class="header-anchor" href="#相关文档" aria-label="Permalink to "相关文档""></a></h2><ul><li><code>/api/chat</code> 的 tool 注入流程 → <a href="./chat-api.html">chat-api.md</a></li><li><code>/v1/*</code> 纯代理路径 → <a href="./proxy.html">proxy.md</a></li><li>MCP server 管理 → <a href="./ui-routes.html#mcp-server-管理-api">ui-routes.md</a></li><li>供应商 tier model → <a href="./providers.html">providers.md</a></li></ul></div></div></main><footer class="VPDocFooter" data-v-b2c5ca34 data-v-2cae581d><!--[--><!--]--><!----><nav class="prev-next" aria-labelledby="doc-footer-aria-label" data-v-2cae581d><span class="visually-hidden" id="doc-footer-aria-label" data-v-2cae581d>Pager</span><div class="pager" data-v-2cae581d><a class="VPLink link pager-link prev" href="/docs/proxy-core/stats.html" data-v-2cae581d><!--[--><span class="desc" data-v-2cae581d>Previous page</span><span class="title" data-v-2cae581d>统计与历史</span><!--]--></a></div><div class="pager" data-v-2cae581d><a class="VPLink link pager-link next" href="/docs/proxy-core/pm2.html" data-v-2cae581d><!--[--><span class="desc" data-v-2cae581d>Next page</span><span class="title" data-v-2cae581d>PM2 集成</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
|
|
121
|
+
<script>window.__VP_HASH_MAP__=JSON.parse("{\"index.md\":\"p9floL_p\",\"overview.md\":\"BKVdLT3o\",\"packages-utils.md\":\"CvgYp5Ll\",\"proxy-admin_env-and-build.md\":\"xkeQNYS3\",\"proxy-admin_index.md\":\"DBLI0n0G\",\"proxy-admin_layout-and-menu.md\":\"BKGcu49p\",\"proxy-admin_pages.md\":\"m_fGvvOH\",\"proxy-admin_routing-and-auth.md\":\"DZsdFese\",\"proxy-admin_services-and-mock.md\":\"sdNPcf25\",\"proxy-cli_api-layer.md\":\"GfdbtEUx\",\"proxy-cli_app-shell.md\":\"Do18CYhW\",\"proxy-cli_components-common.md\":\"Dd6Yazvz\",\"proxy-cli_components-config.md\":\"B81Y3ta2\",\"proxy-cli_components-requests.md\":\"vN8IrwL_\",\"proxy-cli_components-stats.md\":\"B90esaNa\",\"proxy-cli_components-status.md\":\"C5lAzS2c\",\"proxy-cli_data-hooks.md\":\"w9Oe1OD3\",\"proxy-cli_index.md\":\"BA6g8QD-\",\"proxy-cli_utils-and-i18n.md\":\"Ch_e-g_o\",\"proxy-core_chat-api.md\":\"DHihiBxR\",\"proxy-core_cli.md\":\"DCFYX4Bd\",\"proxy-core_config.md\":\"BXSOyxYE\",\"proxy-core_data-layer.md\":\"Cdomb3Gz\",\"proxy-core_entry.md\":\"BZmIcCUM\",\"proxy-core_index.md\":\"BM6VV5Qe\",\"proxy-core_pm2.md\":\"DUhWEtgn\",\"proxy-core_providers.md\":\"CbDZ6b_7\",\"proxy-core_proxy.md\":\"CzQYmX6X\",\"proxy-core_stats.md\":\"BsM6wuK_\",\"proxy-core_tool.md\":\"B6Le1nQr\",\"proxy-core_ui-routes.md\":\"Dyqkeoiv\",\"proxy-core_utilities.md\":\"C18pjnkz\",\"quickstart.md\":\"Cy_FmXIY\",\"request-flow.md\":\"C1fyxdPE\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"zh-CN\",\"dir\":\"ltr\",\"title\":\"CC Monitoring\",\"description\":\"Claude Code 代理 + 监控系统文档\",\"base\":\"/docs/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"nav\":[{\"text\":\"概览\",\"link\":\"/overview\"},{\"text\":\"快速开始\",\"link\":\"/quickstart\"},{\"text\":\"Proxy Core\",\"link\":\"/proxy-core/\"},{\"text\":\"Proxy CLI\",\"link\":\"/proxy-cli/\"},{\"text\":\"Proxy Admin\",\"link\":\"/proxy-admin/\"}],\"sidebar\":{\"/proxy-core/\":[{\"text\":\"Proxy Core\",\"items\":[{\"text\":\"模块概览\",\"link\":\"/proxy-core/\"},{\"text\":\"入口与启动\",\"link\":\"/proxy-core/entry\"},{\"text\":\"CLI 命令\",\"link\":\"/proxy-core/cli\"},{\"text\":\"代理转发\",\"link\":\"/proxy-core/proxy\"},{\"text\":\"UI 路由\",\"link\":\"/proxy-core/ui-routes\"},{\"text\":\"供应商管理\",\"link\":\"/proxy-core/providers\"},{\"text\":\"配置与日志\",\"link\":\"/proxy-core/config\"},{\"text\":\"数据层\",\"link\":\"/proxy-core/data-layer\"},{\"text\":\"统计与历史\",\"link\":\"/proxy-core/stats\"},{\"text\":\"工具注入\",\"link\":\"/proxy-core/tool\"},{\"text\":\"PM2 集成\",\"link\":\"/proxy-core/pm2\"},{\"text\":\"工具函数\",\"link\":\"/proxy-core/utilities\"},{\"text\":\"Chat API\",\"link\":\"/proxy-core/chat-api\"}]}],\"/proxy-cli/\":[{\"text\":\"Proxy CLI\",\"items\":[{\"text\":\"模块概览\",\"link\":\"/proxy-cli/\"},{\"text\":\"应用外壳\",\"link\":\"/proxy-cli/app-shell\"},{\"text\":\"数据 Hooks\",\"link\":\"/proxy-cli/data-hooks\"},{\"text\":\"API 层\",\"link\":\"/proxy-cli/api-layer\"},{\"text\":\"状态组件\",\"link\":\"/proxy-cli/components-status\"},{\"text\":\"统计组件\",\"link\":\"/proxy-cli/components-stats\"},{\"text\":\"请求组件\",\"link\":\"/proxy-cli/components-requests\"},{\"text\":\"配置组件\",\"link\":\"/proxy-cli/components-config\"},{\"text\":\"通用组件\",\"link\":\"/proxy-cli/components-common\"},{\"text\":\"工具与国际化\",\"link\":\"/proxy-cli/utils-and-i18n\"}]}],\"/proxy-admin/\":[{\"text\":\"Proxy Admin\",\"items\":[{\"text\":\"模块概览\",\"link\":\"/proxy-admin/\"},{\"text\":\"路由与认证\",\"link\":\"/proxy-admin/routing-and-auth\"},{\"text\":\"布局与菜单\",\"link\":\"/proxy-admin/layout-and-menu\"},{\"text\":\"页面\",\"link\":\"/proxy-admin/pages\"},{\"text\":\"服务与 Mock\",\"link\":\"/proxy-admin/services-and-mock\"},{\"text\":\"环境与构建\",\"link\":\"/proxy-admin/env-and-build\"}]}]},\"siteTitle\":\"CC Monitoring\",\"search\":{\"provider\":\"local\"}},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
|
|
122
|
+
|
|
123
|
+
</body>
|
|
124
|
+
</html>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN" dir="ltr">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>proxy-core / UI Routes | CC Monitoring</title>
|
|
7
|
+
<meta name="description" content="Claude Code 代理 + 监控系统文档">
|
|
8
|
+
<meta name="generator" content="VitePress v1.6.4">
|
|
9
|
+
<link rel="preload stylesheet" href="/docs/assets/style.DU_kNMWy.css" as="style">
|
|
10
|
+
<link rel="preload stylesheet" href="/docs/vp-icons.css" as="style">
|
|
11
|
+
|
|
12
|
+
<script type="module" src="/docs/assets/app.BxFOH0P1.js"></script>
|
|
13
|
+
<link rel="preload" href="/docs/assets/inter-roman-latin.Di8DUHzh.woff2" as="font" type="font/woff2" crossorigin="">
|
|
14
|
+
<link rel="modulepreload" href="/docs/assets/chunks/theme.BLEczZWC.js">
|
|
15
|
+
<link rel="modulepreload" href="/docs/assets/chunks/framework.D_Xv2a7c.js">
|
|
16
|
+
<link rel="modulepreload" href="/docs/assets/proxy-core_ui-routes.md.Dyqkeoiv.lean.js">
|
|
17
|
+
<script id="check-dark-mode">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"auto",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
|
|
18
|
+
<script id="check-mac-os">document.documentElement.classList.toggle("mac",/Mac|iPhone|iPod|iPad/i.test(navigator.platform));</script>
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
<div id="app"><div class="Layout" data-v-0fd5f79a><!--[--><!--]--><!--[--><span tabindex="-1" data-v-13bcab17></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-13bcab17>Skip to content</a><!--]--><!----><header class="VPNav" data-v-0fd5f79a data-v-221ddc7b><div class="VPNavBar" data-v-221ddc7b data-v-6a82b350><div class="wrapper" data-v-6a82b350><div class="container" data-v-6a82b350><div class="title" data-v-6a82b350><div class="VPNavBarTitle has-sidebar" data-v-6a82b350 data-v-98825d79><a class="title" href="/docs/" data-v-98825d79><!--[--><!--]--><!----><span data-v-98825d79>CC Monitoring</span><!--[--><!--]--></a></div></div><div class="content" data-v-6a82b350><div class="content-body" data-v-6a82b350><!--[--><!--]--><div class="VPNavBarSearch search" data-v-6a82b350><!--[--><!----><div id="local-search"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><span class="vp-icon DocSearch-Search-Icon"></span><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-6a82b350 data-v-2fa375b4><span id="main-nav-aria-label" class="visually-hidden" data-v-2fa375b4> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/docs/overview.html" tabindex="0" data-v-2fa375b4 data-v-88169469><!--[--><span data-v-88169469>概览</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/docs/quickstart.html" tabindex="0" data-v-2fa375b4 data-v-88169469><!--[--><span data-v-88169469>快速开始</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/docs/proxy-core/" tabindex="0" data-v-2fa375b4 data-v-88169469><!--[--><span data-v-88169469>Proxy Core</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/docs/proxy-cli/" tabindex="0" data-v-2fa375b4 data-v-88169469><!--[--><span data-v-88169469>Proxy CLI</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/docs/proxy-admin/" tabindex="0" data-v-2fa375b4 data-v-88169469><!--[--><span data-v-88169469>Proxy Admin</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-6a82b350 data-v-c7859fea><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-c7859fea data-v-458e2939 data-v-2fadb15f><span class="check" data-v-2fadb15f><span class="icon" data-v-2fadb15f><!--[--><span class="vpi-sun sun" data-v-458e2939></span><span class="vpi-moon moon" data-v-458e2939></span><!--]--></span></span></button></div><!----><div class="VPFlyout VPNavBarExtra extra" data-v-6a82b350 data-v-4a332738 data-v-3416cc30><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-3416cc30><span class="vpi-more-horizontal icon" data-v-3416cc30></span></button><div class="menu" data-v-3416cc30><div class="VPMenu" data-v-3416cc30 data-v-70b241a4><!----><!--[--><!--[--><!----><div class="group" data-v-4a332738><div class="item appearance" data-v-4a332738><p class="label" data-v-4a332738>Appearance</p><div class="appearance-action" data-v-4a332738><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-4a332738 data-v-458e2939 data-v-2fadb15f><span class="check" data-v-2fadb15f><span class="icon" data-v-2fadb15f><!--[--><span class="vpi-sun sun" data-v-458e2939></span><span class="vpi-moon moon" data-v-458e2939></span><!--]--></span></span></button></div></div></div><!----><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-6a82b350 data-v-2b9279da><span class="container" data-v-2b9279da><span class="top" data-v-2b9279da></span><span class="middle" data-v-2b9279da></span><span class="bottom" data-v-2b9279da></span></span></button></div></div></div></div><div class="divider" data-v-6a82b350><div class="divider-line" data-v-6a82b350></div></div></div><!----></header><div class="VPLocalNav has-sidebar empty" data-v-0fd5f79a data-v-751e8533><div class="container" data-v-751e8533><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-751e8533><span class="vpi-align-left menu-icon" data-v-751e8533></span><span class="menu-text" data-v-751e8533>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-751e8533 data-v-afd8288e><button data-v-afd8288e>Return to top</button><!----></div></div></div><aside class="VPSidebar" data-v-0fd5f79a data-v-711af969><div class="curtain" data-v-711af969></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-711af969><span class="visually-hidden" id="sidebar-aria-label" data-v-711af969> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="no-transition group" data-v-4ebb8294><section class="VPSidebarItem level-0 has-active" data-v-4ebb8294 data-v-04203d25><div class="item" role="button" tabindex="0" data-v-04203d25><div class="indicator" data-v-04203d25></div><h2 class="text" data-v-04203d25>Proxy Core</h2><!----></div><div class="items" data-v-04203d25><!--[--><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/" data-v-04203d25><!--[--><p class="text" data-v-04203d25>模块概览</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/entry.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>入口与启动</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/cli.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>CLI 命令</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/proxy.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>代理转发</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/ui-routes.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>UI 路由</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/providers.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>供应商管理</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/config.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>配置与日志</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/data-layer.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>数据层</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/stats.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>统计与历史</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/tool.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>工具注入</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/pm2.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>PM2 集成</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/utilities.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>工具函数</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-04203d25><div class="item" data-v-04203d25><div class="indicator" data-v-04203d25></div><a class="VPLink link link" href="/docs/proxy-core/chat-api.html" data-v-04203d25><!--[--><p class="text" data-v-04203d25>Chat API</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-0fd5f79a data-v-3aa167b9><div class="VPDoc has-sidebar has-aside" data-v-3aa167b9 data-v-b2c5ca34><!--[--><!--]--><div class="container" data-v-b2c5ca34><div class="aside" data-v-b2c5ca34><div class="aside-curtain" data-v-b2c5ca34></div><div class="aside-container" data-v-b2c5ca34><div class="aside-content" data-v-b2c5ca34><div class="VPDocAside" data-v-b2c5ca34 data-v-af7efbb6><!--[--><!--]--><!--[--><!--]--><nav aria-labelledby="doc-outline-aria-label" class="VPDocAsideOutline" data-v-af7efbb6 data-v-db542fa8><div class="content" data-v-db542fa8><div class="outline-marker" data-v-db542fa8></div><div aria-level="2" class="outline-title" id="doc-outline-aria-label" role="heading" data-v-db542fa8>On this page</div><ul class="VPDocOutlineItem root" data-v-db542fa8 data-v-1d036821><!--[--><!--]--></ul></div></nav><!--[--><!--]--><div class="spacer" data-v-af7efbb6></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-b2c5ca34><div class="content-container" data-v-b2c5ca34><!--[--><!--]--><main class="main" data-v-b2c5ca34><div style="position:relative;" class="vp-doc _docs_proxy-core_ui-routes" data-v-b2c5ca34><div><h1 id="proxy-core-ui-routes" tabindex="-1">proxy-core / UI Routes <a class="header-anchor" href="#proxy-core-ui-routes" aria-label="Permalink to "proxy-core / UI Routes""></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/ui-route.ts</code></li><li><code>apps/proxy-core/src/server/data-push.ts</code></li><li><code>apps/proxy-core/src/server/auth.ts</code></li><li><code>apps/proxy-core/src/utils/status-history.ts</code></li></ul><p><code>setupUiRoutes(fastify)</code>(<code>ui-route.ts:18</code>)在 <code>setupProxy</code> 之前注册,避免 <code>/ui/*</code> 被通配的 <code>/v1/*</code> 吞掉。它挂载:</p><ul><li>REST 端点 9 条 + 供应商 API(<code>providers-route.ts</code>)+ MCP 管理 API(<code>mcp-route.ts</code>)</li><li>Exec API(<code>ui-route.ts</code> 内联)</li><li>两个 WebSocket 通道(<code>/ui/api/logs/stream</code>、<code>/ui/api/ws</code>)</li><li>静态资源(<code>/ui/</code>)</li></ul><h2 id="rest-端点" tabindex="-1">REST 端点 <a class="header-anchor" href="#rest-端点" aria-label="Permalink to "REST 端点""></a></h2><table tabindex="0"><thead><tr><th>方法</th><th>路径</th><th>认证</th><th>功能</th></tr></thead><tbody><tr><td>GET</td><td><code>/ui/api/status</code></td><td>否</td><td>PM2 进程状态(cpu/memory/uptime/restarts)</td></tr><tr><td>GET</td><td><code>/ui/api/status/history?duration=<ms></code></td><td>否</td><td>CPU/内存滑动窗口(默认 1h)</td></tr><tr><td>GET</td><td><code>/ui/api/config</code></td><td>否</td><td>当前配置 + 版本信息 + 配置文件路径</td></tr><tr><td>POST</td><td><code>/ui/api/config</code></td><td><strong>是</strong></td><td>Zod 校验 + 保存 + 可选延迟 reload</td></tr><tr><td>GET</td><td><code>/ui/api/requests?limit=1000</code></td><td>否</td><td>请求摘要列表</td></tr><tr><td>GET</td><td><code>/ui/api/requests/:id</code></td><td>否</td><td>单条请求详情(含 events)</td></tr><tr><td>GET</td><td><code>/ui/api/stats</code></td><td>否</td><td>预聚合 hourly(24 条) + daily(30 条)</td></tr><tr><td>GET</td><td><code>/ui/api/stats/today?since=<ISO></code></td><td>否</td><td>今日实时统计(按客户端零点)</td></tr><tr><td>POST</td><td><code>/ui/api/control</code></td><td><strong>是</strong></td><td>start/stop/restart 进程</td></tr><tr><td>GET</td><td><code>/ui/api/providers</code></td><td>否</td><td>供应商列表</td></tr><tr><td>POST</td><td><code>/ui/api/providers</code></td><td><strong>是</strong></td><td>批量保存(destructive replace)</td></tr><tr><td>GET</td><td><code>/ui/api/mcp/servers</code></td><td>否</td><td>MCP server 列表(含连接状态)</td></tr><tr><td>POST</td><td><code>/ui/api/mcp/servers</code></td><td><strong>是</strong></td><td>创建 MCP server</td></tr><tr><td>PATCH</td><td><code>/ui/api/mcp/servers/:id</code></td><td><strong>是</strong></td><td>更新 MCP server(触发 reconnect)</td></tr><tr><td>DELETE</td><td><code>/ui/api/mcp/servers/:id</code></td><td><strong>是</strong></td><td>删除 MCP server(先 disconnect)</td></tr><tr><td>GET</td><td><code>/ui/api/mcp/tools</code></td><td>否</td><td>已发现的 MCP tool 列表</td></tr><tr><td>POST</td><td><code>/ui/api/mcp/servers/:id/reconnect</code></td><td><strong>是</strong></td><td>手动 reconnect MCP server</td></tr><tr><td>GET</td><td><code>/ui</code></td><td>否</td><td>返回 index.html</td></tr><tr><td>GET</td><td><code>/ui/**</code></td><td>否</td><td>静态资源(wildcard)</td></tr></tbody></table><p>「认证」列指是否走 <code>checkAuth</code>(见 <a href="#auth-token">auth</a>)。</p><h3 id="get-ui-api-status" tabindex="-1">GET /ui/api/status <a class="header-anchor" href="#get-ui-api-status" aria-label="Permalink to "GET /ui/api/status""></a></h3><p><code>ui-route.ts:44-54</code>。返回:</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</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:#005CC5;--shiki-dark:#79B8FF;">"status"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"online"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"cpu"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0.3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"memory"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">45678912</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"uptime"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">1720029000000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"restarts"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span></code></pre></div><p><code>status</code> 未查到进程时为 <code>'stopped'</code>。<code>uptime</code> 是 PM2 内部的 <code>pm_uptime</code>(进程启动的 Unix 毫秒)。</p><h3 id="get-ui-api-status-history" tabindex="-1">GET /ui/api/status/history <a class="header-anchor" href="#get-ui-api-status-history" aria-label="Permalink to "GET /ui/api/status/history""></a></h3><p><code>ui-route.ts:56-59</code>。<code>duration</code> 是回看时间窗(毫秒,默认 3600000)。数据来自内存中的 <code>statusHistory</code> 单例(下节),<strong>不持久化</strong>——重启进程后清零。</p><h3 id="get-ui-api-config" tabindex="-1">GET /ui/api/config <a class="header-anchor" href="#get-ui-api-config" aria-label="Permalink to "GET /ui/api/config""></a></h3><p><code>ui-route.ts:61-65</code>。合成 <code>{ config, versions, configPath }</code>:</p><ul><li><code>config</code>:Zod 校验后的完整对象</li><li><code>versions</code>:<code>{ projectVersion, claudeVersion }</code>(<code>utils/version.ts</code>)</li><li><code>configPath</code>:<code>getConfigPath()</code> 返回值,供 UI 显示当前用哪份 config</li></ul><h3 id="post-ui-api-config" tabindex="-1">POST /ui/api/config <a class="header-anchor" href="#post-ui-api-config" aria-label="Permalink to "POST /ui/api/config""></a></h3><p><code>ui-route.ts:67-112</code>。请求体:<code>{ ...configFields, reload?: boolean }</code>。</p><ol><li><code>checkAuth</code>:Bearer Token 校验,失败 401</li><li>抽出 <code>reload</code> 字段,剩余对象走 <code>validateConfig</code>(动态 import schema)</li><li>校验失败 → 400 + <code>{ success: false, errors: [{field, message}] }</code></li><li><code>saveConfig(result.data)</code> 写回文件</li><li>若 <code>reload === true</code> 且当前 PM2 状态 online:<code>setTimeout(..., 5000)</code> 延迟 5s 后 <code>reloadProcess</code>(先返回响应,让客户端不因重启而丢连接)</li><li>返回 <code>{ success: true, reloaded, restartingIn?: 5000 }</code></li></ol><h3 id="get-ui-api-requests" tabindex="-1">GET /ui/api/requests <a class="header-anchor" href="#get-ui-api-requests" aria-label="Permalink to "GET /ui/api/requests""></a></h3><p><code>ui-route.ts:129-141</code>。<code>limit</code> 默认 1000。返回 <code>{ requests: RequestSummary[] }</code>(<code>utils/request-writer.ts:152-185</code>)。摘要字段:<code>requestId / sessionId / compositeId / timestamp / method / path / statusCode / responseTime / apiKey / proxyApiKey / baseUrl / model / usage</code>。</p><h3 id="get-ui-api-requests-id" tabindex="-1">GET /ui/api/requests/:id <a class="header-anchor" href="#get-ui-api-requests-id" aria-label="Permalink to "GET /ui/api/requests/:id""></a></h3><p><code>ui-route.ts:115-126</code>。<code>id</code> 是 <code>compositeId</code>(<code>sessionId:requestId</code>)。调用 <code>loadRequestDB</code> 反解表行到 <code>RequestData</code>(含 headers/body/events)。找不到 → 404。</p><h3 id="get-ui-api-stats" tabindex="-1">GET /ui/api/stats <a class="header-anchor" href="#get-ui-api-stats" aria-label="Permalink to "GET /ui/api/stats""></a></h3><p><code>ui-route.ts:144-154</code>。从 <code>stats</code> 表读预聚合数据(详见 <a href="./stats.html">stats.md</a>)。</p><h3 id="get-ui-api-stats-today" tabindex="-1">GET /ui/api/stats/today <a class="header-anchor" href="#get-ui-api-stats-today" aria-label="Permalink to "GET /ui/api/stats/today""></a></h3><p><code>ui-route.ts:158-175</code>。查询串 <code>since</code> 是 ISO 时间戳(客户端本地零点);缺失或非法则用服务器本地零点。返回 <code>TodayStats</code>(tokens 汇总 + 请求数 + 按 model 计数)。</p><h3 id="post-ui-api-control" tabindex="-1">POST /ui/api/control <a class="header-anchor" href="#post-ui-api-control" aria-label="Permalink to "POST /ui/api/control""></a></h3><p><code>ui-route.ts:178-217</code>。请求体 <code>{ action: 'start' | 'stop' | 'restart' }</code>。</p><ul><li><code>start</code>:<code>getCallerDirname('../server') + '../../ecosystem.config.cjs'</code> 拼出 ecosystem 路径,<code>startProcess()</code></li><li><code>stop</code>:<code>stopProcess('cc-monitoring')</code></li><li><code>restart</code>:<code>setTimeout(1000, reloadProcess)</code> 延迟 1s 重启,同步返回 <code>{ success: true, restartingIn: 1000 }</code></li><li>其它 → 400</li></ul><p><code>restart</code> 延迟的目的与 config POST 相同:让本次响应先落地,再让 PM2 接手。</p><h3 id="mcp-server-管理-api" tabindex="-1">MCP Server 管理 API <a class="header-anchor" href="#mcp-server-管理-api" aria-label="Permalink to "MCP Server 管理 API""></a></h3><p><code>mcp-route.ts</code> 提供 6 个端点管理 MCP server 配置与连接。</p><p><strong>GET /ui/api/mcp/servers</strong>:列出所有 MCP server + 实时连接状态。<code>listMcpServers(db)</code> 返回 DB 行,<code>mcpManager.getConnectionsInfo()</code> 提供 status + toolCount,两表 join。</p><p><strong>POST /ui/api/mcp/servers</strong>:创建新 server。认证 required。<code>createMcpServer(db, input)</code> 写 DB + <code>mcpManager.addServer(created)</code> 连接。</p><p><strong>PATCH /ui/api/mcp/servers/:id</strong>:更新 server。认证 required。<code>updateMcpServer(db, id, partial)</code> 合并更新 + <code>mcpManager.reconnectServer(id)</code> 重建连接。</p><p><strong>DELETE /ui/api/mcp/servers/:id</strong>:删除 server。认证 required。先 <code>mcpManager.removeServer(id)</code> 断连接,再 <code>deleteMcpServer(db, id)</code> 清 DB。</p><p><strong>GET /ui/api/mcp/tools</strong>:列出所有已发现 MCP tool(从 <code>mcpManager.getMcpTools()</code> 读缓存)。返回 <code>{ tools }</code>(name + description + input_schema)。</p><p><strong>POST /ui/api/mcp/servers/:id/reconnect</strong>:手动 reconnect。认证 required。<code>mcpManager.reconnectServer(id)</code>。</p><h3 id="exec-api" tabindex="-1">Exec API <a class="header-anchor" href="#exec-api" aria-label="Permalink to "Exec API""></a></h3><p><code>ui-route.ts</code> 内联注册 <code>POST /ui/api/exec</code>。认证 required。让 Claude Code 通过 proxy 执行本地命令。</p><p>请求体:<code>{ tool: 'claude', prompt: string, stream?: boolean, sessionId?: string, allowedTools?: string[] }</code>。</p><p>响应:stream=true 时 SSE 流式输出 Claude Code 执行结果;stream=false 时 JSON。</p><h2 id="checkauth" tabindex="-1">checkAuth <a class="header-anchor" href="#checkauth" aria-label="Permalink to "checkAuth""></a></h2><p><code>server/auth.ts</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;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> checkAuth</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">request</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">config</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;"> boolean</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
22
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> token</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> config?.auth?.token </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;">UI_AUTH_TOKEN</span></span>
|
|
23
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</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;">token) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> true</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 未配置 → 放行(开发模式)</span></span>
|
|
24
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> authHeader</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> request.headers?.authorization</span></span>
|
|
25
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</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;">authHeader) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> false</span></span>
|
|
26
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> provided</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> authHeader.</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;">'Bearer '</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
|
27
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> ?</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> authHeader.</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;">7</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
|
28
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> :</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> authHeader</span></span>
|
|
29
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> provided </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">===</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> token</span></span>
|
|
30
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>三种情况:</p><ul><li>未配置任何 token:完全无认证(本地开发默认状态)</li><li>config.auth.token 存在 → 用它</li><li>否则 fallback 到环境变量 <code>UI_AUTH_TOKEN</code>(PM2 生产部署常用)</li></ul><h2 id="两个-websocket-通道" tabindex="-1">两个 WebSocket 通道 <a class="header-anchor" href="#两个-websocket-通道" aria-label="Permalink to "两个 WebSocket 通道""></a></h2><p><code>ui-route.ts:25-42</code>:不用 <code>@fastify/websocket</code>(有多个既知问题),而是自建两个 <code>WebSocketServer({ noServer: true })</code>,挂到 <code>fastify.server.on('upgrade', ...)</code>:</p><ul><li><code>/ui/api/logs/stream</code> → <code>handleLogStream</code></li><li><code>/ui/api/ws</code> → <code>handleDataStream</code></li></ul><h3 id="handlelogstream-ui-route-ts-226-316" tabindex="-1">handleLogStream(<code>ui-route.ts:226-316</code>) <a class="header-anchor" href="#handlelogstream-ui-route-ts-226-316" aria-label="Permalink to "handleLogStream(`ui-route.ts:226-316`)""></a></h3><p>流程:</p><ol><li>检查 <code>config.logging.file</code> 可读;不可读时发一条 warn 消息给客户端然后 close</li><li>定义 <code>shouldFilterLog(line)</code>:解析 JSON,保留: <ul><li><code>type === 'request_started' | 'request_completed'</code></li><li><code>req.url / path / url</code> 以 <code>/v1/</code> 开头</li><li>存在 <code>requestBody / responseBody / responseType === 'stream/completed'</code></li><li><code>msg</code> 含指定关键词(<code>request body captured</code>、<code>fetching from remote server</code>、<code>response received</code>、<code>response stream completed</code>)</li><li>其余全部过滤掉</li></ul></li><li>初次连接:async 读整个日志文件 → <code>slice(-50)</code> → 过滤后逐行发送</li><li><code>fs.watch(logPath)</code>:<code>change</code> 事件时 async 读文件全文,从 <code>readPosition</code> 起截取增量,过滤后发送;更新 <code>readPosition = content.length</code></li><li><code>ws.on('close')</code> → <code>watcher.close()</code></li></ol><p><strong>日志文件轮转不完美</strong>:使用 <code>readPosition</code> 累加式跟踪;如果 Pino rotator 换新文件,<code>content.length <= readPosition</code> 会跳过更新,直到 watcher 触发新文件事件(<code>fs.watch</code> 对轮转的行为跨平台不一致)。</p><h3 id="handledatastream-ui-route-ts-319-393" tabindex="-1">handleDataStream(<code>ui-route.ts:319-393</code>) <a class="header-anchor" href="#handledatastream-ui-route-ts-319-393" aria-label="Permalink to "handleDataStream(`ui-route.ts:319-393`)""></a></h3><p>会话生命周期:</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;">stateDiagram-v2</span></span>
|
|
31
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [*] --> Connected: WS upgrade</span></span>
|
|
32
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Connected --> Alive: 加入 dataClients<br/>启动 3 个定时器</span></span>
|
|
33
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Alive --> Alive: 每 10s 发 ping<br/>lastPing=now</span></span>
|
|
34
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Alive --> Alive: 收任意消息 → lastPing=now</span></span>
|
|
35
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Alive --> Alive: 每 10s 推 status snapshot<br/>+ 采样 statusHistory</span></span>
|
|
36
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Alive --> Closed: 30s 无活动<br/>ws.close()</span></span>
|
|
37
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Alive --> Closed: 客户端主动 close</span></span>
|
|
38
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Alive --> Closed: error</span></span>
|
|
39
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Closed --> [*]: 3 个 clearInterval + removeDataClient</span></span></code></pre></div><p>三个定时器:</p><ul><li><code>heartbeatTimer</code>:每 10s 发 <code>{ type: 'ping', timestamp }</code>;同时 <code>lastPing = now</code></li><li><code>timeoutTimer</code>:每 5s 检查 <code>now - lastPing > 30000</code> → <code>ws.close()</code></li><li><code>statusTimer</code>:每 10s 取 PM2 状态 + <code>statusHistory.push({ timestamp, cpu, memory })</code> + 发送 <code>type: 'status', action: 'snapshot', data: { ... history: last1h }</code></li></ul><p>客户端任意消息(包括 pong)会刷 <code>lastPing</code>,避免误关。</p><h3 id="广播工具-server-data-push-ts" tabindex="-1">广播工具(<code>server/data-push.ts</code>) <a class="header-anchor" href="#广播工具-server-data-push-ts" aria-label="Permalink to "广播工具(`server/data-push.ts`)""></a></h3><p><code>dataClients: Set<WebSocket></code> 全局连接池。三个函数:</p><ul><li><code>addDataClient / removeDataClient</code>:<code>handleDataStream</code> 生命周期管理</li><li><code>pushNewRequest(summary)</code>:广播 <code>{ type: 'request', action: 'new', data: summary }</code>(<code>setupProxy</code> 的所有分支尾部都会调)</li><li><code>pushStatsNotify()</code>:广播 <code>{ type: 'stats', action: 'notify', data: { reason: 'file_updated' } }</code>(<strong>目前无调用点</strong>——为未来 stats 事件驱动预留)</li></ul><p><code>broadcast(msg)</code> 循环所有连接,只对 <code>readyState === WebSocket.OPEN</code> 的 socket <code>send</code>。</p><h2 id="静态资源" tabindex="-1">静态资源 <a class="header-anchor" href="#静态资源" aria-label="Permalink to "静态资源""></a></h2><p><code>ui-route.ts:396-404</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;"> serverDirname</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> getCallerDirname</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'../server'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
|
40
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> uiDistPath</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;">(serverDirname, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'../../../proxy-cli/dist'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
|
41
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">await</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> fastify.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">register</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(staticPlugin, {</span></span>
|
|
42
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> root: uiDistPath,</span></span>
|
|
43
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> prefix: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'/ui/'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
44
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> decorateReply: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
45
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> wildcard: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
46
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> maxAge: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0</span></span>
|
|
47
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div><ul><li><strong>路径推导</strong>:<code>utils/esm-dirname.ts</code> 让 <code>../server</code> 相对 <code>utils/</code> 得到 server 目录;再向上四层到 monorepo 的 <code>apps/proxy-cli/dist</code></li><li><strong><code>wildcard: true</code></strong>:SPA 路径兜底</li><li><strong><code>maxAge: 0</code></strong>:禁用浏览器缓存,保证开发/生产 UI 行为一致</li><li><strong><code>GET /ui</code>(无斜杠)</strong>:单独 handler(<code>ui-route.ts:220-223</code>)返回 <code>index.html</code>,避免 <code>staticPlugin</code> 对根路径的 302</li></ul><h2 id="statushistory-单例" tabindex="-1"><code>statusHistory</code> 单例 <a class="header-anchor" href="#statushistory-单例" aria-label="Permalink to "`statusHistory` 单例""></a></h2><p><code>utils/status-history.ts</code>:</p><ul><li><code>StatusHistoryStore.data: StatusDataPoint[]</code>(<code>{ timestamp, cpu, memory }</code>)</li><li><code>push(point)</code>:追加;超过 720 条(1h × 60min × 12 sample/min ≈ 5s 间隔)时裁掉最旧</li><li><code>getHistory(durationMs = 3600000)</code>:过滤 <code>timestamp >= now - duration</code></li><li>模块底部 <code>export const statusHistory = new StatusHistoryStore()</code> 单例</li></ul><p><strong>采样频率</strong>:由 <code>handleDataStream</code> 的 <code>statusTimer</code> 决定,实际是 10s 一采(不是 5s)。因此 720 条容量约等于 2 小时。</p><h2 id="相关文档" tabindex="-1">相关文档 <a class="header-anchor" href="#相关文档" aria-label="Permalink to "相关文档""></a></h2><ul><li>认证细节 → <a href="./utilities.html#server-auth">utilities.md</a></li><li>WebSocket 数据流的前端消费 → <a href="./../proxy-cli/data-hooks.html">../proxy-cli/data-hooks</a></li><li>request-writer 的字段映射 → <a href="./data-layer.html">data-layer.md</a></li></ul></div></div></main><footer class="VPDocFooter" data-v-b2c5ca34 data-v-2cae581d><!--[--><!--]--><!----><nav class="prev-next" aria-labelledby="doc-footer-aria-label" data-v-2cae581d><span class="visually-hidden" id="doc-footer-aria-label" data-v-2cae581d>Pager</span><div class="pager" data-v-2cae581d><a class="VPLink link pager-link prev" href="/docs/proxy-core/proxy.html" data-v-2cae581d><!--[--><span class="desc" data-v-2cae581d>Previous page</span><span class="title" data-v-2cae581d>代理转发</span><!--]--></a></div><div class="pager" data-v-2cae581d><a class="VPLink link pager-link next" href="/docs/proxy-core/providers.html" data-v-2cae581d><!--[--><span class="desc" data-v-2cae581d>Next page</span><span class="title" data-v-2cae581d>供应商管理</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
|
|
48
|
+
<script>window.__VP_HASH_MAP__=JSON.parse("{\"index.md\":\"p9floL_p\",\"overview.md\":\"BKVdLT3o\",\"packages-utils.md\":\"CvgYp5Ll\",\"proxy-admin_env-and-build.md\":\"xkeQNYS3\",\"proxy-admin_index.md\":\"DBLI0n0G\",\"proxy-admin_layout-and-menu.md\":\"BKGcu49p\",\"proxy-admin_pages.md\":\"m_fGvvOH\",\"proxy-admin_routing-and-auth.md\":\"DZsdFese\",\"proxy-admin_services-and-mock.md\":\"sdNPcf25\",\"proxy-cli_api-layer.md\":\"GfdbtEUx\",\"proxy-cli_app-shell.md\":\"Do18CYhW\",\"proxy-cli_components-common.md\":\"Dd6Yazvz\",\"proxy-cli_components-config.md\":\"B81Y3ta2\",\"proxy-cli_components-requests.md\":\"vN8IrwL_\",\"proxy-cli_components-stats.md\":\"B90esaNa\",\"proxy-cli_components-status.md\":\"C5lAzS2c\",\"proxy-cli_data-hooks.md\":\"w9Oe1OD3\",\"proxy-cli_index.md\":\"BA6g8QD-\",\"proxy-cli_utils-and-i18n.md\":\"Ch_e-g_o\",\"proxy-core_chat-api.md\":\"DHihiBxR\",\"proxy-core_cli.md\":\"DCFYX4Bd\",\"proxy-core_config.md\":\"BXSOyxYE\",\"proxy-core_data-layer.md\":\"Cdomb3Gz\",\"proxy-core_entry.md\":\"BZmIcCUM\",\"proxy-core_index.md\":\"BM6VV5Qe\",\"proxy-core_pm2.md\":\"DUhWEtgn\",\"proxy-core_providers.md\":\"CbDZ6b_7\",\"proxy-core_proxy.md\":\"CzQYmX6X\",\"proxy-core_stats.md\":\"BsM6wuK_\",\"proxy-core_tool.md\":\"B6Le1nQr\",\"proxy-core_ui-routes.md\":\"Dyqkeoiv\",\"proxy-core_utilities.md\":\"C18pjnkz\",\"quickstart.md\":\"Cy_FmXIY\",\"request-flow.md\":\"C1fyxdPE\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"zh-CN\",\"dir\":\"ltr\",\"title\":\"CC Monitoring\",\"description\":\"Claude Code 代理 + 监控系统文档\",\"base\":\"/docs/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"nav\":[{\"text\":\"概览\",\"link\":\"/overview\"},{\"text\":\"快速开始\",\"link\":\"/quickstart\"},{\"text\":\"Proxy Core\",\"link\":\"/proxy-core/\"},{\"text\":\"Proxy CLI\",\"link\":\"/proxy-cli/\"},{\"text\":\"Proxy Admin\",\"link\":\"/proxy-admin/\"}],\"sidebar\":{\"/proxy-core/\":[{\"text\":\"Proxy Core\",\"items\":[{\"text\":\"模块概览\",\"link\":\"/proxy-core/\"},{\"text\":\"入口与启动\",\"link\":\"/proxy-core/entry\"},{\"text\":\"CLI 命令\",\"link\":\"/proxy-core/cli\"},{\"text\":\"代理转发\",\"link\":\"/proxy-core/proxy\"},{\"text\":\"UI 路由\",\"link\":\"/proxy-core/ui-routes\"},{\"text\":\"供应商管理\",\"link\":\"/proxy-core/providers\"},{\"text\":\"配置与日志\",\"link\":\"/proxy-core/config\"},{\"text\":\"数据层\",\"link\":\"/proxy-core/data-layer\"},{\"text\":\"统计与历史\",\"link\":\"/proxy-core/stats\"},{\"text\":\"工具注入\",\"link\":\"/proxy-core/tool\"},{\"text\":\"PM2 集成\",\"link\":\"/proxy-core/pm2\"},{\"text\":\"工具函数\",\"link\":\"/proxy-core/utilities\"},{\"text\":\"Chat API\",\"link\":\"/proxy-core/chat-api\"}]}],\"/proxy-cli/\":[{\"text\":\"Proxy CLI\",\"items\":[{\"text\":\"模块概览\",\"link\":\"/proxy-cli/\"},{\"text\":\"应用外壳\",\"link\":\"/proxy-cli/app-shell\"},{\"text\":\"数据 Hooks\",\"link\":\"/proxy-cli/data-hooks\"},{\"text\":\"API 层\",\"link\":\"/proxy-cli/api-layer\"},{\"text\":\"状态组件\",\"link\":\"/proxy-cli/components-status\"},{\"text\":\"统计组件\",\"link\":\"/proxy-cli/components-stats\"},{\"text\":\"请求组件\",\"link\":\"/proxy-cli/components-requests\"},{\"text\":\"配置组件\",\"link\":\"/proxy-cli/components-config\"},{\"text\":\"通用组件\",\"link\":\"/proxy-cli/components-common\"},{\"text\":\"工具与国际化\",\"link\":\"/proxy-cli/utils-and-i18n\"}]}],\"/proxy-admin/\":[{\"text\":\"Proxy Admin\",\"items\":[{\"text\":\"模块概览\",\"link\":\"/proxy-admin/\"},{\"text\":\"路由与认证\",\"link\":\"/proxy-admin/routing-and-auth\"},{\"text\":\"布局与菜单\",\"link\":\"/proxy-admin/layout-and-menu\"},{\"text\":\"页面\",\"link\":\"/proxy-admin/pages\"},{\"text\":\"服务与 Mock\",\"link\":\"/proxy-admin/services-and-mock\"},{\"text\":\"环境与构建\",\"link\":\"/proxy-admin/env-and-build\"}]}]},\"siteTitle\":\"CC Monitoring\",\"search\":{\"provider\":\"local\"}},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
|
|
49
|
+
|
|
50
|
+
</body>
|
|
51
|
+
</html>
|