diff --git a/.clang-format b/.clang-format deleted file mode 100644 index bb00198a..00000000 --- a/.clang-format +++ /dev/null @@ -1,53 +0,0 @@ ---- -# Google C++ Style Guide -# https://google.github.io/styleguide/cppguide.html -BasedOnStyle: Google -IndentWidth: 2 -ColumnLimit: 80 ---- -Language: Cpp -# Force pointers to the type for C++. -DerivePointerAlignment: false -PointerAlignment: Left -# Other adjustments -AccessModifierOffset: -1 -AllowShortFunctionsOnASingleLine: All -AllowShortIfStatementsOnASingleLine: false -AllowShortLoopsOnASingleLine: false -AlwaysBreakTemplateDeclarations: true -BinPackParameters: false -BreakBeforeBraces: Attach -BreakConstructorInitializers: BeforeColon -ConstructorInitializerAllOnOneLineOrOnePerLine: true -Cpp11BracedListStyle: true -IncludeBlocks: Regroup -IncludeCategories: - # Standard library headers - - Regex: '^<[^/]+>$' - Priority: 1 - # Other library headers - - Regex: '^<.+>$' - Priority: 2 - # Project headers with quotes - - Regex: '^"mcp/.+"$' - Priority: 3 - # Other project headers - - Regex: '^".+"$' - Priority: 4 -IndentCaseLabels: true -KeepEmptyLinesAtTheStartOfBlocks: false -NamespaceIndentation: None -SortIncludes: true -SpaceAfterCStyleCast: false -SpaceAfterTemplateKeyword: true -SpaceBeforeAssignmentOperators: true -SpaceBeforeParens: ControlStatements -SpaceInEmptyParentheses: false -SpacesInAngles: false -SpacesInCStyleCastParentheses: false -SpacesInParentheses: false -SpacesInSquareBrackets: false -Standard: c++14 -UseTab: Never -# Remove trailing whitespace -InsertTrailingCommas: None \ No newline at end of file diff --git a/.github/workflows/pr-format-check.yml b/.github/workflows/pr-format-check.yml deleted file mode 100644 index 769b322b..00000000 --- a/.github/workflows/pr-format-check.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: PR Format Check - -on: - pull_request: - types: [opened, synchronize, reopened] - -jobs: - clang-format: - name: Clang Format - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - - steps: - - name: Checkout PR - uses: actions/checkout@v4 - with: - fetch-depth: 0 - submodules: recursive - - - name: Install clang-format - run: | - sudo apt-get update - sudo apt-get install -y clang-format-14 - - - name: Check changed files - id: changed-files - run: | - # Get list of changed C/C++ files (excluding submodules) - git diff --name-only origin/${{ github.base_ref }}...HEAD | \ - grep -E '\.(h|hpp|c|cc|cpp)$' | \ - grep -v '^third_party/' > changed_files.txt || true - - if [ -s changed_files.txt ]; then - echo "has_changes=true" >> $GITHUB_OUTPUT - echo "Changed C/C++ files:" - cat changed_files.txt - else - echo "has_changes=false" >> $GITHUB_OUTPUT - echo "No C/C++ files changed" - fi - - - name: Check formatting of changed files - if: steps.changed-files.outputs.has_changes == 'true' - run: | - exit_code=0 - while IFS= read -r file; do - if [ -f "$file" ]; then - echo "Checking $file..." - clang-format-14 --style=file --dry-run --Werror "$file" || { - echo "::error file=$file::File is not properly formatted" - exit_code=1 - } - fi - done < changed_files.txt - - if [ $exit_code -ne 0 ]; then - echo "" - echo "::error::Some files are not properly formatted." - echo "To fix, run: make format" - exit 1 - fi - - - name: Post PR comment on failure - if: failure() && steps.changed-files.outputs.has_changes == 'true' - uses: actions/github-script@v7 - with: - script: | - const comment = `## Code Formatting Check Failed - - Some files in this PR are not properly formatted according to the project's clang-format rules. - - **To fix this issue:** - \`\`\`bash - make format - \`\`\` - - Then commit and push the changes.`; - - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: comment - }); diff --git a/.gitignore b/.gitignore index 457c7687..036f8e23 100644 --- a/.gitignore +++ b/.gitignore @@ -1,109 +1,39 @@ -# Build directories -build/ -build-*/ -build_*/ -cmake-build-*/ -out/ -bin/ -lib/ -# Exception: Allow Ruby SDK lib directory -!sdk/ruby/lib/ - -# CMake generated files -CMakeCache.txt -CMakeFiles/ -cmake_install.cmake -CTestTestfile.cmake -Testing/ -_deps/ -# Note: We have a hand-written Makefile at root, so only ignore generated ones in subdirs -*/Makefile - -# Compiled object files -*.o -*.obj -*.lo -*.slo - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app -test_variant -test_variant_advanced -test_variant_extensive -test_optional -test_optional_advanced -test_optional_extensive -test_type_helpers -test_mcp_types -test_mcp_types_extended -test_mcp_type_helpers -test_compat -test_buffer -test_json -test_event_loop -test_io_socket_handle -test_address -test_socket -test_socket_interface -test_socket_option - -# IDE specific files -.vscode/ +# IDE .idea/ +.vscode/ *.swp *.swo *~ -.DS_Store -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb +# Build artifacts +build/ +dist/ +lib/ +*.node +*.dylib +*.so +*.dll -# Dependency directories +# Node.js node_modules/ -vendor/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json +yarn.lock -# Coverage files -*.gcov -*.gcda -*.gcno +# TypeScript +*.tsbuildinfo + +# Testing coverage/ -*.info +.nyc_output/ -# Documentation -docs/html/ -docs/latex/ -doxygen/ +# OS +.DS_Store +Thumbs.db -# Temporary files -*.tmp -*.temp +# Misc *.log - -# Python cache (if using Python scripts) -__pycache__/ -*.py[cod] -*$py.class - -# OS generated files -Thumbs.db -Desktop.ini \ No newline at end of file +.env +.env.local diff --git a/.gitmodules b/.gitmodules index f7c1a54e..23ddfa4d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ -[submodule "third_party/gopher-mcp"] - path = third_party/gopher-mcp - url = https://github.com/GopherSecurity/gopher-mcp.git - branch = main +[submodule "third_party/gopher-orch"] + path = third_party/gopher-orch + url = https://github.com/GopherSecurity/gopher-orch.git + branch = dev_agent diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 80c90036..00000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,253 +0,0 @@ -cmake_minimum_required(VERSION 3.10) -project(gopher-orch VERSION 0.1.0 LANGUAGES C CXX) - -# Prevent in-source builds -if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) - message(FATAL_ERROR "In-source builds are not allowed. Please create a build directory and run cmake from there.") -endif() - -# Set C++ standard -set(CMAKE_CXX_STANDARD 14) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -message(STATUS "Using C++14") - -# Default to Debug build -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE) -endif() - -# Build options -option(BUILD_SHARED_LIBS "Build shared libraries" ON) -option(BUILD_STATIC_LIBS "Build static libraries" ON) -option(BUILD_TESTS "Build tests" ON) -option(BUILD_EXAMPLES "Build examples" ON) -option(ORCH_STRICT_WARNINGS "Enable strict compiler warnings" OFF) -option(USE_SUBMODULE_GOPHER_MCP "Use gopher-mcp as submodule (vs find_package)" ON) -option(BUILD_WITHOUT_GOPHER_MCP "Build without gopher-mcp dependency (for testing)" OFF) - -# Set output directories -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - -# Compiler flags -if(CMAKE_BUILD_TYPE STREQUAL "Debug") - add_compile_options(-g -O0) - add_compile_definitions(_DEBUG) -elseif(CMAKE_BUILD_TYPE STREQUAL "Release") - add_compile_options(-O3) - add_compile_definitions(NDEBUG) -endif() - -# Platform-specific settings -if(APPLE) - set(CMAKE_MACOSX_RPATH ON) - set(CMAKE_INSTALL_RPATH "@loader_path/../lib") -elseif(UNIX) - set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib") -endif() - -# Warning flags -if(ORCH_STRICT_WARNINGS) - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - add_compile_options( - -Wall -Wextra -Wpedantic - -Wno-unused-parameter - -Wno-unused-variable - -Wno-unused-function - -Werror - ) - elseif(MSVC) - add_compile_options(/W4 /WX) - endif() -else() - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - add_compile_options( - -Wall - -Wno-unused-parameter - -Wno-unused-variable - -Wno-unused-function - ) - endif() -endif() - -# Handle gopher-mcp dependency -if(BUILD_WITHOUT_GOPHER_MCP) - # Build without gopher-mcp for testing - message(STATUS "Building without gopher-mcp dependency") - set(GOPHER_MCP_LIBRARIES "") - set(GOPHER_MCP_INCLUDE_DIR "") -elseif(USE_SUBMODULE_GOPHER_MCP) - # Use gopher-mcp as submodule - if(NOT EXISTS "${CMAKE_SOURCE_DIR}/third_party/gopher-mcp/.git") - message(STATUS "gopher-mcp submodule not found. Initializing...") - execute_process( - COMMAND git submodule add https://github.com/GopherSecurity/gopher-mcp.git third_party/gopher-mcp - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - RESULT_VARIABLE GIT_SUBMOD_RESULT - ) - if(NOT GIT_SUBMOD_RESULT EQUAL "0") - # Submodule might already exist, try update - execute_process( - COMMAND git submodule update --init --recursive third_party/gopher-mcp - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - RESULT_VARIABLE GIT_SUBMOD_UPDATE_RESULT - ) - if(NOT GIT_SUBMOD_UPDATE_RESULT EQUAL "0") - message(FATAL_ERROR "Failed to initialize gopher-mcp submodule") - endif() - endif() - else() - message(STATUS "Updating gopher-mcp submodule...") - execute_process( - COMMAND git submodule update --init --recursive third_party/gopher-mcp - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - ) - endif() - - # Set include directories for gopher-mcp BEFORE adding subdirectory - set(GOPHER_MCP_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/third_party/gopher-mcp/include) - - # Temporarily add gopher-mcp include directories before processing subdirectory - # This ensures gopher-mcp can find its own headers when building as submodule - include_directories(${GOPHER_MCP_INCLUDE_DIR}) - - # Disable gopher-mcp tests and examples to speed up build - set(BUILD_TESTS_SAVED ${BUILD_TESTS}) - set(BUILD_EXAMPLES_SAVED ${BUILD_EXAMPLES}) - set(BUILD_TESTS OFF CACHE BOOL "" FORCE) - set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) - set(BUILD_BINDINGS_EXAMPLES OFF CACHE BOOL "" FORCE) - - # Disable fmt installation if gopher-mcp uses it - set(FMT_INSTALL OFF CACHE BOOL "Disable fmt installation" FORCE) - - # Add gopher-mcp subdirectory - add_subdirectory(third_party/gopher-mcp EXCLUDE_FROM_ALL) - - # Restore our settings - set(BUILD_TESTS ${BUILD_TESTS_SAVED} CACHE BOOL "" FORCE) - set(BUILD_EXAMPLES ${BUILD_EXAMPLES_SAVED} CACHE BOOL "" FORCE) - - # Make gopher-mcp libraries available - # Use static libraries for tests to avoid duplicate initialization - if(BUILD_TESTS AND TARGET gopher-mcp-static) - set(GOPHER_MCP_LIBRARIES gopher-mcp-static gopher-mcp-event-static) - else() - set(GOPHER_MCP_LIBRARIES gopher-mcp gopher-mcp-event) - endif() - - message(STATUS "Using gopher-mcp from submodule") -else() - # Use system-installed gopher-mcp - find_package(gopher-mcp REQUIRED) - message(STATUS "Using system gopher-mcp: ${gopher-mcp_DIR}") -endif() - -# Include directories -message(STATUS "GOPHER_MCP_INCLUDE_DIR: ${GOPHER_MCP_INCLUDE_DIR}") -include_directories( - ${CMAKE_SOURCE_DIR}/include - ${GOPHER_MCP_INCLUDE_DIR} -) - -# Export compile commands for tools like clangd -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -# Find required packages -find_package(Threads REQUIRED) - -# Testing setup -if(BUILD_TESTS) - enable_testing() - include(CTest) - - # Fetch Google Test - include(FetchContent) - - # Prevent Google Test from being installed - set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) - set(INSTALL_GMOCK OFF CACHE BOOL "Disable installation of googlemock" FORCE) - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - - FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG v1.14.0 - CMAKE_ARGS -DINSTALL_GTEST=OFF -DINSTALL_GMOCK=OFF - ) - - FetchContent_MakeAvailable(googletest) - - # Include Google Test and Google Mock - include(GoogleTest) -endif() - -# Add subdirectories -add_subdirectory(src) - -if(BUILD_TESTS) - add_subdirectory(tests) -endif() - -if(BUILD_EXAMPLES) - add_subdirectory(examples) -endif() - -# Installation rules -install(DIRECTORY include/orch - DESTINATION include - COMPONENT development - FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp" -) - -# Package configuration -include(CMakePackageConfigHelpers) - -configure_package_config_file( - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/gopher-orch-config.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/gopher-orch-config.cmake" - INSTALL_DESTINATION lib/cmake/gopher-orch -) - -write_basic_package_version_file( - "${CMAKE_CURRENT_BINARY_DIR}/gopher-orch-config-version.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion -) - -install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/gopher-orch-config.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/gopher-orch-config-version.cmake" - DESTINATION lib/cmake/gopher-orch - COMPONENT development -) - -# Add uninstall target -if(NOT TARGET uninstall) - configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" - IMMEDIATE @ONLY - ) - - add_custom_target(uninstall - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake - ) -endif() - -# Print configuration summary -message(STATUS "") -message(STATUS "=== gopher-orch Configuration Summary ===") -message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") -message(STATUS "C++ Standard: ${CMAKE_CXX_STANDARD}") -message(STATUS "Build shared libs: ${BUILD_SHARED_LIBS}") -message(STATUS "Build static libs: ${BUILD_STATIC_LIBS}") -message(STATUS "Build tests: ${BUILD_TESTS}") -message(STATUS "Build examples: ${BUILD_EXAMPLES}") -message(STATUS "Strict warnings: ${ORCH_STRICT_WARNINGS}") -message(STATUS "Use submodule gopher-mcp: ${USE_SUBMODULE_GOPHER_MCP}") -message(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") -message(STATUS "==========================================") -message(STATUS "") diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 261eeb9e..00000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/Makefile b/Makefile deleted file mode 100644 index 5639a8ff..00000000 --- a/Makefile +++ /dev/null @@ -1,374 +0,0 @@ -# gopher-orch Makefile -# Consolidates all CMake commands for easy building - -# Build configuration -BUILD_DIR ?= build -BUILD_TYPE ?= Debug -GENERATOR ?= "Unix Makefiles" -PARALLEL_JOBS ?= $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) - -# Library build options (both by default) -BUILD_STATIC ?= ON -BUILD_SHARED ?= ON - -# CMake options -CMAKE_OPTIONS ?= -VERBOSE ?= 0 - -# Colors for output -RED := \033[0;31m -GREEN := \033[0;32m -YELLOW := \033[1;33m -BLUE := \033[0;34m -NC := \033[0m # No Color - -# Default target -.PHONY: all -all: build test - @echo "$(GREEN)Build and test completed successfully$(NC)" - -# Configure with CMake -.PHONY: configure -configure: - @echo "$(BLUE)Configuring with CMake...$(NC)" - @echo " Build type: $(BUILD_TYPE)" - @echo " Static library: $(BUILD_STATIC)" - @echo " Shared library: $(BUILD_SHARED)" - @mkdir -p $(BUILD_DIR) - @cd $(BUILD_DIR) && cmake .. -G $(GENERATOR) \ - -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) \ - -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - -DBUILD_STATIC_LIBS=$(BUILD_STATIC) \ - -DBUILD_SHARED_LIBS=$(BUILD_SHARED) \ - $(CMAKE_OPTIONS) - @echo "$(GREEN)Configuration complete$(NC)" - -# Build the project -.PHONY: build -build: configure - @echo "$(BLUE)Building gopher-orch libraries...$(NC)" - @cmake --build $(BUILD_DIR) -- -j$(PARALLEL_JOBS) - @echo "$(GREEN)Build complete$(NC)" - @$(MAKE) --no-print-directory lib-info-summary - -# Build in release mode -.PHONY: release -release: - @echo "$(BLUE)Building in Release mode...$(NC)" - @$(MAKE) BUILD_TYPE=Release build test - @echo "$(GREEN)Release build complete$(NC)" - -# Build in debug mode (explicit) -.PHONY: debug -debug: - @echo "$(BLUE)Building in Debug mode...$(NC)" - @$(MAKE) BUILD_TYPE=Debug build - @echo "$(GREEN)Debug build complete$(NC)" - -# Run tests -.PHONY: test -test: build - @echo "$(BLUE)Running tests...$(NC)" - @cd $(BUILD_DIR) && ctest --output-on-failure - @echo "$(GREEN)All tests passed$(NC)" - -# Run tests with verbose output -.PHONY: test-verbose -test-verbose: build - @echo "$(BLUE)Running tests (verbose)...$(NC)" - @cd $(BUILD_DIR) && ctest -V - @echo "$(GREEN)All tests passed$(NC)" - -# Run tests in parallel -.PHONY: test-parallel -test-parallel: build - @echo "$(BLUE)Running tests in parallel...$(NC)" - @cd $(BUILD_DIR) && ctest -j$(PARALLEL_JOBS) --output-on-failure - @echo "$(GREEN)All tests passed$(NC)" - -# Run specific test -.PHONY: test-one -test-one: build - @if [ -z "$(TEST)" ]; then \ - echo "$(RED)Error: TEST variable not set. Usage: make test-one TEST=test_name$(NC)"; \ - exit 1; \ - fi - @echo "$(BLUE)Running test: $(TEST)...$(NC)" - @cd $(BUILD_DIR) && ctest -R $(TEST) -V - @echo "$(GREEN)Test complete$(NC)" - -# Build only the libraries (respects current configuration) -.PHONY: libs -libs: configure - @echo "$(BLUE)Building libraries...$(NC)" - @if [ -f $(BUILD_DIR)/CMakeCache.txt ]; then \ - if grep -q "BUILD_STATIC_LIBS:BOOL=ON" $(BUILD_DIR)/CMakeCache.txt 2>/dev/null; then \ - cmake --build $(BUILD_DIR) --target gopher-orch-static -- -j$(PARALLEL_JOBS); \ - fi; \ - if grep -q "BUILD_SHARED_LIBS:BOOL=ON" $(BUILD_DIR)/CMakeCache.txt 2>/dev/null; then \ - cmake --build $(BUILD_DIR) --target gopher-orch-shared -- -j$(PARALLEL_JOBS); \ - fi; \ - else \ - cmake --build $(BUILD_DIR) --target gopher-orch-static -- -j$(PARALLEL_JOBS); \ - fi - @echo "$(GREEN)Libraries built$(NC)" - -# Build only the examples -.PHONY: examples -examples: libs - @echo "$(BLUE)Building examples...$(NC)" - @cmake --build $(BUILD_DIR) --target hello_world_example -- -j$(PARALLEL_JOBS) - @echo "$(GREEN)Examples built$(NC)" - -# Run the hello world example -.PHONY: run-hello -run-hello: examples - @echo "$(BLUE)Running hello_world_example...$(NC)" - @$(BUILD_DIR)/bin/hello_world_example - @echo "$(GREEN)Example completed$(NC)" - -# Clean build directory -.PHONY: clean -clean: - @echo "$(YELLOW)Cleaning build directory...$(NC)" - @rm -rf $(BUILD_DIR) - @echo "$(GREEN)Clean complete$(NC)" - -# Deep clean (including submodules) -.PHONY: distclean -distclean: clean - @echo "$(YELLOW)Deep cleaning...$(NC)" - @git submodule deinit -f . - @rm -rf third_party/gopher-mcp - @rm -rf .git/modules/third_party - @echo "$(GREEN)Deep clean complete$(NC)" - -# Format all source files -.PHONY: format -format: - @echo "$(BLUE)Formatting all source files with clang-format...$(NC)" - @find . -path "./$(BUILD_DIR)*" -prune -o -path "./third_party" -prune -o \ - \( -name "*.h" -o -name "*.hpp" -o -name "*.cpp" -o -name "*.cc" -o -name "*.c" \) -print | \ - xargs clang-format -i - @echo "$(GREEN)Formatting complete$(NC)" - -# Check formatting without modifying files -.PHONY: check-format -check-format: - @echo "$(BLUE)Checking source file formatting...$(NC)" - @find . -path "./$(BUILD_DIR)*" -prune -o -path "./third_party" -prune -o \ - \( -name "*.h" -o -name "*.hpp" -o -name "*.cpp" -o -name "*.cc" -o -name "*.c" \) -print | \ - xargs clang-format --dry-run --Werror - @if [ $$? -eq 0 ]; then \ - echo "$(GREEN)All files are properly formatted$(NC)"; \ - else \ - echo "$(RED)Format check failed - run 'make format' to fix$(NC)"; \ - exit 1; \ - fi - -# Alias for consistency with gopher-mcp -.PHONY: format-check -format-check: check-format - -# Install the library -.PHONY: install -install: build - @echo "$(BLUE)Installing gopher-orch...$(NC)" - @cmake --build $(BUILD_DIR) --target install - @echo "$(GREEN)Installation complete$(NC)" - -# Uninstall the library -.PHONY: uninstall -uninstall: - @echo "$(YELLOW)Uninstalling gopher-orch...$(NC)" - @if [ ! -f $(BUILD_DIR)/install_manifest.txt ]; then \ - echo "$(RED)Error: No installation found. Run 'make install' first.$(NC)"; \ - exit 1; \ - fi - @cmake --build $(BUILD_DIR) --target uninstall - @echo "$(GREEN)Uninstall complete$(NC)" - -# Generate documentation (requires doxygen) -.PHONY: docs -docs: - @echo "$(BLUE)Generating documentation...$(NC)" - @doxygen Doxyfile 2>/dev/null || echo "$(YELLOW)Warning: Doxygen not found or configured$(NC)" - @echo "$(GREEN)Documentation generated$(NC)" - -# Update submodules -.PHONY: update-submodules -update-submodules: - @echo "$(BLUE)Updating submodules...$(NC)" - @git submodule update --init --recursive - @echo "$(GREEN)Submodules updated$(NC)" - -# Configure to use system gopher-mcp instead of submodule -.PHONY: use-system-gopher-mcp -use-system-gopher-mcp: - @echo "$(BLUE)Configuring to use system gopher-mcp...$(NC)" - @$(MAKE) CMAKE_OPTIONS="-DUSE_SUBMODULE_GOPHER_MCP=OFF" configure - @echo "$(GREEN)Configured to use system gopher-mcp$(NC)" - -# Configure to use submodule gopher-mcp -.PHONY: use-submodule-gopher-mcp -use-submodule-gopher-mcp: - @echo "$(BLUE)Configuring to use submodule gopher-mcp...$(NC)" - @$(MAKE) CMAKE_OPTIONS="-DUSE_SUBMODULE_GOPHER_MCP=ON" configure - @echo "$(GREEN)Configured to use submodule gopher-mcp$(NC)" - -# Build shared library only -.PHONY: shared -shared: - @$(MAKE) BUILD_STATIC=OFF BUILD_SHARED=ON clean build - -# Build static library only -.PHONY: static -static: - @$(MAKE) BUILD_STATIC=ON BUILD_SHARED=OFF clean build - -# Build both static and shared libraries (default behavior) -.PHONY: both -both: - @$(MAKE) BUILD_STATIC=ON BUILD_SHARED=ON clean build - -# Build standalone (without gopher-mcp dependency) -.PHONY: standalone -standalone: - @echo "$(BLUE)Building standalone (without gopher-mcp)...$(NC)" - @$(MAKE) CMAKE_OPTIONS="-DBUILD_WITHOUT_GOPHER_MCP=ON" build - @echo "$(GREEN)Standalone build complete$(NC)" - -# Show brief library summary (used after build) -.PHONY: lib-info-summary -lib-info-summary: - @if [ -f $(BUILD_DIR)/lib/libgopher-orch.a ]; then \ - echo " $(GREEN)Static library: $(BUILD_DIR)/lib/libgopher-orch.a ($$(du -h $(BUILD_DIR)/lib/libgopher-orch.a 2>/dev/null | cut -f1))$(NC)"; \ - fi - @if [ -f $(BUILD_DIR)/lib/libgopher-orch.so ]; then \ - echo " $(GREEN)Shared library: $(BUILD_DIR)/lib/libgopher-orch.so ($$(du -h $(BUILD_DIR)/lib/libgopher-orch.so 2>/dev/null | cut -f1))$(NC)"; \ - elif [ -f $(BUILD_DIR)/lib/libgopher-orch.dylib ]; then \ - echo " $(GREEN)Shared library: $(BUILD_DIR)/lib/libgopher-orch.dylib ($$(du -h $(BUILD_DIR)/lib/libgopher-orch.dylib 2>/dev/null | cut -f1))$(NC)"; \ - fi - -# Show detailed library information -.PHONY: lib-info -lib-info: - @echo "$(BLUE)Library Information:$(NC)" - @if [ -f $(BUILD_DIR)/lib/libgopher-orch.a ]; then \ - echo "$(GREEN)Static library:$(NC)"; \ - echo " Path: $(BUILD_DIR)/lib/libgopher-orch.a"; \ - echo " Size: $$(du -h $(BUILD_DIR)/lib/libgopher-orch.a | cut -f1)"; \ - if command -v ar >/dev/null 2>&1; then \ - echo " Objects: $$(ar -t $(BUILD_DIR)/lib/libgopher-orch.a 2>/dev/null | wc -l) files"; \ - fi; \ - else \ - echo "$(YELLOW)Static library not found$(NC)"; \ - fi - @echo "" - @if [ -f $(BUILD_DIR)/lib/libgopher-orch.so ] || [ -f $(BUILD_DIR)/lib/libgopher-orch.dylib ]; then \ - echo "$(GREEN)Shared library:$(NC)"; \ - LIB_PATH=$$(find $(BUILD_DIR)/lib -name "libgopher-orch.so*" -o -name "libgopher-orch.dylib" | head -1); \ - if [ -n "$$LIB_PATH" ]; then \ - echo " Path: $$LIB_PATH"; \ - echo " Size: $$(du -h $$LIB_PATH | cut -f1)"; \ - if command -v ldd >/dev/null 2>&1; then \ - echo " Dependencies:"; \ - ldd $$LIB_PATH | head -5 | sed 's/^/ /'; \ - elif command -v otool >/dev/null 2>&1; then \ - echo " Dependencies:"; \ - otool -L $$LIB_PATH | head -5 | sed 's/^/ /'; \ - fi; \ - fi; \ - else \ - echo "$(YELLOW)Shared library not found$(NC)"; \ - fi - @echo "" - @if [ -f $(BUILD_DIR)/CMakeCache.txt ]; then \ - echo "$(BLUE)Current configuration:$(NC)"; \ - grep -E "^(BUILD_SHARED_LIBS|BUILD_STATIC_LIBS):BOOL=" $(BUILD_DIR)/CMakeCache.txt | sed 's/^/ /'; \ - fi - -# Show build configuration -.PHONY: info -info: - @echo "$(BLUE)Build Configuration:$(NC)" - @echo " Build directory: $(BUILD_DIR)" - @echo " Build type: $(BUILD_TYPE)" - @echo " Generator: $(GENERATOR)" - @echo " Parallel jobs: $(PARALLEL_JOBS)" - @echo " Build static libs: $(BUILD_STATIC)" - @echo " Build shared libs: $(BUILD_SHARED)" - @echo " CMake options: $(CMAKE_OPTIONS)" - @if [ -f $(BUILD_DIR)/CMakeCache.txt ]; then \ - echo "\n$(BLUE)Current CMake cache:$(NC)"; \ - grep -E "^(CMAKE_BUILD_TYPE|BUILD_SHARED_LIBS|BUILD_STATIC_LIBS|USE_SUBMODULE_GOPHER_MCP)" $(BUILD_DIR)/CMakeCache.txt || true; \ - else \ - echo "\n$(YELLOW)No build directory found. Run 'make configure' first.$(NC)"; \ - fi - -# Help target -.PHONY: help -help: - @echo "$(BLUE)gopher-orch Build System$(NC)" - @echo "" - @echo "$(GREEN)Common targets:$(NC)" - @echo " make - Build both libraries and run tests (default)" - @echo " make build - Build both static and shared libraries" - @echo " make release - Build and test in release mode" - @echo " make test - Run tests" - @echo " make clean - Clean build directory" - @echo " make install - Install the libraries" - @echo " make uninstall - Uninstall the libraries" - @echo "" - @echo "$(GREEN)Library build targets:$(NC)" - @echo " make both - Build both library types (default)" - @echo " make static - Build static library only (with clean)" - @echo " make shared - Build shared library only (with clean)" - @echo " make libs - Build libraries (current config)" - @echo " make lib-info - Show detailed library information" - @echo "" - @echo "$(GREEN)Build modes:$(NC)" - @echo " make debug - Build in debug mode" - @echo " make release - Build in release mode" - @echo " make standalone - Build without gopher-mcp" - @echo "" - @echo "$(GREEN)Test targets:$(NC)" - @echo " make test-verbose - Run tests with verbose output" - @echo " make test-parallel - Run tests in parallel" - @echo " make test-one TEST=name - Run specific test" - @echo "" - @echo "$(GREEN)Component targets:$(NC)" - @echo " make libs - Build only libraries" - @echo " make examples - Build examples" - @echo " make run-hello - Run hello world example" - @echo "" - @echo "$(GREEN)Dependency management:$(NC)" - @echo " make update-submodules - Update git submodules" - @echo " make use-system-gopher-mcp - Use system gopher-mcp" - @echo " make use-submodule-gopher-mcp - Use submodule gopher-mcp" - @echo "" - @echo "$(GREEN)Code Quality:$(NC)" - @echo " make format - Auto-format all source files" - @echo " make check-format - Check formatting without modifying" - @echo " make format-check - Alias for check-format" - @echo "" - @echo "$(GREEN)Utilities:$(NC)" - @echo " make docs - Generate documentation" - @echo " make info - Show build configuration" - @echo " make distclean - Deep clean including submodules" - @echo "" - @echo "$(GREEN)Variables:$(NC)" - @echo " BUILD_DIR=dir - Set build directory (default: build)" - @echo " BUILD_TYPE=type - Set build type (Debug/Release, default: Debug)" - @echo " BUILD_STATIC=ON/OFF - Build static library (default: ON)" - @echo " BUILD_SHARED=ON/OFF - Build shared library (default: ON)" - @echo " CMAKE_OPTIONS=opts - Additional CMake options" - @echo " PARALLEL_JOBS=n - Number of parallel jobs" - @echo "" - @echo "$(GREEN)Examples:$(NC)" - @echo " make - Build both libraries (default)" - @echo " make BUILD_SHARED=OFF - Build static library only" - @echo " make BUILD_TYPE=Release - Build both libraries in release mode" - @echo " make static - Build only static library" - -.DEFAULT_GOAL := all diff --git a/README.md b/README.md new file mode 100644 index 00000000..14735e0a --- /dev/null +++ b/README.md @@ -0,0 +1,315 @@ +# @gopher/orch - TypeScript SDK + +TypeScript SDK for Gopher Orch - AI Agent orchestration framework with native C++ performance. + +## Features + +- 🚀 **Native Performance** - Powered by C++ core with TypeScript bindings +- 🤖 **AI Agent Framework** - Build intelligent agents with LLM integration +- 🔌 **MCP Protocol** - Model Context Protocol client and server support +- 🔧 **Tool Orchestration** - Manage and execute tools across multiple servers +- 🔄 **State Management** - Built-in state graph for complex workflows +- 💪 **Type Safety** - Full TypeScript type definitions + +## Installation + +```bash +npm install @gopher/orch +``` + +## Quick Start + +```typescript +import { GopherAgent } from '@gopher/orch'; + +// Create an agent with API key (fetches server config from remote API) +const agent = GopherAgent.create({ + provider: 'AnthropicProvider', + model: 'claude-3-haiku-20240307', + apiKey: 'your-api-key' +}); + +// Run the agent +const result = agent.run('What is the weather in Tokyo?'); +console.log(result); + +// Cleanup (optional - happens automatically on exit) +agent.dispose(); +``` + +## Architecture + +``` +TypeScript SDK (gopher-orch-js) + | + | FFI Bindings + v +Native Library (gopher-orch) + | + +-- Agent Framework + +-- LLM Providers (Anthropic, OpenAI) + +-- MCP Client/Server + +-- Tool Registry + +-- State Graph +``` + +## Building from Source + +### Prerequisites + +- Node.js >= 16 +- CMake >= 3.15 +- C++14 compatible compiler +- Git + +### Build Steps + +```bash +# Clone the repository +git clone https://github.com/GopherSecurity/gopher-orch-js.git +cd gopher-orch-js + +# Initialize submodules +git submodule update --init --recursive + +# Build native library and TypeScript +npm install +npm run build + +# Run tests +npm test +``` + +## API Documentation + +### GopherAgent + +The main class for creating and running AI agents: + +```typescript +import { GopherAgent } from '@gopher/orch'; + +// Initialize the library (called automatically on first create) +GopherAgent.init(); + +// Create with API key (fetches server config from remote API) +const agent = GopherAgent.create({ + provider: 'AnthropicProvider', + model: 'claude-3-haiku-20240307', + apiKey: 'your-api-key' +}); + +// Or create with JSON server config +const agent = GopherAgent.create({ + provider: 'AnthropicProvider', + model: 'claude-3-haiku-20240307', + serverConfig: '{"succeeded": true, "data": {...}}' +}); + +// Run a query +const result = agent.run('Your prompt here'); + +// Run with custom timeout (default: 60000ms) +const result = agent.run('Your prompt here', 30000); + +// Run with detailed result information +const detailed = agent.runDetailed('Your prompt here'); +// Returns: { response, status: 'success' | 'error' | 'timeout', iterationCount?, tokensUsed? } + +// Cleanup (optional - happens automatically on exit) +agent.dispose(); + +// Shutdown library (optional - happens automatically on exit) +GopherAgent.shutdown(); +``` + +### ServerConfig + +Utility class for working with server configurations: + +```typescript +import { ServerConfig } from '@gopher/orch'; + +// Fetch MCP server configurations from remote API +const config = ServerConfig.fetch('your-api-key'); + +// Create default configuration for local development +const defaultConfig = ServerConfig.createDefault(); +``` + +### Error Handling + +The SDK provides typed errors for different failure scenarios: + +```typescript +import { AgentError, ApiKeyError, ConnectionError, TimeoutError } from '@gopher/orch'; + +try { + const agent = GopherAgent.create({ provider, model, apiKey }); + const result = agent.run('query'); +} catch (error) { + if (error instanceof ApiKeyError) { + console.error('Invalid API key'); + } else if (error instanceof ConnectionError) { + console.error('Failed to connect to MCP servers'); + } else if (error instanceof TimeoutError) { + console.error('Query timed out'); + } else if (error instanceof AgentError) { + console.error('Agent error:', error.message); + } +} +``` + +## Examples + +### Basic Usage with API Key + +```typescript +import { GopherAgent } from '@gopher/orch'; + +async function main() { + // Create agent with API key (fetches server config from remote API) + const agent = GopherAgent.create({ + provider: 'AnthropicProvider', + model: 'claude-3-haiku-20240307', + apiKey: 'your-api-key' + }); + + const question = 'What time is it in London?'; + console.log(`Question: ${question}`); + + const answer = agent.run(question); + console.log('Answer:', answer); + + // Cleanup (optional - happens automatically on exit) + agent.dispose(); +} + +main().catch(error => { + console.error('Error:', error.message); + process.exit(1); +}); +``` + +### Using JSON Server Config + +```typescript +import { GopherAgent, ServerConfig } from '@gopher/orch'; + +// Use default local development config +const serverConfig = ServerConfig.createDefault(); + +const agent = GopherAgent.create({ + provider: 'AnthropicProvider', + model: 'claude-3-haiku-20240307', + serverConfig: serverConfig +}); + +const result = agent.run('What is 2 + 2?'); +console.log(result); + +agent.dispose(); +``` + +### With Detailed Results + +```typescript +import { GopherAgent } from '@gopher/orch'; + +const agent = GopherAgent.create({ + provider: 'AnthropicProvider', + model: 'claude-3-haiku-20240307', + apiKey: 'your-api-key' +}); + +const result = agent.runDetailed('Explain quantum computing'); + +if (result.status === 'success') { + console.log('Response:', result.response); + console.log('Iterations:', result.iterationCount); +} else if (result.status === 'timeout') { + console.log('Query timed out'); +} else { + console.log('Error:', result.response); +} + +agent.dispose(); +``` + +## Development + +### Project Structure + +``` +gopher-orch-js/ +├── src/ # TypeScript source +│ ├── agent/ # Agent implementation +│ ├── llm/ # LLM provider interfaces +│ ├── mcp/ # MCP client/server +│ ├── tools/ # Tool management +│ ├── native/ # FFI bindings +│ └── index.ts # Main entry point +├── native/ # Built native libraries +│ ├── lib/ # Shared libraries (.dylib, .so) +│ └── include/ # C++ headers +├── third_party/ # Native dependencies +│ └── gopher-orch/ # C++ implementation (submodule) +├── dist/ # Compiled TypeScript +├── build.sh # Native build script +├── package.json # NPM configuration +└── tsconfig.json # TypeScript configuration +``` + +### Build Scripts + +- `npm run build:native` - Build native C++ library +- `npm run build` - Build TypeScript (automatically builds native first) +- `npm run watch` - Watch mode for TypeScript +- `npm test` - Run tests +- `npm run lint` - Lint TypeScript code +- `npm run clean` - Clean all build artifacts + +### Running Tests + +```bash +# Run all tests +npm test + +# Run tests in watch mode +npm run test:watch + +# Run specific test file +npm test -- agent.test.ts +``` + +## Contributing + +Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details. + +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +## License + +MIT License - see [LICENSE](LICENSE) file for details. + +## Links + +- [GitHub Repository](https://github.com/GopherSecurity/gopher-orch-js) +- [Native C++ Implementation](https://github.com/GopherSecurity/gopher-orch) +- [Documentation](https://github.com/GopherSecurity/gopher-orch-js/docs) +- [Examples](https://github.com/GopherSecurity/gopher-orch-js/examples) + +## Support + +- GitHub Issues: [Report a bug](https://github.com/GopherSecurity/gopher-orch-js/issues) +- Discussions: [Ask a question](https://github.com/GopherSecurity/gopher-orch-js/discussions) + +## Acknowledgments + +- Built on top of [gopher-orch](https://github.com/GopherSecurity/gopher-orch) C++ framework +- Inspired by LangChain and LangGraph +- Uses [Model Context Protocol](https://modelcontextprotocol.io/) diff --git a/build.sh b/build.sh index 3aa8a1ca..16b86be8 100755 --- a/build.sh +++ b/build.sh @@ -1,123 +1,130 @@ -#!/bin/bash -x +#!/bin/bash -# Build script for gopher-orch with submodule support - -set -e +set -e # Exit on error # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' -BLUE='\033[0;34m' NC='\033[0m' # No Color -echo -e "${BLUE}=== gopher-orch Build Script ===${NC}" - -# Parse arguments -BUILD_TYPE="${BUILD_TYPE:-Debug}" -BUILD_DIR="${BUILD_DIR:-build}" -USE_SUBMODULE=ON -BUILD_TESTS=ON -BUILD_EXAMPLES=ON - -for arg in "$@"; do - case $arg in - --release) - BUILD_TYPE=Release - shift - ;; - --no-submodule) - USE_SUBMODULE=OFF - shift - ;; - --no-tests) - BUILD_TESTS=OFF - shift - ;; - --no-examples) - BUILD_EXAMPLES=OFF - shift - ;; - --standalone) - # Build without gopher-mcp for testing - USE_SUBMODULE=OFF - BUILD_WITHOUT_MCP=ON - shift - ;; - --clean) - echo -e "${YELLOW}Cleaning build directory...${NC}" - rm -rf "$BUILD_DIR" - shift - ;; - --help) - echo "Usage: $0 [options]" - echo "Options:" - echo " --release Build in Release mode (default: Debug)" - echo " --no-submodule Use system gopher-mcp instead of submodule" - echo " --no-tests Don't build tests" - echo " --no-examples Don't build examples" - echo " --standalone Build without gopher-mcp dependency" - echo " --clean Clean build directory before building" - echo " --help Show this help message" - exit 0 - ;; - esac -done - -# Initialize submodule if needed -if [ "$USE_SUBMODULE" = "ON" ] && [ "${BUILD_WITHOUT_MCP:-OFF}" = "OFF" ]; then - if [ ! -f "third_party/gopher-mcp/CMakeLists.txt" ]; then - echo -e "${YELLOW}Initializing gopher-mcp submodule...${NC}" - git submodule update --init --recursive third_party/gopher-mcp - else - echo -e "${GREEN}gopher-mcp submodule already initialized${NC}" - fi +echo -e "${GREEN}======================================${NC}" +echo -e "${GREEN}Building gopher-orch TypeScript SDK${NC}" +echo -e "${GREEN}======================================${NC}" +echo "" + +# Get the script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +NATIVE_DIR="${SCRIPT_DIR}/third_party/gopher-orch" +BUILD_DIR="${NATIVE_DIR}/build" + +# Step 1: Update submodules recursively +echo -e "${YELLOW}Step 1: Updating submodules recursively...${NC}" +git submodule update --init --recursive +echo -e "${GREEN}✓ Submodules updated${NC}" +echo "" + +# Step 2: Check if gopher-orch exists +if [ ! -d "${NATIVE_DIR}" ]; then + echo -e "${RED}Error: gopher-orch submodule not found at ${NATIVE_DIR}${NC}" + echo -e "${RED}Run: git submodule update --init --recursive${NC}" + exit 1 fi +# Step 3: Build gopher-orch native library +echo -e "${YELLOW}Step 2: Building gopher-orch native library...${NC}" +cd "${NATIVE_DIR}" + # Create build directory -mkdir -p "$BUILD_DIR" - -# Configure -echo -e "${BLUE}Configuring with CMake...${NC}" -echo " Build type: $BUILD_TYPE" -echo " Use submodule: $USE_SUBMODULE" -echo " Build tests: $BUILD_TESTS" -echo " Build examples: $BUILD_EXAMPLES" - -CMAKE_ARGS=( - -DCMAKE_BUILD_TYPE="$BUILD_TYPE" - -DUSE_SUBMODULE_GOPHER_MCP="$USE_SUBMODULE" - -DBUILD_TESTS="$BUILD_TESTS" - -DBUILD_EXAMPLES="$BUILD_EXAMPLES" -) - -if [ "${BUILD_WITHOUT_MCP:-OFF}" = "ON" ]; then - CMAKE_ARGS+=(-DBUILD_WITHOUT_GOPHER_MCP=ON) - echo -e "${YELLOW}Building without gopher-mcp dependency (standalone mode)${NC}" +if [ ! -d "${BUILD_DIR}" ]; then + mkdir -p "${BUILD_DIR}" fi -cmake -B "$BUILD_DIR" -S . "${CMAKE_ARGS[@]}" +cd "${BUILD_DIR}" + +# Configure with CMake +echo -e "${YELLOW} Configuring CMake...${NC}" +cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="${SCRIPT_DIR}/native" \ + -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON # Build -echo -e "${BLUE}Building...${NC}" -cmake --build "$BUILD_DIR" -j$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) +echo -e "${YELLOW} Compiling...${NC}" +cmake --build . --config Release -j$(sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4) -echo -e "${GREEN}Build completed successfully!${NC}" +# Install to native directory +echo -e "${YELLOW} Installing...${NC}" +cmake --install . + +# Copy dependency libraries (gopher-mcp, fmt) that gopher-orch depends on +echo -e "${YELLOW} Copying dependency libraries...${NC}" +NATIVE_LIB_DIR="${SCRIPT_DIR}/native/lib" +mkdir -p "${NATIVE_LIB_DIR}" + +# Copy gopher-mcp libraries +cp -P "${BUILD_DIR}/lib/libgopher-mcp"*.dylib "${NATIVE_LIB_DIR}/" 2>/dev/null || \ +cp -P "${BUILD_DIR}/lib/libgopher-mcp"*.so "${NATIVE_LIB_DIR}/" 2>/dev/null || true + +# Copy fmt library +cp -P "${BUILD_DIR}/lib/libfmt"*.dylib "${NATIVE_LIB_DIR}/" 2>/dev/null || \ +cp -P "${BUILD_DIR}/lib/libfmt"*.so "${NATIVE_LIB_DIR}/" 2>/dev/null || true + +echo -e "${GREEN}✓ Native library built successfully${NC}" +echo "" + +# Step 4: Verify build artifacts +echo -e "${YELLOW}Step 3: Verifying build artifacts...${NC}" -# Run tests if built -if [ "$BUILD_TESTS" = "ON" ]; then - echo -e "${BLUE}Running tests...${NC}" - (cd "$BUILD_DIR" && ctest --output-on-failure) || { - echo -e "${RED}Some tests failed${NC}" - exit 1 - } - echo -e "${GREEN}All tests passed!${NC}" +NATIVE_LIB_DIR="${SCRIPT_DIR}/native/lib" +NATIVE_INCLUDE_DIR="${SCRIPT_DIR}/native/include" + +if [ -d "${NATIVE_LIB_DIR}" ]; then + echo -e "${GREEN}✓ Libraries installed to: ${NATIVE_LIB_DIR}${NC}" + ls -lh "${NATIVE_LIB_DIR}"/*.dylib 2>/dev/null || ls -lh "${NATIVE_LIB_DIR}"/*.so 2>/dev/null || true +else + echo -e "${YELLOW}⚠ Library directory not found: ${NATIVE_LIB_DIR}${NC}" +fi + +if [ -d "${NATIVE_INCLUDE_DIR}" ]; then + echo -e "${GREEN}✓ Headers installed to: ${NATIVE_INCLUDE_DIR}${NC}" +else + echo -e "${YELLOW}⚠ Include directory not found: ${NATIVE_INCLUDE_DIR}${NC}" +fi + +echo "" + +# Step 5: Build TypeScript SDK +echo -e "${YELLOW}Step 4: Building TypeScript SDK...${NC}" +cd "${SCRIPT_DIR}" + +# Install npm dependencies if node_modules doesn't exist +if [ ! -d "node_modules" ]; then + echo -e "${YELLOW} Installing npm dependencies...${NC}" + npm install --ignore-scripts fi -# Show example usage -if [ "$BUILD_EXAMPLES" = "ON" ] && [ -f "$BUILD_DIR/bin/hello_world_example" ]; then - echo -e "${BLUE}Example built:${NC}" - echo " Run: ./$BUILD_DIR/bin/hello_world_example" +# Compile TypeScript (use npx tsc directly to avoid prebuild recursion) +echo -e "${YELLOW} Compiling TypeScript...${NC}" +npx tsc + +echo -e "${GREEN}✓ TypeScript SDK built successfully${NC}" +echo "" + +# Step 6: Verify TypeScript build +echo -e "${YELLOW}Step 5: Verifying TypeScript build...${NC}" +if [ -d "${SCRIPT_DIR}/dist" ]; then + echo -e "${GREEN}✓ TypeScript compiled to: ${SCRIPT_DIR}/dist${NC}" + ls -lh "${SCRIPT_DIR}/dist"/*.js 2>/dev/null | head -5 +else + echo -e "${RED}✗ dist directory not found${NC}" + exit 1 fi -echo -e "${GREEN}=== Build Complete ===${NC}" +echo "" +echo -e "${GREEN}======================================${NC}" +echo -e "${GREEN}Build completed successfully!${NC}" +echo -e "${GREEN}======================================${NC}" +echo "" +echo -e "Run tests: ${YELLOW}npm test${NC}" diff --git a/cmake/cmake_uninstall.cmake.in b/cmake/cmake_uninstall.cmake.in deleted file mode 100644 index 25ae5708..00000000 --- a/cmake/cmake_uninstall.cmake.in +++ /dev/null @@ -1,49 +0,0 @@ -# cmake_uninstall.cmake.in -# Uninstall script for gopher-orch - -if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") - message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") -endif() - -file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) -string(REGEX REPLACE "\n" ";" files "${files}") - -foreach(file ${files}) - message(STATUS "Uninstalling $ENV{DESTDIR}${file}") - if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") - exec_program( - "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" - OUTPUT_VARIABLE rm_out - RETURN_VALUE rm_retval - ) - if(NOT "${rm_retval}" STREQUAL 0) - message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") - endif() - else() - message(STATUS "File $ENV{DESTDIR}${file} does not exist.") - endif() -endforeach() - -# Remove empty directories -set(DIRS_TO_CHECK - "@CMAKE_INSTALL_PREFIX@/lib/cmake/gopher-orch" - "@CMAKE_INSTALL_PREFIX@/include/orch/core" - "@CMAKE_INSTALL_PREFIX@/include/orch" -) - -foreach(dir ${DIRS_TO_CHECK}) - if(EXISTS "$ENV{DESTDIR}${dir}") - file(GLOB dir_contents "$ENV{DESTDIR}${dir}/*") - list(LENGTH dir_contents n_contents) - if(n_contents EQUAL 0) - message(STATUS "Removing empty directory: $ENV{DESTDIR}${dir}") - exec_program( - "@CMAKE_COMMAND@" ARGS "-E remove_directory \"$ENV{DESTDIR}${dir}\"" - OUTPUT_VARIABLE rm_out - RETURN_VALUE rm_retval - ) - endif() - endif() -endforeach() - -message(STATUS "Uninstall complete") diff --git a/cmake/gopher-orch-config.cmake.in b/cmake/gopher-orch-config.cmake.in deleted file mode 100644 index 89457eef..00000000 --- a/cmake/gopher-orch-config.cmake.in +++ /dev/null @@ -1,23 +0,0 @@ -@PACKAGE_INIT@ - -include(CMakeFindDependencyMacro) - -# Find required dependencies -if(@USE_SUBMODULE_GOPHER_MCP@) - # When gopher-orch was built with submodule, users need gopher-mcp - find_dependency(gopher-mcp REQUIRED) -endif() - -# Find threads -find_dependency(Threads REQUIRED) - -# Include the targets file -include("${CMAKE_CURRENT_LIST_DIR}/gopher-orch-targets.cmake") - -# Set variables for compatibility -set(gopher-orch_FOUND TRUE) -set(gopher-orch_INCLUDE_DIRS "@CMAKE_INSTALL_PREFIX@/include") -set(gopher-orch_LIBRARIES gopher-orch) - -# Check required components -check_required_components(gopher-orch) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt deleted file mode 100644 index 240bb3ee..00000000 --- a/examples/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -# gopher-orch examples - -# Hello World example (basic orch functionality) -add_subdirectory(hello_world) - -# MCP Client example (demonstrates gopher-mcp integration) -add_subdirectory(mcp_client) diff --git a/examples/client_example_json.ts b/examples/client_example_json.ts new file mode 100644 index 00000000..bb605e59 --- /dev/null +++ b/examples/client_example_json.ts @@ -0,0 +1,54 @@ +/** + * @file client_example_json.ts + * @brief TypeScript example using JSON server configuration + */ +import { GopherAgent } from '../dist/index.js'; + +// Server configuration for local MCP servers +const serverConfig = JSON.stringify({ + succeeded: true, + code: 200000000, + message: 'success', + data: { + servers: [ + { + version: '2025-01-09', + serverId: '1', + name: 'server1', + transport: 'http_sse', + config: { url: 'http://127.0.0.1:3001/rpc', headers: {} }, + connectTimeout: 5000, + requestTimeout: 30000, + }, + { + version: '2025-01-09', + serverId: '2', + name: 'server2', + transport: 'http_sse', + config: { url: 'http://127.0.0.1:3002/rpc', headers: {} }, + connectTimeout: 5000, + requestTimeout: 30000, + }, + ], + }, +}); + +async function main(): Promise { + const provider = 'AnthropicProvider'; + const model = 'claude-3-haiku-20240307'; + const agent = GopherAgent.create({ provider, model, serverConfig }); + console.log('GopherAgent created!'); + + const args = process.argv.slice(2); + const question = (args.length > 0) ? + args[0] : 'What is the weather like in New York?'; + console.log(`Question: ${question}`); + const answer = agent.run(question); + console.log('Answer:'); + console.log(answer); +} + +main().catch(error => { + console.error('Error:', (error as Error).message); + process.exit(1); +}); diff --git a/examples/client_example_json_run.sh b/examples/client_example_json_run.sh new file mode 100755 index 00000000..cb97570f --- /dev/null +++ b/examples/client_example_json_run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd "$(dirname "$0")/.." +npx tsc && npx tsx examples/client_example_json.ts "$@" diff --git a/examples/hello_world/CMakeLists.txt b/examples/hello_world/CMakeLists.txt deleted file mode 100644 index 236c04f8..00000000 --- a/examples/hello_world/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -add_executable(hello_world_example main.cpp) - -target_link_libraries(hello_world_example - gopher-orch - ${GOPHER_MCP_LIBRARIES} - Threads::Threads -) - -target_include_directories(hello_world_example PRIVATE - ${CMAKE_SOURCE_DIR}/include - ${GOPHER_MCP_INCLUDE_DIR} -) - -# Examples are not installed by default -# To install, use: cmake --install . --component examples diff --git a/examples/hello_world/main.cpp b/examples/hello_world/main.cpp deleted file mode 100644 index 5c79dbd8..00000000 --- a/examples/hello_world/main.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include -#include - -#include "orch/core/hello.h" -#include "orch/core/version.h" - -using namespace gopher::orch::core; - -int main(int argc, char* argv[]) { - std::cout << "gopher-orch version: " << Version::string() << std::endl; - std::cout << "----------------------------------------" << std::endl; - - // Basic usage - { - std::cout << "\n1. Basic Hello usage:" << std::endl; - Hello hello; - std::cout << " " << hello.greet() << std::endl; - - hello.set_name("gopher-orch User"); - std::cout << " " << hello.greet() << std::endl; - } - - // Constructor with parameter - { - std::cout << "\n2. Parameterized constructor:" << std::endl; - Hello hello("Alice"); - std::cout << " " << hello.greet() << std::endl; - } - - // Custom prefix - { - std::cout << "\n3. Custom prefix greetings:" << std::endl; - Hello hello("Bob"); - std::cout << " " << hello.greet_with_prefix("Hi") << std::endl; - std::cout << " " << hello.greet_with_prefix("Welcome") << std::endl; - std::cout << " " << hello.greet_with_prefix("Greetings") << std::endl; - } - - // Builder pattern - { - std::cout << "\n4. Using HelloBuilder:" << std::endl; - HelloBuilder builder; - - auto hello1 = builder.with_name("Charlie").build(); - std::cout << " " << hello1->greet() << std::endl; - - auto hello2 = - builder.with_name("Diana").with_greeting_style("formal").build(); - std::cout << " " << hello2->greet() << std::endl; - } - - // Command line argument - if (argc > 1) { - std::cout << "\n5. Using command line argument:" << std::endl; - Hello hello(argv[1]); - std::cout << " " << hello.greet() << std::endl; - } - - // Multiple instances - { - std::cout << "\n6. Multiple instances:" << std::endl; - std::vector> hellos; - - hellos.push_back(std::make_unique("User1")); - hellos.push_back(std::make_unique("User2")); - hellos.push_back(std::make_unique("User3")); - - for (const auto& hello : hellos) { - std::cout << " " << hello->greet() << std::endl; - } - } - - std::cout << "\n----------------------------------------" << std::endl; - std::cout << "Example completed successfully!" << std::endl; - - return 0; -} diff --git a/examples/mcp_client/CMakeLists.txt b/examples/mcp_client/CMakeLists.txt deleted file mode 100644 index 6437f789..00000000 --- a/examples/mcp_client/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -# MCP Client Example -# Demonstrates gopher-mcp integration with gopher-orch -cmake_minimum_required(VERSION 3.10) - -# Define the executable -add_executable(mcp_client_example - mcp_client_example.cc -) - -# Set target properties -set_target_properties(mcp_client_example PROPERTIES - CXX_STANDARD 14 - CXX_STANDARD_REQUIRED ON - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" -) - -# Link against gopher-orch and gopher-mcp libraries -target_link_libraries(mcp_client_example PRIVATE - gopher-orch-static - gopher-mcp - gopher-mcp-event - ${CMAKE_THREAD_LIBS_INIT} -) - -# Include directories -target_include_directories(mcp_client_example PRIVATE - ${CMAKE_SOURCE_DIR}/include - ${GOPHER_MCP_INCLUDE_DIR} -) - -# Examples are not installed by default -# To install, use: cmake --install . --component examples diff --git a/examples/mcp_client/mcp_client_example.cc b/examples/mcp_client/mcp_client_example.cc deleted file mode 100644 index 28575339..00000000 --- a/examples/mcp_client/mcp_client_example.cc +++ /dev/null @@ -1,152 +0,0 @@ -/** - * @file mcp_client_example.cc - * @brief Example demonstrating gopher-orch integration with gopher-mcp - * - * This example shows how gopher-orch can extend and use gopher-mcp - * functionality. It demonstrates: - * 1. Using gopher-orch's Hello class - * 2. Using gopher-mcp types and utilities - * 3. Integration between both libraries - */ - -#include -#include -#include - -// gopher-orch includes -#include "orch/core/hello.h" -#include "orch/core/version.h" - -// gopher-mcp includes -#include "mcp/json/json_bridge.h" -#include "mcp/types.h" - -using namespace gopher::orch::core; -using namespace mcp; - -int main(int argc, char* argv[]) { - std::cout << "=== gopher-orch + gopher-mcp Integration Example ===" - << std::endl; - std::cout << std::endl; - - // Show versions - std::cout << "Versions:" << std::endl; - std::cout << " gopher-orch: " << Version::string() << std::endl; - std::cout << std::endl; - - // Demonstrate gopher-orch Hello class - std::cout << "1. gopher-orch Hello class:" << std::endl; - Hello hello("MCP User"); - std::cout << " " << hello.greet() << std::endl; - std::cout << std::endl; - - // Demonstrate gopher-mcp types - std::cout << "2. gopher-mcp types:" << std::endl; - - // Create a Tool definition - Tool calculator_tool; - calculator_tool.name = "calculator"; - calculator_tool.description = make_optional( - std::string("A simple calculator tool for basic arithmetic")); - - // Create input schema - json::JsonValue schema; - schema["type"] = "object"; - schema["properties"]["operation"]["type"] = "string"; - schema["properties"]["a"]["type"] = "number"; - schema["properties"]["b"]["type"] = "number"; - - auto required_arr = json::JsonValue::array(); - required_arr.push_back("operation"); - required_arr.push_back("a"); - required_arr.push_back("b"); - schema["required"] = required_arr; - - calculator_tool.inputSchema = make_optional(schema); - - std::cout << " Created Tool: " << calculator_tool.name << std::endl; - if (calculator_tool.description.has_value()) { - std::cout << " Description: " << calculator_tool.description.value() - << std::endl; - } - std::cout << std::endl; - - // Create a Resource definition - Resource sample_resource; - sample_resource.uri = "file:///example/data.json"; - sample_resource.name = "Example Data"; - sample_resource.description = - make_optional(std::string("Sample JSON data resource for testing")); - sample_resource.mimeType = make_optional(std::string("application/json")); - - std::cout << "3. MCP Resource:" << std::endl; - std::cout << " URI: " << sample_resource.uri << std::endl; - std::cout << " Name: " << sample_resource.name << std::endl; - if (sample_resource.mimeType.has_value()) { - std::cout << " MIME Type: " << sample_resource.mimeType.value() - << std::endl; - } - std::cout << std::endl; - - // Create a Prompt definition - Prompt greeting_prompt; - greeting_prompt.name = "greeting"; - greeting_prompt.description = - make_optional(std::string("A simple greeting prompt")); - - PromptArgument name_arg; - name_arg.name = "name"; - name_arg.description = make_optional(std::string("The name to greet")); - name_arg.required = true; - - greeting_prompt.arguments = - make_optional(std::vector{name_arg}); - - std::cout << "4. MCP Prompt:" << std::endl; - std::cout << " Name: " << greeting_prompt.name << std::endl; - if (greeting_prompt.description.has_value()) { - std::cout << " Description: " << greeting_prompt.description.value() - << std::endl; - } - if (greeting_prompt.arguments.has_value()) { - std::cout << " Arguments: " << greeting_prompt.arguments.value().size() - << std::endl; - for (const auto& arg : greeting_prompt.arguments.value()) { - std::cout << " - " << arg.name; - if (arg.required) { - std::cout << " (required)"; - } - std::cout << std::endl; - } - } - std::cout << std::endl; - - // Demonstrate JSON serialization - std::cout << "5. JSON Operations:" << std::endl; - json::JsonValue data; - data["greeting"] = hello.greet(); - data["version"] = Version::string(); - data["tool_count"] = 1; - data["resource_count"] = 1; - - std::cout << " Created JSON object with greeting and version info" - << std::endl; - std::cout << std::endl; - - // Integration example: Using gopher-orch to enhance gopher-mcp - std::cout << "6. Integration Example:" << std::endl; - HelloBuilder builder; - auto orchestrator = builder.with_name("MCP Orchestrator").build(); - std::cout << " " << orchestrator->greet() << std::endl; - std::cout - << " This demonstrates gopher-orch extending gopher-mcp capabilities" - << std::endl; - std::cout << std::endl; - - std::cout << "=== Example Complete ===" << std::endl; - std::cout - << "The gopher-orch library successfully integrates with gopher-mcp!" - << std::endl; - - return 0; -} diff --git a/include/orch/core/hello.h b/include/orch/core/hello.h deleted file mode 100644 index 48c6cdc7..00000000 --- a/include/orch/core/hello.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include -#include - -namespace gopher { -namespace orch { -namespace core { - -// Hello class demonstrates basic gopher-orch functionality -// This is a simple example to verify the build system works correctly -class Hello { - public: - Hello(); - explicit Hello(const std::string& name); - ~Hello(); - - std::string greet() const; - std::string greet_with_prefix(const std::string& prefix) const; - - void set_name(const std::string& name); - const std::string& get_name() const; - - static std::string get_version(); - - private: - class Impl; - std::unique_ptr impl_; -}; - -// Builder pattern for Hello class construction -class HelloBuilder { - public: - HelloBuilder& with_name(const std::string& name); - HelloBuilder& with_greeting_style(const std::string& style); - - std::unique_ptr build() const; - - private: - std::string name_ = "World"; - std::string style_ = "default"; -}; - -} // namespace core -} // namespace orch -} // namespace gopher diff --git a/include/orch/core/version.h b/include/orch/core/version.h deleted file mode 100644 index d86166e9..00000000 --- a/include/orch/core/version.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#define GOPHER_ORCH_VERSION_MAJOR 0 -#define GOPHER_ORCH_VERSION_MINOR 1 -#define GOPHER_ORCH_VERSION_PATCH 0 - -#define GOPHER_ORCH_VERSION_STRING "0.1.0" - -namespace gopher { -namespace orch { -namespace core { - -struct Version { - static constexpr int major() { return GOPHER_ORCH_VERSION_MAJOR; } - static constexpr int minor() { return GOPHER_ORCH_VERSION_MINOR; } - static constexpr int patch() { return GOPHER_ORCH_VERSION_PATCH; } - static const char* string() { return GOPHER_ORCH_VERSION_STRING; } -}; - -} // namespace core -} // namespace orch -} // namespace gopher diff --git a/package.json b/package.json new file mode 100644 index 00000000..3f2accfc --- /dev/null +++ b/package.json @@ -0,0 +1,63 @@ +{ + "name": "@gopher/orch", + "version": "0.1.0", + "description": "TypeScript SDK for Gopher Orch - AI Agent orchestration framework with native performance", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "scripts": { + "build:native": "./build.sh", + "prebuild": "npm run build:native", + "build": "tsc", + "watch": "tsc --watch", + "test": "jest", + "test:watch": "jest --watch", + "lint": "eslint src/**/*.ts", + "format": "prettier --write \"src/**/*.ts\"", + "clean": "rm -rf dist build native third_party/gopher-orch/build", + "prepublishOnly": "npm run build" + }, + "keywords": [ + "ai", + "agent", + "llm", + "mcp", + "orchestration", + "langchain", + "anthropic", + "openai", + "typescript", + "native" + ], + "author": "Gopher Security", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/GopherSecurity/gopher-orch-js.git" + }, + "engines": { + "node": ">=16.0.0" + }, + "files": [ + "dist", + "native/lib", + "native/include", + "build.sh", + "README.md", + "LICENSE" + ], + "devDependencies": { + "@types/node": "^20.11.0", + "@types/jest": "^29.5.11", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", + "eslint": "^8.56.0", + "jest": "^29.7.0", + "prettier": "^3.2.4", + "ts-jest": "^29.1.1", + "typescript": "^5.3.3" + }, + "dependencies": { + "koffi": "^2.8.0" + } +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index 709369ef..00000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,103 +0,0 @@ -# gopher-orch source files - -# Core library sources (orch-specific extensions) -set(ORCH_CORE_SOURCES - orch/hello.cpp -) - -# Combine all sources -set(GOPHER_ORCH_SOURCES - ${ORCH_CORE_SOURCES} -) - -# Build static library -if(BUILD_STATIC_LIBS) - add_library(gopher-orch-static STATIC ${GOPHER_ORCH_SOURCES}) - target_include_directories(gopher-orch-static PUBLIC - $ - $ - $ - ) - - # Link dependencies - if(NOT BUILD_WITHOUT_GOPHER_MCP) - target_link_libraries(gopher-orch-static PUBLIC - ${GOPHER_MCP_LIBRARIES} - Threads::Threads - ) - else() - target_link_libraries(gopher-orch-static PUBLIC - Threads::Threads - ) - endif() - - set_target_properties(gopher-orch-static PROPERTIES - OUTPUT_NAME gopher-orch - POSITION_INDEPENDENT_CODE ON - ) - - # Set the main library alias - add_library(gopher-orch ALIAS gopher-orch-static) - - # Installation - install(TARGETS gopher-orch-static - EXPORT gopher-orch-targets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - RUNTIME DESTINATION bin - COMPONENT libraries - ) -endif() - -# Build shared library -if(BUILD_SHARED_LIBS) - add_library(gopher-orch-shared SHARED ${GOPHER_ORCH_SOURCES}) - target_include_directories(gopher-orch-shared PUBLIC - $ - $ - $ - ) - - # Link dependencies - if(NOT BUILD_WITHOUT_GOPHER_MCP) - target_link_libraries(gopher-orch-shared PUBLIC - ${GOPHER_MCP_LIBRARIES} - Threads::Threads - ) - else() - target_link_libraries(gopher-orch-shared PUBLIC - Threads::Threads - ) - endif() - - set_target_properties(gopher-orch-shared PROPERTIES - OUTPUT_NAME gopher-orch - VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR} - ) - - # If only building shared, set it as the main library - if(NOT BUILD_STATIC_LIBS) - add_library(gopher-orch ALIAS gopher-orch-shared) - endif() - - # Installation - install(TARGETS gopher-orch-shared - EXPORT gopher-orch-targets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - RUNTIME DESTINATION bin - COMPONENT libraries - ) -endif() - -# Export targets only when not using submodule -# (When using submodule, gopher-mcp targets aren't installable) -if(NOT USE_SUBMODULE_GOPHER_MCP) - install(EXPORT gopher-orch-targets - FILE gopher-orch-targets.cmake - NAMESPACE gopher-orch:: - DESTINATION lib/cmake/gopher-orch - COMPONENT development - ) -endif() diff --git a/src/agent.ts b/src/agent.ts new file mode 100644 index 00000000..0590fc72 --- /dev/null +++ b/src/agent.ts @@ -0,0 +1,305 @@ +/** + * @file agent.ts + * @brief TypeScript wrapper for GopherAgent functionality + */ + +import { library, initializeLibrary, shutdownLibrary } from './ffi.js'; +import { + AgentResult, + AgentError, + ApiKeyError, + TimeoutError, +} from './types.js'; + +/** + * Configuration options for creating a GopherAgent + */ +export interface GopherAgentConfig { + /** Provider name (e.g., "AnthropicProvider") */ + provider: string; + /** Model name (e.g., "claude-3-haiku-20240307") */ + model: string; + /** API key for fetching remote server config (mutually exclusive with serverConfig) */ + apiKey?: string; + /** JSON server configuration (mutually exclusive with apiKey) */ + serverConfig?: string; +} + +// Agent handle type +interface AgentHandle { + handle: unknown; + isNull: () => boolean; +} + +/** + * GopherAgent - Main entry point for the gopher-orch TypeScript SDK + * + * Provides a clean, TypeScript-friendly interface to the gopher-orch agent functionality. + * + * @example + * ```typescript + * import { GopherAgent } from "@gopher/orch"; + * + * // Create an agent with API key + * const agent = GopherAgent.create({ + * provider: 'AnthropicProvider', + * model: 'claude-3-haiku-20240307', + * apiKey: 'your-api-key' + * }); + * + * // Run a query + * const answer = agent.run("What time is it in Tokyo?"); + * console.log(answer); + * + * // Cleanup (optional - happens automatically on exit) + * agent.dispose(); + * ``` + */ +export class GopherAgent { + private handle: AgentHandle | null; + private disposed: boolean = false; + private static initialized: boolean = false; + + private constructor(handle: AgentHandle) { + this.handle = handle; + } + + /** + * Initialize the gopher-orch library + * Must be called before creating any agents + * + * @throws {AgentError} If initialization fails + */ + static init(): void { + if (GopherAgent.initialized) { + return; + } + + const success = initializeLibrary(); + if (!success) { + throw new AgentError('Failed to initialize gopher-orch library'); + } + + library.gopher_orch_init(); + GopherAgent.initialized = true; + + // Setup automatic cleanup on process exit + GopherAgent.setupCleanupHandlers(); + } + + /** + * Shutdown the gopher-orch library + * Called automatically on process exit, but can be called manually + */ + static shutdown(): void { + if (GopherAgent.initialized) { + shutdownLibrary(); + GopherAgent.initialized = false; + } + } + + /** + * Check if the library is initialized + */ + static isInitialized(): boolean { + return GopherAgent.initialized; + } + + /** + * Create a new GopherAgent instance + * + * @param config Configuration options + * @returns GopherAgent instance + * @throws {AgentError} If agent creation fails + * + * @example + * ```typescript + * // Create with API key (fetches server config from remote API) + * const agent = GopherAgent.create({ + * provider: 'AnthropicProvider', + * model: 'claude-3-haiku-20240307', + * apiKey: 'your-api-key' + * }); + * + * // Or create with JSON server config + * const agent = GopherAgent.create({ + * provider: 'AnthropicProvider', + * model: 'claude-3-haiku-20240307', + * serverConfig: '{"succeeded": true, "data": {...}}' + * }); + * ``` + */ + static create(config: GopherAgentConfig): GopherAgent { + if (!GopherAgent.initialized) { + GopherAgent.init(); + } + + const { provider, model, apiKey, serverConfig } = config; + + if (!provider || !model) { + throw new AgentError('Provider and model are required'); + } + + if (apiKey && serverConfig) { + throw new AgentError('Cannot specify both apiKey and serverConfig'); + } + + if (!apiKey && !serverConfig) { + throw new AgentError('Either apiKey or serverConfig is required'); + } + + let handle: AgentHandle | null; + + try { + if (apiKey) { + handle = library.gopher_orch_agent_create_by_api_key(provider, model, apiKey); + } else { + handle = library.gopher_orch_agent_create_by_json(provider, model, serverConfig!); + } + + if (!handle || handle.isNull()) { + // Try to get error message from FFI layer + const lastError = library.gopher_orch_last_error(); + const errorMsg = lastError ? String(lastError) : 'Failed to create agent'; + library.gopher_orch_clear_error(); + throw new AgentError(errorMsg); + } + + return new GopherAgent(handle); + } catch (error) { + if (error instanceof AgentError) { + throw error; + } + throw new AgentError(`Failed to create agent: ${(error as Error).message}`); + } + } + + /** + * Run a query against the agent + * + * @param query The user query to process + * @param timeoutMs Optional timeout in milliseconds (default: 60000) + * @returns The agent's response + * @throws {AgentError} If the query fails + */ + run(query: string, timeoutMs: number = 60000): string { + this.ensureNotDisposed(); + + try { + const response = library.gopher_orch_agent_run(this.handle, query, timeoutMs); + return response; + } catch (error) { + throw new AgentError(`Query execution failed: ${(error as Error).message}`); + } + } + + /** + * Run a query with detailed result information + * + * @param query The user query to process + * @param timeoutMs Optional timeout in milliseconds + * @returns AgentResult with response and metadata + */ + runDetailed(query: string, timeoutMs: number = 60000): AgentResult { + try { + const response = this.run(query, timeoutMs); + + return { + response, + status: 'success', + iterationCount: 1, + tokensUsed: 0, + }; + } catch (error) { + if (error instanceof TimeoutError) { + return { + response: error.message, + status: 'timeout', + }; + } else { + return { + response: (error as Error).message, + status: 'error', + }; + } + } + } + + /** + * Dispose of the agent and free resources + */ + dispose(): void { + if (!this.disposed) { + if (this.handle) { + library.gopher_orch_agent_release(this.handle); + this.handle = null; + } + this.disposed = true; + } + } + + /** + * Check if agent is disposed + */ + isDisposed(): boolean { + return this.disposed; + } + + private ensureNotDisposed(): void { + if (this.disposed) { + throw new AgentError('Agent has been disposed'); + } + } + + private static setupCleanupHandlers(): void { + const cleanup = () => { + GopherAgent.shutdown(); + }; + + process.on('exit', cleanup); + process.on('SIGTERM', () => { + cleanup(); + process.exit(0); + }); + process.on('SIGINT', () => { + cleanup(); + process.exit(0); + }); + } +} + +// Backward compatibility alias +export { GopherAgent as ReActAgent }; + +/** + * Utility class for fetching server configurations + */ +export class ServerConfig { + /** + * Fetch MCP server configurations from remote API + * + * @param apiKey API key for authentication + * @returns Server configuration JSON string + */ + static fetch(apiKey: string): string { + if (!GopherAgent.isInitialized()) { + GopherAgent.init(); + } + + try { + if (!apiKey || apiKey.trim().length === 0) { + throw new ApiKeyError('Invalid or missing API key'); + } + + return library.gopher_orch_api_fetch_servers(apiKey); + } catch (error) { + if (error instanceof AgentError) { + throw error; + } + throw new AgentError(`Failed to fetch servers: ${(error as Error).message}`); + } + } +} + +// Backward compatibility alias +export { ServerConfig as ServerConfigHelper }; diff --git a/src/ffi.ts b/src/ffi.ts new file mode 100644 index 00000000..051fc605 --- /dev/null +++ b/src/ffi.ts @@ -0,0 +1,407 @@ +/** + * @file ffi.ts + * @brief FFI interface to gopher-orch C++ library using koffi + */ + +import { existsSync } from 'node:fs'; +import koffi from 'koffi'; +import { arch, platform } from 'node:os'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// ESM equivalent of __dirname +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Library configuration for different platforms and architectures +const LIBRARY_CONFIG = { + darwin: { + x64: { + name: 'libgopher-orch.dylib', + searchPaths: [ + // Primary: native/lib relative to project root (from dist/) + join(__dirname, '../native/lib/libgopher-orch.dylib'), + // Development: when running from src/ + join(__dirname, '../../native/lib/libgopher-orch.dylib'), + // System installation paths + '/usr/local/lib/libgopher-orch.dylib', + '/opt/homebrew/lib/libgopher-orch.dylib', + '/usr/lib/libgopher-orch.dylib', + ], + }, + arm64: { + name: 'libgopher-orch.dylib', + searchPaths: [ + join(__dirname, '../native/lib/libgopher-orch.dylib'), + join(__dirname, '../../native/lib/libgopher-orch.dylib'), + '/usr/local/lib/libgopher-orch.dylib', + '/opt/homebrew/lib/libgopher-orch.dylib', + '/usr/lib/libgopher-orch.dylib', + ], + }, + }, + linux: { + x64: { + name: 'libgopher-orch.so', + searchPaths: [ + join(__dirname, '../native/lib/libgopher-orch.so'), + join(__dirname, '../../native/lib/libgopher-orch.so'), + '/usr/local/lib/libgopher-orch.so', + '/usr/lib/x86_64-linux-gnu/libgopher-orch.so', + '/usr/lib64/libgopher-orch.so', + '/usr/lib/libgopher-orch.so', + ], + }, + arm64: { + name: 'libgopher-orch.so', + searchPaths: [ + join(__dirname, '../native/lib/libgopher-orch.so'), + join(__dirname, '../../native/lib/libgopher-orch.so'), + '/usr/local/lib/libgopher-orch.so', + '/usr/lib/aarch64-linux-gnu/libgopher-orch.so', + '/usr/lib64/libgopher-orch.so', + '/usr/lib/libgopher-orch.so', + ], + }, + }, + win32: { + x64: { + name: 'gopher-orch.dll', + searchPaths: [ + join(__dirname, '../native/lib/gopher-orch.dll'), + join(__dirname, '../../native/lib/gopher-orch.dll'), + 'C:\\Program Files\\gopher-orch\\bin\\gopher-orch.dll', + 'C:\\Program Files\\gopher-orch\\lib\\gopher-orch.dll', + ], + }, + }, +} as const; + +/** + * Get the path to the native library + */ +function getLibraryPath(): string { + // Check for environment variable override first + const envPath = process.env['GOPHER_ORCH_LIBRARY_PATH']; + if (envPath && existsSync(envPath)) { + return envPath; + } + + const currentPlatform = platform() as keyof typeof LIBRARY_CONFIG; + const currentArch = arch() as keyof (typeof LIBRARY_CONFIG)[typeof currentPlatform]; + + if (!LIBRARY_CONFIG[currentPlatform] || !LIBRARY_CONFIG[currentPlatform][currentArch]) { + throw new Error(`Unsupported platform: ${currentPlatform} ${currentArch}`); + } + + const config = LIBRARY_CONFIG[currentPlatform][currentArch]; + + // Search through the paths to find the first one that exists + for (const searchPath of config.searchPaths) { + if (existsSync(searchPath)) { + return searchPath; + } + } + + // If no path found, throw an error with helpful information + const searchedPaths = config.searchPaths.join('\n - '); + throw new Error( + `Gopher-Orch library not found. Searched paths:\n - ${searchedPaths}\n` + + `Please ensure the library is built and available at one of these locations.\n` + + `You can set GOPHER_ORCH_LIBRARY_PATH environment variable to override.` + ); +} + +// Raw library bindings +export let gopherOrchLib: Record unknown> = {}; + +// Error info struct type for koffi +let ErrorInfoType: unknown = null; + +try { + const libPath = getLibraryPath(); + + // Load the shared library + const nativeLib = koffi.load(libPath); + + // Define the error_info struct to match C: + // typedef struct { + // gopher_orch_error_t code; // int + // const char* message; + // const char* details; + // const char* file; + // int32_t line; + // } gopher_orch_error_info_t; + ErrorInfoType = koffi.struct('gopher_orch_error_info_t', { + code: 'int', + message: 'const char*', + details: 'const char*', + file: 'const char*', + line: 'int32_t', + }); + + // List of C API functions to bind + const functionList = [ + // Core initialization functions + { name: 'gopher_orch_init', signature: 'int', args: [] as string[] }, + { name: 'gopher_orch_shutdown', signature: 'void', args: [] as string[] }, + { name: 'gopher_orch_is_initialized', signature: 'int', args: [] as string[] }, + { name: 'gopher_orch_last_error', signature: 'gopher_orch_error_info_t*', args: [] as string[] }, + { name: 'gopher_orch_clear_error', signature: 'void', args: [] as string[] }, + { name: 'gopher_orch_free', signature: 'void', args: ['void*'] }, + + // Agent functions + { + name: 'gopher_orch_agent_create_by_json', + signature: 'void*', + args: ['string', 'string', 'string'], // provider, model, server_json_config + }, + { + name: 'gopher_orch_agent_create_by_api_key', + signature: 'void*', + args: ['string', 'string', 'string'], // provider, model, api_key + }, + { + name: 'gopher_orch_agent_run', + signature: 'string', + args: ['void*', 'string', 'uint64_t'], // agent, query, timeout_ms + }, + { name: 'gopher_orch_agent_add_ref', signature: 'void', args: ['void*'] }, + { name: 'gopher_orch_agent_release', signature: 'void', args: ['void*'] }, + + // API functions + { + name: 'gopher_orch_api_fetch_servers', + signature: 'string', + args: ['string'], // api_key + }, + ]; + + // Try to bind each function + const availableFunctions: Record unknown> = {}; + for (const func of functionList) { + try { + availableFunctions[func.name] = nativeLib.func(func.name, func.signature, func.args); + } catch { + // Function not available - that's OK, we'll provide a fallback + } + } + + gopherOrchLib = availableFunctions; +} catch (error) { + console.error(`Failed to load Gopher-Orch library: ${error}`); + // Continue with empty lib - fallbacks will be used + gopherOrchLib = {}; +} + +// Agent handle wrapper type +interface AgentHandle { + handle: unknown; + isNull: () => boolean; +} + +/** + * FFI interface with fallbacks for when native library is unavailable + */ +export const library = { + gopher_orch_init: (): number => { + if (gopherOrchLib.gopher_orch_init) { + return gopherOrchLib.gopher_orch_init() as number; + } + return 0; // Success fallback + }, + + gopher_orch_shutdown: (): void => { + if (gopherOrchLib.gopher_orch_shutdown) { + gopherOrchLib.gopher_orch_shutdown(); + } + }, + + gopher_orch_is_initialized: (): boolean => { + if (gopherOrchLib.gopher_orch_is_initialized) { + return (gopherOrchLib.gopher_orch_is_initialized() as number) !== 0; + } + return true; // Fallback + }, + + gopher_orch_last_error: (): string | null => { + if (gopherOrchLib.gopher_orch_last_error && ErrorInfoType) { + try { + const errorPtr = gopherOrchLib.gopher_orch_last_error(); + if (errorPtr) { + const errorInfo = koffi.decode(errorPtr, ErrorInfoType as koffi.IKoffiCType) as { + message?: string; + }; + if (errorInfo && errorInfo.message) { + return errorInfo.message; + } + } + } catch { + // Silently fail - will return null + } + } + return null; + }, + + gopher_orch_clear_error: (): void => { + if (gopherOrchLib.gopher_orch_clear_error) { + gopherOrchLib.gopher_orch_clear_error(); + } + }, + + gopher_orch_free: (ptr: unknown): void => { + if (gopherOrchLib.gopher_orch_free && ptr) { + gopherOrchLib.gopher_orch_free(ptr); + } + }, + + gopher_orch_agent_create_by_json: ( + provider: string, + model: string, + serverJson: string + ): AgentHandle | null => { + if (gopherOrchLib.gopher_orch_agent_create_by_json) { + // Configure environment for HTTP/HTTPS handling + const originalEnv = { + CURL_CA_BUNDLE: process.env.CURL_CA_BUNDLE, + SSL_VERIFY_PEER: process.env.SSL_VERIFY_PEER, + SSL_VERIFY_HOST: process.env.SSL_VERIFY_HOST, + }; + + // For HTTP URLs, disable SSL verification + process.env.SSL_VERIFY_PEER = '0'; + process.env.SSL_VERIFY_HOST = '0'; + process.env.CURL_CA_BUNDLE = ''; + + try { + const handle = gopherOrchLib.gopher_orch_agent_create_by_json(provider, model, serverJson); + return handle ? { handle, isNull: () => !handle } : null; + } catch (ffiError) { + console.error('FFI Error in agent creation:', ffiError); + return null; + } finally { + // Restore original environment + for (const [key, value] of Object.entries(originalEnv)) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + } + } + // FFI function not available, use fallback + console.warn('FFI function gopher_orch_agent_create_by_json not available, using fallback'); + return { handle: 'mock-agent-json', isNull: () => false }; + }, + + gopher_orch_agent_create_by_api_key: ( + provider: string, + model: string, + apiKey: string + ): AgentHandle | null => { + if (gopherOrchLib.gopher_orch_agent_create_by_api_key) { + // Configure environment for HTTP/HTTPS handling + const originalEnv = { + CURL_CA_BUNDLE: process.env.CURL_CA_BUNDLE, + SSL_VERIFY_PEER: process.env.SSL_VERIFY_PEER, + SSL_VERIFY_HOST: process.env.SSL_VERIFY_HOST, + }; + + process.env.SSL_VERIFY_PEER = '0'; + process.env.SSL_VERIFY_HOST = '0'; + process.env.CURL_CA_BUNDLE = ''; + + try { + const handle = gopherOrchLib.gopher_orch_agent_create_by_api_key(provider, model, apiKey); + return handle ? { handle, isNull: () => !handle } : null; + } finally { + // Restore original environment + for (const [key, value] of Object.entries(originalEnv)) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + } + } + // Fallback + return { handle: 'mock-agent-api', isNull: () => false }; + }, + + gopher_orch_agent_release: (agent: AgentHandle | null): void => { + if (gopherOrchLib.gopher_orch_agent_release && agent?.handle) { + gopherOrchLib.gopher_orch_agent_release(agent.handle); + } + }, + + gopher_orch_agent_run: ( + agent: AgentHandle | null, + query: string, + timeoutMs: number = 30000 + ): string => { + if (gopherOrchLib.gopher_orch_agent_run && agent?.handle) { + const result = gopherOrchLib.gopher_orch_agent_run(agent.handle, query, timeoutMs); + return (result as string) || `No response for query: "${query}"`; + } + // Fallback + return `[FFI Fallback] Agent processed query: "${query}" - Real gopher-orch library not available`; + }, + + gopher_orch_api_fetch_servers: (apiKey: string): string => { + if (gopherOrchLib.gopher_orch_api_fetch_servers) { + return gopherOrchLib.gopher_orch_api_fetch_servers(apiKey) as string; + } + // Fallback + return JSON.stringify({ + succeeded: true, + code: 200000000, + message: 'success - fallback', + data: { + servers: [ + { + version: '2025-01-09', + serverId: '1877234567890123456', + name: 'fallback-server', + transport: 'http_sse', + config: { + url: 'http://127.0.0.1:3001/rpc', + headers: {}, + }, + connectTimeout: 5000, + requestTimeout: 30000, + }, + ], + }, + }); + }, +}; + +/** + * Initialize the native library + */ +export function initializeLibrary(): boolean { + return library.gopher_orch_init() === 0; +} + +/** + * Shutdown the native library + */ +export function shutdownLibrary(): void { + library.gopher_orch_shutdown(); +} + +/** + * Get the last error message + */ +export function getLastError(): string | null { + return library.gopher_orch_last_error(); +} + +/** + * Clear the last error + */ +export function clearError(): void { + library.gopher_orch_clear_error(); +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..2d4b9a1f --- /dev/null +++ b/src/index.ts @@ -0,0 +1,35 @@ +/** + * @file index.ts + * @brief Main entry point for @gopher/orch TypeScript SDK + * + * @example + * ```typescript + * import { GopherAgent } from '@gopher/orch'; + * + * const agent = GopherAgent.create({ + * provider: 'AnthropicProvider', + * model: 'claude-3-haiku-20240307', + * apiKey: 'your-api-key' + * }); + * + * const answer = agent.run('What time is it in Tokyo?'); + * console.log(answer); + * + * agent.dispose(); + * ``` + */ + +// Main classes +export { GopherAgent, GopherAgentConfig, ServerConfig } from './agent.js'; + +// Backward compatibility aliases +export { ReActAgent, ServerConfigHelper } from './agent.js'; + +// Type definitions +export * from './types.js'; + +// Low-level FFI access for advanced usage +export { library, initializeLibrary, shutdownLibrary, getLastError, clearError } from './ffi.js'; + +// Version +export const version = '0.1.0'; diff --git a/src/orch/hello.cpp b/src/orch/hello.cpp deleted file mode 100644 index 9b05263c..00000000 --- a/src/orch/hello.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "orch/core/hello.h" - -#include - -#include "orch/core/version.h" - -namespace gopher { -namespace orch { -namespace core { - -class Hello::Impl { - public: - Impl() : name_("World") {} - explicit Impl(const std::string& name) : name_(name) {} - - std::string name_; -}; - -Hello::Hello() : impl_(std::make_unique()) {} - -Hello::Hello(const std::string& name) : impl_(std::make_unique(name)) {} - -Hello::~Hello() = default; - -std::string Hello::greet() const { - std::ostringstream oss; - oss << "Hello, " << impl_->name_ << "!"; - return oss.str(); -} - -std::string Hello::greet_with_prefix(const std::string& prefix) const { - std::ostringstream oss; - oss << prefix << " " << impl_->name_ << "!"; - return oss.str(); -} - -void Hello::set_name(const std::string& name) { impl_->name_ = name; } - -const std::string& Hello::get_name() const { return impl_->name_; } - -std::string Hello::get_version() { return Version::string(); } - -HelloBuilder& HelloBuilder::with_name(const std::string& name) { - name_ = name; - return *this; -} - -HelloBuilder& HelloBuilder::with_greeting_style(const std::string& style) { - style_ = style; - return *this; -} - -std::unique_ptr HelloBuilder::build() const { - return std::make_unique(name_); -} - -} // namespace core -} // namespace orch -} // namespace gopher diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..9c399bc2 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,93 @@ +/** + * @file types.ts + * @brief TypeScript type definitions for gopher-orch SDK + */ + +/** + * MCP server configuration + */ +export interface ServerConfig { + version: string; + serverId: string; + name: string; + transport: string; + config: { + url: string; + headers: Record; + }; + connectTimeout: number; + requestTimeout: number; +} + +/** + * API response structure from server config fetch + */ +export interface ApiResponse { + succeeded: boolean; + code: number; + message: string; + data: { + servers: ServerConfig[]; + }; +} + +/** + * Agent configuration options + */ +export interface AgentConfig { + provider: string; + model: string; + systemPrompt?: string; + maxIterations?: number; + temperature?: number; +} + +/** + * Result from agent query execution + */ +export interface AgentResult { + response: string; + status: 'success' | 'error' | 'timeout'; + iterationCount?: number; + tokensUsed?: number; +} + +/** + * Base error class for agent operations + */ +export class AgentError extends Error { + constructor(message: string, public code?: string) { + super(message); + this.name = 'AgentError'; + } +} + +/** + * Error for API key related issues + */ +export class ApiKeyError extends AgentError { + constructor(message: string = 'Invalid or missing API key') { + super(message, 'API_KEY_ERROR'); + this.name = 'ApiKeyError'; + } +} + +/** + * Error for MCP server connection issues + */ +export class ConnectionError extends AgentError { + constructor(message: string = 'Failed to connect to MCP servers') { + super(message, 'CONNECTION_ERROR'); + this.name = 'ConnectionError'; + } +} + +/** + * Error for operation timeout + */ +export class TimeoutError extends AgentError { + constructor(message: string = 'Agent execution timed out') { + super(message, 'TIMEOUT_ERROR'); + this.name = 'TimeoutError'; + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt deleted file mode 100644 index 88a9c7d4..00000000 --- a/tests/CMakeLists.txt +++ /dev/null @@ -1,83 +0,0 @@ -# gopher-orch tests - -# Test utilities and helpers -set(TEST_UTIL_SOURCES - # test_utils.cpp -) - -# Orch-specific core tests -set(ORCH_CORE_TEST_SOURCES - orch/hello_test.cpp -) - -# Helper function to create orch test executables -function(add_orch_test test_name test_sources) - add_executable(${test_name} ${test_sources} ${TEST_UTIL_SOURCES}) - # Use static library for tests to avoid duplicate symbol issues - if(TARGET gopher-orch-static) - set(GOPHER_ORCH_TEST_LIB gopher-orch-static) - else() - set(GOPHER_ORCH_TEST_LIB gopher-orch) - endif() - target_link_libraries(${test_name} - ${GOPHER_ORCH_TEST_LIB} - ${GOPHER_MCP_LIBRARIES} - GTest::gtest - GTest::gtest_main - GTest::gmock - Threads::Threads - ) - target_include_directories(${test_name} PRIVATE - ${CMAKE_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/tests - ${GOPHER_MCP_INCLUDE_DIR} - ) - - # Add test to CTest - gtest_discover_tests(${test_name} - WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} - PROPERTIES LABELS ${ARGN} - ) -endfunction() - -# Create individual orch test executables -add_orch_test(hello_test "${ORCH_CORE_TEST_SOURCES}" "orch") - -# Create a combined orch test executable for convenience -add_executable(gopher-orch-tests - ${ORCH_CORE_TEST_SOURCES} - ${TEST_UTIL_SOURCES} -) - -# Use static library for tests to avoid duplicate symbol issues -if(TARGET gopher-orch-static) - set(GOPHER_ORCH_TEST_LIB gopher-orch-static) -else() - set(GOPHER_ORCH_TEST_LIB gopher-orch) -endif() - -target_link_libraries(gopher-orch-tests - ${GOPHER_ORCH_TEST_LIB} - ${GOPHER_MCP_LIBRARIES} - GTest::gtest - GTest::gtest_main - GTest::gmock - Threads::Threads -) - -target_include_directories(gopher-orch-tests PRIVATE - ${CMAKE_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/tests - ${GOPHER_MCP_INCLUDE_DIR} -) - -# Custom test targets -add_custom_target(test-verbose - COMMAND ${CMAKE_CTEST_COMMAND} -V - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} -) - -add_custom_target(test-parallel - COMMAND ${CMAKE_CTEST_COMMAND} -j${CMAKE_BUILD_PARALLEL_LEVEL} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} -) diff --git a/tests/orch/hello_test.cpp b/tests/orch/hello_test.cpp deleted file mode 100644 index a6672772..00000000 --- a/tests/orch/hello_test.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "orch/core/hello.h" - -#include -#include - -#include "orch/core/version.h" - -using namespace gopher::orch::core; -using namespace testing; - -class HelloTest : public ::testing::Test { - protected: - void SetUp() override { - // Setup code if needed - } - - void TearDown() override { - // Teardown code if needed - } -}; - -TEST_F(HelloTest, DefaultConstructor) { - Hello hello; - EXPECT_EQ(hello.greet(), "Hello, World!"); - EXPECT_EQ(hello.get_name(), "World"); -} - -TEST_F(HelloTest, ParameterizedConstructor) { - Hello hello("Alice"); - EXPECT_EQ(hello.greet(), "Hello, Alice!"); - EXPECT_EQ(hello.get_name(), "Alice"); -} - -TEST_F(HelloTest, SetName) { - Hello hello; - hello.set_name("Bob"); - EXPECT_EQ(hello.greet(), "Hello, Bob!"); - EXPECT_EQ(hello.get_name(), "Bob"); -} - -TEST_F(HelloTest, GreetWithPrefix) { - Hello hello("Charlie"); - EXPECT_EQ(hello.greet_with_prefix("Hi"), "Hi Charlie!"); - EXPECT_EQ(hello.greet_with_prefix("Welcome"), "Welcome Charlie!"); -} - -TEST_F(HelloTest, GetVersion) { EXPECT_EQ(Hello::get_version(), "0.1.0"); } - -TEST_F(HelloTest, EmptyName) { - Hello hello(""); - EXPECT_EQ(hello.greet(), "Hello, !"); - EXPECT_EQ(hello.get_name(), ""); -} - -TEST_F(HelloTest, SpecialCharacters) { - Hello hello("User@123!"); - EXPECT_EQ(hello.greet(), "Hello, User@123!!"); - EXPECT_EQ(hello.get_name(), "User@123!"); -} - -TEST_F(HelloTest, LongName) { - std::string long_name(1000, 'a'); - Hello hello(long_name); - EXPECT_EQ(hello.get_name(), long_name); - EXPECT_THAT(hello.greet(), StartsWith("Hello, ")); - EXPECT_THAT(hello.greet(), EndsWith("!")); -} - -// Test HelloBuilder -class HelloBuilderTest : public ::testing::Test { - protected: - HelloBuilder builder; -}; - -TEST_F(HelloBuilderTest, DefaultBuild) { - auto hello = builder.build(); - EXPECT_EQ(hello->greet(), "Hello, World!"); -} - -TEST_F(HelloBuilderTest, WithName) { - auto hello = builder.with_name("Diana").build(); - EXPECT_EQ(hello->greet(), "Hello, Diana!"); -} - -TEST_F(HelloBuilderTest, ChainedCalls) { - auto hello = builder.with_name("Eve").with_greeting_style("formal").build(); - EXPECT_EQ(hello->greet(), "Hello, Eve!"); -} - -TEST_F(HelloBuilderTest, MultipleBuildsSameBuilder) { - builder.with_name("Frank"); - auto hello1 = builder.build(); - auto hello2 = builder.build(); - - EXPECT_EQ(hello1->greet(), "Hello, Frank!"); - EXPECT_EQ(hello2->greet(), "Hello, Frank!"); - - // Verify they are independent objects - hello1->set_name("George"); - EXPECT_EQ(hello1->get_name(), "George"); - EXPECT_EQ(hello2->get_name(), "Frank"); -} - -// Version tests -TEST(VersionTest, VersionConstants) { - EXPECT_EQ(Version::major(), 0); - EXPECT_EQ(Version::minor(), 1); - EXPECT_EQ(Version::patch(), 0); - EXPECT_STREQ(Version::string(), "0.1.0"); -} diff --git a/third_party/gopher-mcp b/third_party/gopher-mcp deleted file mode 160000 index 5f6d6fd4..00000000 --- a/third_party/gopher-mcp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5f6d6fd4c70149eacab072cf9ba9a333107c0d36 diff --git a/third_party/gopher-orch b/third_party/gopher-orch new file mode 160000 index 00000000..88bcdd75 --- /dev/null +++ b/third_party/gopher-orch @@ -0,0 +1 @@ +Subproject commit 88bcdd75cad1b2d0eef19aeeac0c13154823b0ff diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..eb126d9e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +}