minimal-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +148 -0
- package/README.md +287 -0
- package/dist/main.js +4140 -0
- package/package.json +66 -0
- package/skills/algorithmic-art/LICENSE.txt +202 -0
- package/skills/algorithmic-art/SKILL.md +405 -0
- package/skills/algorithmic-art/templates/generator_template.js +223 -0
- package/skills/algorithmic-art/templates/viewer.html +599 -0
- package/skills/batch/SKILL.md +39 -0
- package/skills/canvas-design/LICENSE.txt +202 -0
- package/skills/canvas-design/SKILL.md +130 -0
- package/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
- package/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/skills/commit/SKILL.md +46 -0
- package/skills/compact/SKILL.md +27 -0
- package/skills/config/SKILL.md +94 -0
- package/skills/diff/SKILL.md +30 -0
- package/skills/docx/LICENSE.txt +30 -0
- package/skills/docx/SKILL.md +660 -0
- package/skills/docx/scripts/__init__.py +1 -0
- package/skills/docx/scripts/accept_changes.py +135 -0
- package/skills/docx/scripts/comment.py +318 -0
- package/skills/docx/scripts/convert_to_docx.py +140 -0
- package/skills/docx/scripts/office/__init__.py +1 -0
- package/skills/docx/scripts/office/helpers/__init__.py +0 -0
- package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/docx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/docx/scripts/office/pack.py +159 -0
- package/skills/docx/scripts/office/schemas/__init__.py +1 -0
- package/skills/docx/scripts/office/soffice.py +183 -0
- package/skills/docx/scripts/office/unpack.py +132 -0
- package/skills/docx/scripts/office/validate.py +111 -0
- package/skills/docx/scripts/office/validators/__init__.py +15 -0
- package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/base.py +847 -0
- package/skills/docx/scripts/office/validators/docx.py +446 -0
- package/skills/docx/scripts/office/validators/pptx.py +275 -0
- package/skills/docx/scripts/office/validators/redlining.py +247 -0
- package/skills/docx/scripts/templates/comments.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/skills/docx/scripts/templates/people.xml +3 -0
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/SKILL.md +42 -0
- package/skills/init/SKILL.md +238 -0
- package/skills/init/evals/evals.json +28 -0
- package/skills/mcp-builder/LICENSE.txt +202 -0
- package/skills/mcp-builder/SKILL.md +236 -0
- package/skills/mcp-builder/reference/evaluation.md +602 -0
- package/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/skills/mcp-builder/scripts/connections.py +151 -0
- package/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/skills/pdf/LICENSE.txt +30 -0
- package/skills/pdf/SKILL.md +314 -0
- package/skills/pdf/forms.md +294 -0
- package/skills/pdf/reference.md +612 -0
- package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
- package/skills/pdf/scripts/check_fillable_fields.py +11 -0
- package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
- package/skills/pdf/scripts/create_validation_image.py +37 -0
- package/skills/pdf/scripts/extract_form_field_info.py +122 -0
- package/skills/pdf/scripts/extract_form_structure.py +115 -0
- package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
- package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/skills/pptx/LICENSE.txt +30 -0
- package/skills/pptx/SKILL.md +232 -0
- package/skills/pptx/editing.md +205 -0
- package/skills/pptx/pptxgenjs.md +420 -0
- package/skills/pptx/scripts/__init__.py +0 -0
- package/skills/pptx/scripts/add_slide.py +195 -0
- package/skills/pptx/scripts/clean.py +286 -0
- package/skills/pptx/scripts/office/helpers/__init__.py +0 -0
- package/skills/pptx/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/pptx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/pptx/scripts/office/pack.py +159 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/ECMA-376_3rd_edition.zip +1 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/README.md +1 -0
- package/skills/pptx/scripts/office/schemas/mce/mce-core.xsd +1 -0
- package/skills/pptx/scripts/office/schemas/mce/mce-override.xsd +1 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/pptx/scripts/office/soffice.py +183 -0
- package/skills/pptx/scripts/office/unpack.py +132 -0
- package/skills/pptx/scripts/office/validate.py +111 -0
- package/skills/pptx/scripts/office/validators/__init__.py +15 -0
- package/skills/pptx/scripts/office/validators/base.py +847 -0
- package/skills/pptx/scripts/office/validators/docx.py +446 -0
- package/skills/pptx/scripts/office/validators/pptx.py +275 -0
- package/skills/pptx/scripts/office/validators/redlining.py +247 -0
- package/skills/pptx/scripts/thumbnail.py +289 -0
- package/skills/simplify/SKILL.md +52 -0
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/SKILL.md +485 -0
- package/skills/skill-creator/agents/analyzer.md +274 -0
- package/skills/skill-creator/agents/comparator.md +202 -0
- package/skills/skill-creator/agents/grader.md +223 -0
- package/skills/skill-creator/assets/eval_review.html +146 -0
- package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/skill-creator/references/schemas.md +430 -0
- package/skills/skill-creator/scripts/__init__.py +0 -0
- package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/skill-creator/scripts/generate_report.py +326 -0
- package/skills/skill-creator/scripts/improve_description.py +247 -0
- package/skills/skill-creator/scripts/package_skill.py +136 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/run_eval.py +310 -0
- package/skills/skill-creator/scripts/run_loop.py +328 -0
- package/skills/skill-creator/scripts/utils.py +47 -0
- package/skills/theme-factory/LICENSE.txt +202 -0
- package/skills/theme-factory/SKILL.md +59 -0
- package/skills/theme-factory/theme-showcase.pdf +0 -0
- package/skills/theme-factory/themes/arctic-frost.md +19 -0
- package/skills/theme-factory/themes/botanical-garden.md +19 -0
- package/skills/theme-factory/themes/desert-rose.md +19 -0
- package/skills/theme-factory/themes/forest-canopy.md +19 -0
- package/skills/theme-factory/themes/golden-hour.md +19 -0
- package/skills/theme-factory/themes/midnight-galaxy.md +19 -0
- package/skills/theme-factory/themes/modern-minimalist.md +19 -0
- package/skills/theme-factory/themes/ocean-depths.md +19 -0
- package/skills/theme-factory/themes/sunset-boulevard.md +19 -0
- package/skills/theme-factory/themes/tech-innovation.md +19 -0
- package/skills/web-artifacts-builder/LICENSE.txt +202 -0
- package/skills/web-artifacts-builder/SKILL.md +79 -0
- package/skills/web-artifacts-builder/scripts/bundle-artifact.sh +53 -0
- package/skills/web-artifacts-builder/scripts/init-artifact.sh +322 -0
- package/skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/skills/webapp-testing/LICENSE.txt +202 -0
- package/skills/webapp-testing/SKILL.md +96 -0
- package/skills/webapp-testing/examples/console_logging.py +35 -0
- package/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/skills/webapp-testing/scripts/with_server.py +106 -0
- package/skills/xlsx/LICENSE.txt +30 -0
- package/skills/xlsx/SKILL.md +292 -0
- package/skills/xlsx/scripts/office/helpers/__init__.py +0 -0
- package/skills/xlsx/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/xlsx/scripts/office/pack.py +159 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/ECMA-376_3rd_edition.zip +1 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/README.md +1 -0
- package/skills/xlsx/scripts/office/schemas/mce/mce-core.xsd +1 -0
- package/skills/xlsx/scripts/office/schemas/mce/mce-override.xsd +1 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/xlsx/scripts/office/soffice.py +183 -0
- package/skills/xlsx/scripts/office/unpack.py +132 -0
- package/skills/xlsx/scripts/office/validate.py +111 -0
- package/skills/xlsx/scripts/office/validators/__init__.py +15 -0
- package/skills/xlsx/scripts/office/validators/base.py +847 -0
- package/skills/xlsx/scripts/office/validators/docx.py +446 -0
- package/skills/xlsx/scripts/office/validators/pptx.py +275 -0
- package/skills/xlsx/scripts/office/validators/redlining.py +247 -0
- package/skills/xlsx/scripts/recalc.py +184 -0
- package/vendor/ripgrep/COPYING +3 -0
- package/vendor/ripgrep/README.md +34 -0
- package/vendor/ripgrep/arm64-darwin/rg +0 -0
- package/vendor/ripgrep/arm64-linux/rg +0 -0
- package/vendor/ripgrep/arm64-win32/rg.exe +0 -0
- package/vendor/ripgrep/x64-darwin/rg +0 -0
- package/vendor/ripgrep/x64-linux/rg +0 -0
- package/vendor/ripgrep/x64-win32/rg.exe +0 -0
package/dist/main.js
ADDED
|
@@ -0,0 +1,4140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/main.tsx
|
|
4
|
+
import { render } from "ink";
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
|
|
7
|
+
// src/config/configFile.ts
|
|
8
|
+
import { chmod, mkdir, readFile, writeFile } from "fs/promises";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
import { dirname, join } from "path";
|
|
11
|
+
function getConfigFilePath() {
|
|
12
|
+
return process.env.MINIMAL_AGENT_CONFIG_FILE ?? join(homedir(), ".minimal-agent", "config.json");
|
|
13
|
+
}
|
|
14
|
+
async function readSavedConfig() {
|
|
15
|
+
const file = getConfigFilePath();
|
|
16
|
+
try {
|
|
17
|
+
const raw = await readFile(file, "utf8");
|
|
18
|
+
const data = JSON.parse(raw);
|
|
19
|
+
if (typeof data.baseURL !== "string" || typeof data.apiKey !== "string" || typeof data.model !== "string" || data.baseURL.length === 0 || data.apiKey.length === 0 || data.model.length === 0) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
baseURL: data.baseURL,
|
|
24
|
+
apiKey: data.apiKey,
|
|
25
|
+
model: data.model,
|
|
26
|
+
provider: typeof data.provider === "string" ? data.provider : void 0,
|
|
27
|
+
contextWindow: typeof data.contextWindow === "number" && data.contextWindow > 0 ? data.contextWindow : void 0,
|
|
28
|
+
savedAt: typeof data.savedAt === "number" ? data.savedAt : 0
|
|
29
|
+
};
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function saveConfig(cfg) {
|
|
35
|
+
const file = getConfigFilePath();
|
|
36
|
+
await mkdir(dirname(file), { recursive: true });
|
|
37
|
+
const data = { ...cfg, savedAt: Date.now() };
|
|
38
|
+
await writeFile(file, JSON.stringify(data, null, 2), "utf8");
|
|
39
|
+
try {
|
|
40
|
+
await chmod(file, 384);
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/config.ts
|
|
46
|
+
var DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
47
|
+
async function loadProvider() {
|
|
48
|
+
const baseURL = process.env.MINIMAL_AGENT_BASE_URL;
|
|
49
|
+
const apiKey = process.env.MINIMAL_AGENT_API_KEY;
|
|
50
|
+
const model = process.env.MINIMAL_AGENT_MODEL;
|
|
51
|
+
if (!baseURL || !apiKey || !model) {
|
|
52
|
+
const missing = [];
|
|
53
|
+
if (!baseURL) missing.push("MINIMAL_AGENT_BASE_URL");
|
|
54
|
+
if (!apiKey) missing.push("MINIMAL_AGENT_API_KEY");
|
|
55
|
+
if (!model) missing.push("MINIMAL_AGENT_MODEL");
|
|
56
|
+
throw new Error(
|
|
57
|
+
`\u7F3A\u5C11\u5FC5\u9700\u7684\u73AF\u5883\u53D8\u91CF\uFF1A${missing.join(", ")}
|
|
58
|
+
|
|
59
|
+
\u8BF7\u5728 .env \u4E2D\u914D\u7F6E\uFF1A
|
|
60
|
+
MINIMAL_AGENT_BASE_URL=https://api.example.com/v1
|
|
61
|
+
MINIMAL_AGENT_API_KEY=your-api-key
|
|
62
|
+
MINIMAL_AGENT_MODEL=your-model
|
|
63
|
+
|
|
64
|
+
\u53C2\u8003 .env.example`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const contextWindowRaw = process.env.MINIMAL_AGENT_CONTEXT_WINDOW;
|
|
68
|
+
let contextWindow = DEFAULT_CONTEXT_WINDOW;
|
|
69
|
+
if (contextWindowRaw) {
|
|
70
|
+
const n = parseInt(contextWindowRaw, 10);
|
|
71
|
+
if (!Number.isNaN(n) && n > 0) {
|
|
72
|
+
contextWindow = n;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
name: process.env.MINIMAL_AGENT_PROVIDER ?? "env",
|
|
77
|
+
baseURL,
|
|
78
|
+
apiKey,
|
|
79
|
+
model,
|
|
80
|
+
contextWindow
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function loadProviderLayered() {
|
|
84
|
+
const envBaseURL = process.env.MINIMAL_AGENT_BASE_URL;
|
|
85
|
+
const envApiKey = process.env.MINIMAL_AGENT_API_KEY;
|
|
86
|
+
const envModel = process.env.MINIMAL_AGENT_MODEL;
|
|
87
|
+
const envName = process.env.MINIMAL_AGENT_PROVIDER;
|
|
88
|
+
const envContextRaw = process.env.MINIMAL_AGENT_CONTEXT_WINDOW;
|
|
89
|
+
let saved = null;
|
|
90
|
+
if (!envBaseURL || !envApiKey || !envModel) {
|
|
91
|
+
saved = await readSavedConfig();
|
|
92
|
+
}
|
|
93
|
+
const baseURL = envBaseURL ?? saved?.baseURL;
|
|
94
|
+
const apiKey = envApiKey ?? saved?.apiKey;
|
|
95
|
+
const model = envModel ?? saved?.model;
|
|
96
|
+
if (!baseURL || !apiKey || !model) return null;
|
|
97
|
+
let contextWindow = DEFAULT_CONTEXT_WINDOW;
|
|
98
|
+
if (envContextRaw) {
|
|
99
|
+
const n = parseInt(envContextRaw, 10);
|
|
100
|
+
if (!Number.isNaN(n) && n > 0) contextWindow = n;
|
|
101
|
+
} else if (saved?.contextWindow) {
|
|
102
|
+
contextWindow = saved.contextWindow;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
name: envName ?? saved?.provider ?? "env",
|
|
106
|
+
baseURL,
|
|
107
|
+
apiKey,
|
|
108
|
+
model,
|
|
109
|
+
contextWindow
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/context/persistContext.ts
|
|
114
|
+
import { mkdir as mkdir2, readFile as readFile2, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
115
|
+
import { homedir as homedir2 } from "os";
|
|
116
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
117
|
+
function getContextPath() {
|
|
118
|
+
return process.env.MINIMAL_AGENT_CONTEXT_FILE ?? join2(homedir2(), ".minimal-agent", "last-context.json");
|
|
119
|
+
}
|
|
120
|
+
async function loadContext() {
|
|
121
|
+
const file = getContextPath();
|
|
122
|
+
try {
|
|
123
|
+
const raw = await readFile2(file, "utf8");
|
|
124
|
+
const data = JSON.parse(raw);
|
|
125
|
+
if (!Array.isArray(data.messages)) return null;
|
|
126
|
+
return data.messages;
|
|
127
|
+
} catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function saveContext(messages) {
|
|
132
|
+
const file = getContextPath();
|
|
133
|
+
try {
|
|
134
|
+
await mkdir2(dirname2(file), { recursive: true });
|
|
135
|
+
const data = { updatedAt: Date.now(), messages };
|
|
136
|
+
await writeFile2(file, JSON.stringify(data), "utf8");
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function clearContext() {
|
|
141
|
+
const file = getContextPath();
|
|
142
|
+
try {
|
|
143
|
+
await unlink(file);
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/prompts/system.ts
|
|
149
|
+
import { homedir as homedir3 } from "os";
|
|
150
|
+
|
|
151
|
+
// src/prompts/projectInstructions.ts
|
|
152
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
153
|
+
import { join as join3 } from "path";
|
|
154
|
+
var FILENAME = "minimal-agent.md";
|
|
155
|
+
async function loadProjectInstructions(cwd) {
|
|
156
|
+
const filePath = join3(cwd, FILENAME);
|
|
157
|
+
try {
|
|
158
|
+
const content = await readFile3(filePath, "utf-8");
|
|
159
|
+
const trimmed = content.trim();
|
|
160
|
+
if (trimmed.length === 0) return null;
|
|
161
|
+
return trimmed;
|
|
162
|
+
} catch (e) {
|
|
163
|
+
const code = e.code;
|
|
164
|
+
if (code !== "ENOENT") {
|
|
165
|
+
process.stderr.write(
|
|
166
|
+
`[minimal-agent] \u8DF3\u8FC7\u9879\u76EE\u6307\u4EE4 ${filePath}\uFF1A${code ?? e.message}
|
|
167
|
+
`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/prompts/skillList.ts
|
|
175
|
+
import { readFile as readFile4, readdir } from "fs/promises";
|
|
176
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
177
|
+
import { fileURLToPath } from "url";
|
|
178
|
+
var __dirname = dirname3(fileURLToPath(import.meta.url));
|
|
179
|
+
var PROJECT_ROOT = join4(__dirname, "..", "..");
|
|
180
|
+
var SKILLS_DIR = join4(PROJECT_ROOT, "skills");
|
|
181
|
+
function stripQuotes(s) {
|
|
182
|
+
const trimmed = s.trim();
|
|
183
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
184
|
+
return trimmed.slice(1, -1);
|
|
185
|
+
}
|
|
186
|
+
return trimmed;
|
|
187
|
+
}
|
|
188
|
+
function parseFrontmatter(content) {
|
|
189
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
190
|
+
if (!match) return null;
|
|
191
|
+
const frontmatter = match[1];
|
|
192
|
+
const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
|
|
193
|
+
const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
|
|
194
|
+
const typeMatch = frontmatter.match(/^type:\s*(.+)$/m);
|
|
195
|
+
const triggersMatch = frontmatter.match(/^triggers:\s*(.+)$/m);
|
|
196
|
+
if (!nameMatch || !descMatch) return null;
|
|
197
|
+
return {
|
|
198
|
+
name: stripQuotes(nameMatch[1]),
|
|
199
|
+
description: stripQuotes(descMatch[1]),
|
|
200
|
+
type: typeMatch ? stripQuotes(typeMatch[1]) : void 0,
|
|
201
|
+
triggers: triggersMatch ? stripQuotes(triggersMatch[1]) : void 0
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async function getSkillList() {
|
|
205
|
+
const skills = [];
|
|
206
|
+
try {
|
|
207
|
+
const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
|
|
208
|
+
for (const entry of entries) {
|
|
209
|
+
if (!entry.isDirectory()) continue;
|
|
210
|
+
const skillPath = join4(SKILLS_DIR, entry.name, "SKILL.md");
|
|
211
|
+
try {
|
|
212
|
+
const content = await readFile4(skillPath, "utf8");
|
|
213
|
+
const meta = parseFrontmatter(content);
|
|
214
|
+
if (meta) {
|
|
215
|
+
skills.push(meta);
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} catch {
|
|
221
|
+
}
|
|
222
|
+
return skills;
|
|
223
|
+
}
|
|
224
|
+
function formatSkillHint(skills) {
|
|
225
|
+
if (skills.length === 0) {
|
|
226
|
+
return "";
|
|
227
|
+
}
|
|
228
|
+
const lines = [];
|
|
229
|
+
lines.push("# \u53EF\u7528\u6280\u80FD\uFF08/\u547D\u4EE4\uFF09");
|
|
230
|
+
lines.push("");
|
|
231
|
+
lines.push(
|
|
232
|
+
"\u4EE5\u4E0B\u662F\u5F53\u524D\u53EF\u7528\u7684\u6280\u80FD\u3002**\u5F53\u7528\u6237\u610F\u56FE\u5339\u914D\u67D0\u4E2A\u6280\u80FD\u7684\u89E6\u53D1\u65F6\u673A\u65F6\uFF0C\u4F60\u5E94\u8BE5\u4E3B\u52A8\u4F7F\u7528\u5B83**\u3002"
|
|
233
|
+
);
|
|
234
|
+
lines.push("\u5728\u4F7F\u7528\u524D\uFF0C\u5148\u7528 Read \u5DE5\u5177\u8BFB\u53D6 `skills/{name}/SKILL.md` \u4E86\u89E3\u5B8C\u6574\u6D41\u7A0B\u3002");
|
|
235
|
+
lines.push("");
|
|
236
|
+
for (const skill of skills) {
|
|
237
|
+
const type = skill.type ?? "prompt\uFF08\u5C55\u5F00\u4E3A\u7CFB\u7EDF\u63D0\u793A\u8BCD\uFF0C\u6307\u5BFC\u6A21\u578B\u6267\u884C\u590D\u6742\u4EFB\u52A1\uFF09";
|
|
238
|
+
const triggers = skill.triggers ?? "\uFF08\u8BE6\u89C1 SKILL.md\uFF09";
|
|
239
|
+
lines.push(`## ${skill.name}`);
|
|
240
|
+
lines.push(`- **\u89E6\u53D1\u65F6\u673A**: ${triggers}`);
|
|
241
|
+
lines.push(`- **\u529F\u80FD**: ${skill.description}`);
|
|
242
|
+
lines.push(`- **\u7C7B\u578B**: ${type}`);
|
|
243
|
+
lines.push("");
|
|
244
|
+
}
|
|
245
|
+
lines.push("## \u6280\u80FD\u8C03\u7528\u6D41\u7A0B");
|
|
246
|
+
lines.push("");
|
|
247
|
+
lines.push("```");
|
|
248
|
+
lines.push("1. \u5728 T \u9636\u6BB5\u5224\u65AD\u7528\u6237\u610F\u56FE\u662F\u5426\u5339\u914D\u67D0\u4E2A\u6280\u80FD\u7684\u89E6\u53D1\u65F6\u673A");
|
|
249
|
+
lines.push("2. \u5339\u914D \u2192 \u5728 A \u9636\u6BB5\u7528 Read \u5DE5\u5177\u8BFB\u53D6 skills/{name}/SKILL.md");
|
|
250
|
+
lines.push("3. \u6309\u7167 SKILL.md \u7684 Quick Reference \u548C\u6D41\u7A0B\u6267\u884C");
|
|
251
|
+
lines.push("4. \u5982\u679C\u6280\u80FD\u7C7B\u578B\u662F prompt\uFF0C\u5C06\u5176\u5185\u5BB9\u4F5C\u4E3A\u989D\u5916\u4E0A\u4E0B\u6587\u6307\u5BFC\u540E\u7EED\u64CD\u4F5C");
|
|
252
|
+
lines.push("```");
|
|
253
|
+
lines.push("");
|
|
254
|
+
lines.push("> \u{1F4A1} \u6280\u80FD\u662F\u5DE5\u5177\u7684\u7EC4\u5408\u62F3\u3002\u5355\u4E2A\u5DE5\u5177\u662F\u57FA\u7840\u64CD\u4F5C\uFF1B\u6280\u80FD\u662F\u9488\u5BF9\u7279\u5B9A\u573A\u666F\u7684\u6807\u51C6\u5316\u5DE5\u4F5C\u6D41\u3002");
|
|
255
|
+
return lines.join("\n");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/prompts/system.ts
|
|
259
|
+
async function getSystemPrompt(cwd, tools) {
|
|
260
|
+
const toolList = tools.map((t) => ` - ${t.name}`).join("\n");
|
|
261
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
262
|
+
const skills = await getSkillList();
|
|
263
|
+
const skillHint = formatSkillHint(skills);
|
|
264
|
+
return `\u4F60\u662F minimal-agent\uFF0C\u4E00\u4E2A\u6700\u5C0F\u5316\u7684 AI \u7F16\u7A0B\u52A9\u624B\u3002
|
|
265
|
+
\u4F60\u8FD0\u884C\u5728\u7528\u6237\u7684\u672C\u5730\u7EC8\u7AEF\u91CC\uFF0C\u53EF\u4EE5\u8BFB\u53D6\u3001\u4FEE\u6539\u3001\u521B\u5EFA\u6587\u4EF6\uFF0C\u53EF\u4EE5\u641C\u7D22\u6587\u4EF6\u540D\u548C\u6587\u4EF6\u5185\u5BB9\uFF0C\u53EF\u4EE5\u8054\u7F51\u641C\u7D22\u6700\u65B0\u4FE1\u606F\u3002
|
|
266
|
+
|
|
267
|
+
# \u5DE5\u4F5C\u73AF\u5883
|
|
268
|
+
- \u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55: ${cwd}
|
|
269
|
+
- \u7528\u6237\u4E3B\u76EE\u5F55: ${homedir3()}
|
|
270
|
+
- \u5E73\u53F0: ${process.platform}
|
|
271
|
+
- \u5F53\u524D\u65E5\u671F: ${today}
|
|
272
|
+
|
|
273
|
+
# \u65F6\u95F4\u611F\u77E5\u89C4\u5219\uFF08\u91CD\u8981\uFF01\u641C\u7D22\u524D\u5FC5\u8BFB\uFF09
|
|
274
|
+
- **\u6BCF\u6B21\u6267\u884C WebSearch \u524D\uFF0C\u5FC5\u987B\u5148\u786E\u8BA4\u5F53\u524D\u65E5\u671F**
|
|
275
|
+
- \u5F53\u524D\u65E5\u671F\uFF1A${today}
|
|
276
|
+
- \u641C\u7D22\u65F6\u5FC5\u987B\u5728 query \u672B\u5C3E\u9644\u5E26\u65E5\u671F\u9650\u5236\uFF0C\u5982 "... as of [\u65E5\u671F]" \u6216 "... ${today}"
|
|
277
|
+
- \u4F8B\u5982\u641C\u7D22\u65E5\u672C\u653F\u7B56\u65F6\u5199\uFF1A"\u65E5\u672C\u5165\u5883\u653F\u7B56 ${today}"
|
|
278
|
+
- \u7981\u6B62\u4F7F\u7528 "\u73B0\u5728"\u3001"\u6700\u8FD1"\u3001"\u6700\u65B0" \u7B49\u6A21\u7CCA\u65F6\u95F4\u8BCD\u2014\u2014\u5FC5\u987B\u7528\u5177\u4F53\u65E5\u671F
|
|
279
|
+
|
|
280
|
+
# \u53EF\u7528\u5DE5\u5177
|
|
281
|
+
${toolList}
|
|
282
|
+
|
|
283
|
+
# \u5DE5\u5177\u4F7F\u7528\u89C4\u8303
|
|
284
|
+
- \u4FEE\u6539\u65E2\u6709\u6587\u4EF6\u4E4B\u524D\uFF0C\u5FC5\u987B\u7528 Read \u5DE5\u5177\u8BFB\u53D6\u76F8\u5173\u4EE3\u7801\u7684\u6700\u65B0\u5185\u5BB9\u3002
|
|
285
|
+
"\u6211\u8BB0\u5F97"\u3001"\u6211\u4E4B\u524D\u770B\u8FC7"\u3001"\u5E94\u8BE5\u5DEE\u4E0D\u591A"\u90FD\u4E0D\u7B97\u8BFB\u8FC7\u2014\u2014\u5FC5\u987B\u5728\u672C\u8F6E\u4EFB\u52A1\u4E2D\u5B9E\u9645\u8C03\u7528 Read\u3002
|
|
286
|
+
\u5C24\u5176\u5F53\u4F60\u8981\u53C2\u8003\u67D0\u4E2A\u6587\u4EF6\u7684\u5199\u6CD5\u6765\u5B9E\u73B0\u7C7B\u4F3C\u529F\u80FD\u65F6\uFF0C\u5FC5\u987B\u5148\u91CD\u8BFB\u8BE5\u6587\u4EF6\u7684\u5173\u952E\u90E8\u5206\uFF08\u51FD\u6570\u5B9E\u73B0\u3001\u5224\u65AD\u903B\u8F91\u3001\u6570\u636E\u6D41\uFF09\uFF0C\u7406\u89E3\u6E05\u695A\u540E\u518D\u52A8\u624B\u3002
|
|
287
|
+
- Edit \u5DE5\u5177\u7684 old_string \u5FC5\u987B\u5728\u6587\u4EF6\u4E2D\u552F\u4E00\uFF1B\u4E0D\u552F\u4E00\u65F6\u8BF7\u6269\u5927\u4E0A\u4E0B\u6587\u6216\u663E\u5F0F replace_all=true\u3002
|
|
288
|
+
- \u521B\u5EFA\u65B0\u6587\u4EF6\u7528 Write\uFF0C\u6216 Edit \u65F6 old_string \u4F20\u7A7A\u5B57\u7B26\u4E32\u3002
|
|
289
|
+
- \u627E\u6587\u4EF6\u7528 Glob\uFF08"**/*.ts"\uFF09\uFF0C\u627E\u6587\u4EF6\u5185\u5BB9\u7528 Grep\uFF08\u57FA\u4E8E ripgrep\uFF09\u3002
|
|
290
|
+
- \u5F53\u7528\u6237\u95EE\u5230\u8BAD\u7EC3\u622A\u6B62\u540E\u624D\u51FA\u73B0\u7684\u4FE1\u606F\uFF08\u6700\u65B0\u7248\u672C\u53F7\u3001\u8FD1\u671F\u65B0\u95FB\u3001\u7B2C\u4E09\u65B9 API \u6587\u6863\uFF09\u65F6\u7528 WebSearch\uFF1B\u4F18\u5148\u7CBE\u786E\u7684\u81EA\u7136\u8BED\u8A00\u67E5\u8BE2\u3002
|
|
291
|
+
- \u5F53\u9700\u8981\u6267\u884C\u7EC8\u7AEF\u547D\u4EE4\uFF08\u5B89\u88C5\u4F9D\u8D56\u3001\u8FD0\u884C\u6D4B\u8BD5/\u6784\u5EFA/lint\u3001git \u64CD\u4F5C\u3001\u6587\u4EF6\u7CFB\u7EDF\u64CD\u4F5C\u7B49\uFF09\u65F6\u7528 Bash\u3002
|
|
292
|
+
\u26A0\uFE0F **Bash \u662F\u4E07\u80FD\u5DE5\u5177\uFF0C\u4F46\u5FC5\u987B\u4F5C\u4E3A\u6700\u540E\u624B\u6BB5**\uFF1A\u5F53 Read/Edit/Write/Glob/Grep \u7B49\u4E13\u7528\u5DE5\u5177\u65E0\u6CD5\u5B8C\u6210\u4EFB\u52A1\u65F6\uFF0C\u624D\u8003\u8651\u7528 Bash\u3002
|
|
293
|
+
\u4E0D\u8981\u7528 Bash \u6267\u884C find/grep/cat/head/tail/sed/awk/echo \u7B49\u547D\u4EE4\u6765\u66FF\u4EE3\u4E13\u7528\u5DE5\u5177\u2014\u2014\u4E13\u7528\u5DE5\u5177\u63D0\u4F9B\u66F4\u597D\u7684\u7528\u6237\u4F53\u9A8C\u548C\u53EF\u5BA1\u8BA1\u6027\u3002
|
|
294
|
+
Bash \u6709\u5B89\u5168\u9ED1\u540D\u5355\uFF08rm -rf /\u3001mkfs\u3001shutdown \u7B49\u5371\u9669\u547D\u4EE4\u4F1A\u88AB\u62E6\u622A\uFF09\uFF0C\u4F46\u4ECD\u9700\u8C28\u614E\uFF1A
|
|
295
|
+
\u5148\u786E\u8BA4\u547D\u4EE4\u65E0\u5BB3\u518D\u6267\u884C\uFF0C\u907F\u514D\u4E0D\u53EF\u9006\u64CD\u4F5C\uFF08\u5982 git push --force\u3001git reset --hard\uFF09\u3002
|
|
296
|
+
\u957F\u65F6\u95F4\u8FD0\u884C\u7684\u547D\u4EE4\uFF08npm install\u3001bun test\uFF09\u6CE8\u610F\u8D85\u65F6\u8BBE\u7F6E\uFF1B\u9700\u8981\u4EA4\u4E92\u8F93\u5165\u7684\u547D\u4EE4\u4E0D\u8981\u7528 Bash\uFF08\u7528 Write \u5199\u811A\u672C\u4EE3\u66FF\uFF09\u3002
|
|
297
|
+
- \u5F53\u9700\u8981\u83B7\u53D6\u7F51\u9875\u9759\u6001\u6587\u672C\u5185\u5BB9\uFF08\u6293\u53D6\u6587\u6863\u3001\u8BFB\u53D6\u6587\u7AE0\uFF09\u65F6\u7528 WebFetch\u3002
|
|
298
|
+
WebBrowser \u4F9D\u8D56\u53EF\u9009\u5305\uFF08playwright-core + chromium\uFF09\u2014\u2014 **\u9ED8\u8BA4\u5047\u5B9A\u672A\u5B89\u88C5**\uFF0C
|
|
299
|
+
\u4EC5\u5728 WebFetch \u660E\u786E\u65E0\u6CD5\u6EE1\u8DB3\uFF08\u5982\u9700\u8981 JS \u6E32\u67D3\u540E\u7684\u5185\u5BB9\u3001\u70B9\u51FB\u6309\u94AE\u3001\u586B\u8868\u5355\u3001\u622A\u56FE\uFF09\u65F6\u518D\u5C1D\u8BD5\u3002
|
|
300
|
+
\u5982\u679C WebBrowser \u62A5"\u65E0\u6CD5\u542F\u52A8\u6D4F\u89C8\u5668/\u7F3A\u5C11\u4F9D\u8D56"\uFF0C\u4E0D\u8981\u91CD\u8BD5\uFF0C\u6539\u56DE WebFetch \u6216\u544A\u77E5\u7528\u6237\u5B89\u88C5\u4F9D\u8D56\u3002
|
|
301
|
+
- \u4E00\u6B21\u56DE\u7B54\u91CC\u53EF\u4EE5\u5E76\u884C\u8C03\u7528\u591A\u4E2A\u53EA\u8BFB\u5DE5\u5177\uFF08Read/Glob/Grep/WebSearch/WebFetch\uFF09\uFF0C\u5199\u5DE5\u5177\uFF08Edit/Write/Bash\uFF09\u8BF7\u9010\u4E2A\u6267\u884C\u3002
|
|
302
|
+
|
|
303
|
+
# \u6280\u80FD\u7CFB\u7EDF\uFF08\u79EF\u6781\u4F7F\u7528\uFF09
|
|
304
|
+
${skillHint}
|
|
305
|
+
|
|
306
|
+
# \u6587\u4EF6\u8BFB\u53D6\u89C4\u8303\uFF08\u91CD\u8981\uFF01\u9632\u6B62\u4E0A\u4E0B\u6587\u6C61\u67D3\uFF09
|
|
307
|
+
## \u8BFB\u53D6\u7B56\u7565\uFF08\u6309\u573A\u666F\u9009\u62E9\uFF09
|
|
308
|
+
### \u573A\u666F 1\uFF1A\u4E0D\u786E\u5B9A\u6587\u4EF6\u5185\u5BB9
|
|
309
|
+
\u2192 \u5148 Grep("\u5173\u952E\u5B57", path="\u6587\u4EF6\u8DEF\u5F84") \u5B9A\u4F4D
|
|
310
|
+
\u2192 \u518D Read(file_path, offset=X, limit=Y) \u8BFB\u6307\u5B9A\u533A\u57DF
|
|
311
|
+
|
|
312
|
+
### \u573A\u666F 2\uFF1A\u77E5\u9053\u8981\u6539\u54EA\u4E2A\u51FD\u6570
|
|
313
|
+
\u2192 \u76F4\u63A5 Read(file_path, offset=1, limit=400) \u5148\u8BFB\u524D 400 \u884C
|
|
314
|
+
\u2192 \u6216\u8005\u7528 Grep \u627E\u5230\u884C\u53F7\u540E Read(file_path, offset=\u884C\u53F7, limit=200)
|
|
315
|
+
|
|
316
|
+
### \u573A\u666F 3\uFF1A\u5927\u6587\u4EF6\uFF08>256KB\uFF09
|
|
317
|
+
\u2192 \u4E0D\u8981\u4E00\u6B21\u6027\u8BFB\uFF0C\u7528 Grep \u5B9A\u4F4D\u5173\u952E\u90E8\u5206
|
|
318
|
+
\u2192 \u5206\u591A\u6B21\u5C0F\u8303\u56F4 Read\uFF0C\u6BCF\u6B21\u4E0D\u8D85\u8FC7 200 \u884C
|
|
319
|
+
\u2192 \u4F8B\u5982\uFF1ARead(file, offset=1, limit=100) \u2192 Read(file, offset=101, limit=100)
|
|
320
|
+
|
|
321
|
+
### \u573A\u666F 4\uFF1A\u9700\u8981\u770B imports/exports
|
|
322
|
+
\u2192 Read(file, offset=1, limit=50) \u53EA\u8BFB\u6587\u4EF6\u5934\u90E8
|
|
323
|
+
|
|
324
|
+
## \u8B66\u60D5\u4FE1\u53F7
|
|
325
|
+
- \u9047\u5230 "\u6587\u4EF6\u8FC7\u5927" \u9519\u8BEF \u2192 \u8BF4\u660E\u4F60\u8BFB\u4E86\u4E0D\u8BE5\u8BFB\u7684\u5927\u6587\u4EF6\uFF0C\u6539\u7528\u5206\u6BB5\u8BFB
|
|
326
|
+
- \u9047\u5230 "\u8F93\u51FA\u8D85\u8FC7 30000 \u5B57\u7B26" \u2192 \u8BF4\u660E\u5355\u6B21\u8BFB\u592A\u591A\uFF0C\u6539\u5C0F limit
|
|
327
|
+
|
|
328
|
+
# \u8F93\u51FA\u98CE\u683C
|
|
329
|
+
- \u7528\u4E2D\u6587\u56DE\u7B54\u7528\u6237\u3002
|
|
330
|
+
- \u7B80\u660E\u627C\u8981\uFF0C\u907F\u514D\u91CD\u590D\u89E3\u91CA\u4EE3\u7801\u505A\u4E86\u4EC0\u4E48 \u2014\u2014 \u8BA9\u4EE3\u7801\u81EA\u8EAB\u8BF4\u8BDD\u3002
|
|
331
|
+
- \u62A5\u9519\u65F6\u7ED9\u51FA\u6839\u56E0\uFF0C\u4E0D\u8981\u53EA\u590D\u8FF0\u9519\u8BEF\u6D88\u606F\u3002
|
|
332
|
+
- \u4E0D\u4E3B\u52A8\u8DD1\u6D4B\u8BD5\u6216 typecheck\uFF0C\u9664\u975E\u7528\u6237\u8981\u6C42\u3002
|
|
333
|
+
|
|
334
|
+
# \u884C\u4E3A\u7EA6\u675F
|
|
335
|
+
- \u6CA1\u6709\u7528\u6237\u8BB8\u53EF\u4E0D\u8981\u6267\u884C\u7834\u574F\u6027\u64CD\u4F5C\uFF08rm -rf / git reset --hard / git push --force \u7B49\uFF09\u3002
|
|
336
|
+
- \u5982\u679C\u7528\u6237\u7684\u8BF7\u6C42\u4E0D\u6E05\u6670\uFF0C\u5148\u7528\u4E00\u53E5\u8BDD\u6F84\u6E05\u518D\u52A8\u624B\u3002
|
|
337
|
+
- \u4EFB\u52A1\u5B8C\u6210\u540E\u7528\u4E00\u4E24\u53E5\u8BDD\u603B\u7ED3\u6539\u4E86\u4EC0\u4E48\uFF0C\u4E0D\u8981\u957F\u7BC7\u5927\u8BBA\u3002
|
|
338
|
+
|
|
339
|
+
# \u4E2D\u65AD\u652F\u6301
|
|
340
|
+
- \u7528\u6237\u53EF\u4EE5\u968F\u65F6\u6309 ESC \u6216 Ctrl+C \u4E2D\u65AD\u4F60\u7684\u5DE5\u4F5C\u3002
|
|
341
|
+
- \u4E2D\u65AD\u540E\u7B49\u5F85\u7528\u6237\u8F93\u5165\u65B0\u4EFB\u52A1\uFF0C\u4E0D\u8981\u81EA\u884C\u7EE7\u7EED\u6267\u884C\u88AB\u4E2D\u65AD\u7684\u64CD\u4F5C\u3002
|
|
342
|
+
- \u5982\u679C\u7528\u6237\u8F93\u5165\u4E86\u65B0\u4EFB\u52A1\uFF0C\u76F4\u63A5\u54CD\u5E94\u65B0\u4EFB\u52A1\uFF0C\u4E0D\u5FC5\u63D0\u53CA\u4E4B\u524D\u88AB\u4E2D\u65AD\u7684\u64CD\u4F5C\u3002
|
|
343
|
+
|
|
344
|
+
# \u4EFB\u52A1\u8BC6\u522B
|
|
345
|
+
- \u7528\u6237\u5F00\u59CB\u65B0\u4EFB\u52A1\u65F6\uFF08"\u5E2E\u6211\u505A X"\u3001"\u8FD9\u6B21\u505A YYY"\uFF09\uFF0C\u4E0D\u8981\u7EE7\u7EED\u4E4B\u524D\u672A\u5B8C\u6210\u7684\u65E7\u4EFB\u52A1
|
|
346
|
+
- \u7528\u6237\u8BF4"\u7EE7\u7EED"\u3001"\u63A5\u7740\u505A"\u3001"\u518D\u52A0\u4E00\u4E2A"\u65F6\uFF0C\u4FDD\u6301\u5F53\u524D\u4EFB\u52A1\u4E0A\u4E0B\u6587
|
|
347
|
+
- \u7528\u6237\u8BF4"\u91CD\u65B0\u5F00\u59CB"\u3001"\u4E0D\u7B97\u4E86\uFF0C\u505A X"\u65F6\uFF0C\u7ACB\u5373\u653E\u4E0B\u65E7\u4EFB\u52A1
|
|
348
|
+
- \u610F\u56FE\u6A21\u7CCA\u65F6\uFF0C\u5148\u7528\u4E00\u53E5\u8BDD\u6F84\u6E05\uFF1A"\u4F60\u662F\u7EE7\u7EED\u521A\u624D\u7684\uFF0C\u8FD8\u662F\u5F00\u59CB\u65B0\u7684\uFF1F"`;
|
|
349
|
+
}
|
|
350
|
+
async function buildFullSystemPrompt(cwd, tools) {
|
|
351
|
+
let content = await getSystemPrompt(cwd, tools);
|
|
352
|
+
const projectInstructions = await loadProjectInstructions(cwd);
|
|
353
|
+
if (projectInstructions) {
|
|
354
|
+
content += [
|
|
355
|
+
"",
|
|
356
|
+
"--- \u9879\u76EE\u6307\u4EE4\uFF08minimal-agent.md\uFF09 ---",
|
|
357
|
+
"",
|
|
358
|
+
"\u26A0\uFE0F \u65F6\u6548\u6027\u58F0\u660E\uFF1A",
|
|
359
|
+
"- \u4EE5\u4E0B\u5185\u5BB9\u6765\u6E90\u4E8E\u9879\u76EE\u6839\u76EE\u5F55\u7684 `minimal-agent.md` \u6587\u4EF6\uFF0C\u5177\u6709\u65F6\u6548\u6027\uFF0C\u53EF\u80FD\u5DF2\u8FC7\u65F6\u3002",
|
|
360
|
+
"- \u5B83\u662F\u53C2\u8003\u7EBF\u7D22\uFF0C\u4E0D\u662F\u7EDD\u5BF9\u771F\u7406\u3002\u5F53\u5B83\u4E0E\u5B9E\u9645\u4EE3\u7801\u6216\u6587\u4EF6\u5185\u5BB9\u51B2\u7A81\u65F6\uFF0C\u4EE5\u4EE3\u7801\u4E3A\u51C6\u3002",
|
|
361
|
+
"- \u6D89\u53CA\u4EE3\u7801\u548C\u9879\u76EE\u6587\u4EF6\u7684\u4EFB\u4F55\u64CD\u4F5C\u524D\uFF0C\u5FC5\u987B\u5148\u7528 Read / Grep \u5DE5\u5177\u786E\u8BA4\u5F53\u524D\u4EE3\u7801\u7684\u5B9E\u9645\u72B6\u6001\uFF0C\u518D\u57FA\u4E8E\u6700\u65B0\u4FE1\u606F\u505A\u51B3\u7B56\uFF0C\u4E0D\u8981\u4EC5\u51ED\u6B64\u6587\u6863\u7684\u63CF\u8FF0\u64CD\u4F5C\u3002",
|
|
362
|
+
"- \u5982\u679C\u4F60\u53D1\u73B0 `minimal-agent.md` \u7684\u63CF\u8FF0\u4E0E\u5F53\u524D\u4EE3\u7801\u4E0D\u4E00\u81F4\uFF0C\u5E94\u4E3B\u52A8\u7528 Write \u5DE5\u5177\u66F4\u65B0 `minimal-agent.md` \u4F7F\u5176\u4E0E\u4EE3\u7801\u4FDD\u6301\u540C\u6B65\u3002",
|
|
363
|
+
"",
|
|
364
|
+
projectInstructions
|
|
365
|
+
].join("\n");
|
|
366
|
+
}
|
|
367
|
+
return content;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/tools/bash/bash.ts
|
|
371
|
+
import { spawn } from "child_process";
|
|
372
|
+
import { z } from "zod";
|
|
373
|
+
|
|
374
|
+
// src/tools/types.ts
|
|
375
|
+
var DEFAULT_MAX_RESULT_SIZE_CHARS = 3e4;
|
|
376
|
+
var MAX_LINES_TO_READ = 2e3;
|
|
377
|
+
var MAX_FILE_SIZE_BYTES = 256 * 1024;
|
|
378
|
+
|
|
379
|
+
// src/utils/zodToJson.ts
|
|
380
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
381
|
+
function toToolParameters(schema) {
|
|
382
|
+
const json = zodToJsonSchema(schema, {
|
|
383
|
+
target: "openApi3",
|
|
384
|
+
$refStrategy: "none"
|
|
385
|
+
});
|
|
386
|
+
const { $schema, ...rest } = json;
|
|
387
|
+
return rest;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/tools/bash/bash.ts
|
|
391
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
392
|
+
var MAX_TIMEOUT_MS = 6e5;
|
|
393
|
+
var FORBIDDEN_PATTERNS = [
|
|
394
|
+
// ---- rm -rf / 及变体 ----
|
|
395
|
+
// 经典:"rm -rf /"、"rm -rf /*"、"rm -fr /usr"、"rm -rf $HOME"、"rm -rf ~"
|
|
396
|
+
// 也覆盖 sudo rm -rf /
|
|
397
|
+
{
|
|
398
|
+
pattern: /\brm\s+(?:-[a-zA-Z]*[rRf][a-zA-Z]*\s+)+(?:\/+\*?|\$HOME|~)(?:\s|$)/,
|
|
399
|
+
reason: "\u7981\u6B62\u9012\u5F52\u5220\u9664\u6839\u76EE\u5F55\u6216\u5BB6\u76EE\u5F55\uFF08rm -rf / \u7C7B\uFF09"
|
|
400
|
+
},
|
|
401
|
+
// 拦截 "rm -rf <绝对路径>" 中目标是关键系统目录的情况(防误伤其它正常 rm -rf ./tmp)
|
|
402
|
+
{
|
|
403
|
+
pattern: /\brm\s+(?:-[a-zA-Z]*[rRf][a-zA-Z]*\s+)+(?:\/etc|\/usr|\/bin|\/sbin|\/var|\/boot|\/lib|\/System|\/Windows|\/Users|\/home)(?:\/|\s|$)/,
|
|
404
|
+
reason: "\u7981\u6B62\u9012\u5F52\u5220\u9664\u7CFB\u7EDF\u5173\u952E\u76EE\u5F55"
|
|
405
|
+
},
|
|
406
|
+
// ---- 格盘 / 改写裸设备 ----
|
|
407
|
+
{
|
|
408
|
+
pattern: /\bmkfs(?:\.[a-z0-9]+)?\b/i,
|
|
409
|
+
reason: "\u7981\u6B62\u683C\u5F0F\u5316\u6587\u4EF6\u7CFB\u7EDF\uFF08mkfs\uFF09"
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
// 末尾不能用 \b:盘符 "C:" 后是非 word char,\b 匹配不到。
|
|
413
|
+
// 改成"盘符后跟空白/反斜杠/结尾"。
|
|
414
|
+
pattern: /\bformat\s+[A-Za-z]:(?=\s|\\|$)/i,
|
|
415
|
+
reason: "\u7981\u6B62 Windows \u683C\u76D8\u547D\u4EE4\uFF08format X:\uFF09"
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
pattern: /\bdd\s+[^|;&]*\bof=\/dev\/(?:sd[a-z]|nvme|disk|hd[a-z]|mmcblk)/i,
|
|
419
|
+
reason: "\u7981\u6B62 dd \u5199\u5165\u88F8\u78C1\u76D8\u8BBE\u5907"
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
pattern: />\s*\/dev\/(?:sd[a-z]|nvme|disk|hd[a-z]|mmcblk)/i,
|
|
423
|
+
reason: "\u7981\u6B62\u91CD\u5B9A\u5411\u5230\u88F8\u78C1\u76D8\u8BBE\u5907"
|
|
424
|
+
},
|
|
425
|
+
// ---- 关机 / 重启 ----
|
|
426
|
+
{
|
|
427
|
+
pattern: /\b(?:shutdown|reboot|halt|poweroff)\b/i,
|
|
428
|
+
reason: "\u7981\u6B62\u5173\u673A/\u91CD\u542F\u547D\u4EE4"
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
pattern: /\binit\s+[06]\b/,
|
|
432
|
+
reason: "\u7981\u6B62 init 0/6\uFF08\u5173\u673A/\u91CD\u542F\uFF09"
|
|
433
|
+
},
|
|
434
|
+
// ---- Fork bomb ----
|
|
435
|
+
{
|
|
436
|
+
pattern: /:\s*\(\s*\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/,
|
|
437
|
+
reason: "\u7981\u6B62 fork bomb"
|
|
438
|
+
},
|
|
439
|
+
// ---- 管道到 shell(典型恶意安装脚本模式) ----
|
|
440
|
+
// curl ... | sh / wget ... | bash / curl ... | python
|
|
441
|
+
{
|
|
442
|
+
pattern: /\b(?:curl|wget|fetch)\b[^|;&]*\|\s*(?:sh|bash|zsh|dash|python|perl|ruby|node)\b/i,
|
|
443
|
+
reason: "\u7981\u6B62\u4ECE\u7F51\u7EDC\u7BA1\u9053\u5230 shell \u89E3\u91CA\u5668\uFF08curl ... | sh \u6A21\u5F0F\uFF09"
|
|
444
|
+
},
|
|
445
|
+
// ---- chmod 整盘 777 ----
|
|
446
|
+
{
|
|
447
|
+
pattern: /\bchmod\s+(?:-R\s+)?[0-7]*7[0-7]*\s+\/(?:\s|$)/,
|
|
448
|
+
reason: "\u7981\u6B62\u5BF9\u6839\u76EE\u5F55 chmod 777"
|
|
449
|
+
},
|
|
450
|
+
// ---- Windows 系统级删除 ----
|
|
451
|
+
{
|
|
452
|
+
pattern: /\b(?:del|erase)\s+\/[sS][^|;&]*[A-Za-z]:\\?\s*$/,
|
|
453
|
+
reason: "\u7981\u6B62 Windows \u5168\u76D8\u5220\u9664\uFF08del /s \u6839\u76EE\u5F55\uFF09"
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
pattern: /\brmdir\s+\/[sS][^|;&]*[A-Za-z]:\\?\s*$/,
|
|
457
|
+
reason: "\u7981\u6B62 Windows \u5168\u76D8 rmdir"
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
pattern: /\bdiskpart\b/i,
|
|
461
|
+
reason: "\u7981\u6B62 diskpart"
|
|
462
|
+
}
|
|
463
|
+
];
|
|
464
|
+
function checkForbidden(command) {
|
|
465
|
+
for (const { pattern, reason } of FORBIDDEN_PATTERNS) {
|
|
466
|
+
if (pattern.test(command)) return reason;
|
|
467
|
+
}
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
var inputSchema = z.object({
|
|
471
|
+
command: z.string().min(1, "\u5FC5\u987B\u63D0\u4F9B command").describe("\u8981\u6267\u884C\u7684 shell \u547D\u4EE4\uFF08\u5047\u5B9A bash \u8BED\u6CD5\uFF09"),
|
|
472
|
+
timeout: z.number().int().positive().max(MAX_TIMEOUT_MS).optional().describe(`\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09\uFF0C\u6700\u591A ${MAX_TIMEOUT_MS}\uFF08${MAX_TIMEOUT_MS / 6e4} \u5206\u949F\uFF09\uFF1B\u4E0D\u586B\u9ED8\u8BA4 ${DEFAULT_TIMEOUT_MS}`),
|
|
473
|
+
description: z.string().optional().describe('\u7528\u4E00\u53E5\u8BDD\u4E3B\u52A8\u8BED\u6001\u63CF\u8FF0\u547D\u4EE4\u505A\u4EC0\u4E48\uFF085-10 \u8BCD\uFF09\uFF0C\u5982 "List files in current directory"')
|
|
474
|
+
});
|
|
475
|
+
var parameters = toToolParameters(inputSchema);
|
|
476
|
+
var description = `Executes a given bash command and returns its output.
|
|
477
|
+
|
|
478
|
+
The working directory persists between commands, but shell state does not. The shell environment is initialized from the user's profile.
|
|
479
|
+
|
|
480
|
+
IMPORTANT: Avoid using this tool to run \`find\`, \`grep\`, \`cat\`, \`head\`, \`tail\`, \`sed\`, \`awk\`, or \`echo\` commands, unless explicitly instructed or after you have verified that a dedicated tool cannot accomplish your task. Instead, use the appropriate dedicated tool as this will provide a much better experience for the user:
|
|
481
|
+
|
|
482
|
+
- File search: Use Glob (NOT find or ls)
|
|
483
|
+
- Content search: Use Grep (NOT grep or rg)
|
|
484
|
+
- Read files: Use Read (NOT cat/head/tail)
|
|
485
|
+
- Edit files: Use Edit (NOT sed/awk)
|
|
486
|
+
- Write files: Use Write (NOT echo >/cat <<EOF)
|
|
487
|
+
- Communication: Output text directly (NOT echo/printf)
|
|
488
|
+
While the Bash tool can do similar things, it's better to use the built-in tools as they provide a better user experience and make it easier to review tool calls and give permission.
|
|
489
|
+
|
|
490
|
+
# Instructions
|
|
491
|
+
- If your command will create new directories or files, first use this tool to run \`ls\` to verify the parent directory exists and is the correct location.
|
|
492
|
+
- Always quote file paths that contain spaces with double quotes in your command (e.g., cd "path with spaces/file.txt").
|
|
493
|
+
- Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of \`cd\`. You may use \`cd\` if the User explicitly requests it.
|
|
494
|
+
- You may specify an optional timeout in milliseconds (up to ${MAX_TIMEOUT_MS}ms / ${MAX_TIMEOUT_MS / 6e4} minutes). By default, your command will timeout after ${DEFAULT_TIMEOUT_MS}ms (${DEFAULT_TIMEOUT_MS / 6e4} minutes).
|
|
495
|
+
- When issuing multiple commands:
|
|
496
|
+
- If the commands are independent and can run in parallel, make multiple Bash tool calls in a single message.
|
|
497
|
+
- If the commands depend on each other and must run sequentially, use a single Bash call with '&&' to chain them.
|
|
498
|
+
- Use ';' only when you need to run commands sequentially but don't care if earlier commands fail.
|
|
499
|
+
- DO NOT use newlines to separate commands (newlines are ok in quoted strings and here-strings).
|
|
500
|
+
- For git commands:
|
|
501
|
+
- Prefer to create a new commit rather than amending an existing commit.
|
|
502
|
+
- Before running destructive operations (e.g., git reset --hard, git push --force, git checkout --), consider whether there is a safer alternative that achieves the same goal. Only use destructive operations when they are truly the best approach.
|
|
503
|
+
- Never skip hooks (--no-verify) or bypass signing (--no-gpg-sign, -c commit.gpgsign=false) unless the user has explicitly asked for it. If a hook fails, investigate and fix the underlying issue.
|
|
504
|
+
- Avoid unnecessary \`sleep\` commands; do not retry failing commands in a sleep loop \u2014 diagnose the root cause.
|
|
505
|
+
|
|
506
|
+
# Safety
|
|
507
|
+
The following command patterns are blocked at the tool level and will fail before execution (no need to try them):
|
|
508
|
+
- \`rm -rf /\` and variants targeting root, $HOME, ~, or system directories (/etc, /usr, /bin, /Windows, /Users, /home, ...)
|
|
509
|
+
- Filesystem destruction: \`mkfs\`, \`format X:\`, \`dd of=/dev/sdX\`, redirects to raw block devices
|
|
510
|
+
- Power management: \`shutdown\`, \`reboot\`, \`halt\`, \`poweroff\`, \`init 0/6\`
|
|
511
|
+
- Fork bombs
|
|
512
|
+
- Pipe-to-shell from network: \`curl ... | sh\`, \`wget ... | bash\`, etc.
|
|
513
|
+
- \`chmod 777 /\`, Windows full-disk \`del /s\` / \`rmdir /s\`, \`diskpart\`
|
|
514
|
+
|
|
515
|
+
If you have a legitimate use case that requires one of the above patterns, ask the user to run the command themselves in their terminal \u2014 do not try to bypass the check.`;
|
|
516
|
+
async function call(input, signal) {
|
|
517
|
+
const command = input.command;
|
|
518
|
+
const timeoutMs = Math.min(input.timeout ?? DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
|
|
519
|
+
const forbiddenReason = checkForbidden(command);
|
|
520
|
+
if (forbiddenReason) {
|
|
521
|
+
return {
|
|
522
|
+
ok: false,
|
|
523
|
+
error: `\u5B89\u5168\u68C0\u67E5\u62D2\u7EDD\u6267\u884C\uFF1A${forbiddenReason}
|
|
524
|
+
\u547D\u4EE4\uFF1A${command}`
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
let stdout = "";
|
|
528
|
+
let stderr = "";
|
|
529
|
+
let exitCode = null;
|
|
530
|
+
let timedOut = false;
|
|
531
|
+
let killedBySignal = false;
|
|
532
|
+
const ac = new AbortController();
|
|
533
|
+
const onAbort = () => ac.abort();
|
|
534
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
535
|
+
const timer = setTimeout(() => {
|
|
536
|
+
timedOut = true;
|
|
537
|
+
ac.abort();
|
|
538
|
+
}, timeoutMs);
|
|
539
|
+
try {
|
|
540
|
+
await new Promise((resolveP, rejectP) => {
|
|
541
|
+
const child = spawn(command, {
|
|
542
|
+
shell: true,
|
|
543
|
+
cwd: process.cwd(),
|
|
544
|
+
signal: ac.signal,
|
|
545
|
+
env: process.env,
|
|
546
|
+
windowsHide: true
|
|
547
|
+
});
|
|
548
|
+
child.stdout?.setEncoding("utf8");
|
|
549
|
+
child.stderr?.setEncoding("utf8");
|
|
550
|
+
child.stdout?.on("data", (chunk) => {
|
|
551
|
+
stdout += chunk;
|
|
552
|
+
});
|
|
553
|
+
child.stderr?.on("data", (chunk) => {
|
|
554
|
+
stderr += chunk;
|
|
555
|
+
});
|
|
556
|
+
child.on("error", (e) => {
|
|
557
|
+
if (e.code === "ABORT_ERR") {
|
|
558
|
+
killedBySignal = true;
|
|
559
|
+
resolveP();
|
|
560
|
+
} else {
|
|
561
|
+
rejectP(e);
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
child.on("close", (code, sig) => {
|
|
565
|
+
exitCode = code;
|
|
566
|
+
if (sig) killedBySignal = true;
|
|
567
|
+
resolveP();
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
} catch (e) {
|
|
571
|
+
return {
|
|
572
|
+
ok: false,
|
|
573
|
+
error: `\u6267\u884C\u547D\u4EE4\u5931\u8D25\uFF1A${e.message}
|
|
574
|
+
\u547D\u4EE4\uFF1A${command}`
|
|
575
|
+
};
|
|
576
|
+
} finally {
|
|
577
|
+
clearTimeout(timer);
|
|
578
|
+
signal?.removeEventListener("abort", onAbort);
|
|
579
|
+
}
|
|
580
|
+
const parts = [];
|
|
581
|
+
if (stdout) parts.push(`<stdout>
|
|
582
|
+
${stdout.replace(/\s+$/, "")}
|
|
583
|
+
</stdout>`);
|
|
584
|
+
if (stderr) parts.push(`<stderr>
|
|
585
|
+
${stderr.replace(/\s+$/, "")}
|
|
586
|
+
</stderr>`);
|
|
587
|
+
if (parts.length === 0) parts.push("<no output>");
|
|
588
|
+
if (timedOut) {
|
|
589
|
+
parts.push(`
|
|
590
|
+
[\u8D85\u65F6 ${timeoutMs}ms \u540E\u88AB\u4E2D\u65AD]`);
|
|
591
|
+
} else if (killedBySignal && !timedOut) {
|
|
592
|
+
parts.push(`
|
|
593
|
+
[\u88AB\u4FE1\u53F7\u4E2D\u65AD]`);
|
|
594
|
+
}
|
|
595
|
+
parts.push(`
|
|
596
|
+
[exit code: ${exitCode === null ? "killed" : exitCode}]`);
|
|
597
|
+
let content = parts.join("\n");
|
|
598
|
+
if (content.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
|
|
599
|
+
content = content.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + `
|
|
600
|
+
|
|
601
|
+
... (\u8F93\u51FA\u8D85\u8FC7 ${DEFAULT_MAX_RESULT_SIZE_CHARS} \u5B57\u7B26\uFF0C\u5DF2\u622A\u65AD)`;
|
|
602
|
+
}
|
|
603
|
+
if (timedOut || exitCode !== null && exitCode !== 0 || killedBySignal) {
|
|
604
|
+
return { ok: false, error: content };
|
|
605
|
+
}
|
|
606
|
+
return { ok: true, content };
|
|
607
|
+
}
|
|
608
|
+
var bashTool = {
|
|
609
|
+
name: "Bash",
|
|
610
|
+
description,
|
|
611
|
+
inputSchema,
|
|
612
|
+
parameters,
|
|
613
|
+
isReadOnly: false,
|
|
614
|
+
isConcurrencySafe: false,
|
|
615
|
+
// 默认按"会改状态"算;保守不并发
|
|
616
|
+
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
617
|
+
call
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
// src/tools/edit/edit.ts
|
|
621
|
+
import { readFile as readFile5, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
622
|
+
import { existsSync } from "fs";
|
|
623
|
+
import { dirname as dirname4, resolve } from "path";
|
|
624
|
+
import { z as z2 } from "zod";
|
|
625
|
+
var MAX_EDIT_FILE_SIZE_BYTES = 1024 * 1024 * 1024;
|
|
626
|
+
var inputSchema2 = z2.object({
|
|
627
|
+
file_path: z2.string().min(1).describe("\u8981\u7F16\u8F91\u7684\u6587\u4EF6\u8DEF\u5F84"),
|
|
628
|
+
old_string: z2.string().describe(
|
|
629
|
+
"\u8981\u66FF\u6362\u7684\u539F\u6587\u672C\uFF08\u5FC5\u987B\u5728\u6587\u4EF6\u4E2D\u552F\u4E00\uFF0C\u9664\u975E replace_all=true\uFF09\uFF1B\u4E3A\u7A7A\u5B57\u7B26\u4E32\u4E14\u6587\u4EF6\u4E0D\u5B58\u5728\u65F6\u521B\u5EFA\u65B0\u6587\u4EF6"
|
|
630
|
+
),
|
|
631
|
+
new_string: z2.string().describe("\u66FF\u6362\u4E3A\u7684\u65B0\u6587\u672C\uFF08\u4E0E old_string \u5FC5\u987B\u4E0D\u540C\uFF09"),
|
|
632
|
+
replace_all: z2.boolean().optional().describe("\u662F\u5426\u66FF\u6362\u6240\u6709\u51FA\u73B0\u4F4D\u7F6E\uFF08\u9ED8\u8BA4 false\uFF09")
|
|
633
|
+
});
|
|
634
|
+
var parameters2 = toToolParameters(inputSchema2);
|
|
635
|
+
var description2 = `Performs exact string replacements in files.
|
|
636
|
+
|
|
637
|
+
Usage:
|
|
638
|
+
- You MUST use your \`Read\` tool to read the current content of the file BEFORE calling Edit.
|
|
639
|
+
Memory is unreliable \u2014 if you think you "remember" how the code looks, you are probably wrong.
|
|
640
|
+
Re-read the relevant sections (function, module, or area you plan to change) every time.
|
|
641
|
+
- To create a new file, pass an empty \`old_string\` and the desired contents as \`new_string\`.
|
|
642
|
+
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
|
|
643
|
+
- The edit will FAIL if \`old_string\` is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use \`replace_all\` to change every instance of \`old_string\`.
|
|
644
|
+
- Use \`replace_all\` for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.
|
|
645
|
+
- Preserve exact indentation (tabs/spaces).`;
|
|
646
|
+
function validatePath(filePath) {
|
|
647
|
+
if (filePath.includes("\0")) {
|
|
648
|
+
return { ok: false, error: "\u8DEF\u5F84\u5305\u542B\u975E\u6CD5\u5B57\u7B26\uFF08null byte\uFF09" };
|
|
649
|
+
}
|
|
650
|
+
if (process.platform === "win32" && filePath.includes("\\\\")) {
|
|
651
|
+
return { ok: false, error: "\u4E0D\u652F\u6301 UNC \u8DEF\u5F84\uFF08\\\\server\\share \u683C\u5F0F\uFF09\uFF0C\u8BF7\u4F7F\u7528\u672C\u5730\u8DEF\u5F84" };
|
|
652
|
+
}
|
|
653
|
+
return { ok: true };
|
|
654
|
+
}
|
|
655
|
+
async function call2(input) {
|
|
656
|
+
const filePath = resolve(input.file_path);
|
|
657
|
+
const { old_string, new_string } = input;
|
|
658
|
+
const replaceAll = input.replace_all ?? false;
|
|
659
|
+
const pathCheck = validatePath(filePath);
|
|
660
|
+
if (!pathCheck.ok) return pathCheck;
|
|
661
|
+
if (old_string === new_string) {
|
|
662
|
+
return { ok: false, error: "old_string \u4E0E new_string \u5B8C\u5168\u76F8\u540C\uFF0C\u6CA1\u6709\u53EF\u6539\u7684\u5185\u5BB9\u3002" };
|
|
663
|
+
}
|
|
664
|
+
if (old_string === "" && !existsSync(filePath)) {
|
|
665
|
+
try {
|
|
666
|
+
await mkdir3(dirname4(filePath), { recursive: true });
|
|
667
|
+
await writeFile3(filePath, new_string, "utf8");
|
|
668
|
+
return {
|
|
669
|
+
ok: true,
|
|
670
|
+
content: `\u5DF2\u521B\u5EFA\u65B0\u6587\u4EF6\uFF1A${filePath}\uFF08${new_string.length} \u5B57\u7B26\uFF09`
|
|
671
|
+
};
|
|
672
|
+
} catch (e) {
|
|
673
|
+
return { ok: false, error: `\u521B\u5EFA\u6587\u4EF6\u5931\u8D25\uFF1A${e.message}` };
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
if (!existsSync(filePath)) {
|
|
677
|
+
return {
|
|
678
|
+
ok: false,
|
|
679
|
+
error: `\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${filePath}
|
|
680
|
+
\uFF08\u8981\u521B\u5EFA\u65B0\u6587\u4EF6\uFF0C\u8BF7\u628A old_string \u8BBE\u4E3A\u7A7A\u5B57\u7B26\u4E32\uFF09`
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
let original;
|
|
684
|
+
try {
|
|
685
|
+
original = await readFile5(filePath, "utf8");
|
|
686
|
+
} catch (e) {
|
|
687
|
+
return { ok: false, error: `\u8BFB\u53D6\u5931\u8D25\uFF1A${e.message}` };
|
|
688
|
+
}
|
|
689
|
+
const fileSize = Buffer.byteLength(original, "utf8");
|
|
690
|
+
if (fileSize > MAX_EDIT_FILE_SIZE_BYTES) {
|
|
691
|
+
return {
|
|
692
|
+
ok: false,
|
|
693
|
+
error: `\u6587\u4EF6\u8FC7\u5927\uFF08${fileSize} \u5B57\u8282 > ${MAX_EDIT_FILE_SIZE_BYTES} \u5B57\u8282 \u2248 1GB\uFF09\u3002Edit \u4E0D\u9002\u5408\u64CD\u4F5C\u8D85\u5927\u6587\u4EF6\uFF0C\u8BF7\u7528 Write \u5DE5\u5177\u3002`
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
if (old_string === "") {
|
|
697
|
+
return {
|
|
698
|
+
ok: false,
|
|
699
|
+
error: "old_string \u4E3A\u7A7A\u4F46\u6587\u4EF6\u5DF2\u5B58\u5728 \u2014\u2014 \u8FD9\u901A\u5E38\u662F\u9519\u8BEF\u7684\u3002\u8981\u66FF\u6362\u5168\u6587\u8BF7\u7528 Write \u5DE5\u5177\u3002"
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
const occurrences = countOccurrences(original, old_string);
|
|
703
|
+
if (occurrences === 0) {
|
|
704
|
+
const hint = findFuzzyMatchHint(original, old_string);
|
|
705
|
+
const extraMsg = hint ? `
|
|
706
|
+
|
|
707
|
+
\u{1F4A1} \u63D0\u793A\uFF1A${hint}` : "";
|
|
708
|
+
return {
|
|
709
|
+
ok: false,
|
|
710
|
+
error: `\u5728 ${filePath} \u4E2D\u627E\u4E0D\u5230 old_string\u3002\u8BF7\u5148\u7528 Read \u5DE5\u5177\u6838\u5BF9\u5185\u5BB9\uFF08\u6CE8\u610F\u7A7A\u683C/\u7F29\u8FDB/\u6362\u884C\uFF09\u3002${extraMsg}`
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
if (occurrences > 1 && !replaceAll) {
|
|
714
|
+
return {
|
|
715
|
+
ok: false,
|
|
716
|
+
error: `old_string \u5728\u6587\u4EF6\u4E2D\u51FA\u73B0\u4E86 ${occurrences} \u6B21\uFF0C\u4E0D\u552F\u4E00\u3002
|
|
717
|
+
\u8BF7\u6269\u5927 old_string \u5305\u542B\u66F4\u591A\u4E0A\u4E0B\u6587\uFF0C\u6216\u663E\u5F0F\u4F20 replace_all=true\u3002`
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
const replaced = replaceAll ? splitReplaceAll(original, old_string, new_string) : original.replace(old_string, new_string);
|
|
721
|
+
try {
|
|
722
|
+
await writeFile3(filePath, replaced, "utf8");
|
|
723
|
+
} catch (e) {
|
|
724
|
+
return { ok: false, error: `\u5199\u5165\u5931\u8D25\uFF1A${e.message}` };
|
|
725
|
+
}
|
|
726
|
+
const linesBefore = original.split("\n").length;
|
|
727
|
+
const linesAfter = replaced.split("\n").length;
|
|
728
|
+
return {
|
|
729
|
+
ok: true,
|
|
730
|
+
content: `\u5DF2\u7F16\u8F91 ${filePath}
|
|
731
|
+
\u66FF\u6362\u6B21\u6570\uFF1A${replaceAll ? occurrences : 1}
|
|
732
|
+
\u884C\u6570\uFF1A${linesBefore} \u2192 ${linesAfter}`
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
function countOccurrences(haystack, needle) {
|
|
736
|
+
if (needle.length === 0) return 0;
|
|
737
|
+
let count = 0;
|
|
738
|
+
let pos = 0;
|
|
739
|
+
while ((pos = haystack.indexOf(needle, pos)) !== -1) {
|
|
740
|
+
count++;
|
|
741
|
+
pos += needle.length;
|
|
742
|
+
}
|
|
743
|
+
return count;
|
|
744
|
+
}
|
|
745
|
+
function splitReplaceAll(haystack, needle, replacement) {
|
|
746
|
+
return haystack.split(needle).join(replacement);
|
|
747
|
+
}
|
|
748
|
+
function findFuzzyMatchHint(fileContent, target) {
|
|
749
|
+
const MIN_OVERLAP_RATIO = 0.5;
|
|
750
|
+
const MAX_HINTS = 3;
|
|
751
|
+
const targetTrimmed = target.trim();
|
|
752
|
+
if (targetTrimmed.length < 5) return null;
|
|
753
|
+
const lines = fileContent.split("\n");
|
|
754
|
+
const candidates = [];
|
|
755
|
+
for (let i = 0; i < lines.length; i++) {
|
|
756
|
+
const line = lines[i];
|
|
757
|
+
const trimmedLine = line.trim();
|
|
758
|
+
if (trimmedLine.length < 5) continue;
|
|
759
|
+
const ratio = lcsRatio(targetTrimmed, trimmedLine);
|
|
760
|
+
if (ratio >= MIN_OVERLAP_RATIO) {
|
|
761
|
+
candidates.push({ lineNum: i + 1, text: trimmedLine, ratio });
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (target.includes("\n") && candidates.length < MAX_HINTS) {
|
|
765
|
+
const targetLines = target.split("\n").filter((l) => l.trim().length > 0);
|
|
766
|
+
if (targetLines.length >= 2) {
|
|
767
|
+
for (let start = 0; start <= lines.length - targetLines.length; start++) {
|
|
768
|
+
const window = lines.slice(start, start + targetLines.length).map((l) => l.trim()).join("\n");
|
|
769
|
+
const ratio = lcsRatio(target.trim(), window);
|
|
770
|
+
if (ratio >= MIN_OVERLAP_RATIO) {
|
|
771
|
+
candidates.push({ lineNum: start + 1, text: window.slice(0, 80) + (window.length > 80 ? "..." : ""), ratio });
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (candidates.length === 0) return null;
|
|
777
|
+
candidates.sort((a, b) => b.ratio - a.ratio);
|
|
778
|
+
const top = candidates.slice(0, MAX_HINTS);
|
|
779
|
+
const hints = top.map(
|
|
780
|
+
(c) => `\u7B2C ${c.lineNum} \u884C\u9644\u8FD1\u6709\u76F8\u4F3C\u5185\u5BB9\uFF08\u76F8\u4F3C\u5EA6 ${(c.ratio * 100).toFixed(0)}%\uFF09:
|
|
781
|
+
"${c.text.slice(0, 100)}${c.text.length > 100 ? "\u2026" : ""}"`
|
|
782
|
+
);
|
|
783
|
+
return `\u53EF\u80FD\u4F60\u60F3\u8981\u4FEE\u6539\u4EE5\u4E0B\u4F4D\u7F6E\u4E4B\u4E00\uFF1F
|
|
784
|
+
${hints.join("\n ")}`;
|
|
785
|
+
}
|
|
786
|
+
function lcsRatio(a, b) {
|
|
787
|
+
const m = a.length;
|
|
788
|
+
const n = b.length;
|
|
789
|
+
const MAX_LEN = 500;
|
|
790
|
+
const aa = m > MAX_LEN ? a.slice(0, MAX_LEN) : a;
|
|
791
|
+
const bb = n > MAX_LEN ? b.slice(0, MAX_LEN) : b;
|
|
792
|
+
const mm = aa.length;
|
|
793
|
+
const nn = bb.length;
|
|
794
|
+
const dp = new Array(nn + 1).fill(0);
|
|
795
|
+
for (let i = 1; i <= mm; i++) {
|
|
796
|
+
let prev = 0;
|
|
797
|
+
for (let j = 1; j <= nn; j++) {
|
|
798
|
+
const temp = dp[j];
|
|
799
|
+
if (aa[i - 1] === bb[j - 1]) {
|
|
800
|
+
dp[j] = prev + 1;
|
|
801
|
+
} else {
|
|
802
|
+
dp[j] = Math.max(dp[j], dp[j - 1]);
|
|
803
|
+
}
|
|
804
|
+
prev = temp;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return dp[nn] / Math.max(mm, nn);
|
|
808
|
+
}
|
|
809
|
+
var editTool = {
|
|
810
|
+
name: "Edit",
|
|
811
|
+
description: description2,
|
|
812
|
+
inputSchema: inputSchema2,
|
|
813
|
+
parameters: parameters2,
|
|
814
|
+
isReadOnly: false,
|
|
815
|
+
isConcurrencySafe: false,
|
|
816
|
+
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
817
|
+
call: call2
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
// src/tools/glob/glob.ts
|
|
821
|
+
import { stat } from "fs/promises";
|
|
822
|
+
import { isAbsolute, resolve as resolve2 } from "path";
|
|
823
|
+
import fg from "fast-glob";
|
|
824
|
+
import { z as z3 } from "zod";
|
|
825
|
+
var inputSchema3 = z3.object({
|
|
826
|
+
pattern: z3.string().min(1).describe('glob \u6A21\u5F0F\uFF0C\u4F8B\u5982 "**/*.ts" \u6216 "src/components/**/*.tsx"'),
|
|
827
|
+
path: z3.string().optional().describe('\u641C\u7D22\u7684\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09\uFF1B\u7701\u7565\u65F6\u4E0D\u8981\u4F20 "undefined" \u5B57\u7B26\u4E32')
|
|
828
|
+
});
|
|
829
|
+
var parameters3 = toToolParameters(inputSchema3);
|
|
830
|
+
var description3 = `- Fast file pattern matching tool that works with any codebase size
|
|
831
|
+
- Supports glob patterns like "**/*.js" or "src/**/*.ts"
|
|
832
|
+
- Returns matching file paths sorted by modification time (oldest first)
|
|
833
|
+
- Use this tool when you need to find files by name patterns
|
|
834
|
+
- When you need to do an open ended search that may require multiple rounds, prefer the Grep tool for content search`;
|
|
835
|
+
async function call3(input) {
|
|
836
|
+
const cwd = input.path ? resolve2(input.path) : process.cwd();
|
|
837
|
+
const pattern = input.pattern.replace(/\\/g, "/");
|
|
838
|
+
let matches;
|
|
839
|
+
try {
|
|
840
|
+
matches = await fg(pattern, {
|
|
841
|
+
cwd,
|
|
842
|
+
dot: true,
|
|
843
|
+
onlyFiles: true,
|
|
844
|
+
followSymbolicLinks: false,
|
|
845
|
+
// 默认排除一些噪音目录;如果用户的 pattern 显式指了它们,这里也不会盖
|
|
846
|
+
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
|
|
847
|
+
absolute: false
|
|
848
|
+
});
|
|
849
|
+
} catch (e) {
|
|
850
|
+
return { ok: false, error: `glob \u5931\u8D25\uFF1A${e.message}` };
|
|
851
|
+
}
|
|
852
|
+
if (matches.length === 0) {
|
|
853
|
+
return { ok: true, content: `(no files matched pattern "${pattern}" in ${cwd})` };
|
|
854
|
+
}
|
|
855
|
+
const withMtime = await Promise.all(
|
|
856
|
+
matches.map(async (rel) => {
|
|
857
|
+
const abs = isAbsolute(rel) ? rel : resolve2(cwd, rel);
|
|
858
|
+
try {
|
|
859
|
+
const st = await stat(abs);
|
|
860
|
+
return { path: rel, mtime: st.mtimeMs };
|
|
861
|
+
} catch {
|
|
862
|
+
return { path: rel, mtime: 0 };
|
|
863
|
+
}
|
|
864
|
+
})
|
|
865
|
+
);
|
|
866
|
+
withMtime.sort((a, b) => a.mtime - b.mtime);
|
|
867
|
+
let content = withMtime.map((m) => m.path).join("\n");
|
|
868
|
+
if (content.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
|
|
869
|
+
content = content.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + `
|
|
870
|
+
|
|
871
|
+
... (\u5171 ${withMtime.length} \u4E2A\u6587\u4EF6\uFF0C\u8F93\u51FA\u8D85\u8FC7 ${DEFAULT_MAX_RESULT_SIZE_CHARS} \u5B57\u7B26\uFF0C\u5DF2\u622A\u65AD\u5C3E\u90E8\u65B0\u6587\u4EF6)`;
|
|
872
|
+
}
|
|
873
|
+
return {
|
|
874
|
+
ok: true,
|
|
875
|
+
content: `${content}
|
|
876
|
+
|
|
877
|
+
(\u5171 ${withMtime.length} \u4E2A\u6587\u4EF6\uFF0C\u6309\u4FEE\u6539\u65F6\u95F4\u5347\u5E8F/\u65E7\u5728\u524D)`
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
var globTool = {
|
|
881
|
+
name: "Glob",
|
|
882
|
+
description: description3,
|
|
883
|
+
inputSchema: inputSchema3,
|
|
884
|
+
parameters: parameters3,
|
|
885
|
+
isReadOnly: true,
|
|
886
|
+
isConcurrencySafe: true,
|
|
887
|
+
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
888
|
+
call: call3
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
// src/tools/grep/grep.ts
|
|
892
|
+
import { spawn as spawn3 } from "child_process";
|
|
893
|
+
import { resolve as resolve4 } from "path";
|
|
894
|
+
import { z as z4 } from "zod";
|
|
895
|
+
|
|
896
|
+
// src/tools/grep/rgPath.ts
|
|
897
|
+
import { spawn as spawn2 } from "child_process";
|
|
898
|
+
import { chmodSync, existsSync as existsSync2 } from "fs";
|
|
899
|
+
import { dirname as dirname5, resolve as resolve3 } from "path";
|
|
900
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
901
|
+
var cached;
|
|
902
|
+
async function resolveRgPath() {
|
|
903
|
+
if (cached !== void 0) return cached;
|
|
904
|
+
cached = await detect();
|
|
905
|
+
return cached;
|
|
906
|
+
}
|
|
907
|
+
async function detect() {
|
|
908
|
+
const fromEnv = process.env.MINIMAL_AGENT_RIPGREP_PATH;
|
|
909
|
+
if (fromEnv && existsSync2(fromEnv)) return fromEnv;
|
|
910
|
+
const vendored = vendoredRgPath();
|
|
911
|
+
if (vendored && existsSync2(vendored)) {
|
|
912
|
+
ensureExecutable(vendored);
|
|
913
|
+
return vendored;
|
|
914
|
+
}
|
|
915
|
+
if (await trySpawn("rg")) return "rg";
|
|
916
|
+
for (const candidate of claudeCodeCandidates()) {
|
|
917
|
+
if (existsSync2(candidate)) {
|
|
918
|
+
ensureExecutable(candidate);
|
|
919
|
+
return candidate;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
function vendoredRgPath() {
|
|
925
|
+
try {
|
|
926
|
+
const here = dirname5(fileURLToPath2(import.meta.url));
|
|
927
|
+
const projectRoot = resolve3(here, "..", "..");
|
|
928
|
+
return resolve3(projectRoot, "vendor", "ripgrep", subdir(), exeName());
|
|
929
|
+
} catch {
|
|
930
|
+
return null;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
function subdir() {
|
|
934
|
+
return `${process.arch}-${process.platform}`;
|
|
935
|
+
}
|
|
936
|
+
function exeName() {
|
|
937
|
+
return process.platform === "win32" ? "rg.exe" : "rg";
|
|
938
|
+
}
|
|
939
|
+
function ensureExecutable(path2) {
|
|
940
|
+
if (process.platform === "win32") return;
|
|
941
|
+
try {
|
|
942
|
+
chmodSync(path2, 493);
|
|
943
|
+
} catch {
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
function trySpawn(cmd) {
|
|
947
|
+
return new Promise((resolveP) => {
|
|
948
|
+
let settled = false;
|
|
949
|
+
const done = (ok) => {
|
|
950
|
+
if (settled) return;
|
|
951
|
+
settled = true;
|
|
952
|
+
resolveP(ok);
|
|
953
|
+
};
|
|
954
|
+
try {
|
|
955
|
+
const child = spawn2(cmd, ["--version"], {
|
|
956
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
957
|
+
windowsHide: true
|
|
958
|
+
});
|
|
959
|
+
let stdout = "";
|
|
960
|
+
child.stdout.setEncoding("utf8");
|
|
961
|
+
child.stdout.on("data", (c) => stdout += c);
|
|
962
|
+
child.on("error", () => done(false));
|
|
963
|
+
child.on(
|
|
964
|
+
"close",
|
|
965
|
+
(code) => done(code === 0 && stdout.startsWith("ripgrep "))
|
|
966
|
+
);
|
|
967
|
+
setTimeout(() => {
|
|
968
|
+
try {
|
|
969
|
+
child.kill();
|
|
970
|
+
} catch {
|
|
971
|
+
}
|
|
972
|
+
done(false);
|
|
973
|
+
}, 5e3);
|
|
974
|
+
} catch {
|
|
975
|
+
done(false);
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
function claudeCodeCandidates() {
|
|
980
|
+
const arch = process.arch;
|
|
981
|
+
const platform = process.platform;
|
|
982
|
+
const subdir2 = `${arch}-${platform}`;
|
|
983
|
+
const exe = platform === "win32" ? "rg.exe" : "rg";
|
|
984
|
+
const npmRoots = [];
|
|
985
|
+
if (platform === "win32") {
|
|
986
|
+
if (process.env.APPDATA) {
|
|
987
|
+
npmRoots.push(resolve3(process.env.APPDATA, "npm", "node_modules"));
|
|
988
|
+
}
|
|
989
|
+
if (process.env.USERPROFILE) {
|
|
990
|
+
npmRoots.push(
|
|
991
|
+
resolve3(process.env.USERPROFILE, "AppData", "Roaming", "npm", "node_modules")
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
} else {
|
|
995
|
+
const home = process.env.HOME ?? "";
|
|
996
|
+
if (home) {
|
|
997
|
+
npmRoots.push(resolve3(home, ".npm-global", "lib", "node_modules"));
|
|
998
|
+
npmRoots.push(resolve3(home, ".npm", "lib", "node_modules"));
|
|
999
|
+
npmRoots.push(resolve3(home, "node_modules"));
|
|
1000
|
+
}
|
|
1001
|
+
npmRoots.push("/usr/local/lib/node_modules");
|
|
1002
|
+
npmRoots.push("/usr/lib/node_modules");
|
|
1003
|
+
npmRoots.push("/opt/homebrew/lib/node_modules");
|
|
1004
|
+
}
|
|
1005
|
+
return npmRoots.map(
|
|
1006
|
+
(root) => resolve3(root, "@anthropic-ai", "claude-code", "vendor", "ripgrep", subdir2, exe)
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// src/tools/grep/grep.ts
|
|
1011
|
+
var inputSchema4 = z4.object({
|
|
1012
|
+
pattern: z4.string().min(1).describe("\u6B63\u5219\u8868\u8FBE\u5F0F\uFF08ripgrep \u517C\u5BB9\u8BED\u6CD5\uFF09"),
|
|
1013
|
+
path: z4.string().optional().describe("\u641C\u7D22\u7684\u6839\u76EE\u5F55\u6216\u6587\u4EF6\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09"),
|
|
1014
|
+
glob: z4.string().optional().describe('\u6587\u4EF6\u540D glob \u8FC7\u6EE4\uFF0C\u5982 "*.ts"'),
|
|
1015
|
+
type: z4.string().optional().describe('rg \u7684\u6587\u4EF6\u7C7B\u578B\u5FEB\u6377\u540D\uFF0C\u5982 "py"\u3001"rust"\u3001"js"'),
|
|
1016
|
+
output_mode: z4.enum(["content", "files_with_matches", "count"]).optional().describe("\u8F93\u51FA\u6A21\u5F0F\uFF1Acontent=\u5339\u914D\u884C\uFF1Bfiles_with_matches=\u53EA\u5217\u6587\u4EF6\uFF1Bcount=\u6BCF\u6587\u4EF6\u8BA1\u6570"),
|
|
1017
|
+
"-i": z4.boolean().optional().describe("\u5FFD\u7565\u5927\u5C0F\u5199"),
|
|
1018
|
+
"-n": z4.boolean().optional().describe("\u663E\u793A\u884C\u53F7\uFF08\u4EC5 content \u6A21\u5F0F\uFF09"),
|
|
1019
|
+
"-A": z4.number().int().min(0).optional().describe("\u5339\u914D\u540E\u5C55\u793A\u51E0\u884C\u4E0A\u4E0B\u6587"),
|
|
1020
|
+
"-B": z4.number().int().min(0).optional().describe("\u5339\u914D\u524D\u5C55\u793A\u51E0\u884C\u4E0A\u4E0B\u6587"),
|
|
1021
|
+
"-C": z4.number().int().min(0).optional().describe("\u5339\u914D\u524D\u540E\u5404\u5C55\u793A\u51E0\u884C\uFF08\u8986\u76D6 -A/-B\uFF09"),
|
|
1022
|
+
head_limit: z4.number().int().positive().optional().describe("\u8F93\u51FA\u6700\u591A\u4FDD\u7559\u524D N \u884C\uFF08\u9632\u6B62\u7ED3\u679C\u8FC7\u5927\uFF09")
|
|
1023
|
+
});
|
|
1024
|
+
var parameters4 = toToolParameters(inputSchema4);
|
|
1025
|
+
var description4 = `A powerful search tool built on ripgrep.
|
|
1026
|
+
|
|
1027
|
+
Usage:
|
|
1028
|
+
- ALWAYS use Grep for content search tasks. Do NOT invoke \`grep\` or \`rg\` directly via Bash.
|
|
1029
|
+
- Supports full regex syntax (e.g., "log.*Error", "function\\s+\\w+")
|
|
1030
|
+
- Filter files with glob parameter (e.g., "*.js", "**/*.tsx") or type parameter (e.g., "js", "py", "rust")
|
|
1031
|
+
- Output modes: "content" shows matching lines, "files_with_matches" shows only file paths (default), "count" shows match counts
|
|
1032
|
+
- Pattern syntax: Uses ripgrep (not classic grep)`;
|
|
1033
|
+
async function call4(input, signal) {
|
|
1034
|
+
const args = [];
|
|
1035
|
+
const mode = input.output_mode ?? "files_with_matches";
|
|
1036
|
+
if (mode === "files_with_matches") args.push("-l");
|
|
1037
|
+
else if (mode === "count") args.push("--count-matches");
|
|
1038
|
+
if (input["-i"]) args.push("-i");
|
|
1039
|
+
if (mode === "content" && input["-n"]) args.push("-n");
|
|
1040
|
+
if (typeof input["-C"] === "number") {
|
|
1041
|
+
args.push("-C", String(input["-C"]));
|
|
1042
|
+
} else {
|
|
1043
|
+
if (typeof input["-A"] === "number") args.push("-A", String(input["-A"]));
|
|
1044
|
+
if (typeof input["-B"] === "number") args.push("-B", String(input["-B"]));
|
|
1045
|
+
}
|
|
1046
|
+
if (input.glob) args.push("--glob", input.glob);
|
|
1047
|
+
if (input.type) args.push("--type", input.type);
|
|
1048
|
+
args.push("--max-columns", "500");
|
|
1049
|
+
args.push("--max-columns-preview");
|
|
1050
|
+
args.push("--sort", "modified");
|
|
1051
|
+
args.push("-e", input.pattern);
|
|
1052
|
+
args.push(input.path ? resolve4(input.path) : ".");
|
|
1053
|
+
const rgPath = await resolveRgPath();
|
|
1054
|
+
if (!rgPath) {
|
|
1055
|
+
return {
|
|
1056
|
+
ok: false,
|
|
1057
|
+
error: "\u627E\u4E0D\u5230 ripgrep\uFF08rg\uFF09\u3002\u53EF\u4EE5\u4E09\u9009\u4E00\uFF1A\n \u2460 \u88C5 Claude Code\uFF08\u81EA\u5E26 rg\uFF09\uFF1Anpm i -g @anthropic-ai/claude-code\n \u2461 \u7CFB\u7EDF\u88C5 ripgrep\uFF1A\n macOS: brew install ripgrep\n Windows: scoop install ripgrep\n Ubuntu: sudo apt install ripgrep\n \u2462 \u5728 .env \u8BBE\u7F6E MINIMAL_AGENT_RIPGREP_PATH=/\u7EDD\u5BF9/\u8DEF\u5F84/\u5230/rg"
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
let stdout = "";
|
|
1061
|
+
let stderr = "";
|
|
1062
|
+
let exitCode = null;
|
|
1063
|
+
try {
|
|
1064
|
+
await new Promise((resolveP, rejectP) => {
|
|
1065
|
+
const child = spawn3(rgPath, args, {
|
|
1066
|
+
cwd: process.cwd(),
|
|
1067
|
+
signal,
|
|
1068
|
+
windowsHide: true
|
|
1069
|
+
});
|
|
1070
|
+
child.stdout.setEncoding("utf8");
|
|
1071
|
+
child.stderr.setEncoding("utf8");
|
|
1072
|
+
child.stdout.on("data", (chunk) => stdout += chunk);
|
|
1073
|
+
child.stderr.on("data", (chunk) => stderr += chunk);
|
|
1074
|
+
child.on("error", (e) => rejectP(e));
|
|
1075
|
+
child.on("close", (code) => {
|
|
1076
|
+
exitCode = code;
|
|
1077
|
+
resolveP();
|
|
1078
|
+
});
|
|
1079
|
+
});
|
|
1080
|
+
} catch (e) {
|
|
1081
|
+
const err = e;
|
|
1082
|
+
return { ok: false, error: `\u6267\u884C rg \u5931\u8D25\uFF08${rgPath}\uFF09\uFF1A${err.message}` };
|
|
1083
|
+
}
|
|
1084
|
+
if (exitCode === 1) {
|
|
1085
|
+
return { ok: true, content: `(no matches for pattern: ${input.pattern})` };
|
|
1086
|
+
}
|
|
1087
|
+
if (exitCode !== 0) {
|
|
1088
|
+
return {
|
|
1089
|
+
ok: false,
|
|
1090
|
+
error: `rg \u9000\u51FA\u7801 ${exitCode}\uFF1A${stderr.slice(0, 500) || "(\u65E0 stderr)"}`
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
let content = stdout;
|
|
1094
|
+
if (input.head_limit && input.head_limit > 0) {
|
|
1095
|
+
const lines = content.split("\n");
|
|
1096
|
+
if (lines.length > input.head_limit) {
|
|
1097
|
+
content = lines.slice(0, input.head_limit).join("\n") + `
|
|
1098
|
+
... (\u5DF2\u622A\u65AD\u5230 ${input.head_limit} \u884C / \u5171 ${lines.length} \u884C\uFF0C\u4FDD\u7559\u6700\u65B0\u5339\u914D)`;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
if (content.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
|
|
1102
|
+
content = content.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + `
|
|
1103
|
+
|
|
1104
|
+
... (\u8F93\u51FA\u8D85\u8FC7 ${DEFAULT_MAX_RESULT_SIZE_CHARS} \u5B57\u7B26\uFF0C\u5DF2\u622A\u65AD\u5C3E\u90E8\u65E7\u7ED3\u679C)`;
|
|
1105
|
+
}
|
|
1106
|
+
return { ok: true, content };
|
|
1107
|
+
}
|
|
1108
|
+
var grepTool = {
|
|
1109
|
+
name: "Grep",
|
|
1110
|
+
description: description4,
|
|
1111
|
+
inputSchema: inputSchema4,
|
|
1112
|
+
parameters: parameters4,
|
|
1113
|
+
isReadOnly: true,
|
|
1114
|
+
isConcurrencySafe: true,
|
|
1115
|
+
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
1116
|
+
call: call4
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
// src/tools/read/read.ts
|
|
1120
|
+
import { readFile as readFile6, stat as stat2 } from "fs/promises";
|
|
1121
|
+
import { resolve as resolve5 } from "path";
|
|
1122
|
+
import { z as z5 } from "zod";
|
|
1123
|
+
var inputSchema5 = z5.object({
|
|
1124
|
+
file_path: z5.string().min(1, "\u5FC5\u987B\u63D0\u4F9B file_path").describe("\u8981\u8BFB\u53D6\u7684\u6587\u4EF6\u8DEF\u5F84\uFF0C\u7EDD\u5BF9\u8DEF\u5F84\u4F18\u5148"),
|
|
1125
|
+
offset: z5.number().int().positive().optional().describe("\u8D77\u59CB\u884C\u53F7\uFF081-indexed\uFF09\uFF1B\u4E0D\u586B\u5219\u4ECE\u6587\u4EF6\u5F00\u5934\u8BFB"),
|
|
1126
|
+
limit: z5.number().int().positive().optional().describe(`\u6700\u591A\u8BFB\u591A\u5C11\u884C\uFF1B\u4E0D\u586B\u5219\u7528\u9ED8\u8BA4\u503C ${MAX_LINES_TO_READ}`)
|
|
1127
|
+
});
|
|
1128
|
+
var parameters5 = toToolParameters(inputSchema5);
|
|
1129
|
+
var description5 = `Reads a file from the local filesystem. You can access any file directly by using this tool.
|
|
1130
|
+
Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.
|
|
1131
|
+
|
|
1132
|
+
Usage:
|
|
1133
|
+
- The file_path parameter must be an absolute path, not a relative path
|
|
1134
|
+
- By default, it reads up to ${MAX_LINES_TO_READ} lines starting from the beginning of the file
|
|
1135
|
+
- You can optionally specify a line offset and limit (especially handy for long files)
|
|
1136
|
+
- Results are returned using cat -n format, with line numbers starting at 1
|
|
1137
|
+
- This tool can only read text files, not directories. To read a directory, use the Glob tool.
|
|
1138
|
+
- If you read a file that exists but has empty contents you will receive a warning in place of file contents.`;
|
|
1139
|
+
async function call5(input) {
|
|
1140
|
+
const filePath = resolve5(input.file_path);
|
|
1141
|
+
const offset = input.offset ?? 1;
|
|
1142
|
+
const limit = input.limit ?? MAX_LINES_TO_READ;
|
|
1143
|
+
let st;
|
|
1144
|
+
try {
|
|
1145
|
+
st = await stat2(filePath);
|
|
1146
|
+
} catch (e) {
|
|
1147
|
+
return {
|
|
1148
|
+
ok: false,
|
|
1149
|
+
error: `\u8BFB\u53D6\u5931\u8D25\uFF1A${filePath} \u4E0D\u5B58\u5728\u6216\u65E0\u6CD5\u8BBF\u95EE\uFF08${e.message}\uFF09`
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
if (!st.isFile()) {
|
|
1153
|
+
return { ok: false, error: `${filePath} \u4E0D\u662F\u6587\u4EF6\uFF08\u53EF\u80FD\u662F\u76EE\u5F55\uFF09\u3002\u8981\u5217\u76EE\u5F55\u7528 Glob \u5DE5\u5177\u3002` };
|
|
1154
|
+
}
|
|
1155
|
+
if (st.size > MAX_FILE_SIZE_BYTES) {
|
|
1156
|
+
return {
|
|
1157
|
+
ok: false,
|
|
1158
|
+
error: `\u6587\u4EF6\u8FC7\u5927\uFF08${st.size} \u5B57\u8282 > ${MAX_FILE_SIZE_BYTES}\uFF09\u3002\u8BF7\u7528 offset/limit \u5206\u6BB5\u8BFB\u3002`
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
let raw;
|
|
1162
|
+
try {
|
|
1163
|
+
raw = await readFile6(filePath, "utf8");
|
|
1164
|
+
} catch (e) {
|
|
1165
|
+
return { ok: false, error: `\u8BFB\u53D6\u5931\u8D25\uFF1A${e.message}` };
|
|
1166
|
+
}
|
|
1167
|
+
if (raw.length === 0) {
|
|
1168
|
+
return { ok: true, content: "<file is empty>" };
|
|
1169
|
+
}
|
|
1170
|
+
const allLines = raw.split("\n");
|
|
1171
|
+
const totalLines = allLines.length;
|
|
1172
|
+
const startIdx = Math.max(0, offset - 1);
|
|
1173
|
+
const endIdx = Math.min(totalLines, startIdx + limit);
|
|
1174
|
+
const slice = allLines.slice(startIdx, endIdx);
|
|
1175
|
+
const numbered = slice.map((line, i) => `${(startIdx + i + 1).toString()} ${line}`).join("\n");
|
|
1176
|
+
let content = numbered;
|
|
1177
|
+
const contentLength = content.length;
|
|
1178
|
+
if (contentLength > DEFAULT_MAX_RESULT_SIZE_CHARS) {
|
|
1179
|
+
content = content.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + `
|
|
1180
|
+
|
|
1181
|
+
... (\u8F93\u51FA\u8D85\u8FC7 ${DEFAULT_MAX_RESULT_SIZE_CHARS} \u5B57\u7B26\uFF0C\u5DF2\u622A\u65AD)`;
|
|
1182
|
+
}
|
|
1183
|
+
if (endIdx < totalLines) {
|
|
1184
|
+
const nextOffset = endIdx + 1;
|
|
1185
|
+
content += `
|
|
1186
|
+
|
|
1187
|
+
... (\u672C\u6B21\u8FD4\u56DE ${slice.length} \u884C / \u6587\u4EF6\u5171 ${totalLines} \u884C\uFF1B\u7528 offset=${nextOffset} \u7EE7\u7EED\u8BFB)`;
|
|
1188
|
+
}
|
|
1189
|
+
if (st.size > 100 * 1024 && offset === 1) {
|
|
1190
|
+
content += `
|
|
1191
|
+
|
|
1192
|
+
\u26A0\uFE0F \u6CE8\u610F\uFF1A\u8FD9\u662F\u4E00\u4E2A\u5927\u6587\u4EF6\uFF08${(st.size / 1024).toFixed(1)} KB\uFF09\u3002\u5EFA\u8BAE\u7528 offset/limit \u5206\u6BB5\u8BFB\u53D6\uFF0C\u4F8B\u5982\u5148\u8BFB\u5173\u952E\u90E8\u5206\uFF08imports\u3001exports\u3001\u51FD\u6570\u7B7E\u540D\uFF09\u3002`;
|
|
1193
|
+
}
|
|
1194
|
+
return { ok: true, content };
|
|
1195
|
+
}
|
|
1196
|
+
var readTool = {
|
|
1197
|
+
name: "Read",
|
|
1198
|
+
description: description5,
|
|
1199
|
+
inputSchema: inputSchema5,
|
|
1200
|
+
parameters: parameters5,
|
|
1201
|
+
isReadOnly: true,
|
|
1202
|
+
isConcurrencySafe: true,
|
|
1203
|
+
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
1204
|
+
call: call5
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
// src/tools/webfetch/webfetch.ts
|
|
1208
|
+
import { z as z6 } from "zod";
|
|
1209
|
+
|
|
1210
|
+
// src/tools/webfetch/preapproved.ts
|
|
1211
|
+
var PREAPPROVED_HOSTS = /* @__PURE__ */ new Set([
|
|
1212
|
+
// GitHub
|
|
1213
|
+
"github.com",
|
|
1214
|
+
"gist.github.com",
|
|
1215
|
+
// 开发文档
|
|
1216
|
+
"docs.github.com",
|
|
1217
|
+
"developer.github.com",
|
|
1218
|
+
"help.github.com",
|
|
1219
|
+
// NPM
|
|
1220
|
+
"npmjs.com",
|
|
1221
|
+
"www.npmjs.com",
|
|
1222
|
+
// 包注册表
|
|
1223
|
+
"pypi.org",
|
|
1224
|
+
"www.pypi.org",
|
|
1225
|
+
"crates.io",
|
|
1226
|
+
"pub.dev",
|
|
1227
|
+
"packagist.org",
|
|
1228
|
+
"rubygems.org",
|
|
1229
|
+
// 开发平台
|
|
1230
|
+
"stackoverflow.com",
|
|
1231
|
+
"www.stackoverflow.com",
|
|
1232
|
+
"serverfault.com",
|
|
1233
|
+
"superuser.com",
|
|
1234
|
+
"askubuntu.com",
|
|
1235
|
+
// 文档与 wiki
|
|
1236
|
+
"readthedocs.io",
|
|
1237
|
+
"www.readthedocs.io",
|
|
1238
|
+
"readthedocs.org",
|
|
1239
|
+
"wiki.python.org",
|
|
1240
|
+
"en.wikipedia.org",
|
|
1241
|
+
"zh.wikipedia.org",
|
|
1242
|
+
// 官方文档
|
|
1243
|
+
"nodejs.org",
|
|
1244
|
+
"www.nodejs.org",
|
|
1245
|
+
"deno.land",
|
|
1246
|
+
"www.deno.land",
|
|
1247
|
+
"bun.sh",
|
|
1248
|
+
"www.bun.sh",
|
|
1249
|
+
// Rust
|
|
1250
|
+
"doc.rust-lang.org",
|
|
1251
|
+
"www.rust-lang.org",
|
|
1252
|
+
"rust-lang.org",
|
|
1253
|
+
// 云平台
|
|
1254
|
+
"aws.amazon.com",
|
|
1255
|
+
"docs.aws.amazon.com",
|
|
1256
|
+
"cloud.google.com",
|
|
1257
|
+
"docs.microsoft.com",
|
|
1258
|
+
"azure.microsoft.com",
|
|
1259
|
+
"developer.microsoft.com",
|
|
1260
|
+
// AI/LLM
|
|
1261
|
+
"openai.com",
|
|
1262
|
+
"platform.openai.com",
|
|
1263
|
+
"docs.anthropic.com",
|
|
1264
|
+
"anthropic.com",
|
|
1265
|
+
"claude.ai",
|
|
1266
|
+
"docs.cohere.com",
|
|
1267
|
+
"cohere.com",
|
|
1268
|
+
// AI 模型文档
|
|
1269
|
+
"ai.google.dev",
|
|
1270
|
+
"ai.google.com",
|
|
1271
|
+
"developers.google.com",
|
|
1272
|
+
"learn.deepmind.com",
|
|
1273
|
+
// AI 开发框架
|
|
1274
|
+
"python.langchain.com",
|
|
1275
|
+
"js.langchain.com",
|
|
1276
|
+
"docs.litellm.ai",
|
|
1277
|
+
"litellm.ai",
|
|
1278
|
+
// 前端框架
|
|
1279
|
+
"react.dev",
|
|
1280
|
+
"reactjs.org",
|
|
1281
|
+
"www.reactjs.org",
|
|
1282
|
+
"nextjs.org",
|
|
1283
|
+
"www.nextjs.org",
|
|
1284
|
+
"vuejs.org",
|
|
1285
|
+
"www.vuejs.org",
|
|
1286
|
+
"svelte.dev",
|
|
1287
|
+
"svelte.org",
|
|
1288
|
+
"angular.io",
|
|
1289
|
+
"www.angular.io",
|
|
1290
|
+
// 构建工具
|
|
1291
|
+
"vitejs.dev",
|
|
1292
|
+
"vite.dev",
|
|
1293
|
+
"webpack.js.org",
|
|
1294
|
+
"esbuild.github.io",
|
|
1295
|
+
"rollupjs.org",
|
|
1296
|
+
"esbuild.github.io",
|
|
1297
|
+
// CSS
|
|
1298
|
+
"tailwindcss.com",
|
|
1299
|
+
"www.tailwindcss.com",
|
|
1300
|
+
"postcss.org",
|
|
1301
|
+
// 数据库
|
|
1302
|
+
"redis.io",
|
|
1303
|
+
"www.redis.io",
|
|
1304
|
+
"postgresql.org",
|
|
1305
|
+
"www.postgresql.org",
|
|
1306
|
+
"www.mysql.com",
|
|
1307
|
+
"dev.mysql.com",
|
|
1308
|
+
"docs.mongodb.com",
|
|
1309
|
+
"www.mongodb.com",
|
|
1310
|
+
"sqlite.org",
|
|
1311
|
+
"www.sqlite.org",
|
|
1312
|
+
// 工具类
|
|
1313
|
+
"regex101.com",
|
|
1314
|
+
"ihateregex.io",
|
|
1315
|
+
"explainshell.com",
|
|
1316
|
+
"tldr.sh",
|
|
1317
|
+
// 代码分享
|
|
1318
|
+
"replit.com",
|
|
1319
|
+
"www.replit.com",
|
|
1320
|
+
"codesandbox.io",
|
|
1321
|
+
"www.codesandbox.io",
|
|
1322
|
+
"codepen.io",
|
|
1323
|
+
"www.codepen.io",
|
|
1324
|
+
"jsfiddle.net",
|
|
1325
|
+
"www.jsfiddle.net",
|
|
1326
|
+
// CI/CD
|
|
1327
|
+
"docs.github.com/en/actions",
|
|
1328
|
+
"circleci.com",
|
|
1329
|
+
"docs.circleci.com",
|
|
1330
|
+
"travis-ci.org",
|
|
1331
|
+
"www.travis-ci.com",
|
|
1332
|
+
"jenkins.io",
|
|
1333
|
+
"www.jenkins.io",
|
|
1334
|
+
// 容器/云原生
|
|
1335
|
+
"kubernetes.io",
|
|
1336
|
+
"www.kubernetes.io",
|
|
1337
|
+
"docker.com",
|
|
1338
|
+
"www.docker.com",
|
|
1339
|
+
"docs.docker.com",
|
|
1340
|
+
// 测试
|
|
1341
|
+
"jestjs.io",
|
|
1342
|
+
"www.jestjs.io",
|
|
1343
|
+
"vitest.dev",
|
|
1344
|
+
"testing-library.com",
|
|
1345
|
+
"www.testing-library.com",
|
|
1346
|
+
"playwright.dev",
|
|
1347
|
+
"www.playwright.dev",
|
|
1348
|
+
"webdriver.io",
|
|
1349
|
+
"webdriver.io",
|
|
1350
|
+
// API 文档
|
|
1351
|
+
"httpbin.org",
|
|
1352
|
+
"restfulapi.net",
|
|
1353
|
+
"swagger.io",
|
|
1354
|
+
"www.swagger.io",
|
|
1355
|
+
"openapi.net",
|
|
1356
|
+
// 安全
|
|
1357
|
+
"owasp.org",
|
|
1358
|
+
"www.owasp.org",
|
|
1359
|
+
"cve.mitre.org",
|
|
1360
|
+
// 证书
|
|
1361
|
+
"letsencrypt.org",
|
|
1362
|
+
"www.letsencrypt.org",
|
|
1363
|
+
"acmev2.pki.duckdns.org",
|
|
1364
|
+
// 博客与技术文章
|
|
1365
|
+
"medium.com",
|
|
1366
|
+
"www.medium.com",
|
|
1367
|
+
"dev.to",
|
|
1368
|
+
"www.dev.to",
|
|
1369
|
+
"hashnode.com",
|
|
1370
|
+
"www.hashnode.com",
|
|
1371
|
+
"devblogs.microsoft.com",
|
|
1372
|
+
// 浏览器
|
|
1373
|
+
"caniuse.com",
|
|
1374
|
+
"developer.mozilla.org",
|
|
1375
|
+
"web.dev",
|
|
1376
|
+
"www.w3.org",
|
|
1377
|
+
// 开源项目
|
|
1378
|
+
"apache.org",
|
|
1379
|
+
"www.apache.org",
|
|
1380
|
+
"gnu.org",
|
|
1381
|
+
"www.gnu.org",
|
|
1382
|
+
"fsf.org",
|
|
1383
|
+
"www.fsf.org",
|
|
1384
|
+
"opensource.org",
|
|
1385
|
+
"www.opensource.org",
|
|
1386
|
+
// 技术社区
|
|
1387
|
+
"reddit.com",
|
|
1388
|
+
"www.reddit.com",
|
|
1389
|
+
"news.ycombinator.com",
|
|
1390
|
+
"lobste.rs",
|
|
1391
|
+
// 文件格式
|
|
1392
|
+
"json.org",
|
|
1393
|
+
"yaml.org",
|
|
1394
|
+
"www.yaml.org",
|
|
1395
|
+
"toml.io",
|
|
1396
|
+
"www.toml.io",
|
|
1397
|
+
// 版本控制
|
|
1398
|
+
"git-scm.com",
|
|
1399
|
+
"www.git-scm.com",
|
|
1400
|
+
"github.blog",
|
|
1401
|
+
"githubstatus.com",
|
|
1402
|
+
// AI 搜索
|
|
1403
|
+
"tavily.com",
|
|
1404
|
+
"www.tavily.com",
|
|
1405
|
+
"perplexity.ai",
|
|
1406
|
+
"www.perplexity.ai",
|
|
1407
|
+
// AI 图片
|
|
1408
|
+
"midjourney.com",
|
|
1409
|
+
"www.midjourney.com",
|
|
1410
|
+
"stability.ai",
|
|
1411
|
+
"www.stability.ai",
|
|
1412
|
+
// Embeddings / 向量
|
|
1413
|
+
"qdrant.tech",
|
|
1414
|
+
"www.qdrant.tech",
|
|
1415
|
+
"weaviate.io",
|
|
1416
|
+
"www.weaviate.io",
|
|
1417
|
+
"pinecone.io",
|
|
1418
|
+
"www.pinecone.io",
|
|
1419
|
+
// API 平台
|
|
1420
|
+
"ngrok.com",
|
|
1421
|
+
"www.ngrok.com",
|
|
1422
|
+
"requestly.io",
|
|
1423
|
+
"www.requestly.io",
|
|
1424
|
+
// MCP
|
|
1425
|
+
"modelcontextprotocol.io",
|
|
1426
|
+
"www.modelcontextprotocol.io",
|
|
1427
|
+
"github.com/modelcontextprotocol"
|
|
1428
|
+
]);
|
|
1429
|
+
var PREAPPROVED_SUFFIXES = [
|
|
1430
|
+
".github.io",
|
|
1431
|
+
".readthedocs.io",
|
|
1432
|
+
".vercel.app",
|
|
1433
|
+
".vercel.dev",
|
|
1434
|
+
".netlify.app",
|
|
1435
|
+
".netlify.com",
|
|
1436
|
+
".cloudflare-pages.com",
|
|
1437
|
+
".pages.dev",
|
|
1438
|
+
".surge.sh",
|
|
1439
|
+
".herokuapp.com",
|
|
1440
|
+
".railway.app",
|
|
1441
|
+
".fly.dev",
|
|
1442
|
+
".repl.co",
|
|
1443
|
+
".workers.dev",
|
|
1444
|
+
".pages.plus",
|
|
1445
|
+
".coded.app",
|
|
1446
|
+
".preview.app",
|
|
1447
|
+
".staging.app"
|
|
1448
|
+
];
|
|
1449
|
+
function isPreapprovedHost(hostname) {
|
|
1450
|
+
const normalized = hostname.toLowerCase();
|
|
1451
|
+
if (PREAPPROVED_HOSTS.has(normalized)) {
|
|
1452
|
+
return true;
|
|
1453
|
+
}
|
|
1454
|
+
for (const suffix of PREAPPROVED_SUFFIXES) {
|
|
1455
|
+
if (normalized.endsWith(suffix)) {
|
|
1456
|
+
return true;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
return false;
|
|
1460
|
+
}
|
|
1461
|
+
var isPreapprovedDomain = isPreapprovedHost;
|
|
1462
|
+
|
|
1463
|
+
// src/tools/webfetch/webfetch.ts
|
|
1464
|
+
var MAX_URL_LENGTH = 2e3;
|
|
1465
|
+
var FETCH_TIMEOUT_MS = 6e4;
|
|
1466
|
+
var MAX_HTTP_CONTENT_LENGTH = 10 * 1024 * 1024;
|
|
1467
|
+
var MAX_REDIRECTS = 10;
|
|
1468
|
+
var URL_CACHE = /* @__PURE__ */ new Map();
|
|
1469
|
+
var CACHE_TTL_MS = 15 * 60 * 1e3;
|
|
1470
|
+
var MAX_CACHE_SIZE_BYTES = 50 * 1024 * 1024;
|
|
1471
|
+
function cleanCache() {
|
|
1472
|
+
const now = Date.now();
|
|
1473
|
+
let totalSize = 0;
|
|
1474
|
+
const entries = [];
|
|
1475
|
+
for (const [url, entry] of URL_CACHE) {
|
|
1476
|
+
if (now - entry.fetchedAt > CACHE_TTL_MS) {
|
|
1477
|
+
URL_CACHE.delete(url);
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
const size = entry.bytes;
|
|
1481
|
+
totalSize += size;
|
|
1482
|
+
entries.push({ url, entry, size });
|
|
1483
|
+
}
|
|
1484
|
+
if (totalSize > MAX_CACHE_SIZE_BYTES) {
|
|
1485
|
+
entries.sort((a, b) => a.entry.fetchedAt - b.entry.fetchedAt);
|
|
1486
|
+
for (const { url, size } of entries) {
|
|
1487
|
+
URL_CACHE.delete(url);
|
|
1488
|
+
totalSize -= size;
|
|
1489
|
+
if (totalSize <= MAX_CACHE_SIZE_BYTES * 0.8) break;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
var inputSchema6 = z6.object({
|
|
1494
|
+
url: z6.string().describe("\u8981\u83B7\u53D6\u5185\u5BB9\u7684 URL"),
|
|
1495
|
+
prompt: z6.string().describe("\u5BF9\u5185\u5BB9\u8FDB\u884C\u5904\u7406\u7684\u6307\u4EE4\uFF0C\u63CF\u8FF0\u4F60\u60F3\u4ECE\u9875\u9762\u63D0\u53D6\u4EC0\u4E48\u4FE1\u606F")
|
|
1496
|
+
});
|
|
1497
|
+
var parameters6 = toToolParameters(inputSchema6);
|
|
1498
|
+
var description6 = `- Fetches content from a specified URL and processes it using an AI model.
|
|
1499
|
+
- Takes a URL and a prompt as input.
|
|
1500
|
+
- Fetches the URL content, converts HTML to markdown.
|
|
1501
|
+
- Processes the content with the prompt (e.g., extract summary, find specific info).
|
|
1502
|
+
- Returns the processed result.
|
|
1503
|
+
- HTTP URLs are automatically upgraded to HTTPS.
|
|
1504
|
+
- When a URL redirects to a different host, returns a warning with the redirect URL.
|
|
1505
|
+
- This tool is read-only and does not modify any files.
|
|
1506
|
+
- Results may be summarized if the content is very large.
|
|
1507
|
+
- \u26A0\uFE0F IMPORTANT: This tool WILL FAIL for authenticated or private URLs.
|
|
1508
|
+
Before using this tool, check if the URL points to an authenticated service
|
|
1509
|
+
(e.g. Google Docs, Confluence, Jira, GitHub private repos). If so, look for
|
|
1510
|
+
a specialized MCP tool that provides authenticated access.
|
|
1511
|
+
- \u{1F4A1} For GitHub URLs (repos, issues, PRs), prefer using the \`gh\` CLI via Bash
|
|
1512
|
+
instead (e.g. \`gh pr view <pr-number>\`, \`gh issue view <number>\`, \`gh api <endpoint>\`).
|
|
1513
|
+
- \u26A1 If an MCP-provided web fetch tool is available, prefer using that tool instead,
|
|
1514
|
+
as it may have fewer restrictions and better performance.
|
|
1515
|
+
- \u{1F512} Domain preapproved list includes common documentation sites (MDN, TypeScript,
|
|
1516
|
+
React, Vue, Angular, Node.js, Bun, Rust, Go, Python, etc.). Other domains
|
|
1517
|
+
will work but results may be less reliable.
|
|
1518
|
+
- \u{1F4DD} This tool includes a self-cleaning 15-minute cache for faster repeated access
|
|
1519
|
+
to the same URL.
|
|
1520
|
+
- \u26A0\uFE0F For PDF files, the tool will attempt to extract readable text but results may
|
|
1521
|
+
be limited. Binary images cannot be processed.`;
|
|
1522
|
+
function validateURL(url) {
|
|
1523
|
+
if (url.length > MAX_URL_LENGTH) {
|
|
1524
|
+
return { ok: false, error: `URL \u592A\u957F\uFF08\u6700\u5927 ${MAX_URL_LENGTH} \u5B57\u7B26\uFF09` };
|
|
1525
|
+
}
|
|
1526
|
+
let parsed;
|
|
1527
|
+
try {
|
|
1528
|
+
parsed = new URL(url);
|
|
1529
|
+
} catch {
|
|
1530
|
+
return { ok: false, error: `\u65E0\u6548\u7684 URL \u683C\u5F0F` };
|
|
1531
|
+
}
|
|
1532
|
+
if (parsed.username || parsed.password) {
|
|
1533
|
+
return { ok: false, error: `URL \u4E0D\u80FD\u5305\u542B\u7528\u6237\u540D\u6216\u5BC6\u7801` };
|
|
1534
|
+
}
|
|
1535
|
+
return { ok: true, parsed };
|
|
1536
|
+
}
|
|
1537
|
+
async function fetchURL(url, signal, depth = 0) {
|
|
1538
|
+
const validated = validateURL(url);
|
|
1539
|
+
if (!validated.ok) return { type: "error", error: validated.error };
|
|
1540
|
+
let targetUrl = url;
|
|
1541
|
+
const parsed = validated.parsed;
|
|
1542
|
+
if (parsed.protocol === "http:") {
|
|
1543
|
+
parsed.protocol = "https:";
|
|
1544
|
+
targetUrl = parsed.toString();
|
|
1545
|
+
}
|
|
1546
|
+
let response;
|
|
1547
|
+
try {
|
|
1548
|
+
const timeoutSignal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
|
|
1549
|
+
const controller = new AbortController();
|
|
1550
|
+
const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
1551
|
+
response = await fetch(targetUrl, {
|
|
1552
|
+
signal: combinedSignal,
|
|
1553
|
+
headers: {
|
|
1554
|
+
Accept: "text/markdown, text/html, */*",
|
|
1555
|
+
"User-Agent": "minimal-agent/1.0"
|
|
1556
|
+
}
|
|
1557
|
+
});
|
|
1558
|
+
} catch (e) {
|
|
1559
|
+
if (signal?.aborted) return { type: "error", error: "\u8BF7\u6C42\u88AB\u4E2D\u65AD" };
|
|
1560
|
+
return { type: "error", error: `\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25\uFF1A${e.message}` };
|
|
1561
|
+
}
|
|
1562
|
+
const code = response.status;
|
|
1563
|
+
const codeText = response.statusText;
|
|
1564
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1565
|
+
const location = response.headers.get("location");
|
|
1566
|
+
if (location && [301, 302, 307, 308].includes(code)) {
|
|
1567
|
+
const redirectUrl = new URL(location, targetUrl).toString();
|
|
1568
|
+
const originalHost = new URL(url).hostname;
|
|
1569
|
+
const redirectHost = new URL(redirectUrl).hostname;
|
|
1570
|
+
const stripWww = (h) => h.replace(/^www\./, "");
|
|
1571
|
+
if (stripWww(originalHost) !== stripWww(redirectHost)) {
|
|
1572
|
+
return {
|
|
1573
|
+
type: "redirect",
|
|
1574
|
+
originalUrl: url,
|
|
1575
|
+
redirectUrl,
|
|
1576
|
+
statusCode: code
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
if (depth >= MAX_REDIRECTS) {
|
|
1580
|
+
return { type: "error", error: `\u91CD\u5B9A\u5411\u5FAA\u73AF\u8D85\u8FC7\u9650\u5236\uFF08\u6700\u591A ${MAX_REDIRECTS} \u6B21\uFF09` };
|
|
1581
|
+
}
|
|
1582
|
+
return fetchURL(redirectUrl, signal, depth + 1);
|
|
1583
|
+
}
|
|
1584
|
+
let rawBuffer;
|
|
1585
|
+
try {
|
|
1586
|
+
rawBuffer = await response.arrayBuffer();
|
|
1587
|
+
} catch (e) {
|
|
1588
|
+
return { type: "error", error: `\u8BFB\u53D6\u54CD\u5E94\u4F53\u5931\u8D25\uFF1A${e.message}` };
|
|
1589
|
+
}
|
|
1590
|
+
if (rawBuffer.byteLength > MAX_HTTP_CONTENT_LENGTH) {
|
|
1591
|
+
return { type: "error", error: `\u5185\u5BB9\u592A\u5927\uFF08${rawBuffer.byteLength} bytes\uFF0C\u8D85\u8FC7 ${MAX_HTTP_CONTENT_LENGTH}\uFF09` };
|
|
1592
|
+
}
|
|
1593
|
+
let content;
|
|
1594
|
+
try {
|
|
1595
|
+
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
1596
|
+
content = decoder.decode(rawBuffer);
|
|
1597
|
+
} catch (e) {
|
|
1598
|
+
return { type: "error", error: `\u89E3\u7801\u5931\u8D25\uFF1A${e.message}` };
|
|
1599
|
+
}
|
|
1600
|
+
return { type: "success", content, bytes: rawBuffer.byteLength, code, codeText, contentType };
|
|
1601
|
+
}
|
|
1602
|
+
async function htmlToMarkdown(html) {
|
|
1603
|
+
const TurndownService = (await import("turndown")).default;
|
|
1604
|
+
const td = new TurndownService();
|
|
1605
|
+
return td.turndown(html);
|
|
1606
|
+
}
|
|
1607
|
+
async function call6(input, signal) {
|
|
1608
|
+
const { url } = input;
|
|
1609
|
+
const start = Date.now();
|
|
1610
|
+
const cacheKey = url;
|
|
1611
|
+
let targetHostname;
|
|
1612
|
+
try {
|
|
1613
|
+
targetHostname = new URL(url).hostname;
|
|
1614
|
+
} catch {
|
|
1615
|
+
targetHostname = "";
|
|
1616
|
+
}
|
|
1617
|
+
const preapproved = isPreapprovedDomain(targetHostname);
|
|
1618
|
+
const cached2 = URL_CACHE.get(cacheKey);
|
|
1619
|
+
if (cached2 && Date.now() - cached2.fetchedAt <= CACHE_TTL_MS) {
|
|
1620
|
+
let content2 = cached2.content;
|
|
1621
|
+
const contentType2 = cached2.contentType;
|
|
1622
|
+
if (contentType2.includes("text/html")) {
|
|
1623
|
+
try {
|
|
1624
|
+
content2 = await htmlToMarkdown(content2);
|
|
1625
|
+
} catch (e) {
|
|
1626
|
+
console.warn(`turndown \u5931\u8D25: ${e.message}`);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
const formattedSize2 = cached2.bytes < 1024 ? `${cached2.bytes} B` : cached2.bytes < 1024 * 1024 ? `${(cached2.bytes / 1024).toFixed(1)} KB` : `${(cached2.bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
1630
|
+
const domainNote2 = preapproved ? "[\u{1F512} \u9884\u6279\u51C6\u57DF\u540D\uFF0C\u5185\u5BB9\u53EF\u4FE1]" : "[\u26A0\uFE0F \u975E\u9884\u6279\u51C6\u57DF\u540D\uFF0C\u5185\u5BB9\u53EF\u80FD\u4E0D\u51C6\u786E]";
|
|
1631
|
+
const output2 = `\u3010WebFetch \u7ED3\u679C\u3011
|
|
1632
|
+
URL: ${url}
|
|
1633
|
+
\u72B6\u6001: ${cached2.code} ${cached2.codeText}
|
|
1634
|
+
\u5927\u5C0F: ${formattedSize2}
|
|
1635
|
+
\u8017\u65F6: 0.00s (\u7F13\u5B58\u547D\u4E2D)
|
|
1636
|
+
${domainNote2}
|
|
1637
|
+
[\u{1F4E6} \u6765\u81EA\u7F13\u5B58\uFF0815\u5206\u949F TTL\uFF09]
|
|
1638
|
+
|
|
1639
|
+
--- \u5185\u5BB9 ---
|
|
1640
|
+
${content2}`;
|
|
1641
|
+
let final2 = output2;
|
|
1642
|
+
if (final2.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
|
|
1643
|
+
final2 = final2.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + `
|
|
1644
|
+
|
|
1645
|
+
... (\u8F93\u51FA\u8D85\u8FC7 ${DEFAULT_MAX_RESULT_SIZE_CHARS} \u5B57\u7B26\uFF0C\u5DF2\u622A\u65AD)`;
|
|
1646
|
+
}
|
|
1647
|
+
return { ok: true, content: final2 };
|
|
1648
|
+
}
|
|
1649
|
+
const fetched = await fetchURL(url, signal ?? new AbortController().signal);
|
|
1650
|
+
const durationMs = Date.now() - start;
|
|
1651
|
+
if (fetched.type === "redirect") {
|
|
1652
|
+
const statusText = fetched.statusCode === 301 ? "Moved Permanently" : fetched.statusCode === 308 ? "Permanent Redirect" : fetched.statusCode === 307 ? "Temporary Redirect" : "Found";
|
|
1653
|
+
return {
|
|
1654
|
+
ok: true,
|
|
1655
|
+
content: `\u3010\u91CD\u5B9A\u5411\u68C0\u6D4B\u3011
|
|
1656
|
+
|
|
1657
|
+
\u539F\u59CB URL: ${fetched.originalUrl}
|
|
1658
|
+
\u91CD\u5B9A\u5411\u5230: ${fetched.redirectUrl}
|
|
1659
|
+
\u72B6\u6001: ${fetched.statusCode} ${statusText}
|
|
1660
|
+
|
|
1661
|
+
\u8BF7\u4F7F\u7528\u91CD\u5B9A\u5411\u540E\u7684 URL \u518D\u6B21\u8C03\u7528 WebFetch \u5DE5\u5177\u3002`
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
if (fetched.type === "error") {
|
|
1665
|
+
return { ok: false, error: fetched.error };
|
|
1666
|
+
}
|
|
1667
|
+
let { content, bytes, code, codeText, contentType } = fetched;
|
|
1668
|
+
URL_CACHE.set(cacheKey, {
|
|
1669
|
+
bytes,
|
|
1670
|
+
code,
|
|
1671
|
+
codeText,
|
|
1672
|
+
content,
|
|
1673
|
+
contentType,
|
|
1674
|
+
fetchedAt: Date.now()
|
|
1675
|
+
});
|
|
1676
|
+
cleanCache();
|
|
1677
|
+
if (contentType.includes("text/html")) {
|
|
1678
|
+
try {
|
|
1679
|
+
content = await htmlToMarkdown(content);
|
|
1680
|
+
} catch (e) {
|
|
1681
|
+
console.warn(`turndown \u5931\u8D25: ${e.message}`);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
const formattedSize = bytes < 1024 ? `${bytes} B` : bytes < 1024 * 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
1685
|
+
const domainNote = preapproved ? "[\u{1F512} \u9884\u6279\u51C6\u57DF\u540D\uFF0C\u5185\u5BB9\u53EF\u4FE1]" : "[\u26A0\uFE0F \u975E\u9884\u6279\u51C6\u57DF\u540D\uFF0C\u5185\u5BB9\u53EF\u80FD\u4E0D\u51C6\u786E]";
|
|
1686
|
+
const output = `\u3010WebFetch \u7ED3\u679C\u3011
|
|
1687
|
+
URL: ${url}
|
|
1688
|
+
\u72B6\u6001: ${code} ${codeText}
|
|
1689
|
+
\u5927\u5C0F: ${formattedSize}
|
|
1690
|
+
\u8017\u65F6: ${(durationMs / 1e3).toFixed(2)}s
|
|
1691
|
+
${domainNote}
|
|
1692
|
+
|
|
1693
|
+
--- \u5185\u5BB9 ---
|
|
1694
|
+
${content}`;
|
|
1695
|
+
let final = output;
|
|
1696
|
+
if (final.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
|
|
1697
|
+
final = final.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + `
|
|
1698
|
+
|
|
1699
|
+
... (\u8F93\u51FA\u8D85\u8FC7 ${DEFAULT_MAX_RESULT_SIZE_CHARS} \u5B57\u7B26\uFF0C\u5DF2\u622A\u65AD)`;
|
|
1700
|
+
}
|
|
1701
|
+
return { ok: true, content: final };
|
|
1702
|
+
}
|
|
1703
|
+
var webfetchTool = {
|
|
1704
|
+
name: "WebFetch",
|
|
1705
|
+
description: description6,
|
|
1706
|
+
inputSchema: inputSchema6,
|
|
1707
|
+
parameters: parameters6,
|
|
1708
|
+
isReadOnly: true,
|
|
1709
|
+
isConcurrencySafe: true,
|
|
1710
|
+
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
1711
|
+
call: call6
|
|
1712
|
+
};
|
|
1713
|
+
|
|
1714
|
+
// src/tools/webbrowser/webbrowser.ts
|
|
1715
|
+
import { z as z7 } from "zod";
|
|
1716
|
+
|
|
1717
|
+
// src/tools/webbrowser/browser.ts
|
|
1718
|
+
import os from "os";
|
|
1719
|
+
import path from "path";
|
|
1720
|
+
var browserInstance = null;
|
|
1721
|
+
var pageInstance = null;
|
|
1722
|
+
async function getBrowserPage() {
|
|
1723
|
+
if (!browserInstance) {
|
|
1724
|
+
const { chromium } = await import("playwright-core");
|
|
1725
|
+
try {
|
|
1726
|
+
browserInstance = await chromium.launch({
|
|
1727
|
+
headless: true,
|
|
1728
|
+
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
|
1729
|
+
});
|
|
1730
|
+
} catch (e) {
|
|
1731
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1732
|
+
throw new Error(
|
|
1733
|
+
`Failed to launch browser: ${message}
|
|
1734
|
+
\u8BF7\u5148\u5B89\u88C5 playwright \u53CA\u6D4F\u89C8\u5668\uFF1A
|
|
1735
|
+
npm install playwright-core
|
|
1736
|
+
npx playwright install chromium`
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
if (!pageInstance) {
|
|
1741
|
+
pageInstance = await browserInstance.newPage();
|
|
1742
|
+
await pageInstance.setViewportSize({ width: 1280, height: 720 });
|
|
1743
|
+
}
|
|
1744
|
+
return pageInstance;
|
|
1745
|
+
}
|
|
1746
|
+
function screenshotPath(prefix = "browser") {
|
|
1747
|
+
return path.join(os.tmpdir(), `${prefix}-${Date.now()}.png`);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// src/tools/webbrowser/webbrowser.ts
|
|
1751
|
+
var inputSchema7 = z7.object({
|
|
1752
|
+
action: z7.enum(["navigate", "screenshot", "getContent", "click", "fill", "submit"]).describe("Browser action to perform"),
|
|
1753
|
+
url: z7.string().url().optional().describe("URL to navigate to (required for navigate action)"),
|
|
1754
|
+
selector: z7.string().optional().describe("CSS selector for click/fill/submit actions"),
|
|
1755
|
+
value: z7.string().optional().describe("Value to fill in input fields"),
|
|
1756
|
+
timeout: z7.number().int().positive().optional().describe("Timeout in milliseconds (default: 30000)")
|
|
1757
|
+
});
|
|
1758
|
+
var parameters7 = toToolParameters(inputSchema7);
|
|
1759
|
+
var description7 = `Control a headless web browser. Navigate to URLs, take screenshots, and interact with web pages.
|
|
1760
|
+
|
|
1761
|
+
When to use WebBrowser vs WebSearch:
|
|
1762
|
+
- WebSearch: When you need to find information or discover URLs through search
|
|
1763
|
+
- WebBrowser: When you need to interact with a specific web page (navigate, click, fill forms, get content, take screenshots)
|
|
1764
|
+
|
|
1765
|
+
Input parameters:
|
|
1766
|
+
- action (required): The browser action. One of:
|
|
1767
|
+
- navigate: Go to a URL (requires url parameter)
|
|
1768
|
+
- screenshot: Take a screenshot of the current page
|
|
1769
|
+
- getContent: Extract text content from the current page
|
|
1770
|
+
- click: Click an element (requires selector parameter)
|
|
1771
|
+
- fill: Fill an input field (requires selector and value parameters)
|
|
1772
|
+
- submit: Submit a form (requires selector parameter)
|
|
1773
|
+
- url (optional): URL to navigate to (required for navigate action, must be a valid URL)
|
|
1774
|
+
- selector (optional): CSS selector for click/fill/submit actions
|
|
1775
|
+
- value (optional): Value to fill in input fields
|
|
1776
|
+
- timeout (optional): Timeout in milliseconds for actions (default: 30000)
|
|
1777
|
+
|
|
1778
|
+
Usage notes:
|
|
1779
|
+
- The browser runs headless (no visible window)
|
|
1780
|
+
- Screenshots are saved to temporary files (/tmp/browser-*.png)
|
|
1781
|
+
- Content is truncated to 50000 characters max
|
|
1782
|
+
- Only one browser instance is maintained per session
|
|
1783
|
+
- Requires: npm install playwright-core && npx playwright install chromium
|
|
1784
|
+
|
|
1785
|
+
Example actions:
|
|
1786
|
+
- Navigate and get content: { action: "navigate", url: "https://example.com" }
|
|
1787
|
+
- Take screenshot: { action: "screenshot" }
|
|
1788
|
+
- Click element: { action: "click", selector: "#submit-btn" }
|
|
1789
|
+
- Fill form field: { action: "fill", selector: "input[name='email']", value: "user@example.com" }`;
|
|
1790
|
+
async function call7(input, signal) {
|
|
1791
|
+
const { action, url, selector, value, timeout = 3e4 } = input;
|
|
1792
|
+
let page;
|
|
1793
|
+
try {
|
|
1794
|
+
page = await getBrowserPage();
|
|
1795
|
+
} catch (e) {
|
|
1796
|
+
return {
|
|
1797
|
+
ok: false,
|
|
1798
|
+
error: `\u65E0\u6CD5\u542F\u52A8\u6D4F\u89C8\u5668\uFF1A${e instanceof Error ? e.message : String(e)}`
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
try {
|
|
1802
|
+
switch (action) {
|
|
1803
|
+
case "navigate": {
|
|
1804
|
+
if (!url) {
|
|
1805
|
+
return { ok: false, error: "navigate action requires url parameter" };
|
|
1806
|
+
}
|
|
1807
|
+
await page.goto(url, { timeout, waitUntil: "domcontentloaded" });
|
|
1808
|
+
const content = await page.evaluate(() => document.body.innerText);
|
|
1809
|
+
const screenshot = screenshotPath("browser-nav");
|
|
1810
|
+
await page.screenshot({ path: screenshot });
|
|
1811
|
+
const truncatedContent = content.substring(0, 5e4);
|
|
1812
|
+
return {
|
|
1813
|
+
ok: true,
|
|
1814
|
+
content: [
|
|
1815
|
+
`[Navigate] ${page.url()}`,
|
|
1816
|
+
`Screenshot: ${screenshot}`,
|
|
1817
|
+
"",
|
|
1818
|
+
"--- Page Content (first 50000 chars) ---",
|
|
1819
|
+
truncatedContent,
|
|
1820
|
+
content.length > 5e4 ? "\n... (truncated)" : ""
|
|
1821
|
+
].join("\n")
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
case "screenshot": {
|
|
1825
|
+
const screenshot = screenshotPath("browser-screenshot");
|
|
1826
|
+
await page.screenshot({ path: screenshot });
|
|
1827
|
+
return {
|
|
1828
|
+
ok: true,
|
|
1829
|
+
content: `[Screenshot] ${page.url()}
|
|
1830
|
+
Saved: ${screenshot}`
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
case "getContent": {
|
|
1834
|
+
const content = await page.evaluate(() => document.body.innerText);
|
|
1835
|
+
const truncated = content.substring(0, 5e4);
|
|
1836
|
+
return {
|
|
1837
|
+
ok: true,
|
|
1838
|
+
content: [
|
|
1839
|
+
`[Page Content] ${page.url()}`,
|
|
1840
|
+
"",
|
|
1841
|
+
truncated,
|
|
1842
|
+
content.length > 5e4 ? "\n... (truncated at 50000 chars)" : ""
|
|
1843
|
+
].join("\n")
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
case "click": {
|
|
1847
|
+
if (!selector) {
|
|
1848
|
+
return { ok: false, error: "click action requires selector parameter" };
|
|
1849
|
+
}
|
|
1850
|
+
await page.click(selector, { timeout });
|
|
1851
|
+
return {
|
|
1852
|
+
ok: true,
|
|
1853
|
+
content: `[Click] ${selector}
|
|
1854
|
+
Current URL: ${page.url()}`
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
case "fill": {
|
|
1858
|
+
if (!selector || value === void 0) {
|
|
1859
|
+
return { ok: false, error: "fill action requires selector and value parameters" };
|
|
1860
|
+
}
|
|
1861
|
+
await page.fill(selector, value);
|
|
1862
|
+
return {
|
|
1863
|
+
ok: true,
|
|
1864
|
+
content: `[Fill] ${selector} = "${value}"
|
|
1865
|
+
Current URL: ${page.url()}`
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
case "submit": {
|
|
1869
|
+
if (!selector) {
|
|
1870
|
+
return { ok: false, error: "submit action requires selector parameter" };
|
|
1871
|
+
}
|
|
1872
|
+
await Promise.all([
|
|
1873
|
+
page.waitForNavigation({ timeout }).catch(() => {
|
|
1874
|
+
}),
|
|
1875
|
+
page.click(selector, { timeout })
|
|
1876
|
+
]);
|
|
1877
|
+
return {
|
|
1878
|
+
ok: true,
|
|
1879
|
+
content: `[Submit] ${selector}
|
|
1880
|
+
Current URL: ${page.url()}`
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
default:
|
|
1884
|
+
return { ok: false, error: `Unknown action: ${action}` };
|
|
1885
|
+
}
|
|
1886
|
+
} catch (e) {
|
|
1887
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1888
|
+
const truncated = message.length > DEFAULT_MAX_RESULT_SIZE_CHARS ? message.substring(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + "\n... (truncated)" : message;
|
|
1889
|
+
return { ok: false, error: `[${action}] Error: ${truncated}` };
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
var webbrowserTool = {
|
|
1893
|
+
name: "WebBrowser",
|
|
1894
|
+
description: description7,
|
|
1895
|
+
inputSchema: inputSchema7,
|
|
1896
|
+
parameters: parameters7,
|
|
1897
|
+
isReadOnly: false,
|
|
1898
|
+
isConcurrencySafe: false,
|
|
1899
|
+
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
1900
|
+
call: call7
|
|
1901
|
+
};
|
|
1902
|
+
|
|
1903
|
+
// src/tools/websearch/websearch.ts
|
|
1904
|
+
import { z as z8 } from "zod";
|
|
1905
|
+
var inputSchema8 = z8.object({
|
|
1906
|
+
query: z8.string().min(1, "\u5FC5\u987B\u63D0\u4F9B\u641C\u7D22\u5173\u952E\u8BCD").max(400, "\u641C\u7D22\u5173\u952E\u8BCD\u592A\u957F\uFF08>400 \u5B57\uFF09").describe("\u641C\u7D22\u5173\u952E\u8BCD\uFF0C\u5EFA\u8BAE\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u9700\u8981\u67E5\u7684\u4FE1\u606F"),
|
|
1907
|
+
max_results: z8.number().int().min(1).max(20).optional().describe("\u8FD4\u56DE\u7ED3\u679C\u6570\u91CF\uFF0C1-20\uFF0C\u9ED8\u8BA4 5"),
|
|
1908
|
+
search_depth: z8.enum(["basic", "advanced"]).optional().describe("basic \u5FEB\u4F46\u6D45\uFF1Badvanced \u6162\u4F46\u6DF1\uFF08\u542B answer \u6458\u8981\uFF09\uFF0C\u9ED8\u8BA4 basic"),
|
|
1909
|
+
topic: z8.enum(["general", "news"]).optional().describe("general=\u901A\u7528\u7F51\u9875\uFF1Bnews=\u504F\u65B0\u95FB\u6E90\uFF1B\u9ED8\u8BA4 general")
|
|
1910
|
+
});
|
|
1911
|
+
var parameters8 = toToolParameters(inputSchema8);
|
|
1912
|
+
var description8 = `- Searches the public web via the Tavily Search API and returns structured results.
|
|
1913
|
+
- Use this when you need up-to-date information that is not in your training data, or when the user asks for recent news / docs / API references.
|
|
1914
|
+
- Returns the top N results, each with a title, URL, and content snippet. With \`search_depth: "advanced"\` Tavily also returns a synthesized answer at the top.
|
|
1915
|
+
- Prefer specific natural-language queries over keyword soup (e.g. "how does Bun handle .env files in version 1.1").
|
|
1916
|
+
- Requires the TAVILY_API_KEY environment variable to be set; if missing the tool returns a friendly error.`;
|
|
1917
|
+
async function call8(input, signal) {
|
|
1918
|
+
const apiKey = process.env.TAVILY_API_KEY;
|
|
1919
|
+
if (!apiKey) {
|
|
1920
|
+
return {
|
|
1921
|
+
ok: false,
|
|
1922
|
+
error: "WebSearch \u4E0D\u53EF\u7528\uFF1A\u73AF\u5883\u53D8\u91CF TAVILY_API_KEY \u672A\u8BBE\u7F6E\u3002\n\u8BF7\u5728\u9879\u76EE\u6839 .env \u91CC\u52A0\uFF1A\n TAVILY_API_KEY=tvly-\u4F60\u7684key\n\u514D\u8D39 key \u7533\u8BF7\uFF1Ahttps://tavily.com/"
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1926
|
+
const queryWithDate = input.query.includes(today) ? input.query : `${input.query} (as of ${today})`;
|
|
1927
|
+
const body = {
|
|
1928
|
+
api_key: apiKey,
|
|
1929
|
+
query: queryWithDate,
|
|
1930
|
+
max_results: input.max_results ?? 5,
|
|
1931
|
+
search_depth: input.search_depth ?? "basic",
|
|
1932
|
+
topic: input.topic ?? "general",
|
|
1933
|
+
// 让 advanced 模式自动给 answer;basic 不会有
|
|
1934
|
+
include_answer: input.search_depth === "advanced"
|
|
1935
|
+
};
|
|
1936
|
+
let res;
|
|
1937
|
+
try {
|
|
1938
|
+
res = await fetch("https://api.tavily.com/search", {
|
|
1939
|
+
method: "POST",
|
|
1940
|
+
headers: { "content-type": "application/json" },
|
|
1941
|
+
body: JSON.stringify(body),
|
|
1942
|
+
signal
|
|
1943
|
+
});
|
|
1944
|
+
} catch (e) {
|
|
1945
|
+
return {
|
|
1946
|
+
ok: false,
|
|
1947
|
+
error: `\u8C03\u7528 Tavily \u5931\u8D25\uFF08\u7F51\u7EDC\uFF09\uFF1A${e.message}`
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
if (!res.ok) {
|
|
1951
|
+
const text = await res.text().catch(() => "");
|
|
1952
|
+
return {
|
|
1953
|
+
ok: false,
|
|
1954
|
+
error: `Tavily HTTP ${res.status}\uFF1A${text.slice(0, 500)}`
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
let data;
|
|
1958
|
+
try {
|
|
1959
|
+
data = await res.json();
|
|
1960
|
+
} catch (e) {
|
|
1961
|
+
return {
|
|
1962
|
+
ok: false,
|
|
1963
|
+
error: `Tavily \u54CD\u5E94\u4E0D\u662F\u5408\u6CD5 JSON\uFF1A${e.message}`
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
const results = data.results ?? [];
|
|
1967
|
+
if (results.length === 0) {
|
|
1968
|
+
return {
|
|
1969
|
+
ok: true,
|
|
1970
|
+
content: `(no results for "${input.query}")`
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
const parts = [];
|
|
1974
|
+
if (data.answer) {
|
|
1975
|
+
parts.push(`\u3010\u7EFC\u5408\u56DE\u7B54\u3011
|
|
1976
|
+
${data.answer}
|
|
1977
|
+
`);
|
|
1978
|
+
}
|
|
1979
|
+
parts.push(`\u3010\u5171 ${results.length} \u6761\u7ED3\u679C\u3011`);
|
|
1980
|
+
results.forEach((r, i) => {
|
|
1981
|
+
const date = r.published_date ? `\uFF08${r.published_date}\uFF09` : "";
|
|
1982
|
+
parts.push(
|
|
1983
|
+
`
|
|
1984
|
+
[${i + 1}] ${r.title}${date}
|
|
1985
|
+
URL: ${r.url}
|
|
1986
|
+
${(r.content ?? "").trim()}`
|
|
1987
|
+
);
|
|
1988
|
+
});
|
|
1989
|
+
let content = parts.join("\n");
|
|
1990
|
+
if (content.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
|
|
1991
|
+
content = content.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + `
|
|
1992
|
+
|
|
1993
|
+
... (\u8F93\u51FA\u8D85\u8FC7 ${DEFAULT_MAX_RESULT_SIZE_CHARS} \u5B57\u7B26\uFF0C\u5DF2\u622A\u65AD)`;
|
|
1994
|
+
}
|
|
1995
|
+
return { ok: true, content };
|
|
1996
|
+
}
|
|
1997
|
+
var webSearchTool = {
|
|
1998
|
+
name: "WebSearch",
|
|
1999
|
+
description: description8,
|
|
2000
|
+
inputSchema: inputSchema8,
|
|
2001
|
+
parameters: parameters8,
|
|
2002
|
+
isReadOnly: true,
|
|
2003
|
+
isConcurrencySafe: true,
|
|
2004
|
+
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
2005
|
+
call: call8
|
|
2006
|
+
};
|
|
2007
|
+
|
|
2008
|
+
// src/tools/write/write.ts
|
|
2009
|
+
import { existsSync as existsSync3 } from "fs";
|
|
2010
|
+
import { mkdir as mkdir4, stat as stat3, writeFile as writeFile4 } from "fs/promises";
|
|
2011
|
+
import { dirname as dirname6, resolve as resolve6 } from "path";
|
|
2012
|
+
import { z as z9 } from "zod";
|
|
2013
|
+
var MAX_WRITE_SIZE_BYTES = 1024 * 1024 * 1024;
|
|
2014
|
+
var inputSchema9 = z9.object({
|
|
2015
|
+
file_path: z9.string().min(1).describe("\u8981\u5199\u5165\u7684\u6587\u4EF6\u8DEF\u5F84"),
|
|
2016
|
+
content: z9.string().describe("\u6587\u4EF6\u5B8C\u6574\u5185\u5BB9\uFF08\u4F1A\u8986\u76D6\u65E2\u6709\u5185\u5BB9\uFF09")
|
|
2017
|
+
});
|
|
2018
|
+
var parameters9 = toToolParameters(inputSchema9);
|
|
2019
|
+
var description9 = `Writes a file to the local filesystem.
|
|
2020
|
+
|
|
2021
|
+
Usage:
|
|
2022
|
+
- This tool will overwrite the existing file if there is one at the provided path.
|
|
2023
|
+
- If the parent directory does not exist, it will be created recursively.
|
|
2024
|
+
- ALWAYS prefer editing existing files in the codebase via the Edit tool. NEVER write new files unless explicitly required.
|
|
2025
|
+
- NEVER create documentation files (*.md) or README files unless explicitly requested by the User.`;
|
|
2026
|
+
function validatePath2(filePath) {
|
|
2027
|
+
if (filePath.includes("\0")) {
|
|
2028
|
+
return { ok: false, error: "\u8DEF\u5F84\u5305\u542B\u975E\u6CD5\u5B57\u7B26\uFF08null byte\uFF09" };
|
|
2029
|
+
}
|
|
2030
|
+
if (process.platform === "win32" && filePath.includes("\\\\")) {
|
|
2031
|
+
return { ok: false, error: "\u4E0D\u652F\u6301 UNC \u8DEF\u5F84\uFF08\\\\server\\share \u683C\u5F0F\uFF09\uFF0C\u8BF7\u4F7F\u7528\u672C\u5730\u8DEF\u5F84" };
|
|
2032
|
+
}
|
|
2033
|
+
return { ok: true };
|
|
2034
|
+
}
|
|
2035
|
+
async function call9(input) {
|
|
2036
|
+
const filePath = resolve6(input.file_path);
|
|
2037
|
+
const pathCheck = validatePath2(filePath);
|
|
2038
|
+
if (!pathCheck.ok) return pathCheck;
|
|
2039
|
+
const contentSize = Buffer.byteLength(input.content, "utf8");
|
|
2040
|
+
if (contentSize > MAX_WRITE_SIZE_BYTES) {
|
|
2041
|
+
return {
|
|
2042
|
+
ok: false,
|
|
2043
|
+
error: `\u5199\u5165\u5185\u5BB9\u8FC7\u5927\uFF08${contentSize} \u5B57\u8282 > ${MAX_WRITE_SIZE_BYTES} \u5B57\u8282 \u2248 1GB\uFF09\u3002\u8BF7\u62C6\u5206\u5185\u5BB9\u6216\u4F7F\u7528 Edit \u5DE5\u5177\u5206\u5757\u4FEE\u6539\u3002`
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
try {
|
|
2047
|
+
await mkdir4(dirname6(filePath), { recursive: true });
|
|
2048
|
+
let originalSize = 0;
|
|
2049
|
+
const fileExisted = existsSync3(filePath);
|
|
2050
|
+
if (fileExisted) {
|
|
2051
|
+
try {
|
|
2052
|
+
const st = await stat3(filePath);
|
|
2053
|
+
originalSize = st.size;
|
|
2054
|
+
} catch {
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
await writeFile4(filePath, input.content, "utf8");
|
|
2058
|
+
const action = fileExisted ? "\u5DF2\u8986\u76D6" : "\u5DF2\u521B\u5EFA\u65B0\u6587\u4EF6";
|
|
2059
|
+
const sizeInfo = fileExisted ? `\uFF08\u539F\u6587\u4EF6 ${originalSize} \u5B57\u7B26 \u2192 \u65B0\u5185\u5BB9 ${input.content.length} \u5B57\u7B26\uFF09` : `\uFF08${input.content.length} \u5B57\u7B26\uFF09`;
|
|
2060
|
+
return {
|
|
2061
|
+
ok: true,
|
|
2062
|
+
content: `${action} ${filePath}${sizeInfo}`
|
|
2063
|
+
};
|
|
2064
|
+
} catch (e) {
|
|
2065
|
+
return { ok: false, error: `\u5199\u5165\u5931\u8D25\uFF1A${e.message}` };
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
var writeTool = {
|
|
2069
|
+
name: "Write",
|
|
2070
|
+
description: description9,
|
|
2071
|
+
inputSchema: inputSchema9,
|
|
2072
|
+
parameters: parameters9,
|
|
2073
|
+
isReadOnly: false,
|
|
2074
|
+
isConcurrencySafe: false,
|
|
2075
|
+
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
2076
|
+
call: call9
|
|
2077
|
+
};
|
|
2078
|
+
|
|
2079
|
+
// src/tools/index.ts
|
|
2080
|
+
var ALL_TOOLS = [
|
|
2081
|
+
readTool,
|
|
2082
|
+
editTool,
|
|
2083
|
+
writeTool,
|
|
2084
|
+
globTool,
|
|
2085
|
+
grepTool,
|
|
2086
|
+
webSearchTool,
|
|
2087
|
+
webfetchTool,
|
|
2088
|
+
webbrowserTool,
|
|
2089
|
+
bashTool
|
|
2090
|
+
];
|
|
2091
|
+
function getToolByName(name) {
|
|
2092
|
+
return ALL_TOOLS.find((t) => t.name === name);
|
|
2093
|
+
}
|
|
2094
|
+
async function executeTool(name, rawArguments, signal) {
|
|
2095
|
+
const tool = getToolByName(name);
|
|
2096
|
+
if (!tool) {
|
|
2097
|
+
const available = ALL_TOOLS.map((t) => t.name).join(", ");
|
|
2098
|
+
return { ok: false, error: `\u672A\u77E5\u5DE5\u5177 "${name}"\u3002\u53EF\u7528\u5DE5\u5177\uFF1A${available}` };
|
|
2099
|
+
}
|
|
2100
|
+
let parsedJson;
|
|
2101
|
+
try {
|
|
2102
|
+
parsedJson = rawArguments.length === 0 ? {} : JSON.parse(rawArguments);
|
|
2103
|
+
} catch (e) {
|
|
2104
|
+
return {
|
|
2105
|
+
ok: false,
|
|
2106
|
+
error: `\u5DE5\u5177\u53C2\u6570\u4E0D\u662F\u5408\u6CD5 JSON\uFF1A${e.message}
|
|
2107
|
+
\u6536\u5230\u7684\u5B57\u7B26\u4E32\uFF1A${rawArguments.slice(0, 200)}`
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
const checked = tool.inputSchema.safeParse(parsedJson);
|
|
2111
|
+
if (!checked.success) {
|
|
2112
|
+
const issues = checked.error.issues.map((i) => ` - ${i.path.join(".") || "<root>"}: ${i.message}`).join("\n");
|
|
2113
|
+
return {
|
|
2114
|
+
ok: false,
|
|
2115
|
+
error: `\u53C2\u6570\u6821\u9A8C\u5931\u8D25\uFF08\u8BF7\u68C0\u67E5\u53C2\u6570\u540D/\u7C7B\u578B\uFF09\uFF1A
|
|
2116
|
+
${issues}`
|
|
2117
|
+
};
|
|
2118
|
+
}
|
|
2119
|
+
try {
|
|
2120
|
+
return await tool.call(checked.data, signal);
|
|
2121
|
+
} catch (e) {
|
|
2122
|
+
return {
|
|
2123
|
+
ok: false,
|
|
2124
|
+
error: `\u5DE5\u5177\u6267\u884C\u629B\u51FA\u5F02\u5E38\uFF1A${e.message}`
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
// src/ui/Root.tsx
|
|
2130
|
+
import { useEffect as useEffect2, useState as useState6 } from "react";
|
|
2131
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
2132
|
+
|
|
2133
|
+
// src/cli/configWizard.tsx
|
|
2134
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2135
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
2136
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2137
|
+
var DEFAULT_CONTEXT_WINDOW2 = 128e3;
|
|
2138
|
+
var PRESETS = [
|
|
2139
|
+
{
|
|
2140
|
+
name: "minimax",
|
|
2141
|
+
label: "MiniMax (\u6D77\u87BA)",
|
|
2142
|
+
baseURL: "https://api.minimax.chat/v1",
|
|
2143
|
+
models: ["MiniMax-M2.7", "MiniMax-M1", "abab6.5s-chat"]
|
|
2144
|
+
},
|
|
2145
|
+
{
|
|
2146
|
+
name: "deepseek",
|
|
2147
|
+
label: "DeepSeek",
|
|
2148
|
+
baseURL: "https://api.deepseek.com/v1",
|
|
2149
|
+
models: ["deepseek-chat", "deepseek-reasoner"]
|
|
2150
|
+
},
|
|
2151
|
+
{
|
|
2152
|
+
name: "openai",
|
|
2153
|
+
label: "OpenAI",
|
|
2154
|
+
baseURL: "https://api.openai.com/v1",
|
|
2155
|
+
models: ["gpt-4o", "gpt-4o-mini"]
|
|
2156
|
+
},
|
|
2157
|
+
{
|
|
2158
|
+
name: "moonshot",
|
|
2159
|
+
label: "Moonshot (Kimi)",
|
|
2160
|
+
baseURL: "https://api.moonshot.cn/v1",
|
|
2161
|
+
models: ["moonshot-v1-8k", "moonshot-v1-32k", "moonshot-v1-128k"]
|
|
2162
|
+
},
|
|
2163
|
+
{
|
|
2164
|
+
name: "custom",
|
|
2165
|
+
label: "\u81EA\u5B9A\u4E49\uFF08\u624B\u52A8\u586B baseURL\uFF09",
|
|
2166
|
+
baseURL: "",
|
|
2167
|
+
models: []
|
|
2168
|
+
}
|
|
2169
|
+
];
|
|
2170
|
+
function ConfigWizard({ onSuccess }) {
|
|
2171
|
+
const { exit } = useApp();
|
|
2172
|
+
const [step, setStep] = useState("preset");
|
|
2173
|
+
const [presetIndex, setPresetIndex] = useState(0);
|
|
2174
|
+
const [baseURL, setBaseURL] = useState("");
|
|
2175
|
+
const [apiKey, setApiKey] = useState("");
|
|
2176
|
+
const [model, setModel] = useState("");
|
|
2177
|
+
const [modelIndex, setModelIndex] = useState(0);
|
|
2178
|
+
const [draft, setDraft] = useState("");
|
|
2179
|
+
const [errorMsg, setErrorMsg] = useState(null);
|
|
2180
|
+
const preset = PRESETS[presetIndex];
|
|
2181
|
+
const enterStep = useCallback(
|
|
2182
|
+
(next) => {
|
|
2183
|
+
setErrorMsg(null);
|
|
2184
|
+
if (next === "baseURL") setDraft(baseURL || preset.baseURL);
|
|
2185
|
+
if (next === "apiKey") setDraft("");
|
|
2186
|
+
if (next === "model") {
|
|
2187
|
+
if (preset.models.length > 0) {
|
|
2188
|
+
setDraft(model || preset.models[0]);
|
|
2189
|
+
setModelIndex(0);
|
|
2190
|
+
} else {
|
|
2191
|
+
setDraft(model);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
setStep(next);
|
|
2195
|
+
},
|
|
2196
|
+
[baseURL, model, preset]
|
|
2197
|
+
);
|
|
2198
|
+
const handleTest = useCallback(async () => {
|
|
2199
|
+
setStep("testing");
|
|
2200
|
+
setErrorMsg(null);
|
|
2201
|
+
try {
|
|
2202
|
+
const url = `${baseURL.replace(/\/$/, "")}/chat/completions`;
|
|
2203
|
+
const resp = await fetch(url, {
|
|
2204
|
+
method: "POST",
|
|
2205
|
+
headers: {
|
|
2206
|
+
"Content-Type": "application/json",
|
|
2207
|
+
Authorization: `Bearer ${apiKey}`
|
|
2208
|
+
},
|
|
2209
|
+
body: JSON.stringify({
|
|
2210
|
+
model,
|
|
2211
|
+
messages: [{ role: "user", content: "ping" }],
|
|
2212
|
+
max_tokens: 5,
|
|
2213
|
+
stream: false
|
|
2214
|
+
})
|
|
2215
|
+
});
|
|
2216
|
+
if (!resp.ok) {
|
|
2217
|
+
const body = await resp.text().catch(() => "");
|
|
2218
|
+
throw new Error(`HTTP ${resp.status}: ${body.slice(0, 200) || resp.statusText}`);
|
|
2219
|
+
}
|
|
2220
|
+
const data = await resp.json();
|
|
2221
|
+
if (!Array.isArray(data.choices)) {
|
|
2222
|
+
throw new Error("\u54CD\u5E94\u4E2D\u6CA1\u6709 choices \u5B57\u6BB5\uFF0C\u53EF\u80FD\u4E0D\u662F OpenAI \u517C\u5BB9\u534F\u8BAE");
|
|
2223
|
+
}
|
|
2224
|
+
await saveConfig({
|
|
2225
|
+
baseURL,
|
|
2226
|
+
apiKey,
|
|
2227
|
+
model,
|
|
2228
|
+
provider: preset.name,
|
|
2229
|
+
contextWindow: DEFAULT_CONTEXT_WINDOW2
|
|
2230
|
+
});
|
|
2231
|
+
onSuccess({
|
|
2232
|
+
name: preset.name,
|
|
2233
|
+
baseURL,
|
|
2234
|
+
apiKey,
|
|
2235
|
+
model,
|
|
2236
|
+
contextWindow: DEFAULT_CONTEXT_WINDOW2
|
|
2237
|
+
});
|
|
2238
|
+
} catch (e) {
|
|
2239
|
+
setErrorMsg(e.message || "\u8FDE\u63A5\u5931\u8D25");
|
|
2240
|
+
setStep("error");
|
|
2241
|
+
}
|
|
2242
|
+
}, [apiKey, baseURL, model, onSuccess, preset.name]);
|
|
2243
|
+
useInput((input, key) => {
|
|
2244
|
+
if (key.escape || key.ctrl && input === "c") {
|
|
2245
|
+
exit();
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
if (step === "preset") {
|
|
2249
|
+
if (key.upArrow) {
|
|
2250
|
+
setPresetIndex((i) => (i - 1 + PRESETS.length) % PRESETS.length);
|
|
2251
|
+
} else if (key.downArrow) {
|
|
2252
|
+
setPresetIndex((i) => (i + 1) % PRESETS.length);
|
|
2253
|
+
} else if (key.return) {
|
|
2254
|
+
enterStep("baseURL");
|
|
2255
|
+
}
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
if (step === "baseURL") {
|
|
2259
|
+
if (key.return) {
|
|
2260
|
+
if (draft.trim().length === 0) return;
|
|
2261
|
+
setBaseURL(draft.trim());
|
|
2262
|
+
enterStep("apiKey");
|
|
2263
|
+
} else if (key.backspace || key.delete) {
|
|
2264
|
+
setDraft((s) => s.slice(0, -1));
|
|
2265
|
+
} else if (input && !key.meta && !key.ctrl) {
|
|
2266
|
+
setDraft((s) => s + input);
|
|
2267
|
+
}
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
if (step === "apiKey") {
|
|
2271
|
+
if (key.return) {
|
|
2272
|
+
if (draft.length === 0) return;
|
|
2273
|
+
setApiKey(draft);
|
|
2274
|
+
enterStep("model");
|
|
2275
|
+
} else if (key.backspace || key.delete) {
|
|
2276
|
+
setDraft((s) => s.slice(0, -1));
|
|
2277
|
+
} else if (input && !key.meta && !key.ctrl) {
|
|
2278
|
+
setDraft((s) => s + input);
|
|
2279
|
+
}
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
if (step === "model") {
|
|
2283
|
+
if (preset.models.length > 0) {
|
|
2284
|
+
if (key.upArrow) {
|
|
2285
|
+
const ni = (modelIndex - 1 + preset.models.length) % preset.models.length;
|
|
2286
|
+
setModelIndex(ni);
|
|
2287
|
+
setDraft(preset.models[ni]);
|
|
2288
|
+
} else if (key.downArrow) {
|
|
2289
|
+
const ni = (modelIndex + 1) % preset.models.length;
|
|
2290
|
+
setModelIndex(ni);
|
|
2291
|
+
setDraft(preset.models[ni]);
|
|
2292
|
+
} else if (key.tab) {
|
|
2293
|
+
setDraft((s) => s + "");
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
if (key.return) {
|
|
2297
|
+
if (draft.trim().length === 0) return;
|
|
2298
|
+
setModel(draft.trim());
|
|
2299
|
+
void handleTest();
|
|
2300
|
+
} else if (key.backspace || key.delete) {
|
|
2301
|
+
setDraft((s) => s.slice(0, -1));
|
|
2302
|
+
} else if (input && !key.meta && !key.ctrl && !key.upArrow && !key.downArrow) {
|
|
2303
|
+
setDraft((s) => s + input);
|
|
2304
|
+
}
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
if (step === "error") {
|
|
2308
|
+
if (key.return) {
|
|
2309
|
+
enterStep("preset");
|
|
2310
|
+
}
|
|
2311
|
+
return;
|
|
2312
|
+
}
|
|
2313
|
+
});
|
|
2314
|
+
const maskedApiKey = useMemo(() => "*".repeat(Math.min(draft.length, 32)), [draft]);
|
|
2315
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
2316
|
+
/* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "minimal-agent \xB7 \u9996\u6B21\u914D\u7F6E\u5411\u5BFC" }) }),
|
|
2317
|
+
/* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u6309 ESC \u6216 Ctrl+C \u9000\u51FA\uFF08\u4E0D\u5199\u914D\u7F6E\uFF0C\u4E0B\u6B21\u542F\u52A8\u7EE7\u7EED\u5411\u5BFC\uFF09" }) }),
|
|
2318
|
+
step === "preset" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2319
|
+
/* @__PURE__ */ jsx(Text, { children: "Step 1/4 \xB7 \u9009\u62E9 provider \u9884\u8BBE\uFF08\u2191\u2193 \u79FB\u52A8\uFF0CEnter \u786E\u8BA4\uFF09" }),
|
|
2320
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: PRESETS.map((p, i) => /* @__PURE__ */ jsxs(Text, { color: i === presetIndex ? "cyan" : void 0, children: [
|
|
2321
|
+
i === presetIndex ? "> " : " ",
|
|
2322
|
+
p.label,
|
|
2323
|
+
p.baseURL ? ` (${p.baseURL})` : ""
|
|
2324
|
+
] }, p.name)) })
|
|
2325
|
+
] }),
|
|
2326
|
+
step === "baseURL" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2327
|
+
/* @__PURE__ */ jsx(Text, { children: "Step 2/4 \xB7 \u8F93\u5165 API base URL\uFF08\u9ED8\u8BA4\u4E3A\u9884\u8BBE\u503C\uFF0C\u53EF\u6539\uFF09" }),
|
|
2328
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { children: draft || " " }) }),
|
|
2329
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Enter \u786E\u8BA4 \xB7 Backspace \u5220\u9664" })
|
|
2330
|
+
] }),
|
|
2331
|
+
step === "apiKey" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2332
|
+
/* @__PURE__ */ jsx(Text, { children: "Step 3/4 \xB7 \u8F93\u5165 API key\uFF08\u8F93\u5165\u5185\u5BB9\u4E0D\u4F1A\u663E\u793A\uFF09" }),
|
|
2333
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { children: maskedApiKey || " " }) }),
|
|
2334
|
+
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
2335
|
+
"Enter \u786E\u8BA4 \xB7 Backspace \u5220\u9664\uFF08\u5DF2\u8F93\u5165 ",
|
|
2336
|
+
draft.length,
|
|
2337
|
+
" \u5B57\u7B26\uFF09"
|
|
2338
|
+
] })
|
|
2339
|
+
] }),
|
|
2340
|
+
step === "model" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2341
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
2342
|
+
"Step 4/4 \xB7 \u8F93\u5165\u6216\u9009\u62E9 model",
|
|
2343
|
+
preset.models.length > 0 ? "\uFF08\u2191\u2193 \u5207\u6362\u9884\u8BBE\uFF0C\u6216\u76F4\u63A5\u7F16\u8F91\uFF09" : ""
|
|
2344
|
+
] }),
|
|
2345
|
+
preset.models.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: preset.models.map((m, i) => /* @__PURE__ */ jsxs(Text, { color: i === modelIndex ? "cyan" : "gray", children: [
|
|
2346
|
+
i === modelIndex ? "> " : " ",
|
|
2347
|
+
m
|
|
2348
|
+
] }, m)) }),
|
|
2349
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { children: draft || " " }) }),
|
|
2350
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Enter \u63D0\u4EA4\u5E76\u6D4B\u8BD5\u8FDE\u63A5" })
|
|
2351
|
+
] }),
|
|
2352
|
+
step === "testing" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2353
|
+
/* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
2354
|
+
"\u23F3 \u6B63\u5728\u6D4B\u8BD5\u8FDE\u63A5 ",
|
|
2355
|
+
baseURL,
|
|
2356
|
+
" ..."
|
|
2357
|
+
] }),
|
|
2358
|
+
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
2359
|
+
"model = ",
|
|
2360
|
+
model
|
|
2361
|
+
] })
|
|
2362
|
+
] }),
|
|
2363
|
+
step === "error" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2364
|
+
/* @__PURE__ */ jsx(Text, { color: "red", children: "\u2717 \u8FDE\u63A5\u6D4B\u8BD5\u5931\u8D25" }),
|
|
2365
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: errorMsg ?? "\u672A\u77E5\u9519\u8BEF" }) }),
|
|
2366
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u5DF2\u8F93\u5165\uFF1A" }) }),
|
|
2367
|
+
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
2368
|
+
" provider = ",
|
|
2369
|
+
preset.name
|
|
2370
|
+
] }),
|
|
2371
|
+
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
2372
|
+
" baseURL = ",
|
|
2373
|
+
baseURL
|
|
2374
|
+
] }),
|
|
2375
|
+
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
2376
|
+
" model = ",
|
|
2377
|
+
model
|
|
2378
|
+
] }),
|
|
2379
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u6309 Enter \u56DE\u5230 Step 1 \u91CD\u8BD5 \xB7 ESC \u9000\u51FA" }) })
|
|
2380
|
+
] })
|
|
2381
|
+
] });
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
// src/ui/App.tsx
|
|
2385
|
+
import React3 from "react";
|
|
2386
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
2387
|
+
|
|
2388
|
+
// src/ui/InputBox.tsx
|
|
2389
|
+
import { useRef as useRef2, useState as useState4, useCallback as useCallback4 } from "react";
|
|
2390
|
+
import { Box as Box2, Text as Text2, useApp as useApp2, useInput as useInput2 } from "ink";
|
|
2391
|
+
|
|
2392
|
+
// src/ui/hooks/useTextBuffer.ts
|
|
2393
|
+
import { useCallback as useCallback2, useMemo as useMemo2, useState as useState2 } from "react";
|
|
2394
|
+
|
|
2395
|
+
// src/ui/textBuffer.ts
|
|
2396
|
+
var EMPTY_BUFFER = { text: "", cursor: 0 };
|
|
2397
|
+
function toCps(s) {
|
|
2398
|
+
return Array.from(s);
|
|
2399
|
+
}
|
|
2400
|
+
function fromCps(cps) {
|
|
2401
|
+
return cps.join("");
|
|
2402
|
+
}
|
|
2403
|
+
function isWordChar(c) {
|
|
2404
|
+
if (!c) return false;
|
|
2405
|
+
return /[A-Za-z0-9_]/.test(c);
|
|
2406
|
+
}
|
|
2407
|
+
function findLineStart(cps, idx) {
|
|
2408
|
+
let i = idx;
|
|
2409
|
+
while (i > 0 && cps[i - 1] !== "\n") i--;
|
|
2410
|
+
return i;
|
|
2411
|
+
}
|
|
2412
|
+
function findLineEnd(cps, idx) {
|
|
2413
|
+
let i = idx;
|
|
2414
|
+
while (i < cps.length && cps[i] !== "\n") i++;
|
|
2415
|
+
return i;
|
|
2416
|
+
}
|
|
2417
|
+
function clamp(cps, idx) {
|
|
2418
|
+
return Math.max(0, Math.min(cps.length, idx));
|
|
2419
|
+
}
|
|
2420
|
+
function bufInsert(s, content) {
|
|
2421
|
+
if (content.length === 0) return s;
|
|
2422
|
+
const cps = toCps(s.text);
|
|
2423
|
+
const ins = toCps(content);
|
|
2424
|
+
cps.splice(s.cursor, 0, ...ins);
|
|
2425
|
+
return { text: fromCps(cps), cursor: s.cursor + ins.length };
|
|
2426
|
+
}
|
|
2427
|
+
function bufSetText(text) {
|
|
2428
|
+
return { text, cursor: toCps(text).length };
|
|
2429
|
+
}
|
|
2430
|
+
function bufClear() {
|
|
2431
|
+
return EMPTY_BUFFER;
|
|
2432
|
+
}
|
|
2433
|
+
function bufDeleteBefore(s) {
|
|
2434
|
+
if (s.cursor === 0) return s;
|
|
2435
|
+
const cps = toCps(s.text);
|
|
2436
|
+
cps.splice(s.cursor - 1, 1);
|
|
2437
|
+
return { text: fromCps(cps), cursor: s.cursor - 1 };
|
|
2438
|
+
}
|
|
2439
|
+
function bufDeleteAt(s) {
|
|
2440
|
+
const cps = toCps(s.text);
|
|
2441
|
+
if (s.cursor >= cps.length) return s;
|
|
2442
|
+
cps.splice(s.cursor, 1);
|
|
2443
|
+
return { text: fromCps(cps), cursor: s.cursor };
|
|
2444
|
+
}
|
|
2445
|
+
function bufDeleteWordBefore(s) {
|
|
2446
|
+
if (s.cursor === 0) return s;
|
|
2447
|
+
const cps = toCps(s.text);
|
|
2448
|
+
let i = s.cursor;
|
|
2449
|
+
while (i > 0 && !isWordChar(cps[i - 1])) i--;
|
|
2450
|
+
while (i > 0 && isWordChar(cps[i - 1])) i--;
|
|
2451
|
+
cps.splice(i, s.cursor - i);
|
|
2452
|
+
return { text: fromCps(cps), cursor: i };
|
|
2453
|
+
}
|
|
2454
|
+
function bufKillToLineStart(s) {
|
|
2455
|
+
const cps = toCps(s.text);
|
|
2456
|
+
const lineStart = findLineStart(cps, s.cursor);
|
|
2457
|
+
if (lineStart === s.cursor) return s;
|
|
2458
|
+
cps.splice(lineStart, s.cursor - lineStart);
|
|
2459
|
+
return { text: fromCps(cps), cursor: lineStart };
|
|
2460
|
+
}
|
|
2461
|
+
function bufKillToLineEnd(s) {
|
|
2462
|
+
const cps = toCps(s.text);
|
|
2463
|
+
const lineEnd = findLineEnd(cps, s.cursor);
|
|
2464
|
+
if (lineEnd === s.cursor) return s;
|
|
2465
|
+
cps.splice(s.cursor, lineEnd - s.cursor);
|
|
2466
|
+
return { text: fromCps(cps), cursor: s.cursor };
|
|
2467
|
+
}
|
|
2468
|
+
function bufMoveLeft(s) {
|
|
2469
|
+
return { text: s.text, cursor: Math.max(0, s.cursor - 1) };
|
|
2470
|
+
}
|
|
2471
|
+
function bufMoveRight(s) {
|
|
2472
|
+
const len = toCps(s.text).length;
|
|
2473
|
+
return { text: s.text, cursor: Math.min(len, s.cursor + 1) };
|
|
2474
|
+
}
|
|
2475
|
+
function bufMoveWordLeft(s) {
|
|
2476
|
+
const cps = toCps(s.text);
|
|
2477
|
+
let i = s.cursor;
|
|
2478
|
+
while (i > 0 && !isWordChar(cps[i - 1])) i--;
|
|
2479
|
+
while (i > 0 && isWordChar(cps[i - 1])) i--;
|
|
2480
|
+
return { text: s.text, cursor: i };
|
|
2481
|
+
}
|
|
2482
|
+
function bufMoveWordRight(s) {
|
|
2483
|
+
const cps = toCps(s.text);
|
|
2484
|
+
let i = s.cursor;
|
|
2485
|
+
while (i < cps.length && !isWordChar(cps[i])) i++;
|
|
2486
|
+
while (i < cps.length && isWordChar(cps[i])) i++;
|
|
2487
|
+
return { text: s.text, cursor: i };
|
|
2488
|
+
}
|
|
2489
|
+
function bufMoveLineStart(s) {
|
|
2490
|
+
const cps = toCps(s.text);
|
|
2491
|
+
return { text: s.text, cursor: findLineStart(cps, s.cursor) };
|
|
2492
|
+
}
|
|
2493
|
+
function bufMoveLineEnd(s) {
|
|
2494
|
+
const cps = toCps(s.text);
|
|
2495
|
+
return { text: s.text, cursor: findLineEnd(cps, s.cursor) };
|
|
2496
|
+
}
|
|
2497
|
+
function bufMoveBufferStart(s) {
|
|
2498
|
+
return { text: s.text, cursor: 0 };
|
|
2499
|
+
}
|
|
2500
|
+
function bufMoveBufferEnd(s) {
|
|
2501
|
+
return { text: s.text, cursor: toCps(s.text).length };
|
|
2502
|
+
}
|
|
2503
|
+
function bufMoveUp(s) {
|
|
2504
|
+
const cps = toCps(s.text);
|
|
2505
|
+
const lineStart = findLineStart(cps, s.cursor);
|
|
2506
|
+
if (lineStart === 0) return s;
|
|
2507
|
+
const prevLineEnd = lineStart - 1;
|
|
2508
|
+
const prevLineStart = findLineStart(cps, prevLineEnd);
|
|
2509
|
+
const col = s.cursor - lineStart;
|
|
2510
|
+
const prevLineLen = prevLineEnd - prevLineStart;
|
|
2511
|
+
return { text: s.text, cursor: prevLineStart + Math.min(col, prevLineLen) };
|
|
2512
|
+
}
|
|
2513
|
+
function bufMoveDown(s) {
|
|
2514
|
+
const cps = toCps(s.text);
|
|
2515
|
+
const lineStart = findLineStart(cps, s.cursor);
|
|
2516
|
+
const lineEnd = findLineEnd(cps, s.cursor);
|
|
2517
|
+
if (lineEnd === cps.length) return s;
|
|
2518
|
+
const nextLineStart = lineEnd + 1;
|
|
2519
|
+
const nextLineEnd = findLineEnd(cps, nextLineStart);
|
|
2520
|
+
const col = s.cursor - lineStart;
|
|
2521
|
+
const nextLineLen = nextLineEnd - nextLineStart;
|
|
2522
|
+
return { text: s.text, cursor: nextLineStart + Math.min(col, nextLineLen) };
|
|
2523
|
+
}
|
|
2524
|
+
function layoutBuffer(s) {
|
|
2525
|
+
const cps = toCps(s.text);
|
|
2526
|
+
const cursor = clamp(cps, s.cursor);
|
|
2527
|
+
const lineStart = findLineStart(cps, cursor);
|
|
2528
|
+
const lines = [];
|
|
2529
|
+
let buf = [];
|
|
2530
|
+
let cursorRow = 0;
|
|
2531
|
+
let charCount = 0;
|
|
2532
|
+
for (const ch of cps) {
|
|
2533
|
+
if (ch === "\n") {
|
|
2534
|
+
lines.push(fromCps(buf));
|
|
2535
|
+
buf = [];
|
|
2536
|
+
if (charCount < lineStart) cursorRow++;
|
|
2537
|
+
} else {
|
|
2538
|
+
buf.push(ch);
|
|
2539
|
+
}
|
|
2540
|
+
charCount++;
|
|
2541
|
+
}
|
|
2542
|
+
lines.push(fromCps(buf));
|
|
2543
|
+
const cursorCol = cursor - lineStart;
|
|
2544
|
+
return { lines, cursorRow, cursorCol };
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
// src/ui/hooks/useTextBuffer.ts
|
|
2548
|
+
function useTextBuffer() {
|
|
2549
|
+
const [state, setState] = useState2(EMPTY_BUFFER);
|
|
2550
|
+
const insert = useCallback2((s) => setState((p) => bufInsert(p, s)), []);
|
|
2551
|
+
const setText = useCallback2((s) => setState(bufSetText(s)), []);
|
|
2552
|
+
const clear = useCallback2(() => setState(bufClear()), []);
|
|
2553
|
+
const deleteBefore = useCallback2(() => setState(bufDeleteBefore), []);
|
|
2554
|
+
const deleteAt = useCallback2(() => setState(bufDeleteAt), []);
|
|
2555
|
+
const deleteWordBefore = useCallback2(() => setState(bufDeleteWordBefore), []);
|
|
2556
|
+
const killToLineStart = useCallback2(() => setState(bufKillToLineStart), []);
|
|
2557
|
+
const killToLineEnd = useCallback2(() => setState(bufKillToLineEnd), []);
|
|
2558
|
+
const moveLeft = useCallback2(() => setState(bufMoveLeft), []);
|
|
2559
|
+
const moveRight = useCallback2(() => setState(bufMoveRight), []);
|
|
2560
|
+
const moveWordLeft = useCallback2(() => setState(bufMoveWordLeft), []);
|
|
2561
|
+
const moveWordRight = useCallback2(() => setState(bufMoveWordRight), []);
|
|
2562
|
+
const moveLineStart = useCallback2(() => setState(bufMoveLineStart), []);
|
|
2563
|
+
const moveLineEnd = useCallback2(() => setState(bufMoveLineEnd), []);
|
|
2564
|
+
const moveBufferStart = useCallback2(() => setState(bufMoveBufferStart), []);
|
|
2565
|
+
const moveBufferEnd = useCallback2(() => setState(bufMoveBufferEnd), []);
|
|
2566
|
+
const moveUp = useCallback2(() => setState(bufMoveUp), []);
|
|
2567
|
+
const moveDown = useCallback2(() => setState(bufMoveDown), []);
|
|
2568
|
+
const layout = useMemo2(() => layoutBuffer(state), [state]);
|
|
2569
|
+
return {
|
|
2570
|
+
state,
|
|
2571
|
+
layout,
|
|
2572
|
+
insert,
|
|
2573
|
+
setText,
|
|
2574
|
+
clear,
|
|
2575
|
+
deleteBefore,
|
|
2576
|
+
deleteAt,
|
|
2577
|
+
deleteWordBefore,
|
|
2578
|
+
killToLineStart,
|
|
2579
|
+
killToLineEnd,
|
|
2580
|
+
moveLeft,
|
|
2581
|
+
moveRight,
|
|
2582
|
+
moveWordLeft,
|
|
2583
|
+
moveWordRight,
|
|
2584
|
+
moveLineStart,
|
|
2585
|
+
moveLineEnd,
|
|
2586
|
+
moveBufferStart,
|
|
2587
|
+
moveBufferEnd,
|
|
2588
|
+
moveUp,
|
|
2589
|
+
moveDown
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
// src/ui/hooks/usePasteHandler.ts
|
|
2594
|
+
import { useCallback as useCallback3, useRef, useState as useState3 } from "react";
|
|
2595
|
+
var PASTE_THRESHOLD = 20;
|
|
2596
|
+
var PASTE_TIMEOUT_MS = 100;
|
|
2597
|
+
function usePasteHandler({
|
|
2598
|
+
onPasteComplete,
|
|
2599
|
+
multiline = false
|
|
2600
|
+
}) {
|
|
2601
|
+
const [isPasting, setIsPasting] = useState3(false);
|
|
2602
|
+
const pasteStateRef = useRef({
|
|
2603
|
+
chunks: [],
|
|
2604
|
+
timeoutId: null
|
|
2605
|
+
});
|
|
2606
|
+
const onPasteCompleteRef = useRef(onPasteComplete);
|
|
2607
|
+
onPasteCompleteRef.current = onPasteComplete;
|
|
2608
|
+
const setIsPastingRef = useRef(setIsPasting);
|
|
2609
|
+
setIsPastingRef.current = setIsPasting;
|
|
2610
|
+
const flushPaste = useCallback3(() => {
|
|
2611
|
+
const { chunks } = pasteStateRef.current;
|
|
2612
|
+
if (chunks.length === 0) return;
|
|
2613
|
+
const rawText = chunks.join("");
|
|
2614
|
+
const cleanedText = rawText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n{3,}/g, "\n\n");
|
|
2615
|
+
pasteStateRef.current = { chunks: [], timeoutId: null };
|
|
2616
|
+
setIsPastingRef.current(false);
|
|
2617
|
+
onPasteCompleteRef.current(cleanedText);
|
|
2618
|
+
}, []);
|
|
2619
|
+
const handleInput = useCallback3(
|
|
2620
|
+
(input, key) => {
|
|
2621
|
+
if (isPasting && key.return) {
|
|
2622
|
+
return "enter-in-paste";
|
|
2623
|
+
}
|
|
2624
|
+
if (multiline && key.return && key.meta && !key.ctrl) {
|
|
2625
|
+
return "enter-newline";
|
|
2626
|
+
}
|
|
2627
|
+
if (key.return) {
|
|
2628
|
+
return "continue";
|
|
2629
|
+
}
|
|
2630
|
+
const isLongInput = input.length > PASTE_THRESHOLD;
|
|
2631
|
+
if (isLongInput || isPasting) {
|
|
2632
|
+
setIsPasting(true);
|
|
2633
|
+
if (pasteStateRef.current.timeoutId) {
|
|
2634
|
+
clearTimeout(pasteStateRef.current.timeoutId);
|
|
2635
|
+
}
|
|
2636
|
+
pasteStateRef.current.chunks.push(input);
|
|
2637
|
+
pasteStateRef.current.timeoutId = setTimeout(() => {
|
|
2638
|
+
flushPaste();
|
|
2639
|
+
}, PASTE_TIMEOUT_MS);
|
|
2640
|
+
return "paste";
|
|
2641
|
+
}
|
|
2642
|
+
return "continue";
|
|
2643
|
+
},
|
|
2644
|
+
[isPasting, multiline, flushPaste]
|
|
2645
|
+
);
|
|
2646
|
+
return {
|
|
2647
|
+
handleInput,
|
|
2648
|
+
isPasting
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
// src/ui/InputBox.tsx
|
|
2653
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
2654
|
+
function InputBox({ onSubmit, disabled, onAbort, onClear, onCompact }) {
|
|
2655
|
+
const buf = useTextBuffer();
|
|
2656
|
+
const ctrlCCountRef = useRef2(0);
|
|
2657
|
+
const ctrlCTimerRef = useRef2(null);
|
|
2658
|
+
const { exit } = useApp2();
|
|
2659
|
+
const [refreshCounter, setRefreshCounter] = useState4(0);
|
|
2660
|
+
const lastDeleteTime = useRef2(0);
|
|
2661
|
+
const DELETE_DEBOUNCE_MS = 20;
|
|
2662
|
+
const forceRefresh = useCallback4(() => {
|
|
2663
|
+
setRefreshCounter((c) => c + 1);
|
|
2664
|
+
}, []);
|
|
2665
|
+
const { handleInput: handlePasteInput } = usePasteHandler({
|
|
2666
|
+
onPasteComplete: (text) => {
|
|
2667
|
+
buf.insert(text);
|
|
2668
|
+
forceRefresh();
|
|
2669
|
+
},
|
|
2670
|
+
multiline: true
|
|
2671
|
+
});
|
|
2672
|
+
useInput2((input, key) => {
|
|
2673
|
+
const pasteResult = handlePasteInput(input, key);
|
|
2674
|
+
if (pasteResult === "paste") {
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2677
|
+
if (pasteResult === "enter-in-paste") {
|
|
2678
|
+
return;
|
|
2679
|
+
}
|
|
2680
|
+
if (pasteResult === "enter-newline") {
|
|
2681
|
+
buf.insert("\n");
|
|
2682
|
+
forceRefresh();
|
|
2683
|
+
return;
|
|
2684
|
+
}
|
|
2685
|
+
if (key.escape) {
|
|
2686
|
+
if (disabled) onAbort();
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
if (key.ctrl && input === "c") {
|
|
2690
|
+
if (buf.state.text.length > 0) {
|
|
2691
|
+
buf.clear();
|
|
2692
|
+
forceRefresh();
|
|
2693
|
+
return;
|
|
2694
|
+
}
|
|
2695
|
+
if (disabled) {
|
|
2696
|
+
onAbort();
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
ctrlCCountRef.current++;
|
|
2700
|
+
if (ctrlCCountRef.current >= 2) exit();
|
|
2701
|
+
if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
|
|
2702
|
+
ctrlCTimerRef.current = setTimeout(() => {
|
|
2703
|
+
ctrlCCountRef.current = 0;
|
|
2704
|
+
}, 1e3);
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
if (key.return) {
|
|
2708
|
+
const text = buf.state.text.trim();
|
|
2709
|
+
if (text.length === 0) return;
|
|
2710
|
+
if (text === "/exit" || text === "/quit") exit();
|
|
2711
|
+
else if (text === "/new" || text === "/clear") {
|
|
2712
|
+
buf.clear();
|
|
2713
|
+
forceRefresh();
|
|
2714
|
+
onClear();
|
|
2715
|
+
} else if (text === "/compact") {
|
|
2716
|
+
buf.clear();
|
|
2717
|
+
forceRefresh();
|
|
2718
|
+
onCompact();
|
|
2719
|
+
} else {
|
|
2720
|
+
buf.clear();
|
|
2721
|
+
forceRefresh();
|
|
2722
|
+
onSubmit(text);
|
|
2723
|
+
}
|
|
2724
|
+
return;
|
|
2725
|
+
}
|
|
2726
|
+
if (key.leftArrow) {
|
|
2727
|
+
if (key.ctrl || key.meta) buf.moveWordLeft();
|
|
2728
|
+
else buf.moveLeft();
|
|
2729
|
+
forceRefresh();
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
if (key.rightArrow) {
|
|
2733
|
+
if (key.ctrl || key.meta) buf.moveWordRight();
|
|
2734
|
+
else buf.moveRight();
|
|
2735
|
+
forceRefresh();
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
if (key.upArrow) {
|
|
2739
|
+
buf.moveUp();
|
|
2740
|
+
forceRefresh();
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
if (key.downArrow) {
|
|
2744
|
+
buf.moveDown();
|
|
2745
|
+
forceRefresh();
|
|
2746
|
+
return;
|
|
2747
|
+
}
|
|
2748
|
+
if (key.backspace) {
|
|
2749
|
+
buf.deleteBefore();
|
|
2750
|
+
forceRefresh();
|
|
2751
|
+
return;
|
|
2752
|
+
}
|
|
2753
|
+
if (key.delete) {
|
|
2754
|
+
const now = Date.now();
|
|
2755
|
+
if (now - lastDeleteTime.current < DELETE_DEBOUNCE_MS) {
|
|
2756
|
+
return;
|
|
2757
|
+
}
|
|
2758
|
+
lastDeleteTime.current = now;
|
|
2759
|
+
buf.deleteBefore();
|
|
2760
|
+
forceRefresh();
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
if (key.ctrl && !key.meta) {
|
|
2764
|
+
switch (input) {
|
|
2765
|
+
case "a":
|
|
2766
|
+
buf.moveLineStart();
|
|
2767
|
+
break;
|
|
2768
|
+
case "e":
|
|
2769
|
+
buf.moveLineEnd();
|
|
2770
|
+
break;
|
|
2771
|
+
case "b":
|
|
2772
|
+
buf.moveLeft();
|
|
2773
|
+
break;
|
|
2774
|
+
case "f":
|
|
2775
|
+
buf.moveRight();
|
|
2776
|
+
break;
|
|
2777
|
+
case "u":
|
|
2778
|
+
buf.killToLineStart();
|
|
2779
|
+
break;
|
|
2780
|
+
case "k":
|
|
2781
|
+
buf.killToLineEnd();
|
|
2782
|
+
break;
|
|
2783
|
+
case "w":
|
|
2784
|
+
buf.deleteWordBefore();
|
|
2785
|
+
break;
|
|
2786
|
+
case "h":
|
|
2787
|
+
buf.deleteBefore();
|
|
2788
|
+
break;
|
|
2789
|
+
// Ctrl+H = Backspace
|
|
2790
|
+
default:
|
|
2791
|
+
return;
|
|
2792
|
+
}
|
|
2793
|
+
forceRefresh();
|
|
2794
|
+
return;
|
|
2795
|
+
}
|
|
2796
|
+
if (key.meta && input === "<") {
|
|
2797
|
+
buf.moveBufferStart();
|
|
2798
|
+
forceRefresh();
|
|
2799
|
+
return;
|
|
2800
|
+
}
|
|
2801
|
+
if (key.meta && input === ">") {
|
|
2802
|
+
buf.moveBufferEnd();
|
|
2803
|
+
forceRefresh();
|
|
2804
|
+
return;
|
|
2805
|
+
}
|
|
2806
|
+
if (input && !key.meta) {
|
|
2807
|
+
const normalized = input.replace(/\r\n?/g, "\n");
|
|
2808
|
+
buf.insert(normalized);
|
|
2809
|
+
forceRefresh();
|
|
2810
|
+
}
|
|
2811
|
+
});
|
|
2812
|
+
return /* @__PURE__ */ jsx2(
|
|
2813
|
+
BufferView,
|
|
2814
|
+
{
|
|
2815
|
+
buf,
|
|
2816
|
+
disabled,
|
|
2817
|
+
refreshCounter
|
|
2818
|
+
}
|
|
2819
|
+
);
|
|
2820
|
+
}
|
|
2821
|
+
function BufferView({ buf, disabled, refreshCounter }) {
|
|
2822
|
+
const { lines, cursorRow, cursorCol } = buf.layout;
|
|
2823
|
+
const promptColor = disabled ? "gray" : "cyan";
|
|
2824
|
+
const prompt = disabled ? "\u23F3 " : "> ";
|
|
2825
|
+
return /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: promptColor, paddingX: 1, flexDirection: "column", children: lines.map((line, row) => {
|
|
2826
|
+
const isFirst = row === 0;
|
|
2827
|
+
const isCursorRow = row === cursorRow && !disabled;
|
|
2828
|
+
return /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
2829
|
+
/* @__PURE__ */ jsx2(Text2, { color: promptColor, bold: true, children: isFirst ? prompt : " " }),
|
|
2830
|
+
isCursorRow ? /* @__PURE__ */ jsx2(CursorLine, { line, col: cursorCol }) : /* @__PURE__ */ jsx2(Text2, { children: line || " " })
|
|
2831
|
+
] }, row);
|
|
2832
|
+
}) });
|
|
2833
|
+
}
|
|
2834
|
+
function CursorLine({ line, col }) {
|
|
2835
|
+
const cps = Array.from(line);
|
|
2836
|
+
const before = cps.slice(0, col).join("");
|
|
2837
|
+
const cursorChar = cps[col] ?? " ";
|
|
2838
|
+
const after = cps.slice(col + 1).join("");
|
|
2839
|
+
return /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
2840
|
+
before,
|
|
2841
|
+
/* @__PURE__ */ jsx2(Text2, { inverse: true, children: cursorChar }),
|
|
2842
|
+
after
|
|
2843
|
+
] });
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
// src/ui/MessageList.tsx
|
|
2847
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
2848
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2849
|
+
var MAX_TOOL_PREVIEW_LINES = 3;
|
|
2850
|
+
function MessageList({ history, streamingText }) {
|
|
2851
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
2852
|
+
history.map((m, i) => /* @__PURE__ */ jsx3(MessageRow, { message: m }, i)),
|
|
2853
|
+
streamingText.length > 0 && /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
|
|
2854
|
+
/* @__PURE__ */ jsx3(Text3, { children: streamingText }),
|
|
2855
|
+
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: " \u258D" })
|
|
2856
|
+
] })
|
|
2857
|
+
] });
|
|
2858
|
+
}
|
|
2859
|
+
function MessageRow({ message }) {
|
|
2860
|
+
switch (message.role) {
|
|
2861
|
+
case "system":
|
|
2862
|
+
return null;
|
|
2863
|
+
// 不展示系统提示词
|
|
2864
|
+
case "user":
|
|
2865
|
+
return /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
|
|
2866
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", bold: true, children: "> " }),
|
|
2867
|
+
/* @__PURE__ */ jsx3(Text3, { children: message.content })
|
|
2868
|
+
] });
|
|
2869
|
+
case "assistant": {
|
|
2870
|
+
const text = message.content ?? "";
|
|
2871
|
+
const tcCount = message.tool_calls?.length ?? 0;
|
|
2872
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginBottom: 1, children: [
|
|
2873
|
+
text.length > 0 && /* @__PURE__ */ jsx3(Text3, { children: text }),
|
|
2874
|
+
tcCount > 0 && /* @__PURE__ */ jsx3(Text3, { color: "yellow", dimColor: true, children: ` \u2514\u2500 \u8C03\u7528\u4E86 ${tcCount} \u4E2A\u5DE5\u5177\uFF1A${(message.tool_calls ?? []).map((tc) => tc.function.name).join(", ")}` })
|
|
2875
|
+
] });
|
|
2876
|
+
}
|
|
2877
|
+
case "tool": {
|
|
2878
|
+
const lines = message.content.split("\n");
|
|
2879
|
+
const preview = lines.slice(0, MAX_TOOL_PREVIEW_LINES).join("\n");
|
|
2880
|
+
const hidden = Math.max(0, lines.length - MAX_TOOL_PREVIEW_LINES);
|
|
2881
|
+
return /* @__PURE__ */ jsx3(Box3, { marginBottom: 1, paddingLeft: 2, children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
2882
|
+
/* @__PURE__ */ jsx3(Text3, { color: "gray", dimColor: true, children: "[tool result]" }),
|
|
2883
|
+
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: preview }),
|
|
2884
|
+
hidden > 0 && /* @__PURE__ */ jsx3(Text3, { color: "gray", dimColor: true, children: `... (\u7701\u7565 ${hidden} \u884C)` })
|
|
2885
|
+
] }) });
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
// src/ui/StatusLine.tsx
|
|
2891
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
2892
|
+
|
|
2893
|
+
// src/llm/client.ts
|
|
2894
|
+
async function* chat(args) {
|
|
2895
|
+
const { provider, messages, tools, signal } = args;
|
|
2896
|
+
const openaiTools = tools.map((t) => ({
|
|
2897
|
+
type: "function",
|
|
2898
|
+
function: {
|
|
2899
|
+
name: t.name,
|
|
2900
|
+
description: typeof t.description === "function" ? t.description() : t.description,
|
|
2901
|
+
parameters: t.parameters
|
|
2902
|
+
}
|
|
2903
|
+
}));
|
|
2904
|
+
const body = {
|
|
2905
|
+
model: provider.model,
|
|
2906
|
+
messages,
|
|
2907
|
+
tools: openaiTools.length > 0 ? openaiTools : void 0,
|
|
2908
|
+
stream: true,
|
|
2909
|
+
// tool_choice: 'auto' 是默认值,可不写;某些 provider 必须显式
|
|
2910
|
+
tool_choice: openaiTools.length > 0 ? "auto" : void 0
|
|
2911
|
+
};
|
|
2912
|
+
const url = `${provider.baseURL.replace(/\/$/, "")}/chat/completions`;
|
|
2913
|
+
const resp = await fetch(url, {
|
|
2914
|
+
method: "POST",
|
|
2915
|
+
headers: {
|
|
2916
|
+
"Content-Type": "application/json",
|
|
2917
|
+
Authorization: `Bearer ${provider.apiKey}`
|
|
2918
|
+
},
|
|
2919
|
+
body: JSON.stringify(body),
|
|
2920
|
+
signal
|
|
2921
|
+
});
|
|
2922
|
+
if (!resp.ok) {
|
|
2923
|
+
const errText = await resp.text().catch(() => "");
|
|
2924
|
+
throw new Error(
|
|
2925
|
+
`LLM \u8BF7\u6C42\u5931\u8D25 [${resp.status} ${resp.statusText}]\uFF1A${errText.slice(0, 500)}`
|
|
2926
|
+
);
|
|
2927
|
+
}
|
|
2928
|
+
if (!resp.body) {
|
|
2929
|
+
throw new Error("LLM \u54CD\u5E94\u6CA1\u6709 body\uFF08provider \u53EF\u80FD\u4E0D\u652F\u6301\u6D41\u5F0F\uFF1F\uFF09");
|
|
2930
|
+
}
|
|
2931
|
+
const reader = resp.body.getReader();
|
|
2932
|
+
const decoder = new TextDecoder();
|
|
2933
|
+
let buffer = "";
|
|
2934
|
+
let stopReason = "unknown";
|
|
2935
|
+
try {
|
|
2936
|
+
while (true) {
|
|
2937
|
+
if (signal?.aborted) {
|
|
2938
|
+
yield { type: "done", stopReason: "aborted" };
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2941
|
+
const { value, done } = await reader.read();
|
|
2942
|
+
if (done) break;
|
|
2943
|
+
if (signal?.aborted) {
|
|
2944
|
+
yield { type: "done", stopReason: "aborted" };
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2947
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2948
|
+
let lineEnd;
|
|
2949
|
+
while ((lineEnd = buffer.indexOf("\n")) !== -1) {
|
|
2950
|
+
const line = buffer.slice(0, lineEnd).trim();
|
|
2951
|
+
buffer = buffer.slice(lineEnd + 1);
|
|
2952
|
+
if (!line || !line.startsWith("data:")) continue;
|
|
2953
|
+
const dataStr = line.slice(5).trim();
|
|
2954
|
+
if (dataStr === "[DONE]") {
|
|
2955
|
+
yield { type: "done", stopReason };
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
let chunk;
|
|
2959
|
+
try {
|
|
2960
|
+
chunk = JSON.parse(dataStr);
|
|
2961
|
+
} catch {
|
|
2962
|
+
continue;
|
|
2963
|
+
}
|
|
2964
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
2965
|
+
const finish = chunk.choices?.[0]?.finish_reason;
|
|
2966
|
+
if (finish) {
|
|
2967
|
+
stopReason = finish === "stop" || finish === "tool_calls" || finish === "length" ? finish : "unknown";
|
|
2968
|
+
}
|
|
2969
|
+
if (!delta) continue;
|
|
2970
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
2971
|
+
yield { type: "text_delta", delta: delta.content };
|
|
2972
|
+
}
|
|
2973
|
+
if (Array.isArray(delta.tool_calls)) {
|
|
2974
|
+
for (const tc of delta.tool_calls) {
|
|
2975
|
+
yield {
|
|
2976
|
+
type: "tool_call_delta",
|
|
2977
|
+
index: tc.index ?? 0,
|
|
2978
|
+
id: tc.id,
|
|
2979
|
+
name: tc.function?.name,
|
|
2980
|
+
argumentsDelta: tc.function?.arguments
|
|
2981
|
+
};
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
|
|
2985
|
+
yield {
|
|
2986
|
+
type: "reasoning_delta",
|
|
2987
|
+
field: "reasoning_content",
|
|
2988
|
+
delta: delta.reasoning_content
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
if (typeof delta.reasoning === "string" && delta.reasoning.length > 0) {
|
|
2992
|
+
yield {
|
|
2993
|
+
type: "reasoning_delta",
|
|
2994
|
+
field: "reasoning",
|
|
2995
|
+
delta: delta.reasoning
|
|
2996
|
+
};
|
|
2997
|
+
}
|
|
2998
|
+
if (Array.isArray(delta.reasoning_details) && delta.reasoning_details.length > 0) {
|
|
2999
|
+
yield {
|
|
3000
|
+
type: "reasoning_delta",
|
|
3001
|
+
field: "reasoning_details",
|
|
3002
|
+
items: delta.reasoning_details
|
|
3003
|
+
};
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
yield { type: "done", stopReason };
|
|
3008
|
+
} finally {
|
|
3009
|
+
reader.releaseLock?.();
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
// src/context/tokenCounter.ts
|
|
3014
|
+
function countTextTokens(text) {
|
|
3015
|
+
if (!text) return 0;
|
|
3016
|
+
let asciiChars = 0;
|
|
3017
|
+
let nonAsciiChars = 0;
|
|
3018
|
+
for (let i = 0; i < text.length; i++) {
|
|
3019
|
+
const code = text.charCodeAt(i);
|
|
3020
|
+
if (code < 128) asciiChars++;
|
|
3021
|
+
else nonAsciiChars++;
|
|
3022
|
+
}
|
|
3023
|
+
return Math.ceil(asciiChars / 4) + nonAsciiChars;
|
|
3024
|
+
}
|
|
3025
|
+
function countMessagesTokens(messages) {
|
|
3026
|
+
let total = 0;
|
|
3027
|
+
for (const m of messages) {
|
|
3028
|
+
total += 4;
|
|
3029
|
+
if (typeof m.content === "string") {
|
|
3030
|
+
total += countTextTokens(m.content);
|
|
3031
|
+
} else if (m.content === null) {
|
|
3032
|
+
}
|
|
3033
|
+
if (m.role === "assistant" && m.tool_calls) {
|
|
3034
|
+
for (const tc of m.tool_calls) {
|
|
3035
|
+
total += 8;
|
|
3036
|
+
total += countTextTokens(tc.function.name);
|
|
3037
|
+
total += countTextTokens(tc.function.arguments);
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
if (m.role === "tool") {
|
|
3041
|
+
total += countTextTokens(m.tool_call_id);
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
return total;
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
// src/context/compact.ts
|
|
3048
|
+
var NO_TOOLS_PREAMBLE = `CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.
|
|
3049
|
+
|
|
3050
|
+
- Do NOT use Read, Bash, Grep, Glob, Edit, Write, or ANY other tool.
|
|
3051
|
+
- You already have all the context you need in the conversation above.
|
|
3052
|
+
- Tool calls will be REJECTED and will waste your only turn \u2014 you will fail the task.
|
|
3053
|
+
- Your entire response must be plain text: an <analysis> block followed by a <summary> block.
|
|
3054
|
+
|
|
3055
|
+
`;
|
|
3056
|
+
var NO_TOOLS_TRAILER = "\n\nREMINDER: Do NOT call any tools. Respond with plain text only \u2014 an <analysis> block followed by a <summary> block. Tool calls will be rejected and you will fail the task.";
|
|
3057
|
+
var COMPACT_PROMPT_BODY = `Your task is to create a detailed summary of this conversation. This summary will be placed at the start of a continuing session; newer messages that build on this context will follow after your summary. Summarize thoroughly so that someone reading only your summary and then the newer messages can fully understand what happened and continue the work.
|
|
3058
|
+
|
|
3059
|
+
Before providing your final summary, wrap your analysis in <analysis> tags to organize your thoughts and ensure you've covered all necessary points. In your analysis process:
|
|
3060
|
+
|
|
3061
|
+
1. Chronologically analyze each message and section of the conversation. For each section thoroughly identify:
|
|
3062
|
+
- The user's explicit requests and intents
|
|
3063
|
+
- Your approach to addressing the user's requests
|
|
3064
|
+
- Key decisions, technical concepts and code patterns
|
|
3065
|
+
- Specific details like file names, full code snippets, function signatures, file edits
|
|
3066
|
+
- Errors that you ran into and how you fixed them
|
|
3067
|
+
- Pay special attention to specific user feedback that you received, especially if the user told you to do something differently.
|
|
3068
|
+
2. Double-check for technical accuracy and completeness, addressing each required element thoroughly.
|
|
3069
|
+
|
|
3070
|
+
Your summary should include the following sections:
|
|
3071
|
+
|
|
3072
|
+
1. Primary Request and Intent: Capture all of the user's explicit requests and intents in detail
|
|
3073
|
+
2. Key Technical Concepts: List all important technical concepts, technologies, and frameworks discussed.
|
|
3074
|
+
3. Files and Code Sections: Enumerate specific files and code sections examined, modified, or created. Include FULL code snippets where applicable and include a summary of why this file read or edit is important. (Code snippets are NOT compressed \u2014 preserve them verbatim.)
|
|
3075
|
+
4. Errors and fixes: List all errors that you ran into, and how you fixed them. Pay special attention to specific user feedback that you received, especially if the user told you to do something differently.
|
|
3076
|
+
5. Problem Solving: Document problems solved and any ongoing troubleshooting efforts.
|
|
3077
|
+
6. All user messages: List ALL user messages that are not tool results. These are critical for understanding the users' feedback and changing intent. (User messages are NOT compressed \u2014 preserve them verbatim.)
|
|
3078
|
+
7. Pending Tasks: Outline any pending tasks that you have explicitly been asked to work on.
|
|
3079
|
+
8. Work Completed: Describe what was accomplished by the end of this portion.
|
|
3080
|
+
9. Context for Continuing Work: Summarize any context, decisions, or state that would be needed to understand and continue the work in subsequent messages. Include direct quotes from the most recent conversation showing exactly where work was left off \u2014 this should be verbatim to ensure there's no drift.
|
|
3081
|
+
|
|
3082
|
+
Here's an example of how your output should be structured:
|
|
3083
|
+
|
|
3084
|
+
<example>
|
|
3085
|
+
<analysis>
|
|
3086
|
+
[Your thought process, ensuring all points are covered thoroughly and accurately]
|
|
3087
|
+
</analysis>
|
|
3088
|
+
|
|
3089
|
+
<summary>
|
|
3090
|
+
1. Primary Request and Intent:
|
|
3091
|
+
[Detailed description]
|
|
3092
|
+
|
|
3093
|
+
2. Key Technical Concepts:
|
|
3094
|
+
- [Concept 1]
|
|
3095
|
+
- [...]
|
|
3096
|
+
|
|
3097
|
+
3. Files and Code Sections:
|
|
3098
|
+
- [File Name 1]
|
|
3099
|
+
- [Summary of why this file is important]
|
|
3100
|
+
- [Important Code Snippet]
|
|
3101
|
+
- [...]
|
|
3102
|
+
|
|
3103
|
+
4. Errors and fixes:
|
|
3104
|
+
- [Detailed description of error 1]:
|
|
3105
|
+
- [How you fixed the error]
|
|
3106
|
+
- [User feedback on the error if any]
|
|
3107
|
+
- [...]
|
|
3108
|
+
|
|
3109
|
+
5. Problem Solving:
|
|
3110
|
+
[Description of solved problems and ongoing troubleshooting]
|
|
3111
|
+
|
|
3112
|
+
6. All user messages:
|
|
3113
|
+
- [Detailed non tool use user message]
|
|
3114
|
+
- [...]
|
|
3115
|
+
|
|
3116
|
+
7. Pending Tasks:
|
|
3117
|
+
- [Task 1]
|
|
3118
|
+
- [...]
|
|
3119
|
+
|
|
3120
|
+
8. Work Completed:
|
|
3121
|
+
[What was accomplished]
|
|
3122
|
+
|
|
3123
|
+
9. Context for Continuing Work:
|
|
3124
|
+
[Key context, decisions, or state needed to continue]
|
|
3125
|
+
|
|
3126
|
+
</summary>
|
|
3127
|
+
</example>
|
|
3128
|
+
|
|
3129
|
+
Please provide your summary following this structure, ensuring precision and thoroughness in your response.`;
|
|
3130
|
+
function buildCompactPrompt(customInstructions) {
|
|
3131
|
+
let prompt = NO_TOOLS_PREAMBLE + COMPACT_PROMPT_BODY;
|
|
3132
|
+
if (customInstructions && customInstructions.trim() !== "") {
|
|
3133
|
+
prompt += `
|
|
3134
|
+
|
|
3135
|
+
Additional Instructions:
|
|
3136
|
+
${customInstructions.trim()}`;
|
|
3137
|
+
}
|
|
3138
|
+
prompt += NO_TOOLS_TRAILER;
|
|
3139
|
+
return prompt;
|
|
3140
|
+
}
|
|
3141
|
+
var BASE_COMPACT_PROMPT = buildCompactPrompt();
|
|
3142
|
+
function buildContinuationUserMessage(args) {
|
|
3143
|
+
const { summary, recentMessagesPreserved } = args;
|
|
3144
|
+
let msg = `This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.
|
|
3145
|
+
|
|
3146
|
+
${summary}`;
|
|
3147
|
+
if (recentMessagesPreserved) {
|
|
3148
|
+
msg += `
|
|
3149
|
+
|
|
3150
|
+
Recent messages are preserved verbatim.`;
|
|
3151
|
+
}
|
|
3152
|
+
msg += `
|
|
3153
|
+
|
|
3154
|
+
Continue the conversation from where it left off without asking the user any further questions. Resume directly \u2014 do not acknowledge the summary, do not recap what was happening, do not preface with "I'll continue" or similar. Pick up the last task as if the break never happened.`;
|
|
3155
|
+
return msg;
|
|
3156
|
+
}
|
|
3157
|
+
function formatCompactSummary(rawResponse) {
|
|
3158
|
+
const match = rawResponse.match(/<summary>([\s\S]*?)<\/summary>/);
|
|
3159
|
+
if (match) return match[1].trim();
|
|
3160
|
+
const stripped = rawResponse.replace(/<analysis>[\s\S]*?<\/analysis>/g, "").trim();
|
|
3161
|
+
return stripped || rawResponse.trim();
|
|
3162
|
+
}
|
|
3163
|
+
var AUTOCOMPACT_BUFFER_TOKENS = 25e3;
|
|
3164
|
+
var MIN_KEEP_RECENT_MESSAGES = 4;
|
|
3165
|
+
function getCompactThreshold(provider) {
|
|
3166
|
+
return Math.max(1e3, provider.contextWindow - AUTOCOMPACT_BUFFER_TOKENS);
|
|
3167
|
+
}
|
|
3168
|
+
function findTailWithCompleteToolChains(messages, minKeep = MIN_KEEP_RECENT_MESSAGES) {
|
|
3169
|
+
if (messages.length <= minKeep) return [...messages];
|
|
3170
|
+
const tailEnd = messages.length;
|
|
3171
|
+
let tailStart = tailEnd - minKeep;
|
|
3172
|
+
const knownToolCallIds = /* @__PURE__ */ new Set();
|
|
3173
|
+
for (let i = tailStart; i < tailEnd; i++) {
|
|
3174
|
+
const msg = messages[i];
|
|
3175
|
+
if (msg.role === "assistant" && msg.tool_calls) {
|
|
3176
|
+
for (const tc of msg.tool_calls) {
|
|
3177
|
+
knownToolCallIds.add(tc.id);
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
let needsExpansion = true;
|
|
3182
|
+
while (needsExpansion && tailStart > 0) {
|
|
3183
|
+
needsExpansion = false;
|
|
3184
|
+
for (let i = tailStart; i < tailEnd; i++) {
|
|
3185
|
+
const msg = messages[i];
|
|
3186
|
+
if (msg.role === "tool" && msg.tool_call_id) {
|
|
3187
|
+
if (!knownToolCallIds.has(msg.tool_call_id)) {
|
|
3188
|
+
needsExpansion = true;
|
|
3189
|
+
tailStart--;
|
|
3190
|
+
const newMsg = messages[tailStart];
|
|
3191
|
+
if (newMsg.role === "assistant" && newMsg.tool_calls) {
|
|
3192
|
+
for (const tc of newMsg.tool_calls) {
|
|
3193
|
+
knownToolCallIds.add(tc.id);
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
break;
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
return messages.slice(tailStart);
|
|
3202
|
+
}
|
|
3203
|
+
async function autoCompactIfNeeded(messages, provider) {
|
|
3204
|
+
const before = countMessagesTokens(messages);
|
|
3205
|
+
const threshold = getCompactThreshold(provider);
|
|
3206
|
+
if (before < threshold) {
|
|
3207
|
+
return { messages, compacted: false, before, after: before };
|
|
3208
|
+
}
|
|
3209
|
+
const compactedMessages = await runCompaction(messages, provider);
|
|
3210
|
+
const after = countMessagesTokens(compactedMessages);
|
|
3211
|
+
return { messages: compactedMessages, compacted: true, before, after };
|
|
3212
|
+
}
|
|
3213
|
+
async function forceCompact(messages, provider) {
|
|
3214
|
+
const before = countMessagesTokens(messages);
|
|
3215
|
+
const compactedMessages = await runCompaction(messages, provider);
|
|
3216
|
+
const after = countMessagesTokens(compactedMessages);
|
|
3217
|
+
return { messages: compactedMessages, before, after };
|
|
3218
|
+
}
|
|
3219
|
+
async function runCompaction(messages, provider) {
|
|
3220
|
+
const systemMsg = messages.find((m) => m.role === "system");
|
|
3221
|
+
const nonSystem = messages.filter((m) => m.role !== "system");
|
|
3222
|
+
const compactRequest = [
|
|
3223
|
+
...systemMsg ? [systemMsg] : [],
|
|
3224
|
+
...nonSystem,
|
|
3225
|
+
{
|
|
3226
|
+
role: "user",
|
|
3227
|
+
content: buildCompactPrompt()
|
|
3228
|
+
}
|
|
3229
|
+
];
|
|
3230
|
+
let summary = "";
|
|
3231
|
+
try {
|
|
3232
|
+
for await (const ev of chat({
|
|
3233
|
+
provider,
|
|
3234
|
+
messages: compactRequest,
|
|
3235
|
+
tools: []
|
|
3236
|
+
})) {
|
|
3237
|
+
if (ev.type === "text_delta") summary += ev.delta;
|
|
3238
|
+
}
|
|
3239
|
+
} catch (e) {
|
|
3240
|
+
return [
|
|
3241
|
+
...systemMsg ? [systemMsg] : [],
|
|
3242
|
+
{
|
|
3243
|
+
role: "user",
|
|
3244
|
+
content: `(\u81EA\u52A8\u538B\u7F29\u5931\u8D25\uFF1A${e.message}\uFF0C\u5DF2\u76F4\u63A5\u622A\u65AD\u65E7\u5386\u53F2)`
|
|
3245
|
+
},
|
|
3246
|
+
...findTailWithCompleteToolChains(nonSystem)
|
|
3247
|
+
];
|
|
3248
|
+
}
|
|
3249
|
+
const cleanSummary = formatCompactSummary(summary);
|
|
3250
|
+
const recentTail = findTailWithCompleteToolChains(nonSystem);
|
|
3251
|
+
return [
|
|
3252
|
+
...systemMsg ? [systemMsg] : [],
|
|
3253
|
+
{
|
|
3254
|
+
role: "user",
|
|
3255
|
+
content: buildContinuationUserMessage({
|
|
3256
|
+
summary: cleanSummary,
|
|
3257
|
+
recentMessagesPreserved: recentTail.length > 0
|
|
3258
|
+
})
|
|
3259
|
+
},
|
|
3260
|
+
...recentTail
|
|
3261
|
+
];
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
// src/ui/hooks/useTokenUsage.ts
|
|
3265
|
+
function useTokenUsage(messages, provider) {
|
|
3266
|
+
const tokens = countMessagesTokens(messages);
|
|
3267
|
+
const threshold = Math.max(1e3, provider.contextWindow - AUTOCOMPACT_BUFFER_TOKENS);
|
|
3268
|
+
return {
|
|
3269
|
+
tokens,
|
|
3270
|
+
threshold,
|
|
3271
|
+
contextWindow: provider.contextWindow,
|
|
3272
|
+
percentageOfWindow: tokens / provider.contextWindow,
|
|
3273
|
+
toThreshold: threshold - tokens
|
|
3274
|
+
};
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
// src/ui/StatusLine.tsx
|
|
3278
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
3279
|
+
function StatusLine({ provider, history }) {
|
|
3280
|
+
const usage = useTokenUsage(history, provider);
|
|
3281
|
+
const ratio = usage.tokens / usage.threshold;
|
|
3282
|
+
const color = ratio >= 1 ? "red" : ratio >= 0.7 ? "yellow" : "green";
|
|
3283
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
3284
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "gray", children: [
|
|
3285
|
+
provider.name,
|
|
3286
|
+
"/",
|
|
3287
|
+
provider.model
|
|
3288
|
+
] }),
|
|
3289
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: " \xB7 " }),
|
|
3290
|
+
/* @__PURE__ */ jsxs4(Text4, { color, children: [
|
|
3291
|
+
"tokens ",
|
|
3292
|
+
fmt(usage.tokens),
|
|
3293
|
+
" / ",
|
|
3294
|
+
fmt(usage.contextWindow)
|
|
3295
|
+
] }),
|
|
3296
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: " \xB7 " }),
|
|
3297
|
+
/* @__PURE__ */ jsx4(Text4, { color, children: usage.toThreshold > 0 ? `\u8DDD\u538B\u7F29 ${fmt(usage.toThreshold)}` : `\u5DF2\u8D85\u9608\u503C\uFF08\u4E0B\u8F6E\u538B\u7F29\uFF09` })
|
|
3298
|
+
] });
|
|
3299
|
+
}
|
|
3300
|
+
function fmt(n) {
|
|
3301
|
+
if (n < 1e3) return `${n}`;
|
|
3302
|
+
if (n < 1e6) return `${(n / 1e3).toFixed(1)}K`;
|
|
3303
|
+
return `${(n / 1e6).toFixed(2)}M`;
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
// src/ui/ToolStatus.tsx
|
|
3307
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
3308
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
3309
|
+
function ToolStatus({ status, compacting }) {
|
|
3310
|
+
if (compacting) {
|
|
3311
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { color: "magenta", bold: true, children: "\u{1F5DC} \u6B63\u5728\u538B\u7F29\u5BF9\u8BDD\u5386\u53F2..." }) });
|
|
3312
|
+
}
|
|
3313
|
+
if (status) {
|
|
3314
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsxs5(Text5, { color: "yellow", children: [
|
|
3315
|
+
"\u23F3 ",
|
|
3316
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: status.toolName }),
|
|
3317
|
+
`(${status.argsPreview})`
|
|
3318
|
+
] }) });
|
|
3319
|
+
}
|
|
3320
|
+
return null;
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
// src/ui/hooks/useChat.ts
|
|
3324
|
+
import { useCallback as useCallback5, useEffect, useMemo as useMemo3, useRef as useRef3, useState as useState5 } from "react";
|
|
3325
|
+
|
|
3326
|
+
// src/context/snipCompact.ts
|
|
3327
|
+
var DEFAULT_SNIP_PERCENT = 0.2;
|
|
3328
|
+
var DEFAULT_MIN_KEEP = 5;
|
|
3329
|
+
var DEFAULT_MAX_SNIP = 50;
|
|
3330
|
+
var DEFAULT_THRESHOLD = 10;
|
|
3331
|
+
function snipCompactIfNeeded(messages, options = {}) {
|
|
3332
|
+
const force = options.force ?? false;
|
|
3333
|
+
const snipPercent = options.snipPercent ?? DEFAULT_SNIP_PERCENT;
|
|
3334
|
+
const minKeep = options.minMessagesToKeep ?? DEFAULT_MIN_KEEP;
|
|
3335
|
+
const maxSnip = options.maxMessagesToSnip ?? DEFAULT_MAX_SNIP;
|
|
3336
|
+
if (messages.length === 0) {
|
|
3337
|
+
return { messages: [], messagesRemoved: 0, tokensFreed: 0 };
|
|
3338
|
+
}
|
|
3339
|
+
const systemMsg = messages[0].role === "system" ? messages[0] : null;
|
|
3340
|
+
const rest = systemMsg ? messages.slice(1) : messages.slice();
|
|
3341
|
+
if (!force && rest.length < DEFAULT_THRESHOLD) {
|
|
3342
|
+
return { messages: messages.slice(), messagesRemoved: 0, tokensFreed: 0 };
|
|
3343
|
+
}
|
|
3344
|
+
const byPercent = Math.floor(rest.length * snipPercent);
|
|
3345
|
+
const proposedSnipCount = Math.min(
|
|
3346
|
+
byPercent,
|
|
3347
|
+
maxSnip,
|
|
3348
|
+
Math.max(0, rest.length - minKeep)
|
|
3349
|
+
);
|
|
3350
|
+
if (proposedSnipCount <= 0) {
|
|
3351
|
+
return { messages: messages.slice(), messagesRemoved: 0, tokensFreed: 0 };
|
|
3352
|
+
}
|
|
3353
|
+
const actualCut = findHeadCutpoint(rest, proposedSnipCount);
|
|
3354
|
+
if (actualCut === 0 || actualCut >= rest.length) {
|
|
3355
|
+
return { messages: messages.slice(), messagesRemoved: 0, tokensFreed: 0 };
|
|
3356
|
+
}
|
|
3357
|
+
const snipped = rest.slice(0, actualCut);
|
|
3358
|
+
const remaining = rest.slice(actualCut);
|
|
3359
|
+
const tokensFreed = countMessagesTokens(snipped);
|
|
3360
|
+
const marker = {
|
|
3361
|
+
role: "user",
|
|
3362
|
+
content: `[\u5DF2\u81EA\u52A8\u5220\u9664\u6700\u65E9\u7684 ${actualCut} \u6761\u5BF9\u8BDD\u4EE5\u8282\u7701 token\uFF08\u7EA6\u91CA\u653E ${tokensFreed.toLocaleString()} tokens\uFF09\u3002\u5982\u9700\u5B8C\u6574\u5386\u53F2\u8BF7\u7528 /new \u91CD\u542F\u4F1A\u8BDD\u6216 /compact \u89E6\u53D1 LLM \u6458\u8981\u538B\u7F29\u3002]`
|
|
3363
|
+
};
|
|
3364
|
+
return {
|
|
3365
|
+
messages: [...systemMsg ? [systemMsg] : [], marker, ...remaining],
|
|
3366
|
+
messagesRemoved: actualCut,
|
|
3367
|
+
tokensFreed
|
|
3368
|
+
};
|
|
3369
|
+
}
|
|
3370
|
+
function findHeadCutpoint(messages, proposedCut) {
|
|
3371
|
+
let cut = Math.max(0, Math.min(proposedCut, messages.length));
|
|
3372
|
+
while (cut < messages.length) {
|
|
3373
|
+
const knownIds = /* @__PURE__ */ new Set();
|
|
3374
|
+
let foundOrphan = false;
|
|
3375
|
+
for (let i = cut; i < messages.length; i++) {
|
|
3376
|
+
const msg = messages[i];
|
|
3377
|
+
if (msg.role === "assistant" && msg.tool_calls) {
|
|
3378
|
+
for (const tc of msg.tool_calls) knownIds.add(tc.id);
|
|
3379
|
+
} else if (msg.role === "tool" && !knownIds.has(msg.tool_call_id)) {
|
|
3380
|
+
foundOrphan = true;
|
|
3381
|
+
break;
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
if (!foundOrphan) return cut;
|
|
3385
|
+
cut++;
|
|
3386
|
+
}
|
|
3387
|
+
return cut;
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
// src/context/reactiveCompact.ts
|
|
3391
|
+
var attemptedThisSession = false;
|
|
3392
|
+
function resetReactiveCompactState() {
|
|
3393
|
+
attemptedThisSession = false;
|
|
3394
|
+
}
|
|
3395
|
+
function isPromptTooLongError(error) {
|
|
3396
|
+
const msg = errorMessage(error).toLowerCase();
|
|
3397
|
+
if (!msg) return false;
|
|
3398
|
+
const hasSubject = /prompt|context|token|input length/.test(msg);
|
|
3399
|
+
const hasIssue = /too long|exceed|limit|maximum|over/.test(msg);
|
|
3400
|
+
return hasSubject && hasIssue;
|
|
3401
|
+
}
|
|
3402
|
+
function errorMessage(error) {
|
|
3403
|
+
if (typeof error === "string") return error;
|
|
3404
|
+
if (error instanceof Error) return error.message;
|
|
3405
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
3406
|
+
return String(error.message ?? "");
|
|
3407
|
+
}
|
|
3408
|
+
return String(error ?? "");
|
|
3409
|
+
}
|
|
3410
|
+
async function reactiveCompactIfApplicable(messages, provider, error) {
|
|
3411
|
+
if (!isPromptTooLongError(error)) {
|
|
3412
|
+
return { recovered: false, messages, reason: "not a prompt-too-long error" };
|
|
3413
|
+
}
|
|
3414
|
+
if (attemptedThisSession) {
|
|
3415
|
+
return {
|
|
3416
|
+
recovered: false,
|
|
3417
|
+
messages,
|
|
3418
|
+
reason: "already attempted this session \u2014 use /new or /compact manually"
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3421
|
+
attemptedThisSession = true;
|
|
3422
|
+
try {
|
|
3423
|
+
const r = await forceCompact(messages, provider);
|
|
3424
|
+
return {
|
|
3425
|
+
recovered: true,
|
|
3426
|
+
messages: r.messages,
|
|
3427
|
+
reason: `LLM \u538B\u7F29\u6210\u529F\uFF08${r.before} \u2192 ${r.after} tokens\uFF09`,
|
|
3428
|
+
before: r.before,
|
|
3429
|
+
after: r.after
|
|
3430
|
+
};
|
|
3431
|
+
} catch (compactErr) {
|
|
3432
|
+
}
|
|
3433
|
+
const beforeSnip = countMessagesTokens(messages);
|
|
3434
|
+
const snipped = snipCompactIfNeeded(messages, {
|
|
3435
|
+
force: true,
|
|
3436
|
+
snipPercent: 0.4
|
|
3437
|
+
});
|
|
3438
|
+
if (snipped.messagesRemoved > 0) {
|
|
3439
|
+
const afterSnip = countMessagesTokens(snipped.messages);
|
|
3440
|
+
return {
|
|
3441
|
+
recovered: true,
|
|
3442
|
+
messages: snipped.messages,
|
|
3443
|
+
reason: `snip \u515C\u5E95\u6210\u529F\uFF08\u5220\u9664 ${snipped.messagesRemoved} \u6761\u6700\u8001\u6D88\u606F\uFF0C\u91CA\u653E ~${snipped.tokensFreed} tokens\uFF09`,
|
|
3444
|
+
before: beforeSnip,
|
|
3445
|
+
after: afterSnip
|
|
3446
|
+
};
|
|
3447
|
+
}
|
|
3448
|
+
return {
|
|
3449
|
+
recovered: false,
|
|
3450
|
+
messages,
|
|
3451
|
+
reason: "\u53CD\u5E94\u5F0F\u538B\u7F29\u5931\u8D25\uFF1ALLM \u538B\u7F29\u629B\u9519\u4E14 snip \u4E5F\u6CA1\u4E1C\u897F\u53EF\u780D"
|
|
3452
|
+
};
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3455
|
+
// src/context/microCompactLite.ts
|
|
3456
|
+
import { createHash } from "crypto";
|
|
3457
|
+
var MAX_REPEAT_COUNT = 3;
|
|
3458
|
+
var MAX_RESULT_SIZE = 4e3;
|
|
3459
|
+
var HEAD_KEEP_CHARS = 2e3;
|
|
3460
|
+
var TAIL_KEEP_CHARS = 1e3;
|
|
3461
|
+
var MAX_KEEP_ROUNDS = 10;
|
|
3462
|
+
var SHORT_CONTENT_THRESHOLD = 200;
|
|
3463
|
+
var currentTurn = 0;
|
|
3464
|
+
var contentCache = /* @__PURE__ */ new Map();
|
|
3465
|
+
function incrementTurn() {
|
|
3466
|
+
currentTurn++;
|
|
3467
|
+
}
|
|
3468
|
+
var COMPRESSIBLE_TOOLS = /* @__PURE__ */ new Set([
|
|
3469
|
+
"Grep",
|
|
3470
|
+
"Bash",
|
|
3471
|
+
"WebFetch",
|
|
3472
|
+
"Glob"
|
|
3473
|
+
]);
|
|
3474
|
+
function microCompact(toolName, content) {
|
|
3475
|
+
if (!content) return content;
|
|
3476
|
+
if (content.startsWith("Error:") || content.startsWith("\u9519\u8BEF")) {
|
|
3477
|
+
return content;
|
|
3478
|
+
}
|
|
3479
|
+
if (content.length <= SHORT_CONTENT_THRESHOLD) {
|
|
3480
|
+
return content;
|
|
3481
|
+
}
|
|
3482
|
+
const hash = sha1(content);
|
|
3483
|
+
const existing = contentCache.get(hash);
|
|
3484
|
+
if (existing) {
|
|
3485
|
+
existing.count++;
|
|
3486
|
+
if (existing.count > MAX_REPEAT_COUNT) {
|
|
3487
|
+
return `[\u2191 ${existing.firstToolName} \u7ED3\u679C\u5DF2\u91CD\u590D\u51FA\u73B0 ${existing.count} \u6B21\uFF08\u76F8\u540C\u5185\u5BB9\u5DF2\u7701\u7565\uFF09]`;
|
|
3488
|
+
}
|
|
3489
|
+
return content;
|
|
3490
|
+
}
|
|
3491
|
+
contentCache.set(hash, { count: 1, firstToolName: toolName, firstSeenTurn: currentTurn });
|
|
3492
|
+
if (content.length > MAX_RESULT_SIZE && COMPRESSIBLE_TOOLS.has(toolName)) {
|
|
3493
|
+
return truncateContent(content);
|
|
3494
|
+
}
|
|
3495
|
+
return content;
|
|
3496
|
+
}
|
|
3497
|
+
function expireOldEntries() {
|
|
3498
|
+
let expired = 0;
|
|
3499
|
+
for (const [hash, entry] of contentCache) {
|
|
3500
|
+
if (currentTurn - entry.firstSeenTurn > MAX_KEEP_ROUNDS) {
|
|
3501
|
+
contentCache.delete(hash);
|
|
3502
|
+
expired++;
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
return expired;
|
|
3506
|
+
}
|
|
3507
|
+
function sha1(str) {
|
|
3508
|
+
return createHash("sha1").update(str).digest("hex");
|
|
3509
|
+
}
|
|
3510
|
+
function truncateContent(content) {
|
|
3511
|
+
const omitted = content.length - HEAD_KEEP_CHARS - TAIL_KEEP_CHARS;
|
|
3512
|
+
return content.slice(0, HEAD_KEEP_CHARS) + `
|
|
3513
|
+
|
|
3514
|
+
[... \u7701\u7565\u4E86 ${omitted.toLocaleString()} \u5B57\u7B26 ...]
|
|
3515
|
+
|
|
3516
|
+
` + content.slice(-TAIL_KEEP_CHARS);
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3519
|
+
// src/loop.ts
|
|
3520
|
+
async function* runQuery(userInput, options) {
|
|
3521
|
+
const { provider, history, signal } = options;
|
|
3522
|
+
const maxTurns = options.maxTurns ?? 50;
|
|
3523
|
+
history.push({ role: "user", content: userInput });
|
|
3524
|
+
let reactiveAttempted = false;
|
|
3525
|
+
let turn = 0;
|
|
3526
|
+
while (turn < maxTurns) {
|
|
3527
|
+
turn++;
|
|
3528
|
+
incrementTurn();
|
|
3529
|
+
expireOldEntries();
|
|
3530
|
+
if (signal?.aborted) {
|
|
3531
|
+
history.push({
|
|
3532
|
+
role: "user",
|
|
3533
|
+
content: "(\u7528\u6237\u6309\u4E0B\u4E86 ESC/Ctrl+C \u4E2D\u65AD\u4E86\u4EFB\u52A1)"
|
|
3534
|
+
});
|
|
3535
|
+
yield { type: "error", error: "\u5DF2\u88AB\u7528\u6237\u4E2D\u65AD" };
|
|
3536
|
+
return;
|
|
3537
|
+
}
|
|
3538
|
+
try {
|
|
3539
|
+
const compact = await autoCompactIfNeeded(history, provider);
|
|
3540
|
+
if (compact.compacted) {
|
|
3541
|
+
yield { type: "compact_start" };
|
|
3542
|
+
history.length = 0;
|
|
3543
|
+
history.push(...compact.messages);
|
|
3544
|
+
yield { type: "compact_done", before: compact.before, after: compact.after };
|
|
3545
|
+
}
|
|
3546
|
+
} catch (e) {
|
|
3547
|
+
yield {
|
|
3548
|
+
type: "error",
|
|
3549
|
+
error: `\u81EA\u52A8\u538B\u7F29\u5931\u8D25\uFF08\u7EE7\u7EED\u4E0D\u538B\u7F29\uFF09\uFF1A${e.message}`
|
|
3550
|
+
};
|
|
3551
|
+
}
|
|
3552
|
+
let assistantText = "";
|
|
3553
|
+
const toolCallsByIndex = [];
|
|
3554
|
+
let reasoningContent = "";
|
|
3555
|
+
let reasoningString = "";
|
|
3556
|
+
const reasoningDetails = [];
|
|
3557
|
+
try {
|
|
3558
|
+
for await (const ev of chat({
|
|
3559
|
+
provider,
|
|
3560
|
+
messages: history,
|
|
3561
|
+
tools: ALL_TOOLS,
|
|
3562
|
+
signal
|
|
3563
|
+
})) {
|
|
3564
|
+
if (ev.type === "text_delta") {
|
|
3565
|
+
assistantText += ev.delta;
|
|
3566
|
+
yield { type: "text", delta: ev.delta };
|
|
3567
|
+
} else if (ev.type === "tool_call_delta") {
|
|
3568
|
+
mergeToolCallDelta(toolCallsByIndex, ev);
|
|
3569
|
+
} else if (ev.type === "reasoning_delta") {
|
|
3570
|
+
if (ev.field === "reasoning_content" && ev.delta) {
|
|
3571
|
+
reasoningContent += ev.delta;
|
|
3572
|
+
} else if (ev.field === "reasoning" && ev.delta) {
|
|
3573
|
+
reasoningString += ev.delta;
|
|
3574
|
+
} else if (ev.field === "reasoning_details" && ev.items) {
|
|
3575
|
+
reasoningDetails.push(...ev.items);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
} catch (e) {
|
|
3580
|
+
if (e.name === "AbortError") {
|
|
3581
|
+
history.push({
|
|
3582
|
+
role: "user",
|
|
3583
|
+
content: "(\u7528\u6237\u6309\u4E0B\u4E86 ESC/Ctrl+C \u4E2D\u65AD\u4E86\u4EFB\u52A1)"
|
|
3584
|
+
});
|
|
3585
|
+
yield { type: "interrupted" };
|
|
3586
|
+
return;
|
|
3587
|
+
}
|
|
3588
|
+
if (isPromptTooLongError(e) && !reactiveAttempted) {
|
|
3589
|
+
reactiveAttempted = true;
|
|
3590
|
+
yield { type: "compact_start" };
|
|
3591
|
+
const result = await reactiveCompactIfApplicable(history, provider, e);
|
|
3592
|
+
if (result.recovered) {
|
|
3593
|
+
history.length = 0;
|
|
3594
|
+
history.push(...result.messages);
|
|
3595
|
+
yield {
|
|
3596
|
+
type: "compact_done",
|
|
3597
|
+
before: result.before ?? 0,
|
|
3598
|
+
after: result.after ?? 0
|
|
3599
|
+
};
|
|
3600
|
+
turn--;
|
|
3601
|
+
continue;
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
yield { type: "error", error: `LLM \u8C03\u7528\u5931\u8D25\uFF1A${e.message}` };
|
|
3605
|
+
return;
|
|
3606
|
+
}
|
|
3607
|
+
const assistantMsg = {
|
|
3608
|
+
role: "assistant",
|
|
3609
|
+
content: assistantText.length > 0 ? assistantText : null,
|
|
3610
|
+
...toolCallsByIndex.length > 0 ? { tool_calls: toolCallsByIndex } : {},
|
|
3611
|
+
...reasoningContent ? { reasoning_content: reasoningContent } : {},
|
|
3612
|
+
...reasoningString ? { reasoning: reasoningString } : {},
|
|
3613
|
+
...reasoningDetails.length > 0 ? { reasoning_details: reasoningDetails } : {}
|
|
3614
|
+
};
|
|
3615
|
+
history.push(assistantMsg);
|
|
3616
|
+
yield { type: "assistant_message", message: assistantMsg };
|
|
3617
|
+
if (toolCallsByIndex.length === 0) {
|
|
3618
|
+
yield { type: "turn_done" };
|
|
3619
|
+
return;
|
|
3620
|
+
}
|
|
3621
|
+
for (const tc of toolCallsByIndex) {
|
|
3622
|
+
if (signal?.aborted) {
|
|
3623
|
+
history.push({
|
|
3624
|
+
role: "user",
|
|
3625
|
+
content: "(\u7528\u6237\u6309\u4E0B\u4E86 ESC/Ctrl+C \u4E2D\u65AD\u4E86\u4EFB\u52A1)"
|
|
3626
|
+
});
|
|
3627
|
+
yield { type: "error", error: "\u5DF2\u88AB\u7528\u6237\u4E2D\u65AD" };
|
|
3628
|
+
return;
|
|
3629
|
+
}
|
|
3630
|
+
const argsPreview = previewArgs(tc.function.arguments);
|
|
3631
|
+
yield {
|
|
3632
|
+
type: "tool_start",
|
|
3633
|
+
toolName: tc.function.name,
|
|
3634
|
+
toolCallId: tc.id,
|
|
3635
|
+
argsPreview
|
|
3636
|
+
};
|
|
3637
|
+
const result = await executeTool(tc.function.name, tc.function.arguments, signal);
|
|
3638
|
+
const rawContent = result.ok ? result.content : `Error: ${result.error}`;
|
|
3639
|
+
const content = microCompact(tc.function.name, rawContent);
|
|
3640
|
+
history.push({
|
|
3641
|
+
role: "tool",
|
|
3642
|
+
content,
|
|
3643
|
+
tool_call_id: tc.id
|
|
3644
|
+
});
|
|
3645
|
+
yield {
|
|
3646
|
+
type: "tool_end",
|
|
3647
|
+
toolName: tc.function.name,
|
|
3648
|
+
toolCallId: tc.id,
|
|
3649
|
+
ok: result.ok,
|
|
3650
|
+
content
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
yield {
|
|
3655
|
+
type: "error",
|
|
3656
|
+
error: `\u8FBE\u5230\u6700\u5927\u8F6E\u6570 ${maxTurns}\uFF0C\u63D0\u524D\u7ED3\u675F\uFF08\u9632\u6B62\u5931\u63A7\uFF09\u3002\u5982\u679C\u5408\u7406\u53EF\u4EE5\u63D0\u9AD8 maxTurns\u3002`
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3659
|
+
function mergeToolCallDelta(acc, ev) {
|
|
3660
|
+
let slot = acc[ev.index];
|
|
3661
|
+
if (!slot) {
|
|
3662
|
+
slot = {
|
|
3663
|
+
id: "",
|
|
3664
|
+
type: "function",
|
|
3665
|
+
function: { name: "", arguments: "" }
|
|
3666
|
+
};
|
|
3667
|
+
acc[ev.index] = slot;
|
|
3668
|
+
}
|
|
3669
|
+
if (ev.id) slot.id = ev.id;
|
|
3670
|
+
if (ev.name) slot.function.name += ev.name;
|
|
3671
|
+
if (ev.argumentsDelta) slot.function.arguments += ev.argumentsDelta;
|
|
3672
|
+
}
|
|
3673
|
+
function previewArgs(rawJson) {
|
|
3674
|
+
const oneLine = rawJson.replace(/\s+/g, " ").trim();
|
|
3675
|
+
if (oneLine.length <= 60) return oneLine;
|
|
3676
|
+
return oneLine.slice(0, 60) + "...";
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3679
|
+
// src/ui/hooks/useChat.ts
|
|
3680
|
+
function useChat(args) {
|
|
3681
|
+
const historyRef = useRef3(args.initialHistory.slice());
|
|
3682
|
+
const [version, setVersion] = useState5(0);
|
|
3683
|
+
const bump = useCallback5(() => setVersion((v) => v + 1), []);
|
|
3684
|
+
const [streamingText, setStreamingText] = useState5("");
|
|
3685
|
+
const [toolStatus, setToolStatus] = useState5(null);
|
|
3686
|
+
const [isLoading, setIsLoading] = useState5(false);
|
|
3687
|
+
const [error, setError] = useState5(null);
|
|
3688
|
+
const [interrupted, setInterrupted] = useState5(false);
|
|
3689
|
+
const [compacting, setCompacting] = useState5(false);
|
|
3690
|
+
const abortRef = useRef3(null);
|
|
3691
|
+
useEffect(() => {
|
|
3692
|
+
return () => {
|
|
3693
|
+
abortRef.current?.abort();
|
|
3694
|
+
};
|
|
3695
|
+
}, []);
|
|
3696
|
+
const submit = useCallback5(
|
|
3697
|
+
async (input) => {
|
|
3698
|
+
if (isLoading) return;
|
|
3699
|
+
const trimmed = input.trim();
|
|
3700
|
+
if (trimmed.length === 0) return;
|
|
3701
|
+
setIsLoading(true);
|
|
3702
|
+
setError(null);
|
|
3703
|
+
setInterrupted(false);
|
|
3704
|
+
setStreamingText("");
|
|
3705
|
+
const ac = new AbortController();
|
|
3706
|
+
abortRef.current = ac;
|
|
3707
|
+
try {
|
|
3708
|
+
for await (const ev of runQuery(trimmed, {
|
|
3709
|
+
provider: args.provider,
|
|
3710
|
+
history: historyRef.current,
|
|
3711
|
+
signal: ac.signal
|
|
3712
|
+
})) {
|
|
3713
|
+
handleEvent(ev, {
|
|
3714
|
+
setStreamingText,
|
|
3715
|
+
setToolStatus,
|
|
3716
|
+
setCompacting,
|
|
3717
|
+
setError,
|
|
3718
|
+
setInterrupted,
|
|
3719
|
+
bump
|
|
3720
|
+
});
|
|
3721
|
+
}
|
|
3722
|
+
} catch (e) {
|
|
3723
|
+
setError(`\u672A\u6355\u83B7\u5F02\u5E38\uFF1A${e.message}`);
|
|
3724
|
+
} finally {
|
|
3725
|
+
setIsLoading(false);
|
|
3726
|
+
setStreamingText("");
|
|
3727
|
+
setToolStatus(null);
|
|
3728
|
+
setCompacting(false);
|
|
3729
|
+
abortRef.current = null;
|
|
3730
|
+
args.onPersist?.(historyRef.current);
|
|
3731
|
+
}
|
|
3732
|
+
},
|
|
3733
|
+
[args.provider, args.onPersist, bump, isLoading]
|
|
3734
|
+
);
|
|
3735
|
+
const abort = useCallback5(() => {
|
|
3736
|
+
abortRef.current?.abort();
|
|
3737
|
+
}, []);
|
|
3738
|
+
const clearHistory = useCallback5(async () => {
|
|
3739
|
+
if (isLoading) return;
|
|
3740
|
+
const newSystemPrompt = await buildFullSystemPrompt(process.cwd(), ALL_TOOLS);
|
|
3741
|
+
historyRef.current.length = 0;
|
|
3742
|
+
historyRef.current.push({ role: "system", content: newSystemPrompt });
|
|
3743
|
+
resetReactiveCompactState();
|
|
3744
|
+
setStreamingText("");
|
|
3745
|
+
setToolStatus(null);
|
|
3746
|
+
setError(null);
|
|
3747
|
+
setInterrupted(false);
|
|
3748
|
+
setCompacting(false);
|
|
3749
|
+
bump();
|
|
3750
|
+
await clearContext();
|
|
3751
|
+
}, [bump, isLoading]);
|
|
3752
|
+
const compactNow = useCallback5(async () => {
|
|
3753
|
+
if (isLoading) return;
|
|
3754
|
+
setIsLoading(true);
|
|
3755
|
+
setCompacting(true);
|
|
3756
|
+
setError(null);
|
|
3757
|
+
let success = false;
|
|
3758
|
+
try {
|
|
3759
|
+
const r = await forceCompact(historyRef.current, args.provider);
|
|
3760
|
+
historyRef.current.length = 0;
|
|
3761
|
+
historyRef.current.push(...r.messages);
|
|
3762
|
+
bump();
|
|
3763
|
+
success = true;
|
|
3764
|
+
} catch (e) {
|
|
3765
|
+
setError(`\u624B\u52A8\u538B\u7F29\u5931\u8D25\uFF1A${e.message}`);
|
|
3766
|
+
} finally {
|
|
3767
|
+
setCompacting(false);
|
|
3768
|
+
setIsLoading(false);
|
|
3769
|
+
if (success) {
|
|
3770
|
+
args.onPersist?.(historyRef.current);
|
|
3771
|
+
args.onCompactDone?.();
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
}, [args.provider, args.onPersist, args.onCompactDone, bump, isLoading]);
|
|
3775
|
+
const history = useMemo3(() => historyRef.current, [version]);
|
|
3776
|
+
return {
|
|
3777
|
+
history,
|
|
3778
|
+
streamingText,
|
|
3779
|
+
toolStatus,
|
|
3780
|
+
isLoading,
|
|
3781
|
+
error,
|
|
3782
|
+
interrupted,
|
|
3783
|
+
compacting,
|
|
3784
|
+
submit,
|
|
3785
|
+
abort,
|
|
3786
|
+
clearHistory,
|
|
3787
|
+
compactNow
|
|
3788
|
+
};
|
|
3789
|
+
}
|
|
3790
|
+
function handleEvent(ev, setters) {
|
|
3791
|
+
switch (ev.type) {
|
|
3792
|
+
case "text":
|
|
3793
|
+
setters.setStreamingText((prev) => prev + ev.delta);
|
|
3794
|
+
break;
|
|
3795
|
+
case "assistant_message":
|
|
3796
|
+
setters.setStreamingText(() => "");
|
|
3797
|
+
setters.bump();
|
|
3798
|
+
break;
|
|
3799
|
+
case "tool_start":
|
|
3800
|
+
setters.setToolStatus({
|
|
3801
|
+
toolName: ev.toolName,
|
|
3802
|
+
argsPreview: ev.argsPreview,
|
|
3803
|
+
status: "running"
|
|
3804
|
+
});
|
|
3805
|
+
setters.bump();
|
|
3806
|
+
break;
|
|
3807
|
+
case "tool_end":
|
|
3808
|
+
setters.setToolStatus(null);
|
|
3809
|
+
setters.bump();
|
|
3810
|
+
break;
|
|
3811
|
+
case "compact_start":
|
|
3812
|
+
setters.setStreamingText(() => "");
|
|
3813
|
+
setters.setCompacting(true);
|
|
3814
|
+
setters.bump();
|
|
3815
|
+
break;
|
|
3816
|
+
case "compact_done":
|
|
3817
|
+
setters.setCompacting(false);
|
|
3818
|
+
setters.bump();
|
|
3819
|
+
break;
|
|
3820
|
+
case "turn_done":
|
|
3821
|
+
setters.bump();
|
|
3822
|
+
break;
|
|
3823
|
+
case "interrupted":
|
|
3824
|
+
setters.setInterrupted(true);
|
|
3825
|
+
setters.bump();
|
|
3826
|
+
break;
|
|
3827
|
+
case "error":
|
|
3828
|
+
setters.setError(ev.error);
|
|
3829
|
+
setters.bump();
|
|
3830
|
+
break;
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
// src/ui/App.tsx
|
|
3835
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3836
|
+
var CLEAR_SCREEN = "\x1B[2J\x1B[H";
|
|
3837
|
+
function App({ provider, initialHistory }) {
|
|
3838
|
+
const onPersist = React3.useCallback((messages) => {
|
|
3839
|
+
void saveContext(messages);
|
|
3840
|
+
}, []);
|
|
3841
|
+
const chat2 = useChat({
|
|
3842
|
+
provider,
|
|
3843
|
+
initialHistory,
|
|
3844
|
+
onPersist,
|
|
3845
|
+
onCompactDone: () => process.stdout.write(CLEAR_SCREEN)
|
|
3846
|
+
});
|
|
3847
|
+
const handleClear = React3.useCallback(async () => {
|
|
3848
|
+
await chat2.clearHistory();
|
|
3849
|
+
process.stdout.write(CLEAR_SCREEN);
|
|
3850
|
+
}, [chat2]);
|
|
3851
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
3852
|
+
/* @__PURE__ */ jsxs6(Box6, { marginBottom: 1, children: [
|
|
3853
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", bold: true, children: "minimal-agent" }),
|
|
3854
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: ` \xB7 ${provider.name}/${provider.model} \xB7 /new \u6E05\u7A7A \xB7 /compact \u538B\u7F29 \xB7 /exit \u9000\u51FA \xB7 Ctrl+C \u4E2D\u65AD` })
|
|
3855
|
+
] }),
|
|
3856
|
+
/* @__PURE__ */ jsx6(MessageList, { history: chat2.history, streamingText: chat2.streamingText }),
|
|
3857
|
+
/* @__PURE__ */ jsx6(
|
|
3858
|
+
ToolStatus,
|
|
3859
|
+
{
|
|
3860
|
+
status: chat2.toolStatus,
|
|
3861
|
+
compacting: chat2.compacting
|
|
3862
|
+
}
|
|
3863
|
+
),
|
|
3864
|
+
chat2.error && /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
|
|
3865
|
+
"\u26A0 ",
|
|
3866
|
+
chat2.error
|
|
3867
|
+
] }) }),
|
|
3868
|
+
chat2.interrupted && /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: "\u26A1 \u64CD\u4F5C\u88AB\u7528\u6237\u4E2D\u65AD\uFF0C\u7B49\u5F85\u65B0\u7684\u4EFB\u52A1\u8F93\u5165..." }) }),
|
|
3869
|
+
/* @__PURE__ */ jsx6(
|
|
3870
|
+
InputBox,
|
|
3871
|
+
{
|
|
3872
|
+
onSubmit: chat2.submit,
|
|
3873
|
+
disabled: chat2.isLoading,
|
|
3874
|
+
onAbort: chat2.abort,
|
|
3875
|
+
onClear: handleClear,
|
|
3876
|
+
onCompact: chat2.compactNow
|
|
3877
|
+
}
|
|
3878
|
+
),
|
|
3879
|
+
/* @__PURE__ */ jsx6(StatusLine, { provider, history: chat2.history })
|
|
3880
|
+
] });
|
|
3881
|
+
}
|
|
3882
|
+
|
|
3883
|
+
// src/ui/Root.tsx
|
|
3884
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
3885
|
+
function Root({ initialProvider }) {
|
|
3886
|
+
const [phase, setPhase] = useState6(
|
|
3887
|
+
initialProvider ? { kind: "loading-history", provider: initialProvider } : { kind: "wizard" }
|
|
3888
|
+
);
|
|
3889
|
+
useEffect2(() => {
|
|
3890
|
+
if (phase.kind !== "loading-history") return;
|
|
3891
|
+
let cancelled = false;
|
|
3892
|
+
void (async () => {
|
|
3893
|
+
const history = await buildInitialHistory();
|
|
3894
|
+
if (cancelled) return;
|
|
3895
|
+
setPhase({ kind: "app", provider: phase.provider, initialHistory: history });
|
|
3896
|
+
})();
|
|
3897
|
+
return () => {
|
|
3898
|
+
cancelled = true;
|
|
3899
|
+
};
|
|
3900
|
+
}, [phase]);
|
|
3901
|
+
if (phase.kind === "wizard") {
|
|
3902
|
+
return /* @__PURE__ */ jsx7(
|
|
3903
|
+
ConfigWizard,
|
|
3904
|
+
{
|
|
3905
|
+
onSuccess: (provider) => {
|
|
3906
|
+
setPhase({ kind: "loading-history", provider });
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3909
|
+
);
|
|
3910
|
+
}
|
|
3911
|
+
if (phase.kind === "loading-history") {
|
|
3912
|
+
return /* @__PURE__ */ jsx7(Box7, { paddingX: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "\u6B63\u5728\u52A0\u8F7D\u5386\u53F2\u4E0A\u4E0B\u6587..." }) });
|
|
3913
|
+
}
|
|
3914
|
+
return /* @__PURE__ */ jsx7(App, { provider: phase.provider, initialHistory: phase.initialHistory });
|
|
3915
|
+
}
|
|
3916
|
+
async function buildInitialHistory() {
|
|
3917
|
+
const content = await buildFullSystemPrompt(process.cwd(), ALL_TOOLS);
|
|
3918
|
+
const fresh = { role: "system", content };
|
|
3919
|
+
const persisted = await loadContext();
|
|
3920
|
+
if (!persisted || persisted.length === 0) return [fresh];
|
|
3921
|
+
const rest = persisted[0]?.role === "system" ? persisted.slice(1) : persisted;
|
|
3922
|
+
return [fresh, ...rest];
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
// src/cli/print.ts
|
|
3926
|
+
var TOOL_OUTPUT_PREVIEW_MAX = 200;
|
|
3927
|
+
var STDIN_TIMEOUT_MS = 3e3;
|
|
3928
|
+
function handleEPIPE(stream) {
|
|
3929
|
+
return (err) => {
|
|
3930
|
+
if (err.code === "EPIPE") stream.destroy();
|
|
3931
|
+
};
|
|
3932
|
+
}
|
|
3933
|
+
function truncateForDisplay(content, max = TOOL_OUTPUT_PREVIEW_MAX) {
|
|
3934
|
+
if (content.length <= max) return content;
|
|
3935
|
+
return content.slice(0, max) + "...";
|
|
3936
|
+
}
|
|
3937
|
+
function extractPromptArgs(args) {
|
|
3938
|
+
const FLAGS = /* @__PURE__ */ new Set(["-p", "--print", "--verbose", "-v", "-h", "--help"]);
|
|
3939
|
+
return args.filter((a) => !FLAGS.has(a));
|
|
3940
|
+
}
|
|
3941
|
+
async function runPrintMode(provider, args, initialHistory, options) {
|
|
3942
|
+
process.stdout.on("error", handleEPIPE(process.stdout));
|
|
3943
|
+
process.stderr.on("error", handleEPIPE(process.stderr));
|
|
3944
|
+
const promptArgs = extractPromptArgs(args);
|
|
3945
|
+
let prompt;
|
|
3946
|
+
if (promptArgs.length > 0) {
|
|
3947
|
+
prompt = promptArgs.join(" ");
|
|
3948
|
+
} else if (!process.stdin.isTTY) {
|
|
3949
|
+
prompt = await readFromStdin();
|
|
3950
|
+
} else {
|
|
3951
|
+
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u63D0\u793A\u6587\u672C\u6216\u4F7F\u7528\u7BA1\u9053\u8F93\u5165");
|
|
3952
|
+
process.exit(1);
|
|
3953
|
+
}
|
|
3954
|
+
if (!prompt.trim()) {
|
|
3955
|
+
console.error("\u9519\u8BEF: \u63D0\u793A\u4E0D\u80FD\u4E3A\u7A7A");
|
|
3956
|
+
process.exit(1);
|
|
3957
|
+
}
|
|
3958
|
+
const abortController = new AbortController();
|
|
3959
|
+
let interrupted = false;
|
|
3960
|
+
process.on("SIGINT", () => {
|
|
3961
|
+
if (interrupted) process.exit(130);
|
|
3962
|
+
interrupted = true;
|
|
3963
|
+
abortController.abort();
|
|
3964
|
+
console.error("\n\u5DF2\u4E2D\u65AD");
|
|
3965
|
+
void saveContext(initialHistory).finally(() => process.exit(130));
|
|
3966
|
+
});
|
|
3967
|
+
const history = initialHistory;
|
|
3968
|
+
const output = { buffer: "" };
|
|
3969
|
+
try {
|
|
3970
|
+
for await (const event of runQuery(prompt, {
|
|
3971
|
+
provider,
|
|
3972
|
+
history,
|
|
3973
|
+
signal: abortController.signal
|
|
3974
|
+
})) {
|
|
3975
|
+
const result = handleEvent2(event, output, options.verbose);
|
|
3976
|
+
if (result.exitCode !== void 0) {
|
|
3977
|
+
await saveContext(history);
|
|
3978
|
+
process.exit(result.exitCode);
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
} catch (e) {
|
|
3982
|
+
if (e.name === "AbortError") {
|
|
3983
|
+
await saveContext(history);
|
|
3984
|
+
process.exit(130);
|
|
3985
|
+
}
|
|
3986
|
+
console.error(`
|
|
3987
|
+
\u672A\u6355\u83B7\u5F02\u5E38: ${e.message}`);
|
|
3988
|
+
await saveContext(history);
|
|
3989
|
+
process.exit(1);
|
|
3990
|
+
}
|
|
3991
|
+
await saveContext(history);
|
|
3992
|
+
}
|
|
3993
|
+
function handleEvent2(event, output, verbose) {
|
|
3994
|
+
switch (event.type) {
|
|
3995
|
+
case "text":
|
|
3996
|
+
if (verbose) process.stdout.write(event.delta);
|
|
3997
|
+
output.buffer += event.delta;
|
|
3998
|
+
return {};
|
|
3999
|
+
case "assistant_message":
|
|
4000
|
+
return {};
|
|
4001
|
+
case "tool_start":
|
|
4002
|
+
if (verbose) {
|
|
4003
|
+
process.stderr.write(`
|
|
4004
|
+
\u{1F527} \u5DE5\u5177: ${event.toolName}(${event.argsPreview})
|
|
4005
|
+
`);
|
|
4006
|
+
}
|
|
4007
|
+
return {};
|
|
4008
|
+
case "tool_end":
|
|
4009
|
+
if (verbose) {
|
|
4010
|
+
process.stderr.write(
|
|
4011
|
+
`${event.ok ? "\u2705" : "\u274C"} ${event.toolName}: ${truncateForDisplay(event.content)}
|
|
4012
|
+
`
|
|
4013
|
+
);
|
|
4014
|
+
}
|
|
4015
|
+
return {};
|
|
4016
|
+
case "compact_start":
|
|
4017
|
+
if (verbose) process.stderr.write("\n\u{1F4DD} \u6B63\u5728\u538B\u7F29\u4E0A\u4E0B\u6587...\n");
|
|
4018
|
+
return {};
|
|
4019
|
+
case "compact_done":
|
|
4020
|
+
if (verbose) {
|
|
4021
|
+
process.stderr.write(
|
|
4022
|
+
`\u{1F4DD} \u538B\u7F29\u5B8C\u6210: ${event.before} \u2192 ${event.after} tokens
|
|
4023
|
+
`
|
|
4024
|
+
);
|
|
4025
|
+
}
|
|
4026
|
+
return {};
|
|
4027
|
+
case "turn_done":
|
|
4028
|
+
if (!verbose) console.log(output.buffer);
|
|
4029
|
+
return {};
|
|
4030
|
+
case "error":
|
|
4031
|
+
console.error(`
|
|
4032
|
+
\u9519\u8BEF: ${event.error}`);
|
|
4033
|
+
return { exitCode: 1 };
|
|
4034
|
+
default:
|
|
4035
|
+
return {};
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
function readFromStdin() {
|
|
4039
|
+
return new Promise((resolve7) => {
|
|
4040
|
+
let data = "";
|
|
4041
|
+
let settled = false;
|
|
4042
|
+
const timer = setTimeout(() => {
|
|
4043
|
+
if (!settled) {
|
|
4044
|
+
settled = true;
|
|
4045
|
+
resolve7("");
|
|
4046
|
+
}
|
|
4047
|
+
}, STDIN_TIMEOUT_MS);
|
|
4048
|
+
process.stdin.setEncoding("utf8");
|
|
4049
|
+
function onData(chunk) {
|
|
4050
|
+
data += chunk;
|
|
4051
|
+
}
|
|
4052
|
+
function onEnd() {
|
|
4053
|
+
if (!settled) {
|
|
4054
|
+
clearTimeout(timer);
|
|
4055
|
+
settled = true;
|
|
4056
|
+
resolve7(data.trim());
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
process.stdin.on("data", onData);
|
|
4060
|
+
process.stdin.on("end", onEnd);
|
|
4061
|
+
});
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
// src/main.tsx
|
|
4065
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
4066
|
+
var require2 = createRequire(import.meta.url);
|
|
4067
|
+
var pkg = require2("../package.json");
|
|
4068
|
+
async function main() {
|
|
4069
|
+
const args = process.argv.slice(2);
|
|
4070
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
4071
|
+
printHelp();
|
|
4072
|
+
return;
|
|
4073
|
+
}
|
|
4074
|
+
if (args.includes("--version") || args.includes("-V")) {
|
|
4075
|
+
console.log(`minimal-agent v${pkg.version}`);
|
|
4076
|
+
return;
|
|
4077
|
+
}
|
|
4078
|
+
const isPrintMode = args.includes("-p") || args.includes("--print");
|
|
4079
|
+
if (isPrintMode) {
|
|
4080
|
+
let provider;
|
|
4081
|
+
try {
|
|
4082
|
+
provider = await loadProvider();
|
|
4083
|
+
} catch (e) {
|
|
4084
|
+
process.stderr.write(
|
|
4085
|
+
`
|
|
4086
|
+
${e.message}
|
|
4087
|
+
|
|
4088
|
+
\u63D0\u793A\uFF1A-p \u6A21\u5F0F\u4E0D\u652F\u6301\u4EA4\u4E92\u914D\u7F6E\u3002\u8BF7\u5148\u76F4\u63A5\u8FD0\u884C \`minimal-agent\` \u5B8C\u6210\u9996\u6B21\u914D\u7F6E\u5411\u5BFC\uFF0C\u6216\u5728 .env \u4E2D\u624B\u52A8\u586B\u597D BASE_URL / API_KEY / MODEL\u3002
|
|
4089
|
+
|
|
4090
|
+
`
|
|
4091
|
+
);
|
|
4092
|
+
process.exit(1);
|
|
4093
|
+
}
|
|
4094
|
+
const initialHistory = await buildInitialHistory2();
|
|
4095
|
+
const verbose = args.includes("--verbose") || args.includes("-v");
|
|
4096
|
+
await runPrintMode(provider, args, initialHistory, { verbose });
|
|
4097
|
+
return;
|
|
4098
|
+
}
|
|
4099
|
+
const initialProvider = await loadProviderLayered();
|
|
4100
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx8(Root, { initialProvider }));
|
|
4101
|
+
await waitUntilExit();
|
|
4102
|
+
}
|
|
4103
|
+
async function buildInitialHistory2() {
|
|
4104
|
+
const content = await buildFullSystemPrompt(process.cwd(), ALL_TOOLS);
|
|
4105
|
+
const fresh = { role: "system", content };
|
|
4106
|
+
const persisted = await loadContext();
|
|
4107
|
+
if (!persisted || persisted.length === 0) return [fresh];
|
|
4108
|
+
const rest = persisted[0]?.role === "system" ? persisted.slice(1) : persisted;
|
|
4109
|
+
return [fresh, ...rest];
|
|
4110
|
+
}
|
|
4111
|
+
function printHelp() {
|
|
4112
|
+
console.log(`
|
|
4113
|
+
minimal-agent - \u8F7B\u91CF\u7EA7 AI \u7F16\u7A0B\u52A9\u624B
|
|
4114
|
+
|
|
4115
|
+
\u7528\u6CD5:
|
|
4116
|
+
minimal-agent [\u9009\u9879] [\u63D0\u793A...]
|
|
4117
|
+
echo "\u63D0\u793A" | minimal-agent [\u9009\u9879]
|
|
4118
|
+
|
|
4119
|
+
\u9009\u9879:
|
|
4120
|
+
-p, --print \u975E\u4EA4\u4E92\u6A21\u5F0F\uFF0C\u76F4\u63A5\u6267\u884C\u5355\u6B21\u95EE\u7B54
|
|
4121
|
+
-v, --verbose \u663E\u793A\u8BE6\u7EC6\u8F93\u51FA\uFF08\u5DE5\u5177\u8C03\u7528\u3001\u538B\u7F29\u4FE1\u606F\uFF09
|
|
4122
|
+
-h, --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F
|
|
4123
|
+
|
|
4124
|
+
\u4F1A\u8BDD\u8BB0\u5FC6:
|
|
4125
|
+
\u81EA\u52A8\u52A0\u8F7D / \u4FDD\u5B58 ~/.minimal-agent/last-context.json
|
|
4126
|
+
TUI \u4E0E -p \u5171\u4EAB\u540C\u4E00\u4EFD\u4E0A\u4E0B\u6587\uFF1BTUI \u4E2D\u8F93\u5165 /new \u6E05\u7A7A
|
|
4127
|
+
|
|
4128
|
+
\u793A\u4F8B:
|
|
4129
|
+
minimal-agent -p "\u5E2E\u6211\u5199\u4E00\u4E2A hello world"
|
|
4130
|
+
echo "\u89E3\u91CA\u4EE3\u7801" | minimal-agent -p
|
|
4131
|
+
minimal-agent -p --verbose "\u8FD0\u884C\u6D4B\u8BD5\u5E76\u62A5\u544A\u7ED3\u679C"
|
|
4132
|
+
`);
|
|
4133
|
+
}
|
|
4134
|
+
main().catch((e) => {
|
|
4135
|
+
process.stderr.write(`
|
|
4136
|
+
\u672A\u6355\u83B7\u5F02\u5E38\uFF1A${e.message}
|
|
4137
|
+
${e.stack ?? ""}
|
|
4138
|
+
`);
|
|
4139
|
+
process.exit(1);
|
|
4140
|
+
});
|