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,245 @@
|
|
|
1
|
+
//! Telemetry Spans - OpenTelemetry span helpers for execution tracing
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides utilities for creating and managing OpenTelemetry spans
|
|
4
|
+
//! that correlate with ExecutionId and StepId for distributed tracing.
|
|
5
|
+
//!
|
|
6
|
+
//! ## Span Hierarchy
|
|
7
|
+
//! ```text
|
|
8
|
+
//! Execution Span (trace_id = TraceContext.trace_id)
|
|
9
|
+
//! ├── Step Span (span_id = unique per step)
|
|
10
|
+
//! │ └── Tool Span
|
|
11
|
+
//! ├── Step Span
|
|
12
|
+
//! │ └── LLM Span
|
|
13
|
+
//! └── Child Execution Span (sub-agent)
|
|
14
|
+
//! └── Step Span...
|
|
15
|
+
//! ```
|
|
16
|
+
//!
|
|
17
|
+
//! @see docs/TECHNICAL/01-EXECUTION-TELEMETRY.md
|
|
18
|
+
|
|
19
|
+
use crate::kernel::{ExecutionId, StepId, StepType};
|
|
20
|
+
use crate::runner::TraceContext;
|
|
21
|
+
|
|
22
|
+
/// Span attributes for execution-level spans
|
|
23
|
+
pub struct ExecutionSpanAttributes {
|
|
24
|
+
/// Execution ID
|
|
25
|
+
pub execution_id: String,
|
|
26
|
+
/// Parent ID (if nested execution)
|
|
27
|
+
pub parent_id: Option<String>,
|
|
28
|
+
/// Parent type
|
|
29
|
+
pub parent_type: Option<String>,
|
|
30
|
+
/// Tenant ID
|
|
31
|
+
pub tenant_id: Option<String>,
|
|
32
|
+
/// User ID
|
|
33
|
+
pub user_id: Option<String>,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
impl ExecutionSpanAttributes {
|
|
37
|
+
/// Create from an ExecutionId
|
|
38
|
+
pub fn new(execution_id: &ExecutionId) -> Self {
|
|
39
|
+
Self {
|
|
40
|
+
execution_id: execution_id.as_str().to_string(),
|
|
41
|
+
parent_id: None,
|
|
42
|
+
parent_type: None,
|
|
43
|
+
tenant_id: None,
|
|
44
|
+
user_id: None,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Add parent context
|
|
49
|
+
pub fn with_parent(mut self, parent_id: impl Into<String>, parent_type: impl Into<String>) -> Self {
|
|
50
|
+
self.parent_id = Some(parent_id.into());
|
|
51
|
+
self.parent_type = Some(parent_type.into());
|
|
52
|
+
self
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Add tenant context
|
|
56
|
+
pub fn with_tenant(mut self, tenant_id: impl Into<String>) -> Self {
|
|
57
|
+
self.tenant_id = Some(tenant_id.into());
|
|
58
|
+
self
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Add user context
|
|
62
|
+
pub fn with_user(mut self, user_id: impl Into<String>) -> Self {
|
|
63
|
+
self.user_id = Some(user_id.into());
|
|
64
|
+
self
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Convert to a map of attributes for OpenTelemetry
|
|
68
|
+
pub fn to_attributes(&self) -> Vec<(&'static str, String)> {
|
|
69
|
+
let mut attrs = vec![("enact.execution_id", self.execution_id.clone())];
|
|
70
|
+
|
|
71
|
+
if let Some(ref parent_id) = self.parent_id {
|
|
72
|
+
attrs.push(("enact.parent_id", parent_id.clone()));
|
|
73
|
+
}
|
|
74
|
+
if let Some(ref parent_type) = self.parent_type {
|
|
75
|
+
attrs.push(("enact.parent_type", parent_type.clone()));
|
|
76
|
+
}
|
|
77
|
+
if let Some(ref tenant_id) = self.tenant_id {
|
|
78
|
+
attrs.push(("enact.tenant_id", tenant_id.clone()));
|
|
79
|
+
}
|
|
80
|
+
if let Some(ref user_id) = self.user_id {
|
|
81
|
+
attrs.push(("enact.user_id", user_id.clone()));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
attrs
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Span attributes for step-level spans
|
|
89
|
+
pub struct StepSpanAttributes {
|
|
90
|
+
/// Execution ID
|
|
91
|
+
pub execution_id: String,
|
|
92
|
+
/// Step ID
|
|
93
|
+
pub step_id: String,
|
|
94
|
+
/// Step type
|
|
95
|
+
pub step_type: String,
|
|
96
|
+
/// Step name
|
|
97
|
+
pub name: String,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
impl StepSpanAttributes {
|
|
101
|
+
/// Create from ExecutionId and StepId
|
|
102
|
+
pub fn new(
|
|
103
|
+
execution_id: &ExecutionId,
|
|
104
|
+
step_id: &StepId,
|
|
105
|
+
step_type: StepType,
|
|
106
|
+
name: impl Into<String>,
|
|
107
|
+
) -> Self {
|
|
108
|
+
Self {
|
|
109
|
+
execution_id: execution_id.as_str().to_string(),
|
|
110
|
+
step_id: step_id.as_str().to_string(),
|
|
111
|
+
step_type: step_type.to_string(),
|
|
112
|
+
name: name.into(),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Convert to a map of attributes for OpenTelemetry
|
|
117
|
+
pub fn to_attributes(&self) -> Vec<(&'static str, String)> {
|
|
118
|
+
vec![
|
|
119
|
+
("enact.execution_id", self.execution_id.clone()),
|
|
120
|
+
("enact.step_id", self.step_id.clone()),
|
|
121
|
+
("enact.step_type", self.step_type.clone()),
|
|
122
|
+
("enact.step_name", self.name.clone()),
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// Span attributes for tool calls
|
|
128
|
+
pub struct ToolSpanAttributes {
|
|
129
|
+
/// Execution ID
|
|
130
|
+
pub execution_id: String,
|
|
131
|
+
/// Step ID
|
|
132
|
+
pub step_id: String,
|
|
133
|
+
/// Tool name
|
|
134
|
+
pub tool_name: String,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
impl ToolSpanAttributes {
|
|
138
|
+
/// Create from ExecutionId, StepId, and tool name
|
|
139
|
+
pub fn new(
|
|
140
|
+
execution_id: &ExecutionId,
|
|
141
|
+
step_id: &StepId,
|
|
142
|
+
tool_name: impl Into<String>,
|
|
143
|
+
) -> Self {
|
|
144
|
+
Self {
|
|
145
|
+
execution_id: execution_id.as_str().to_string(),
|
|
146
|
+
step_id: step_id.as_str().to_string(),
|
|
147
|
+
tool_name: tool_name.into(),
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/// Convert to a map of attributes for OpenTelemetry
|
|
152
|
+
pub fn to_attributes(&self) -> Vec<(&'static str, String)> {
|
|
153
|
+
vec![
|
|
154
|
+
("enact.execution_id", self.execution_id.clone()),
|
|
155
|
+
("enact.step_id", self.step_id.clone()),
|
|
156
|
+
("enact.tool_name", self.tool_name.clone()),
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/// Span attributes for LLM calls
|
|
162
|
+
pub struct LlmSpanAttributes {
|
|
163
|
+
/// Execution ID
|
|
164
|
+
pub execution_id: String,
|
|
165
|
+
/// Step ID
|
|
166
|
+
pub step_id: String,
|
|
167
|
+
/// Model provider
|
|
168
|
+
pub provider: String,
|
|
169
|
+
/// Model name
|
|
170
|
+
pub model: String,
|
|
171
|
+
/// Input tokens (optional)
|
|
172
|
+
pub input_tokens: Option<u32>,
|
|
173
|
+
/// Output tokens (optional)
|
|
174
|
+
pub output_tokens: Option<u32>,
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
impl LlmSpanAttributes {
|
|
178
|
+
/// Create from ExecutionId, StepId, provider, and model
|
|
179
|
+
pub fn new(
|
|
180
|
+
execution_id: &ExecutionId,
|
|
181
|
+
step_id: &StepId,
|
|
182
|
+
provider: impl Into<String>,
|
|
183
|
+
model: impl Into<String>,
|
|
184
|
+
) -> Self {
|
|
185
|
+
Self {
|
|
186
|
+
execution_id: execution_id.as_str().to_string(),
|
|
187
|
+
step_id: step_id.as_str().to_string(),
|
|
188
|
+
provider: provider.into(),
|
|
189
|
+
model: model.into(),
|
|
190
|
+
input_tokens: None,
|
|
191
|
+
output_tokens: None,
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/// Add token counts
|
|
196
|
+
pub fn with_tokens(mut self, input: u32, output: u32) -> Self {
|
|
197
|
+
self.input_tokens = Some(input);
|
|
198
|
+
self.output_tokens = Some(output);
|
|
199
|
+
self
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// Convert to a map of attributes for OpenTelemetry
|
|
203
|
+
pub fn to_attributes(&self) -> Vec<(&'static str, String)> {
|
|
204
|
+
let mut attrs = vec![
|
|
205
|
+
("enact.execution_id", self.execution_id.clone()),
|
|
206
|
+
("enact.step_id", self.step_id.clone()),
|
|
207
|
+
("gen_ai.system", self.provider.clone()),
|
|
208
|
+
("gen_ai.request.model", self.model.clone()),
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
if let Some(tokens) = self.input_tokens {
|
|
212
|
+
attrs.push(("gen_ai.usage.input_tokens", tokens.to_string()));
|
|
213
|
+
}
|
|
214
|
+
if let Some(tokens) = self.output_tokens {
|
|
215
|
+
attrs.push(("gen_ai.usage.output_tokens", tokens.to_string()));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
attrs
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/// Create span name for an execution
|
|
223
|
+
pub fn execution_span_name(execution_id: &ExecutionId) -> String {
|
|
224
|
+
format!("execution:{}", execution_id.as_str())
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/// Create span name for a step
|
|
228
|
+
pub fn step_span_name(step_type: &StepType, name: &str) -> String {
|
|
229
|
+
format!("step:{}:{}", step_type, name)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/// Create span name for a tool call
|
|
233
|
+
pub fn tool_span_name(tool_name: &str) -> String {
|
|
234
|
+
format!("tool:{}", tool_name)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/// Create span name for an LLM call
|
|
238
|
+
pub fn llm_span_name(provider: &str, model: &str) -> String {
|
|
239
|
+
format!("llm:{}:{}", provider, model)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/// Extract trace context from a RuntimeContext trace
|
|
243
|
+
pub fn extract_trace_context(trace: &TraceContext) -> (String, String) {
|
|
244
|
+
(trace.trace_id.clone(), trace.span_id.clone())
|
|
245
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
//! AgentTool - wraps a Callable as a Tool (agent-as-tool pattern)
|
|
2
|
+
//!
|
|
3
|
+
//! ⚠️ **SECURITY WARNING: PRIVILEGED ADAPTER**
|
|
4
|
+
//!
|
|
5
|
+
//! `AgentTool` is **NOT** a general-purpose adapter. It is a **privileged** component
|
|
6
|
+
//! that must only be constructed by **trusted runtime components**.
|
|
7
|
+
//!
|
|
8
|
+
//! ## Why This Is Dangerous
|
|
9
|
+
//!
|
|
10
|
+
//! `AgentTool` allows exposing a `Callable` (which may be an agent, graph, or other
|
|
11
|
+
//! complex execution unit) as a `Tool`. This creates a powerful capability:
|
|
12
|
+
//!
|
|
13
|
+
//! - **Callable** = execution (agents, graphs, complex workflows)
|
|
14
|
+
//! - **Tool** = capability (side-effect functions for LLMs to invoke)
|
|
15
|
+
//!
|
|
16
|
+
//! Wrapping a Callable as a Tool means an LLM can invoke agents/graphs as tools,
|
|
17
|
+
//! creating recursive agent execution patterns. This is powerful but **dangerous**
|
|
18
|
+
//! if exposed to untrusted contexts.
|
|
19
|
+
//!
|
|
20
|
+
//! ## Security Invariant
|
|
21
|
+
//!
|
|
22
|
+
//! **AgentTool may only be constructed by trusted runtime components.**
|
|
23
|
+
//!
|
|
24
|
+
//! This means:
|
|
25
|
+
//! - ✅ **Allowed**: Kernel, runner, or other trusted execution components
|
|
26
|
+
//! - ❌ **Forbidden**: User-provided code, untrusted plugins, dynamic tool registries
|
|
27
|
+
//!
|
|
28
|
+
//! ## Why This Matters
|
|
29
|
+
//!
|
|
30
|
+
//! If `AgentTool` is exposed to untrusted contexts:
|
|
31
|
+
//! - Malicious users could expose arbitrary agents as tools
|
|
32
|
+
//! - Untrusted code could create recursive agent loops
|
|
33
|
+
//! - Policy boundaries could be bypassed
|
|
34
|
+
//! - Quota limits could be circumvented
|
|
35
|
+
//!
|
|
36
|
+
//! ## Usage Pattern
|
|
37
|
+
//!
|
|
38
|
+
//! ```rust,ignore
|
|
39
|
+
//! // ✅ CORRECT: Created by trusted runtime component
|
|
40
|
+
//! // In kernel or runner, with proper policy checks
|
|
41
|
+
//! let agent = Arc::new(LlmCallable::new(...));
|
|
42
|
+
//! let agent_tool = AgentTool::new(agent, "agent_name", "Agent description");
|
|
43
|
+
//!
|
|
44
|
+
//! // ❌ WRONG: Created from user input or untrusted source
|
|
45
|
+
//! // let agent_tool = AgentTool::from_user_input(...); // DON'T DO THIS!
|
|
46
|
+
//! ```
|
|
47
|
+
//!
|
|
48
|
+
//! ## Policy Enforcement
|
|
49
|
+
//!
|
|
50
|
+
//! Even when created by trusted components, `AgentTool` instances must still:
|
|
51
|
+
//! - Go through `ToolExecutor` for execution (policy enforcement)
|
|
52
|
+
//! - Respect `ToolPolicy` trust levels
|
|
53
|
+
//! - Be subject to quota limits
|
|
54
|
+
//! - Follow the same security boundaries as any other tool
|
|
55
|
+
//!
|
|
56
|
+
//! The privilege is in **construction**, not in **execution**.
|
|
57
|
+
|
|
58
|
+
use super::Tool;
|
|
59
|
+
use crate::callable::DynCallable;
|
|
60
|
+
use async_trait::async_trait;
|
|
61
|
+
use serde_json::{json, Value};
|
|
62
|
+
|
|
63
|
+
/// AgentTool - wraps a Callable as a Tool
|
|
64
|
+
///
|
|
65
|
+
/// ⚠️ **PRIVILEGED**: This adapter may only be constructed by trusted runtime components.
|
|
66
|
+
/// See module-level documentation for security implications.
|
|
67
|
+
///
|
|
68
|
+
/// This allows exposing agents, graphs, or other Callables as tools that can be
|
|
69
|
+
/// invoked by LLMs. The Callable's `run()` method is invoked with the tool's
|
|
70
|
+
/// JSON arguments serialized as a string.
|
|
71
|
+
pub struct AgentTool {
|
|
72
|
+
/// The underlying callable being wrapped
|
|
73
|
+
callable: DynCallable,
|
|
74
|
+
/// Tool name (may differ from callable name)
|
|
75
|
+
name: String,
|
|
76
|
+
/// Tool description
|
|
77
|
+
description: String,
|
|
78
|
+
/// JSON schema for tool parameters
|
|
79
|
+
parameters: Value,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
impl AgentTool {
|
|
83
|
+
/// Create a new AgentTool
|
|
84
|
+
///
|
|
85
|
+
/// ⚠️ **SECURITY**: This constructor is privileged. Only trusted runtime components
|
|
86
|
+
/// (kernel, runner) should create `AgentTool` instances.
|
|
87
|
+
///
|
|
88
|
+
/// # Arguments
|
|
89
|
+
///
|
|
90
|
+
/// * `callable` - The callable to wrap (agent, graph, etc.)
|
|
91
|
+
/// * `name` - Tool name (for LLM tool schema)
|
|
92
|
+
/// * `description` - Tool description (for LLM tool schema)
|
|
93
|
+
/// * `parameters` - JSON schema for tool parameters (defaults to empty object)
|
|
94
|
+
///
|
|
95
|
+
/// # Example
|
|
96
|
+
///
|
|
97
|
+
/// ```rust,ignore
|
|
98
|
+
/// // ✅ CORRECT: Created by trusted runtime
|
|
99
|
+
/// let agent = Arc::new(LlmCallable::new(...));
|
|
100
|
+
/// let tool = AgentTool::new(
|
|
101
|
+
/// agent,
|
|
102
|
+
/// "sub_agent",
|
|
103
|
+
/// "A sub-agent that handles specific tasks",
|
|
104
|
+
/// json!({
|
|
105
|
+
/// "type": "object",
|
|
106
|
+
/// "properties": {
|
|
107
|
+
/// "task": {"type": "string", "description": "Task description"}
|
|
108
|
+
/// },
|
|
109
|
+
/// "required": ["task"]
|
|
110
|
+
/// })
|
|
111
|
+
/// );
|
|
112
|
+
/// ```
|
|
113
|
+
pub fn new(
|
|
114
|
+
callable: DynCallable,
|
|
115
|
+
name: impl Into<String>,
|
|
116
|
+
description: impl Into<String>,
|
|
117
|
+
parameters: Value,
|
|
118
|
+
) -> Self {
|
|
119
|
+
Self {
|
|
120
|
+
callable,
|
|
121
|
+
name: name.into(),
|
|
122
|
+
description: description.into(),
|
|
123
|
+
parameters,
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// Create a new AgentTool with default empty parameters schema
|
|
128
|
+
///
|
|
129
|
+
/// ⚠️ **SECURITY**: Same privilege requirements as `new()`.
|
|
130
|
+
pub fn simple(
|
|
131
|
+
callable: DynCallable,
|
|
132
|
+
name: impl Into<String>,
|
|
133
|
+
description: impl Into<String>,
|
|
134
|
+
) -> Self {
|
|
135
|
+
Self::new(
|
|
136
|
+
callable,
|
|
137
|
+
name,
|
|
138
|
+
description,
|
|
139
|
+
json!({
|
|
140
|
+
"type": "object",
|
|
141
|
+
"properties": {},
|
|
142
|
+
"required": []
|
|
143
|
+
}),
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#[async_trait]
|
|
149
|
+
impl Tool for AgentTool {
|
|
150
|
+
fn name(&self) -> &str {
|
|
151
|
+
&self.name
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fn description(&self) -> &str {
|
|
155
|
+
&self.description
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fn parameters_schema(&self) -> Value {
|
|
159
|
+
self.parameters.clone()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async fn execute(&self, args: Value) -> anyhow::Result<Value> {
|
|
163
|
+
// Serialize JSON arguments to string for Callable::run()
|
|
164
|
+
let input = serde_json::to_string(&args)?;
|
|
165
|
+
|
|
166
|
+
// Execute the underlying callable
|
|
167
|
+
let output = self.callable.run(&input).await?;
|
|
168
|
+
|
|
169
|
+
// Parse output as JSON (callable returns string, but we expect JSON)
|
|
170
|
+
// If parsing fails, wrap the string in a JSON object
|
|
171
|
+
match serde_json::from_str::<Value>(&output) {
|
|
172
|
+
Ok(json) => Ok(json),
|
|
173
|
+
Err(_) => Ok(json!({ "result": output })),
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
//! Cost tracking tool for monitoring API usage
|
|
2
|
+
|
|
3
|
+
use crate::tool::Tool;
|
|
4
|
+
use async_trait::async_trait;
|
|
5
|
+
use serde::{Deserialize, Serialize};
|
|
6
|
+
use serde_json::json;
|
|
7
|
+
use std::collections::HashMap;
|
|
8
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
9
|
+
use std::sync::Arc;
|
|
10
|
+
use std::time::SystemTime;
|
|
11
|
+
|
|
12
|
+
/// Cost tracking for LLM API calls
|
|
13
|
+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
14
|
+
pub struct CostMetrics {
|
|
15
|
+
pub total_requests: u64,
|
|
16
|
+
pub total_input_tokens: u64,
|
|
17
|
+
pub total_output_tokens: u64,
|
|
18
|
+
pub total_cost_usd: f64,
|
|
19
|
+
pub requests_by_model: HashMap<String, u64>,
|
|
20
|
+
pub cost_by_model: HashMap<String, f64>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Cost tracker with thread-safe counters
|
|
24
|
+
pub struct CostTracker {
|
|
25
|
+
metrics: Arc<std::sync::Mutex<CostMetrics>>,
|
|
26
|
+
model_pricing: HashMap<String, ModelPricing>,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[derive(Debug, Clone)]
|
|
30
|
+
struct ModelPricing {
|
|
31
|
+
input_price_per_1k: f64, // USD per 1K input tokens
|
|
32
|
+
output_price_per_1k: f64, // USD per 1K output tokens
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl CostTracker {
|
|
36
|
+
pub fn new() -> Self {
|
|
37
|
+
let mut model_pricing = HashMap::new();
|
|
38
|
+
|
|
39
|
+
// OpenAI pricing (as of 2024)
|
|
40
|
+
model_pricing.insert(
|
|
41
|
+
"gpt-4o".to_string(),
|
|
42
|
+
ModelPricing {
|
|
43
|
+
input_price_per_1k: 0.0025,
|
|
44
|
+
output_price_per_1k: 0.01,
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
model_pricing.insert(
|
|
48
|
+
"gpt-4o-mini".to_string(),
|
|
49
|
+
ModelPricing {
|
|
50
|
+
input_price_per_1k: 0.00015,
|
|
51
|
+
output_price_per_1k: 0.0006,
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
model_pricing.insert(
|
|
55
|
+
"gpt-4-turbo".to_string(),
|
|
56
|
+
ModelPricing {
|
|
57
|
+
input_price_per_1k: 0.01,
|
|
58
|
+
output_price_per_1k: 0.03,
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Anthropic pricing
|
|
63
|
+
model_pricing.insert(
|
|
64
|
+
"claude-3-opus".to_string(),
|
|
65
|
+
ModelPricing {
|
|
66
|
+
input_price_per_1k: 0.015,
|
|
67
|
+
output_price_per_1k: 0.075,
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
model_pricing.insert(
|
|
71
|
+
"claude-3-sonnet".to_string(),
|
|
72
|
+
ModelPricing {
|
|
73
|
+
input_price_per_1k: 0.003,
|
|
74
|
+
output_price_per_1k: 0.015,
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Gemini pricing
|
|
79
|
+
model_pricing.insert(
|
|
80
|
+
"gemini-pro".to_string(),
|
|
81
|
+
ModelPricing {
|
|
82
|
+
input_price_per_1k: 0.0005,
|
|
83
|
+
output_price_per_1k: 0.0015,
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
Self {
|
|
88
|
+
metrics: Arc::new(std::sync::Mutex::new(CostMetrics::default())),
|
|
89
|
+
model_pricing,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
pub fn track_request(
|
|
94
|
+
&self,
|
|
95
|
+
model: &str,
|
|
96
|
+
input_tokens: u64,
|
|
97
|
+
output_tokens: u64,
|
|
98
|
+
) {
|
|
99
|
+
let mut metrics = self.metrics.lock().unwrap();
|
|
100
|
+
|
|
101
|
+
metrics.total_requests += 1;
|
|
102
|
+
metrics.total_input_tokens += input_tokens;
|
|
103
|
+
metrics.total_output_tokens += output_tokens;
|
|
104
|
+
|
|
105
|
+
// Track by model
|
|
106
|
+
*metrics.requests_by_model.entry(model.to_string()).or_insert(0) += 1;
|
|
107
|
+
|
|
108
|
+
// Calculate cost
|
|
109
|
+
let cost = if let Some(pricing) = self.model_pricing.get(model) {
|
|
110
|
+
let input_cost = (input_tokens as f64 / 1000.0) * pricing.input_price_per_1k;
|
|
111
|
+
let output_cost = (output_tokens as f64 / 1000.0) * pricing.output_price_per_1k;
|
|
112
|
+
input_cost + output_cost
|
|
113
|
+
} else {
|
|
114
|
+
// Default pricing if model unknown
|
|
115
|
+
let input_cost = (input_tokens as f64 / 1000.0) * 0.001;
|
|
116
|
+
let output_cost = (output_tokens as f64 / 1000.0) * 0.002;
|
|
117
|
+
input_cost + output_cost
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
metrics.total_cost_usd += cost;
|
|
121
|
+
*metrics.cost_by_model.entry(model.to_string()).or_insert(0.0) += cost;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
pub fn get_metrics(&self) -> CostMetrics {
|
|
125
|
+
self.metrics.lock().unwrap().clone()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
pub fn reset(&self) {
|
|
129
|
+
let mut metrics = self.metrics.lock().unwrap();
|
|
130
|
+
*metrics = CostMetrics::default();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
impl Default for CostTracker {
|
|
135
|
+
fn default() -> Self {
|
|
136
|
+
Self::new()
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// Cost tool for querying cost metrics
|
|
141
|
+
pub struct CostTool {
|
|
142
|
+
tracker: Arc<CostTracker>,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
impl CostTool {
|
|
146
|
+
pub fn new(tracker: Arc<CostTracker>) -> Self {
|
|
147
|
+
Self { tracker }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#[async_trait]
|
|
152
|
+
impl Tool for CostTool {
|
|
153
|
+
fn name(&self) -> &str {
|
|
154
|
+
"cost"
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
fn description(&self) -> &str {
|
|
158
|
+
"Get cost metrics and usage statistics for LLM API calls"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
fn parameters_schema(&self) -> serde_json::Value {
|
|
162
|
+
json!({
|
|
163
|
+
"type": "object",
|
|
164
|
+
"properties": {
|
|
165
|
+
"action": {
|
|
166
|
+
"type": "string",
|
|
167
|
+
"enum": ["get", "reset"],
|
|
168
|
+
"description": "Action to perform",
|
|
169
|
+
"default": "get"
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
fn requires_network(&self) -> bool {
|
|
176
|
+
false
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<serde_json::Value> {
|
|
180
|
+
let action = args
|
|
181
|
+
.get("action")
|
|
182
|
+
.and_then(|v| v.as_str())
|
|
183
|
+
.unwrap_or("get");
|
|
184
|
+
|
|
185
|
+
match action {
|
|
186
|
+
"reset" => {
|
|
187
|
+
self.tracker.reset();
|
|
188
|
+
Ok(json!({
|
|
189
|
+
"success": true,
|
|
190
|
+
"message": "Cost metrics reset"
|
|
191
|
+
}))
|
|
192
|
+
}
|
|
193
|
+
_ => {
|
|
194
|
+
let metrics = self.tracker.get_metrics();
|
|
195
|
+
Ok(json!({
|
|
196
|
+
"success": true,
|
|
197
|
+
"metrics": metrics
|
|
198
|
+
}))
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#[cfg(test)]
|
|
205
|
+
mod tests {
|
|
206
|
+
use super::*;
|
|
207
|
+
|
|
208
|
+
#[test]
|
|
209
|
+
fn test_cost_tracker() {
|
|
210
|
+
let tracker = CostTracker::new();
|
|
211
|
+
|
|
212
|
+
tracker.track_request("gpt-4o-mini", 1000, 500);
|
|
213
|
+
tracker.track_request("gpt-4o-mini", 2000, 1000);
|
|
214
|
+
|
|
215
|
+
let metrics = tracker.get_metrics();
|
|
216
|
+
assert_eq!(metrics.total_requests, 2);
|
|
217
|
+
assert_eq!(metrics.total_input_tokens, 3000);
|
|
218
|
+
assert_eq!(metrics.total_output_tokens, 1500);
|
|
219
|
+
assert!(metrics.total_cost_usd > 0.0);
|
|
220
|
+
|
|
221
|
+
// Check model breakdown
|
|
222
|
+
assert_eq!(metrics.requests_by_model.get("gpt-4o-mini"), Some(&2));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#[test]
|
|
226
|
+
fn test_cost_tracker_reset() {
|
|
227
|
+
let tracker = CostTracker::new();
|
|
228
|
+
tracker.track_request("gpt-4o-mini", 1000, 500);
|
|
229
|
+
|
|
230
|
+
tracker.reset();
|
|
231
|
+
let metrics = tracker.get_metrics();
|
|
232
|
+
assert_eq!(metrics.total_requests, 0);
|
|
233
|
+
assert_eq!(metrics.total_cost_usd, 0.0);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
#[tokio::test]
|
|
237
|
+
async fn test_cost_tool_get() {
|
|
238
|
+
let tracker = Arc::new(CostTracker::new());
|
|
239
|
+
tracker.track_request("gpt-4o-mini", 1000, 500);
|
|
240
|
+
|
|
241
|
+
let tool = CostTool::new(tracker);
|
|
242
|
+
let result = tool.execute(json!({"action": "get"})).await.unwrap();
|
|
243
|
+
|
|
244
|
+
assert_eq!(result["success"], true);
|
|
245
|
+
assert!(result["metrics"]["total_requests"].as_u64().unwrap() > 0);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
File without changes
|