enya-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +20 -0
- package/.github/workflows/ci.yml +70 -0
- package/.github/workflows/publish.yml +250 -0
- package/.gitmodules +3 -0
- package/Cargo.lock +3584 -0
- package/Cargo.toml +97 -0
- package/crates/enact/Cargo.toml +27 -0
- package/crates/enact/src/lib.rs +60 -0
- package/crates/enact-a2a/Cargo.toml +25 -0
- package/crates/enact-a2a/src/lib.rs +411 -0
- package/crates/enact-channels/Cargo.toml +64 -0
- package/crates/enact-channels/examples/README.md +80 -0
- package/crates/enact-channels/examples/channel_bot.rs +169 -0
- package/crates/enact-channels/examples/telegram-echo.rs +34 -0
- package/crates/enact-channels/examples/whatsapp-echo.rs +142 -0
- package/crates/enact-channels/src/config.rs +213 -0
- package/crates/enact-channels/src/lib.rs +25 -0
- package/crates/enact-channels/src/runtime.rs +237 -0
- package/crates/enact-channels/src/security/mod.rs +5 -0
- package/crates/enact-channels/src/security/pairing.rs +205 -0
- package/crates/enact-channels/src/teams.rs +601 -0
- package/crates/enact-channels/src/telegram.rs +2833 -0
- package/crates/enact-channels/src/traits.rs +200 -0
- package/crates/enact-channels/src/webhook.rs +262 -0
- package/crates/enact-channels/src/whatsapp.rs +310 -0
- package/crates/enact-cli/Cargo.toml +40 -0
- package/crates/enact-cli/src/commands/doctor.rs +62 -0
- package/crates/enact-cli/src/commands/mod.rs +3 -0
- package/crates/enact-cli/src/commands/run.rs +69 -0
- package/crates/enact-cli/src/commands/serve.rs +81 -0
- package/crates/enact-cli/src/config.rs +2 -0
- package/crates/enact-cli/src/main.rs +79 -0
- package/crates/enact-config/Cargo.toml +36 -0
- package/crates/enact-config/ENV_VAR_MAPPING.md +135 -0
- package/crates/enact-config/QUICK_REFERENCE.md +92 -0
- package/crates/enact-config/README.md +107 -0
- package/crates/enact-config/TESTING.md +161 -0
- package/crates/enact-config/examples/test-env-vars.rs +100 -0
- package/crates/enact-config/src/config.rs +399 -0
- package/crates/enact-config/src/encrypted_store.rs +211 -0
- package/crates/enact-config/src/lib.rs +298 -0
- package/crates/enact-config/src/secrets.rs +149 -0
- package/crates/enact-config/src/sync.rs +260 -0
- package/crates/enact-config/test-env-vars.sh +34 -0
- package/crates/enact-config/tests/README.md +99 -0
- package/crates/enact-config/tests/config_integration_test.rs +202 -0
- package/crates/enact-config/tests/security_test.rs +140 -0
- package/crates/enact-context/Cargo.toml +41 -0
- package/crates/enact-context/src/budget.rs +314 -0
- package/crates/enact-context/src/calibrator.rs +535 -0
- package/crates/enact-context/src/compactor.rs +392 -0
- package/crates/enact-context/src/condenser.rs +826 -0
- package/crates/enact-context/src/lib.rs +94 -0
- package/crates/enact-context/src/segment.rs +238 -0
- package/crates/enact-context/src/step_context.rs +645 -0
- package/crates/enact-context/src/token_counter.rs +148 -0
- package/crates/enact-context/src/window.rs +372 -0
- package/crates/enact-core/Cargo.toml +42 -0
- package/crates/enact-core/README.md +98 -0
- package/crates/enact-core/src/background/executor.rs +524 -0
- package/crates/enact-core/src/background/mod.rs +48 -0
- package/crates/enact-core/src/background/target_binding.rs +390 -0
- package/crates/enact-core/src/background/trigger.rs +511 -0
- package/crates/enact-core/src/callable/callable.rs +152 -0
- package/crates/enact-core/src/callable/composite.rs +817 -0
- package/crates/enact-core/src/callable/graph.rs +104 -0
- package/crates/enact-core/src/callable/llm.rs +211 -0
- package/crates/enact-core/src/callable/mod.rs +64 -0
- package/crates/enact-core/src/callable/registry.rs +206 -0
- package/crates/enact-core/src/context/execution_context.rs +757 -0
- package/crates/enact-core/src/context/invocation.rs +99 -0
- package/crates/enact-core/src/context/mod.rs +50 -0
- package/crates/enact-core/src/context/tenant.rs +175 -0
- package/crates/enact-core/src/context/trace.rs +127 -0
- package/crates/enact-core/src/flow/conditional.rs +293 -0
- package/crates/enact-core/src/flow/mod.rs +43 -0
- package/crates/enact-core/src/flow/parallel.rs +437 -0
- package/crates/enact-core/src/flow/repeat.rs +534 -0
- package/crates/enact-core/src/flow/sequential.rs +248 -0
- package/crates/enact-core/src/graph/checkpoint.rs +79 -0
- package/crates/enact-core/src/graph/checkpoint_store.rs +76 -0
- package/crates/enact-core/src/graph/compiled.rs +189 -0
- package/crates/enact-core/src/graph/edge.rs +59 -0
- package/crates/enact-core/src/graph/graph_schema.rs +218 -0
- package/crates/enact-core/src/graph/loader.rs +155 -0
- package/crates/enact-core/src/graph/mod.rs +18 -0
- package/crates/enact-core/src/graph/node/function.rs +49 -0
- package/crates/enact-core/src/graph/node/mod.rs +48 -0
- package/crates/enact-core/src/graph/schema.rs +62 -0
- package/crates/enact-core/src/inbox/message.rs +405 -0
- package/crates/enact-core/src/inbox/mod.rs +31 -0
- package/crates/enact-core/src/inbox/store.rs +355 -0
- package/crates/enact-core/src/kernel/artifact/filesystem.rs +546 -0
- package/crates/enact-core/src/kernel/artifact/metadata.rs +283 -0
- package/crates/enact-core/src/kernel/artifact/mod.rs +27 -0
- package/crates/enact-core/src/kernel/artifact/store.rs +427 -0
- package/crates/enact-core/src/kernel/enforcement.rs +1315 -0
- package/crates/enact-core/src/kernel/error.rs +1200 -0
- package/crates/enact-core/src/kernel/event.rs +1394 -0
- package/crates/enact-core/src/kernel/execution_model.rs +831 -0
- package/crates/enact-core/src/kernel/execution_state.rs +189 -0
- package/crates/enact-core/src/kernel/execution_strategy.rs +117 -0
- package/crates/enact-core/src/kernel/ids.rs +2086 -0
- package/crates/enact-core/src/kernel/interrupt.rs +125 -0
- package/crates/enact-core/src/kernel/kernel.rs +1283 -0
- package/crates/enact-core/src/kernel/mod.rs +205 -0
- package/crates/enact-core/src/kernel/persistence/event_store.rs +270 -0
- package/crates/enact-core/src/kernel/persistence/message_store.rs +908 -0
- package/crates/enact-core/src/kernel/persistence/mod.rs +102 -0
- package/crates/enact-core/src/kernel/persistence/state_store.rs +228 -0
- package/crates/enact-core/src/kernel/persistence/vector_store.rs +299 -0
- package/crates/enact-core/src/kernel/reducer.rs +808 -0
- package/crates/enact-core/src/kernel/replay.rs +153 -0
- package/crates/enact-core/src/lib.rs +413 -0
- package/crates/enact-core/src/memory/episodic.rs +0 -0
- package/crates/enact-core/src/memory/mod.rs +6 -0
- package/crates/enact-core/src/memory/semantic.rs +0 -0
- package/crates/enact-core/src/memory/trait.rs +0 -0
- package/crates/enact-core/src/memory/vector_db.rs +0 -0
- package/crates/enact-core/src/memory/working.rs +0 -0
- package/crates/enact-core/src/policy/execution_policy.rs +292 -0
- package/crates/enact-core/src/policy/filters.rs +458 -0
- package/crates/enact-core/src/policy/input_processor.rs +407 -0
- package/crates/enact-core/src/policy/long_running.rs +134 -0
- package/crates/enact-core/src/policy/mod.rs +193 -0
- package/crates/enact-core/src/policy/pii_input.rs +274 -0
- package/crates/enact-core/src/policy/tenant_policy.rs +453 -0
- package/crates/enact-core/src/policy/tool_policy.rs +407 -0
- package/crates/enact-core/src/providers/mod.rs +63 -0
- package/crates/enact-core/src/providers/trait.rs +292 -0
- package/crates/enact-core/src/runner/callbacks.rs +6 -0
- package/crates/enact-core/src/runner/execution_runner.rs +476 -0
- package/crates/enact-core/src/runner/loop.rs +117 -0
- package/crates/enact-core/src/runner/mod.rs +58 -0
- package/crates/enact-core/src/runner/protected_runner.rs +280 -0
- package/crates/enact-core/src/signal/inmemory.rs +231 -0
- package/crates/enact-core/src/signal/mod.rs +108 -0
- package/crates/enact-core/src/streaming/event_logger.rs +195 -0
- package/crates/enact-core/src/streaming/event_stream.rs +1423 -0
- package/crates/enact-core/src/streaming/mod.rs +108 -0
- package/crates/enact-core/src/streaming/pause_cancel.rs +0 -0
- package/crates/enact-core/src/streaming/protected_emitter.rs +173 -0
- package/crates/enact-core/src/streaming/protection/context.rs +136 -0
- package/crates/enact-core/src/streaming/protection/encryption.rs +289 -0
- package/crates/enact-core/src/streaming/protection/mod.rs +43 -0
- package/crates/enact-core/src/streaming/protection/pii_protection.rs +243 -0
- package/crates/enact-core/src/streaming/protection/processor.rs +166 -0
- package/crates/enact-core/src/streaming/sse.rs +0 -0
- package/crates/enact-core/src/telemetry/exporter.rs +0 -0
- package/crates/enact-core/src/telemetry/init.rs +0 -0
- package/crates/enact-core/src/telemetry/mod.rs +49 -0
- package/crates/enact-core/src/telemetry/spans.rs +245 -0
- package/crates/enact-core/src/tool/agent_tool.rs +177 -0
- package/crates/enact-core/src/tool/browser/mod.rs +0 -0
- package/crates/enact-core/src/tool/browser/webdriver.rs +0 -0
- package/crates/enact-core/src/tool/cost.rs +247 -0
- package/crates/enact-core/src/tool/discovery.rs +0 -0
- package/crates/enact-core/src/tool/dispatcher.rs +347 -0
- package/crates/enact-core/src/tool/filesystem.rs +231 -0
- package/crates/enact-core/src/tool/function.rs +99 -0
- package/crates/enact-core/src/tool/git.rs +162 -0
- package/crates/enact-core/src/tool/http.rs +214 -0
- package/crates/enact-core/src/tool/mcp/client.rs +0 -0
- package/crates/enact-core/src/tool/mcp/mod.rs +0 -0
- package/crates/enact-core/src/tool/mod.rs +51 -0
- package/crates/enact-core/src/tool/reasoning/debugging.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/mcts.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/mod.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/sequential.rs +0 -0
- package/crates/enact-core/src/tool/sandbox/dagger.rs +0 -0
- package/crates/enact-core/src/tool/sandbox/mod.rs +0 -0
- package/crates/enact-core/src/tool/shell.rs +147 -0
- package/crates/enact-core/src/tool/trait.rs +33 -0
- package/crates/enact-core/src/tool/web_search.rs +277 -0
- package/crates/enact-core/src/util/config.rs +0 -0
- package/crates/enact-core/src/util/errors.rs +0 -0
- package/crates/enact-core/src/util/mod.rs +6 -0
- package/crates/enact-core/tests/airgapped_e2e_test.rs +291 -0
- package/crates/enact-core/tests/e2e_agentic_loop.rs +119 -0
- package/crates/enact-core/tests/e2e_test.rs +259 -0
- package/crates/enact-core/tests/graph_test.rs +130 -0
- package/crates/enact-core/tests/stream_event_id_validation.rs +435 -0
- package/crates/enact-cron/Cargo.toml +28 -0
- package/crates/enact-cron/src/lib.rs +44 -0
- package/crates/enact-cron/src/schedule.rs +156 -0
- package/crates/enact-cron/src/store.rs +589 -0
- package/crates/enact-cron/src/types.rs +148 -0
- package/crates/enact-gateway/Cargo.toml +31 -0
- package/crates/enact-gateway/README.md +30 -0
- package/crates/enact-gateway/examples/whatsapp-gateway-runner-mock.rs +59 -0
- package/crates/enact-gateway/examples/whatsapp-gateway.rs +42 -0
- package/crates/enact-gateway/src/lib.rs +582 -0
- package/crates/enact-mcp/Cargo.toml +24 -0
- package/crates/enact-mcp/src/lib.rs +178 -0
- package/crates/enact-memory/Cargo.toml +25 -0
- package/crates/enact-memory/src/backend.rs +20 -0
- package/crates/enact-memory/src/chunker.rs +230 -0
- package/crates/enact-memory/src/embeddings.rs +221 -0
- package/crates/enact-memory/src/lib.rs +67 -0
- package/crates/enact-memory/src/markdown.rs +127 -0
- package/crates/enact-memory/src/none.rs +61 -0
- package/crates/enact-memory/src/sqlite.rs +276 -0
- package/crates/enact-memory/src/traits.rs +65 -0
- package/crates/enact-memory/src/vector.rs +198 -0
- package/crates/enact-oauth/Cargo.toml +27 -0
- package/crates/enact-oauth/src/lib.rs +584 -0
- package/crates/enact-observability/Cargo.toml +22 -0
- package/crates/enact-observability/src/lib.rs +197 -0
- package/crates/enact-providers/Cargo.toml +33 -0
- package/crates/enact-providers/examples/hello-agent.rs +33 -0
- package/crates/enact-providers/src/anthropic.rs +182 -0
- package/crates/enact-providers/src/azure.rs +96 -0
- package/crates/enact-providers/src/bridge.rs +221 -0
- package/crates/enact-providers/src/gemini.rs +227 -0
- package/crates/enact-providers/src/http.rs +78 -0
- package/crates/enact-providers/src/lib.rs +53 -0
- package/crates/enact-providers/src/openai_compatible.rs +167 -0
- package/crates/enact-providers/src/openrouter.rs +33 -0
- package/crates/enact-runner/Cargo.toml +24 -0
- package/crates/enact-runner/README.md +76 -0
- package/crates/enact-runner/src/compaction.rs +225 -0
- package/crates/enact-runner/src/config.rs +118 -0
- package/crates/enact-runner/src/lib.rs +63 -0
- package/crates/enact-runner/src/loop_driver.rs +414 -0
- package/crates/enact-runner/src/parser.rs +421 -0
- package/crates/enact-runner/src/retry.rs +262 -0
- package/crates/enact-runner/tests/integration.rs +278 -0
- package/crates/enact-security/Cargo.toml +22 -0
- package/crates/enact-security/src/audit.rs +375 -0
- package/crates/enact-security/src/lib.rs +37 -0
- package/crates/enact-security/src/policy.rs +406 -0
- package/crates/enact-skills/Cargo.toml +25 -0
- package/crates/enact-skills/src/lib.rs +506 -0
- package/crates/enact-tools/Cargo.toml +22 -0
- package/crates/enact-tools/src/file_read.rs +166 -0
- package/crates/enact-tools/src/file_write.rs +216 -0
- package/crates/enact-tools/src/git_operations.rs +513 -0
- package/crates/enact-tools/src/http_request.rs +417 -0
- package/crates/enact-tools/src/lib.rs +104 -0
- package/crates/enact-tools/src/security.rs +227 -0
- package/crates/enact-tools/src/shell.rs +191 -0
- package/crates/enact-tools/src/traits.rs +159 -0
- package/docs/Makefile +74 -0
- package/docs/config.toml +62 -0
- package/docs/content/_index.md +174 -0
- package/docs/content/a2a/_index.md +431 -0
- package/docs/content/api/_index.md +323 -0
- package/docs/content/channels/_index.md +160 -0
- package/docs/content/channels/teams.md +205 -0
- package/docs/content/channels/telegram.md +182 -0
- package/docs/content/channels/webhook.md +423 -0
- package/docs/content/channels/whatsapp.md +240 -0
- package/docs/content/cli/_index.md +261 -0
- package/docs/content/concepts/_index.md +273 -0
- package/docs/content/configuration/_index.md +241 -0
- package/docs/content/cron/_index.md +248 -0
- package/docs/content/developers/_index.md +278 -0
- package/docs/content/getting-started/_index.md +180 -0
- package/docs/content/installation/_index.md +186 -0
- package/docs/content/installation/uninstall.md +101 -0
- package/docs/content/installation/updating.md +120 -0
- package/docs/content/mcp/_index.md +215 -0
- package/docs/content/memory/_index.md +163 -0
- package/docs/content/oauth/_index.md +515 -0
- package/docs/content/providers/_index.md +206 -0
- package/docs/content/roadmap/_index.md +199 -0
- package/docs/content/security/_index.md +219 -0
- package/docs/content/skills/_index.md +228 -0
- package/docs/content/tools/_index.md +485 -0
- package/docs/content/troubleshooting/_index.md +259 -0
- package/docs/content/yaml-schema/_index.md +294 -0
- package/docs/static/giallo-dark.css +91 -0
- package/docs/static/giallo-light.css +91 -0
- package/docs/themes/tanuki/.github/workflows/deploy.yml +44 -0
- package/docs/themes/tanuki/LICENSE +21 -0
- package/docs/themes/tanuki/README.md +166 -0
- package/docs/themes/tanuki/examples/blog/config.toml +58 -0
- package/docs/themes/tanuki/examples/blog/content/_index.md +4 -0
- package/docs/themes/tanuki/examples/blog/content/about.md +33 -0
- package/docs/themes/tanuki/examples/blog/content/blog/_index.md +7 -0
- package/docs/themes/tanuki/examples/blog/content/blog/api-design-best-practices.md +245 -0
- package/docs/themes/tanuki/examples/blog/content/blog/building-accessible-websites.md +147 -0
- package/docs/themes/tanuki/examples/blog/content/blog/css-grid-vs-flexbox.md +165 -0
- package/docs/themes/tanuki/examples/blog/content/blog/customizing-catppuccin-colors.md +137 -0
- package/docs/themes/tanuki/examples/blog/content/blog/dark-mode-best-practices.md +82 -0
- package/docs/themes/tanuki/examples/blog/content/blog/docker-essentials.md +301 -0
- package/docs/themes/tanuki/examples/blog/content/blog/getting-started-with-zola.md +129 -0
- package/docs/themes/tanuki/examples/blog/content/blog/git-workflow-for-content.md +112 -0
- package/docs/themes/tanuki/examples/blog/content/blog/introduction-to-webassembly.md +183 -0
- package/docs/themes/tanuki/examples/blog/content/blog/modern-javascript-features.md +234 -0
- package/docs/themes/tanuki/examples/blog/content/blog/testing-strategies.md +311 -0
- package/docs/themes/tanuki/examples/blog/content/blog/typography-for-developers.md +104 -0
- package/docs/themes/tanuki/examples/blog/content/blog/welcome-to-tanuki.md +67 -0
- package/docs/themes/tanuki/examples/blog/content/blog/why-static-sites.md +85 -0
- package/docs/themes/tanuki/examples/blog/content/projects.md +64 -0
- package/docs/themes/tanuki/examples/book/config.toml +17 -0
- package/docs/themes/tanuki/examples/book/content/_index.md +12 -0
- package/docs/themes/tanuki/examples/book/content/chapter-1.md +90 -0
- package/docs/themes/tanuki/examples/book/content/chapter-2.md +143 -0
- package/docs/themes/tanuki/examples/book/content/chapter-3.md +217 -0
- package/docs/themes/tanuki/examples/book/content/chapter-4.md +224 -0
- package/docs/themes/tanuki/examples/book/content/chapter-5.md +297 -0
- package/docs/themes/tanuki/examples/book/content/print.md +6 -0
- package/docs/themes/tanuki/examples/docs/config.toml +28 -0
- package/docs/themes/tanuki/examples/docs/content/_index.md +20 -0
- package/docs/themes/tanuki/examples/docs/content/components.md +156 -0
- package/docs/themes/tanuki/examples/docs/content/configuration.md +94 -0
- package/docs/themes/tanuki/examples/docs/content/customization.md +202 -0
- package/docs/themes/tanuki/examples/docs/content/deployment.md +204 -0
- package/docs/themes/tanuki/examples/docs/content/installation.md +59 -0
- package/docs/themes/tanuki/examples/docs/content/print.md +6 -0
- package/docs/themes/tanuki/examples/docs/static/img/tanuki-icon.avif +0 -0
- package/docs/themes/tanuki/examples/index.html +2104 -0
- package/docs/themes/tanuki/mise.toml +108 -0
- package/docs/themes/tanuki/sass/base/_catppuccin.scss +164 -0
- package/docs/themes/tanuki/sass/base/_fonts.scss +64 -0
- package/docs/themes/tanuki/sass/base/_reset.scss +152 -0
- package/docs/themes/tanuki/sass/base/_typography.scss +523 -0
- package/docs/themes/tanuki/sass/components/_buttons.scss +209 -0
- package/docs/themes/tanuki/sass/components/_code.scss +457 -0
- package/docs/themes/tanuki/sass/components/_landing.scss +633 -0
- package/docs/themes/tanuki/sass/components/_layout.scss +294 -0
- package/docs/themes/tanuki/sass/components/_navigation.scss +1200 -0
- package/docs/themes/tanuki/sass/components/_print.scss +237 -0
- package/docs/themes/tanuki/sass/components/_search.scss +224 -0
- package/docs/themes/tanuki/sass/components/_sidebar.scss +473 -0
- package/docs/themes/tanuki/sass/components/_theme-toggle.scss +186 -0
- package/docs/themes/tanuki/sass/modes/_blog.scss +366 -0
- package/docs/themes/tanuki/sass/modes/_product.scss +875 -0
- package/docs/themes/tanuki/sass/modes/_raskell.scss +1696 -0
- package/docs/themes/tanuki/sass/patterns/_buttons.scss +183 -0
- package/docs/themes/tanuki/sass/patterns/_cards.scss +144 -0
- package/docs/themes/tanuki/sass/patterns/_index.scss +9 -0
- package/docs/themes/tanuki/sass/patterns/_lists.scss +259 -0
- package/docs/themes/tanuki/sass/patterns/_sections.scss +243 -0
- package/docs/themes/tanuki/sass/style.scss +47 -0
- package/docs/themes/tanuki/sass/tokens/_colors.scss +139 -0
- package/docs/themes/tanuki/sass/tokens/_spacing.scss +100 -0
- package/docs/themes/tanuki/sass/tokens/_typography.scss +186 -0
- package/docs/themes/tanuki/screenshot.png +0 -0
- package/docs/themes/tanuki/sentinel.kdl +59 -0
- package/docs/themes/tanuki/static/elasticlunr.min.js +10 -0
- package/docs/themes/tanuki/static/fonts/GEIST-LICENSE.txt +92 -0
- package/docs/themes/tanuki/static/fonts/Geist-Variable.woff2 +0 -0
- package/docs/themes/tanuki/static/fonts/GeistMono-Variable.woff2 +0 -0
- package/docs/themes/tanuki/static/img/tanuki-icon.avif +0 -0
- package/docs/themes/tanuki/static/img/tanuki-icon.png +0 -0
- package/docs/themes/tanuki/static/js/anchors.js +18 -0
- package/docs/themes/tanuki/static/js/app.js +274 -0
- package/docs/themes/tanuki/static/js/code.js +394 -0
- package/docs/themes/tanuki/static/js/navigation.js +778 -0
- package/docs/themes/tanuki/static/js/scroll-to-top.js +33 -0
- package/docs/themes/tanuki/static/js/search-raskell.js +240 -0
- package/docs/themes/tanuki/static/js/search.js +215 -0
- package/docs/themes/tanuki/static/js/theme.js +169 -0
- package/docs/themes/tanuki/static/syntax-dark.css +151 -0
- package/docs/themes/tanuki/static/syntax-light.css +151 -0
- package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm.js +486 -0
- package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm_bg.wasm +0 -0
- package/docs/themes/tanuki/templates/404.html +52 -0
- package/docs/themes/tanuki/templates/base.html +428 -0
- package/docs/themes/tanuki/templates/blog.html +66 -0
- package/docs/themes/tanuki/templates/home.html +108 -0
- package/docs/themes/tanuki/templates/index.html +178 -0
- package/docs/themes/tanuki/templates/landing.html +168 -0
- package/docs/themes/tanuki/templates/macros/nav.html +128 -0
- package/docs/themes/tanuki/templates/macros/posts.html +101 -0
- package/docs/themes/tanuki/templates/macros/ui.html +159 -0
- package/docs/themes/tanuki/templates/page.html +135 -0
- package/docs/themes/tanuki/templates/partials/footer.html +38 -0
- package/docs/themes/tanuki/templates/partials/header.html +366 -0
- package/docs/themes/tanuki/templates/partials/nav-buttons.html +55 -0
- package/docs/themes/tanuki/templates/partials/nav-overlay.html +81 -0
- package/docs/themes/tanuki/templates/partials/page-toc-panel.html +43 -0
- package/docs/themes/tanuki/templates/partials/search.html +52 -0
- package/docs/themes/tanuki/templates/partials/sidebar.html +107 -0
- package/docs/themes/tanuki/templates/partials/theme-toggle.html +35 -0
- package/docs/themes/tanuki/templates/partials/toc-overlay.html +146 -0
- package/docs/themes/tanuki/templates/partials/version-picker.html +38 -0
- package/docs/themes/tanuki/templates/print.html +244 -0
- package/docs/themes/tanuki/templates/section.html +186 -0
- package/docs/themes/tanuki/templates/taxonomy_list.html +18 -0
- package/docs/themes/tanuki/templates/taxonomy_single.html +31 -0
- package/docs/themes/tanuki/theme.toml +58 -0
- package/examples/hello-agent.rs +55 -0
- package/package.json +36 -0
- package/proto/config.proto +60 -0
- package/proto/events.proto +0 -0
- package/proto/runtime.proto +215 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
//! Git operations tool
|
|
2
|
+
|
|
3
|
+
use crate::tool::Tool;
|
|
4
|
+
use async_trait::async_trait;
|
|
5
|
+
use serde_json::json;
|
|
6
|
+
use std::process::Stdio;
|
|
7
|
+
use tokio::process::Command;
|
|
8
|
+
|
|
9
|
+
/// Git operations tool
|
|
10
|
+
pub struct GitTool;
|
|
11
|
+
|
|
12
|
+
impl GitTool {
|
|
13
|
+
pub fn new() -> Self {
|
|
14
|
+
Self
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
impl Default for GitTool {
|
|
19
|
+
fn default() -> Self {
|
|
20
|
+
Self::new()
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#[async_trait]
|
|
25
|
+
impl Tool for GitTool {
|
|
26
|
+
fn name(&self) -> &str {
|
|
27
|
+
"git"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fn description(&self) -> &str {
|
|
31
|
+
"Execute git commands (clone, status, add, commit, push, pull, etc.)"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fn parameters_schema(&self) -> serde_json::Value {
|
|
35
|
+
json!({
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"operation": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"enum": ["clone", "status", "add", "commit", "push", "pull", "branch", "checkout", "log"],
|
|
41
|
+
"description": "Git operation to perform"
|
|
42
|
+
},
|
|
43
|
+
"args": {
|
|
44
|
+
"type": "array",
|
|
45
|
+
"items": { "type": "string" },
|
|
46
|
+
"description": "Additional arguments for the git command"
|
|
47
|
+
},
|
|
48
|
+
"message": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "Commit message (for commit operation)"
|
|
51
|
+
},
|
|
52
|
+
"repo_url": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "Repository URL (for clone operation)"
|
|
55
|
+
},
|
|
56
|
+
"directory": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"description": "Target directory (for clone) or repository directory"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"required": ["operation"]
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn requires_network(&self) -> bool {
|
|
66
|
+
true // Git often requires network access
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<serde_json::Value> {
|
|
70
|
+
let operation = args
|
|
71
|
+
.get("operation")
|
|
72
|
+
.and_then(|v| v.as_str())
|
|
73
|
+
.ok_or_else(|| anyhow::anyhow!("Missing 'operation' parameter"))?;
|
|
74
|
+
|
|
75
|
+
let operation_args = args
|
|
76
|
+
.get("args")
|
|
77
|
+
.and_then(|v| v.as_array())
|
|
78
|
+
.map(|arr| {
|
|
79
|
+
arr.iter()
|
|
80
|
+
.filter_map(|v| v.as_str())
|
|
81
|
+
.map(|s| s.to_string())
|
|
82
|
+
.collect::<Vec<_>>()
|
|
83
|
+
})
|
|
84
|
+
.unwrap_or_default();
|
|
85
|
+
|
|
86
|
+
let mut cmd = Command::new("git");
|
|
87
|
+
cmd.arg(operation);
|
|
88
|
+
|
|
89
|
+
// Handle specific operations with special parameters
|
|
90
|
+
match operation {
|
|
91
|
+
"clone" => {
|
|
92
|
+
if let Some(url) = args.get("repo_url").and_then(|v| v.as_str()) {
|
|
93
|
+
cmd.arg(url);
|
|
94
|
+
} else {
|
|
95
|
+
anyhow::bail!("clone operation requires 'repo_url' parameter");
|
|
96
|
+
}
|
|
97
|
+
if let Some(dir) = args.get("directory").and_then(|v| v.as_str()) {
|
|
98
|
+
cmd.arg(dir);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
"commit" => {
|
|
102
|
+
if let Some(msg) = args.get("message").and_then(|v| v.as_str()) {
|
|
103
|
+
cmd.arg("-m").arg(msg);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
_ => {}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Add additional args
|
|
110
|
+
for arg in operation_args {
|
|
111
|
+
cmd.arg(arg);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Set working directory if specified
|
|
115
|
+
if let Some(dir) = args.get("directory").and_then(|v| v.as_str()) {
|
|
116
|
+
cmd.current_dir(dir);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let output = cmd.output().await?;
|
|
120
|
+
|
|
121
|
+
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
|
122
|
+
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
|
123
|
+
|
|
124
|
+
Ok(json!({
|
|
125
|
+
"success": output.status.success(),
|
|
126
|
+
"stdout": stdout,
|
|
127
|
+
"stderr": stderr,
|
|
128
|
+
"exit_code": output.status.code(),
|
|
129
|
+
"operation": operation
|
|
130
|
+
}))
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#[cfg(test)]
|
|
135
|
+
mod tests {
|
|
136
|
+
use super::*;
|
|
137
|
+
|
|
138
|
+
#[tokio::test]
|
|
139
|
+
async fn test_git_status() {
|
|
140
|
+
let tool = GitTool::new();
|
|
141
|
+
let result = tool.execute(json!({
|
|
142
|
+
"operation": "status",
|
|
143
|
+
"directory": "."
|
|
144
|
+
})).await.unwrap();
|
|
145
|
+
|
|
146
|
+
// Should succeed if we're in a git repo
|
|
147
|
+
if result["success"].as_bool().unwrap_or(false) {
|
|
148
|
+
assert!(result["stdout"].as_str().is_some());
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#[tokio::test]
|
|
153
|
+
async fn test_git_version() {
|
|
154
|
+
let tool = GitTool::new();
|
|
155
|
+
let result = tool.execute(json!({
|
|
156
|
+
"operation": "version"
|
|
157
|
+
})).await.unwrap();
|
|
158
|
+
|
|
159
|
+
assert_eq!(result["success"], true);
|
|
160
|
+
assert!(result["stdout"].as_str().unwrap().contains("git version"));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
//! HTTP request tool
|
|
2
|
+
|
|
3
|
+
use crate::tool::Tool;
|
|
4
|
+
use async_trait::async_trait;
|
|
5
|
+
use serde_json::json;
|
|
6
|
+
use std::collections::HashMap;
|
|
7
|
+
use std::time::Duration;
|
|
8
|
+
|
|
9
|
+
const DEFAULT_TIMEOUT_SECS: u64 = 30;
|
|
10
|
+
const MAX_RESPONSE_SIZE: usize = 10 * 1024 * 1024; // 10MB
|
|
11
|
+
|
|
12
|
+
/// HTTP request tool for making API calls
|
|
13
|
+
pub struct HttpRequestTool;
|
|
14
|
+
|
|
15
|
+
impl HttpRequestTool {
|
|
16
|
+
pub fn new() -> Self {
|
|
17
|
+
Self
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl Default for HttpRequestTool {
|
|
22
|
+
fn default() -> Self {
|
|
23
|
+
Self::new()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[async_trait]
|
|
28
|
+
impl Tool for HttpRequestTool {
|
|
29
|
+
fn name(&self) -> &str {
|
|
30
|
+
"http_request"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fn description(&self) -> &str {
|
|
34
|
+
"Make HTTP requests (GET, POST, PUT, DELETE, etc.) to APIs and web services"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn parameters_schema(&self) -> serde_json::Value {
|
|
38
|
+
json!({
|
|
39
|
+
"type": "object",
|
|
40
|
+
"properties": {
|
|
41
|
+
"url": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "URL to request"
|
|
44
|
+
},
|
|
45
|
+
"method": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"enum": ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
|
|
48
|
+
"default": "GET"
|
|
49
|
+
},
|
|
50
|
+
"headers": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"description": "HTTP headers as key-value pairs"
|
|
53
|
+
},
|
|
54
|
+
"body": {
|
|
55
|
+
"type": ["string", "object"],
|
|
56
|
+
"description": "Request body (for POST, PUT, PATCH)"
|
|
57
|
+
},
|
|
58
|
+
"timeout": {
|
|
59
|
+
"type": "integer",
|
|
60
|
+
"description": "Timeout in seconds (default: 30)",
|
|
61
|
+
"minimum": 1,
|
|
62
|
+
"maximum": 300
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"required": ["url"]
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fn requires_network(&self) -> bool {
|
|
70
|
+
true
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<serde_json::Value> {
|
|
74
|
+
let url = args
|
|
75
|
+
.get("url")
|
|
76
|
+
.and_then(|v| v.as_str())
|
|
77
|
+
.ok_or_else(|| anyhow::anyhow!("Missing 'url' parameter"))?;
|
|
78
|
+
|
|
79
|
+
let method = args
|
|
80
|
+
.get("method")
|
|
81
|
+
.and_then(|v| v.as_str())
|
|
82
|
+
.unwrap_or("GET");
|
|
83
|
+
|
|
84
|
+
let timeout_secs = args
|
|
85
|
+
.get("timeout")
|
|
86
|
+
.and_then(|v| v.as_u64())
|
|
87
|
+
.unwrap_or(DEFAULT_TIMEOUT_SECS);
|
|
88
|
+
|
|
89
|
+
let client = reqwest::Client::builder()
|
|
90
|
+
.timeout(Duration::from_secs(timeout_secs))
|
|
91
|
+
.build()?;
|
|
92
|
+
|
|
93
|
+
let mut request = match method.to_uppercase().as_str() {
|
|
94
|
+
"GET" => client.get(url),
|
|
95
|
+
"POST" => client.post(url),
|
|
96
|
+
"PUT" => client.put(url),
|
|
97
|
+
"DELETE" => client.delete(url),
|
|
98
|
+
"PATCH" => client.patch(url),
|
|
99
|
+
"HEAD" => client.head(url),
|
|
100
|
+
_ => client.get(url),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Add headers
|
|
104
|
+
if let Some(headers) = args.get("headers").and_then(|h| h.as_object()) {
|
|
105
|
+
for (key, value) in headers {
|
|
106
|
+
if let Some(val_str) = value.as_str() {
|
|
107
|
+
request = request.header(key, val_str);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Add body
|
|
113
|
+
if let Some(body) = args.get("body") {
|
|
114
|
+
let body_str = if body.is_object() {
|
|
115
|
+
serde_json::to_string(body)?
|
|
116
|
+
} else {
|
|
117
|
+
body.as_str().unwrap_or("").to_string()
|
|
118
|
+
};
|
|
119
|
+
request = request.body(body_str);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let response = request.send().await?;
|
|
123
|
+
let status = response.status();
|
|
124
|
+
let headers: HashMap<String, String> = response
|
|
125
|
+
.headers()
|
|
126
|
+
.iter()
|
|
127
|
+
.filter_map(|(k, v)| {
|
|
128
|
+
v.to_str()
|
|
129
|
+
.ok()
|
|
130
|
+
.map(|v| (k.to_string(), v.to_string()))
|
|
131
|
+
})
|
|
132
|
+
.collect();
|
|
133
|
+
|
|
134
|
+
let content_type = response
|
|
135
|
+
.headers()
|
|
136
|
+
.get("content-type")
|
|
137
|
+
.and_then(|v| v.to_str().ok())
|
|
138
|
+
.unwrap_or("")
|
|
139
|
+
.to_lowercase();
|
|
140
|
+
|
|
141
|
+
// Read body with size limit
|
|
142
|
+
let body_bytes = response.bytes().await?;
|
|
143
|
+
if body_bytes.len() > MAX_RESPONSE_SIZE {
|
|
144
|
+
anyhow::bail!(
|
|
145
|
+
"Response too large: {} bytes (max: {})",
|
|
146
|
+
body_bytes.len(),
|
|
147
|
+
MAX_RESPONSE_SIZE
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Try to parse as JSON, fallback to text
|
|
152
|
+
let body = if content_type.contains("application/json") {
|
|
153
|
+
match serde_json::from_slice::<serde_json::Value>(&body_bytes) {
|
|
154
|
+
Ok(json) => json,
|
|
155
|
+
Err(_) => {
|
|
156
|
+
serde_json::Value::String(
|
|
157
|
+
String::from_utf8_lossy(&body_bytes).to_string()
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
serde_json::Value::String(String::from_utf8_lossy(&body_bytes).to_string())
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
Ok(json!({
|
|
166
|
+
"success": status.is_success(),
|
|
167
|
+
"status_code": status.as_u16(),
|
|
168
|
+
"status_text": status.canonical_reason(),
|
|
169
|
+
"headers": headers,
|
|
170
|
+
"body": body,
|
|
171
|
+
"url": url,
|
|
172
|
+
"method": method
|
|
173
|
+
}))
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#[cfg(test)]
|
|
178
|
+
mod tests {
|
|
179
|
+
use super::*;
|
|
180
|
+
|
|
181
|
+
#[tokio::test]
|
|
182
|
+
async fn test_http_get() {
|
|
183
|
+
let tool = HttpRequestTool::new();
|
|
184
|
+
let result = tool
|
|
185
|
+
.execute(json!({
|
|
186
|
+
"url": "https://httpbin.org/get",
|
|
187
|
+
"method": "GET"
|
|
188
|
+
}))
|
|
189
|
+
.await;
|
|
190
|
+
|
|
191
|
+
// May fail if no network
|
|
192
|
+
if let Ok(response) = result {
|
|
193
|
+
assert_eq!(response["success"], true);
|
|
194
|
+
assert_eq!(response["status_code"], 200);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#[tokio::test]
|
|
199
|
+
async fn test_http_post() {
|
|
200
|
+
let tool = HttpRequestTool::new();
|
|
201
|
+
let result = tool
|
|
202
|
+
.execute(json!({
|
|
203
|
+
"url": "https://httpbin.org/post",
|
|
204
|
+
"method": "POST",
|
|
205
|
+
"body": { "test": "data" }
|
|
206
|
+
}))
|
|
207
|
+
.await;
|
|
208
|
+
|
|
209
|
+
if let Ok(response) = result {
|
|
210
|
+
assert_eq!(response["success"], true);
|
|
211
|
+
assert_eq!(response["status_code"], 200);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//! Tool module - Tool trait and policy-aware execution
|
|
2
|
+
//!
|
|
3
|
+
//! ## Key Invariant: All tool execution must go through ToolExecutor
|
|
4
|
+
//!
|
|
5
|
+
//! The ToolExecutor ensures that every tool invocation passes through
|
|
6
|
+
//! ToolPolicy::evaluate() before execution. This is the security boundary.
|
|
7
|
+
//!
|
|
8
|
+
//! ```ignore
|
|
9
|
+
//! // CORRECT: Use ToolExecutor for all tool calls
|
|
10
|
+
//! let executor = ToolExecutor::new(policy);
|
|
11
|
+
//! let result = executor.execute(&tool, args, &ctx).await?;
|
|
12
|
+
//!
|
|
13
|
+
//! // WRONG: Direct tool.execute() bypasses policy!
|
|
14
|
+
//! // let result = tool.execute(args).await?; // Don't do this!
|
|
15
|
+
//! ```
|
|
16
|
+
|
|
17
|
+
mod agent_tool;
|
|
18
|
+
mod discovery;
|
|
19
|
+
mod dispatcher;
|
|
20
|
+
mod function;
|
|
21
|
+
mod r#trait;
|
|
22
|
+
|
|
23
|
+
pub mod browser;
|
|
24
|
+
pub mod cost;
|
|
25
|
+
pub mod filesystem;
|
|
26
|
+
pub mod git;
|
|
27
|
+
pub mod http;
|
|
28
|
+
pub mod mcp;
|
|
29
|
+
pub mod reasoning;
|
|
30
|
+
pub mod sandbox;
|
|
31
|
+
pub mod shell;
|
|
32
|
+
pub mod web_search;
|
|
33
|
+
|
|
34
|
+
// Tool trait
|
|
35
|
+
pub use r#trait::{DynTool, Tool};
|
|
36
|
+
|
|
37
|
+
// Tool implementations
|
|
38
|
+
pub use cost::{CostMetrics, CostTool, CostTracker};
|
|
39
|
+
pub use filesystem::{FileReadTool, FileWriteTool};
|
|
40
|
+
pub use function::FunctionTool;
|
|
41
|
+
pub use git::GitTool;
|
|
42
|
+
pub use http::HttpRequestTool;
|
|
43
|
+
pub use shell::ShellTool;
|
|
44
|
+
pub use web_search::WebSearchTool;
|
|
45
|
+
|
|
46
|
+
// AgentTool - privileged adapter (see agent_tool.rs for security warnings)
|
|
47
|
+
// Note: AgentTool is available but should only be used by trusted runtime components
|
|
48
|
+
pub use agent_tool::AgentTool;
|
|
49
|
+
|
|
50
|
+
// Policy-aware executor (REQUIRED for all tool invocations)
|
|
51
|
+
pub use dispatcher::{ToolExecutionContext, ToolExecutionError, ToolExecutor};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
//! Shell command execution tool
|
|
2
|
+
|
|
3
|
+
use crate::tool::Tool;
|
|
4
|
+
use async_trait::async_trait;
|
|
5
|
+
use serde_json::json;
|
|
6
|
+
use std::time::Duration;
|
|
7
|
+
use tokio::process::Command;
|
|
8
|
+
|
|
9
|
+
const SHELL_TIMEOUT_SECS: u64 = 60;
|
|
10
|
+
const MAX_OUTPUT_BYTES: usize = 1_048_576; // 1MB
|
|
11
|
+
|
|
12
|
+
/// Shell command execution tool
|
|
13
|
+
pub struct ShellTool;
|
|
14
|
+
|
|
15
|
+
impl ShellTool {
|
|
16
|
+
pub fn new() -> Self {
|
|
17
|
+
Self
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl Default for ShellTool {
|
|
22
|
+
fn default() -> Self {
|
|
23
|
+
Self::new()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[async_trait]
|
|
28
|
+
impl Tool for ShellTool {
|
|
29
|
+
fn name(&self) -> &str {
|
|
30
|
+
"shell"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fn description(&self) -> &str {
|
|
34
|
+
"Execute a shell command in the workspace directory"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn parameters_schema(&self) -> serde_json::Value {
|
|
38
|
+
json!({
|
|
39
|
+
"type": "object",
|
|
40
|
+
"properties": {
|
|
41
|
+
"command": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "The shell command to execute"
|
|
44
|
+
},
|
|
45
|
+
"timeout": {
|
|
46
|
+
"type": "integer",
|
|
47
|
+
"description": "Timeout in seconds (default: 60)",
|
|
48
|
+
"minimum": 1,
|
|
49
|
+
"maximum": 300
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"required": ["command"]
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fn requires_network(&self) -> bool {
|
|
57
|
+
false // Shell can work offline, though commands might need network
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<serde_json::Value> {
|
|
61
|
+
let command = args
|
|
62
|
+
.get("command")
|
|
63
|
+
.and_then(|v| v.as_str())
|
|
64
|
+
.ok_or_else(|| anyhow::anyhow!("Missing 'command' parameter"))?;
|
|
65
|
+
|
|
66
|
+
let timeout_secs = args
|
|
67
|
+
.get("timeout")
|
|
68
|
+
.and_then(|v| v.as_u64())
|
|
69
|
+
.unwrap_or(SHELL_TIMEOUT_SECS);
|
|
70
|
+
|
|
71
|
+
let timeout = Duration::from_secs(timeout_secs.min(300));
|
|
72
|
+
|
|
73
|
+
// Use sh -c for shell commands
|
|
74
|
+
let output = tokio::time::timeout(
|
|
75
|
+
timeout,
|
|
76
|
+
Command::new("sh")
|
|
77
|
+
.arg("-c")
|
|
78
|
+
.arg(command)
|
|
79
|
+
.output(),
|
|
80
|
+
)
|
|
81
|
+
.await
|
|
82
|
+
.map_err(|_| anyhow::anyhow!("Command timed out after {} seconds", timeout_secs))??;
|
|
83
|
+
|
|
84
|
+
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
85
|
+
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
86
|
+
|
|
87
|
+
// Truncate if too large
|
|
88
|
+
let stdout = if stdout.len() > MAX_OUTPUT_BYTES {
|
|
89
|
+
format!("{}... [truncated]", &stdout[..MAX_OUTPUT_BYTES])
|
|
90
|
+
} else {
|
|
91
|
+
stdout.to_string()
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
let stderr = if stderr.len() > MAX_OUTPUT_BYTES {
|
|
95
|
+
format!("{}... [truncated]", &stderr[..MAX_OUTPUT_BYTES])
|
|
96
|
+
} else {
|
|
97
|
+
stderr.to_string()
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
Ok(json!({
|
|
101
|
+
"success": output.status.success(),
|
|
102
|
+
"stdout": stdout,
|
|
103
|
+
"stderr": stderr,
|
|
104
|
+
"exit_code": output.status.code(),
|
|
105
|
+
"command": command
|
|
106
|
+
}))
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#[cfg(test)]
|
|
111
|
+
mod tests {
|
|
112
|
+
use super::*;
|
|
113
|
+
|
|
114
|
+
#[tokio::test]
|
|
115
|
+
async fn test_shell_echo() {
|
|
116
|
+
let tool = ShellTool::new();
|
|
117
|
+
let result = tool.execute(json!({
|
|
118
|
+
"command": "echo 'Hello World'"
|
|
119
|
+
})).await.unwrap();
|
|
120
|
+
|
|
121
|
+
assert_eq!(result["success"], true);
|
|
122
|
+
assert!(result["stdout"].as_str().unwrap().contains("Hello World"));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#[tokio::test]
|
|
126
|
+
async fn test_shell_error() {
|
|
127
|
+
let tool = ShellTool::new();
|
|
128
|
+
let result = tool.execute(json!({
|
|
129
|
+
"command": "exit 1"
|
|
130
|
+
})).await.unwrap();
|
|
131
|
+
|
|
132
|
+
assert_eq!(result["success"], false);
|
|
133
|
+
assert_eq!(result["exit_code"], 1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#[tokio::test]
|
|
137
|
+
async fn test_shell_timeout() {
|
|
138
|
+
let tool = ShellTool::new();
|
|
139
|
+
let result = tool.execute(json!({
|
|
140
|
+
"command": "sleep 10",
|
|
141
|
+
"timeout": 1
|
|
142
|
+
})).await;
|
|
143
|
+
|
|
144
|
+
assert!(result.is_err());
|
|
145
|
+
assert!(result.unwrap_err().to_string().contains("timed out"));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//! Tool trait definition
|
|
2
|
+
|
|
3
|
+
use async_trait::async_trait;
|
|
4
|
+
use serde_json::Value;
|
|
5
|
+
use std::sync::Arc;
|
|
6
|
+
|
|
7
|
+
/// Tool trait - all tools implement this
|
|
8
|
+
#[async_trait]
|
|
9
|
+
pub trait Tool: Send + Sync {
|
|
10
|
+
/// Tool name
|
|
11
|
+
fn name(&self) -> &str;
|
|
12
|
+
|
|
13
|
+
/// Tool description
|
|
14
|
+
fn description(&self) -> &str;
|
|
15
|
+
|
|
16
|
+
/// JSON schema for tool parameters
|
|
17
|
+
fn parameters_schema(&self) -> Value {
|
|
18
|
+
serde_json::json!({})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Whether this tool requires network access.
|
|
22
|
+
/// Default is `true` (conservative).
|
|
23
|
+
/// Override to `false` for local-only tools (e.g., file, calculator).
|
|
24
|
+
fn requires_network(&self) -> bool {
|
|
25
|
+
true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Execute the tool with the given arguments
|
|
29
|
+
async fn execute(&self, args: Value) -> anyhow::Result<Value>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Boxed tool for dynamic dispatch
|
|
33
|
+
pub type DynTool = Arc<dyn Tool>;
|