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,167 @@
|
|
|
1
|
+
//! OpenAI-compatible provider (Ollama, vLLM, LocalAI, etc.)
|
|
2
|
+
//!
|
|
3
|
+
//! This provider works with any service that follows the OpenAI API spec:
|
|
4
|
+
//! - Ollama (local models)
|
|
5
|
+
//! - vLLM (local inference server)
|
|
6
|
+
//! - LocalAI
|
|
7
|
+
//! - Any OpenAI-compatible endpoint
|
|
8
|
+
//!
|
|
9
|
+
//! ## Usage
|
|
10
|
+
//!
|
|
11
|
+
//! ```rust
|
|
12
|
+
//! use enact_providers::OpenAICompatible;
|
|
13
|
+
//!
|
|
14
|
+
//! // For Ollama (no API key needed)
|
|
15
|
+
//! let provider = OpenAICompatible::new(
|
|
16
|
+
//! "http://localhost:11434",
|
|
17
|
+
//! "llama3",
|
|
18
|
+
//! None,
|
|
19
|
+
//! );
|
|
20
|
+
//!
|
|
21
|
+
//! // For OpenAI or other services requiring API key
|
|
22
|
+
//! let provider = OpenAICompatible::new(
|
|
23
|
+
//! "https://api.openai.com",
|
|
24
|
+
//! "gpt-4",
|
|
25
|
+
//! Some("sk-...".to_string()),
|
|
26
|
+
//! );
|
|
27
|
+
//! ```
|
|
28
|
+
|
|
29
|
+
use crate::http::{HttpRequestSpec, post_json};
|
|
30
|
+
use enact_core::providers::{
|
|
31
|
+
ChatRequest, ChatResponse, EmbeddingRequest, EmbeddingResponse, ModelCapabilities, ModelProvider,
|
|
32
|
+
};
|
|
33
|
+
use async_trait::async_trait;
|
|
34
|
+
use reqwest::Client;
|
|
35
|
+
use std::time::Duration;
|
|
36
|
+
|
|
37
|
+
/// OpenAI-compatible provider
|
|
38
|
+
///
|
|
39
|
+
/// Works with any service that follows the OpenAI API specification:
|
|
40
|
+
/// - Ollama (local models)
|
|
41
|
+
/// - vLLM (local inference server)
|
|
42
|
+
/// - LocalAI
|
|
43
|
+
/// - OpenAI API
|
|
44
|
+
/// - Any other OpenAI-compatible endpoint
|
|
45
|
+
pub struct OpenAICompatible {
|
|
46
|
+
client: Client,
|
|
47
|
+
base_url: String,
|
|
48
|
+
api_key: Option<String>,
|
|
49
|
+
model: String,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
impl OpenAICompatible {
|
|
53
|
+
/// Create a new OpenAI-compatible provider
|
|
54
|
+
///
|
|
55
|
+
/// # Arguments
|
|
56
|
+
///
|
|
57
|
+
/// * `base_url` - Base URL of the API (e.g., "http://localhost:11434" for Ollama)
|
|
58
|
+
/// * `model` - Model name to use (e.g., "llama3", "gpt-4")
|
|
59
|
+
/// * `api_key` - Optional API key (Ollama doesn't need one, OpenAI does)
|
|
60
|
+
pub fn new(
|
|
61
|
+
base_url: impl Into<String>,
|
|
62
|
+
model: impl Into<String>,
|
|
63
|
+
api_key: Option<String>,
|
|
64
|
+
) -> Self {
|
|
65
|
+
Self {
|
|
66
|
+
client: Client::new(),
|
|
67
|
+
base_url: base_url.into().trim_end_matches('/').to_string(),
|
|
68
|
+
api_key,
|
|
69
|
+
model: model.into(),
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fn build_chat_url(&self) -> String {
|
|
74
|
+
format!("{}/v1/chat/completions", self.base_url)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fn build_embedding_url(&self) -> String {
|
|
78
|
+
format!("{}/v1/embeddings", self.base_url)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fn build_headers(&self) -> Vec<(String, String)> {
|
|
82
|
+
let mut headers = vec![
|
|
83
|
+
("Content-Type".to_string(), "application/json".to_string()),
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
if let Some(api_key) = &self.api_key {
|
|
87
|
+
headers.push(("Authorization".to_string(), format!("Bearer {}", api_key)));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
headers
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#[async_trait]
|
|
95
|
+
impl ModelProvider for OpenAICompatible {
|
|
96
|
+
fn name(&self) -> &str {
|
|
97
|
+
"openai-compatible"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fn model(&self) -> &str {
|
|
101
|
+
&self.model
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fn capabilities(&self) -> ModelCapabilities {
|
|
105
|
+
ModelCapabilities {
|
|
106
|
+
max_tokens: 8192,
|
|
107
|
+
max_output_tokens: 4096,
|
|
108
|
+
supports_streaming: true,
|
|
109
|
+
supports_tools: true,
|
|
110
|
+
supports_vision: false, // Most local models don't support vision
|
|
111
|
+
supports_json_mode: true,
|
|
112
|
+
supports_embeddings: true,
|
|
113
|
+
pii_safe: false,
|
|
114
|
+
cost_per_1k_input: None, // Local models typically have no cost
|
|
115
|
+
cost_per_1k_output: None,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async fn chat(&self, request: ChatRequest) -> anyhow::Result<ChatResponse> {
|
|
120
|
+
let url = self.build_chat_url();
|
|
121
|
+
|
|
122
|
+
// Build request body with model
|
|
123
|
+
let mut body = serde_json::to_value(&request)?;
|
|
124
|
+
body["model"] = serde_json::Value::String(self.model.clone());
|
|
125
|
+
|
|
126
|
+
// Build HTTP request spec
|
|
127
|
+
let http_req = HttpRequestSpec {
|
|
128
|
+
url,
|
|
129
|
+
headers: self.build_headers(),
|
|
130
|
+
body,
|
|
131
|
+
timeout: Duration::from_secs(60), // Longer timeout for local models
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Execute request using HTTP helper
|
|
135
|
+
let json_response = post_json(&self.client, http_req).await?;
|
|
136
|
+
|
|
137
|
+
// Parse response
|
|
138
|
+
let chat_response: ChatResponse = serde_json::from_value(json_response)?;
|
|
139
|
+
Ok(chat_response)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async fn embed(&self, request: EmbeddingRequest) -> anyhow::Result<EmbeddingResponse> {
|
|
143
|
+
let url = self.build_embedding_url();
|
|
144
|
+
|
|
145
|
+
// Build request body with model
|
|
146
|
+
let mut body = serde_json::to_value(&request)?;
|
|
147
|
+
body["model"] = serde_json::Value::String(
|
|
148
|
+
request.model.clone().unwrap_or_else(|| self.model.clone())
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Build HTTP request spec
|
|
152
|
+
let http_req = HttpRequestSpec {
|
|
153
|
+
url,
|
|
154
|
+
headers: self.build_headers(),
|
|
155
|
+
body,
|
|
156
|
+
timeout: Duration::from_secs(60),
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Execute request using HTTP helper
|
|
160
|
+
let json_response = post_json(&self.client, http_req).await?;
|
|
161
|
+
|
|
162
|
+
// Parse response
|
|
163
|
+
let embedding_response: EmbeddingResponse = serde_json::from_value(json_response)?;
|
|
164
|
+
Ok(embedding_response)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//! OpenRouter provider
|
|
2
|
+
//!
|
|
3
|
+
//! **Status**: [planned]
|
|
4
|
+
//!
|
|
5
|
+
//! ## Implementation Notes
|
|
6
|
+
//!
|
|
7
|
+
//! This provider will implement the `ModelProvider` trait for OpenRouter's unified API.
|
|
8
|
+
//!
|
|
9
|
+
//! - **Endpoint**: HTTP POST to `https://openrouter.ai/api/v1/chat/completions`
|
|
10
|
+
//! - **Documentation**: https://openrouter.ai/docs
|
|
11
|
+
//! - **Authentication**: `Authorization: Bearer <api_key>` header
|
|
12
|
+
//!
|
|
13
|
+
//! ## Environment Variables
|
|
14
|
+
//!
|
|
15
|
+
//! - `OPENROUTER_API_KEY` (required) - Your OpenRouter API key
|
|
16
|
+
//! - `OPENROUTER_MODEL` (optional, default: "openai/gpt-4") - Model to use
|
|
17
|
+
//!
|
|
18
|
+
//! ## Important Notes
|
|
19
|
+
//!
|
|
20
|
+
//! Providers must be side-effect free outside HTTP calls. All retries, rate limiting,
|
|
21
|
+
//! and quotas are enforced by the kernel, not by the provider implementation.
|
|
22
|
+
//!
|
|
23
|
+
//! ## Example Usage (Future)
|
|
24
|
+
//!
|
|
25
|
+
//! ```rust,ignore
|
|
26
|
+
//! use enact_providers::{OpenRouter, ModelProvider};
|
|
27
|
+
//! use enact_config::Config;
|
|
28
|
+
//!
|
|
29
|
+
//! let config = Config::load()?;
|
|
30
|
+
//! let provider = OpenRouter::new(config)?;
|
|
31
|
+
//! let response = provider.chat(request).await?;
|
|
32
|
+
//! ```
|
|
33
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "enact-runner"
|
|
3
|
+
version.workspace = true
|
|
4
|
+
edition.workspace = true
|
|
5
|
+
license.workspace = true
|
|
6
|
+
description = "Robust agent loop runner for Enact — retries, compaction, multi-format parsing"
|
|
7
|
+
repository.workspace = true
|
|
8
|
+
homepage.workspace = true
|
|
9
|
+
keywords = ["runner", "agent", "loop", "retry"]
|
|
10
|
+
categories.workspace = true
|
|
11
|
+
|
|
12
|
+
[dependencies]
|
|
13
|
+
enact-core = { workspace = true }
|
|
14
|
+
|
|
15
|
+
tokio.workspace = true
|
|
16
|
+
async-trait.workspace = true
|
|
17
|
+
serde.workspace = true
|
|
18
|
+
serde_json.workspace = true
|
|
19
|
+
tracing.workspace = true
|
|
20
|
+
anyhow.workspace = true
|
|
21
|
+
regex.workspace = true
|
|
22
|
+
|
|
23
|
+
[dev-dependencies]
|
|
24
|
+
tokio = { workspace = true, features = ["test-util"] }
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# enact-runner
|
|
2
|
+
|
|
3
|
+
> Robust agent loop runner for Enact — retries, compaction, multi-format parsing.
|
|
4
|
+
|
|
5
|
+
## What Is This?
|
|
6
|
+
|
|
7
|
+
`enact-runner` is the **"missing middle"** between the apps (`cli`, `api`) and `enact-core`'s deterministic kernel. It implements the robust agent loop mandated by the technical specs but never built.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```rust
|
|
12
|
+
use enact_runner::{AgentRunner, DefaultAgentRunner, RunnerConfig, LoopOutcome};
|
|
13
|
+
|
|
14
|
+
// Default config (max 25 iterations, compaction at 40 messages)
|
|
15
|
+
let mut runner = DefaultAgentRunner::default_new()
|
|
16
|
+
.add_tool(my_search_tool)
|
|
17
|
+
.add_tool(my_read_file_tool);
|
|
18
|
+
|
|
19
|
+
let outcome = runner.run(&my_llm_callable, "user input").await?;
|
|
20
|
+
|
|
21
|
+
match outcome {
|
|
22
|
+
LoopOutcome::Completed(output) => println!("Done: {}", output),
|
|
23
|
+
LoopOutcome::MaxIterationsReached { .. } => println!("Hit limit"),
|
|
24
|
+
LoopOutcome::Cancelled => println!("Cancelled"),
|
|
25
|
+
LoopOutcome::TimedOut { .. } => println!("Timed out"),
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Presets
|
|
30
|
+
|
|
31
|
+
```rust
|
|
32
|
+
// Short interactive sessions (10 iterations, 2 min timeout)
|
|
33
|
+
let config = RunnerConfig::interactive();
|
|
34
|
+
|
|
35
|
+
// Long-running background agents (100 iterations, 1 hour timeout)
|
|
36
|
+
let config = RunnerConfig::long_running();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
| Feature | Description |
|
|
42
|
+
|---|---|
|
|
43
|
+
| **Multi-format parsing** | Parses tool calls from JSON, XML, and Markdown |
|
|
44
|
+
| **Retry with backoff** | Exponential backoff for rate-limits and transient errors |
|
|
45
|
+
| **Error classification** | Distinguishes retryable (rate-limit, network) from fatal (auth, invalid) |
|
|
46
|
+
| **Auto-compaction** | Summarizes older messages when history exceeds threshold |
|
|
47
|
+
| **Checkpointing** | Saves state at configurable intervals |
|
|
48
|
+
| **Cancel/Pause** | Delegates to `enact-core::Runner` for cancel/pause signals |
|
|
49
|
+
| **Stream events** | Emits `StreamEvent`s for real-time observability |
|
|
50
|
+
|
|
51
|
+
## Module Structure
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
src/
|
|
55
|
+
├── lib.rs # Module root + re-exports
|
|
56
|
+
├── config.rs # RunnerConfig, RetryConfig, presets
|
|
57
|
+
├── loop_driver.rs # AgentRunner::run() — the core loop
|
|
58
|
+
├── parser.rs # Multi-format tool call parsing
|
|
59
|
+
├── compaction.rs # Auto context summarization
|
|
60
|
+
└── retry.rs # Error classification + backoff
|
|
61
|
+
tests/
|
|
62
|
+
└── integration.rs # Full loop integration tests
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Testing
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# All tests (31 total)
|
|
69
|
+
cargo test -p enact-runner
|
|
70
|
+
|
|
71
|
+
# Only integration tests
|
|
72
|
+
cargo test -p enact-runner --test integration
|
|
73
|
+
|
|
74
|
+
# Specific test
|
|
75
|
+
cargo test -p enact-runner test_retry_on_transient_error
|
|
76
|
+
```
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
//! Context compaction for long-running agent loops
|
|
2
|
+
//!
|
|
3
|
+
//! When message history exceeds a threshold, older messages are summarized
|
|
4
|
+
//! into a single context message to prevent context window overflow.
|
|
5
|
+
//!
|
|
6
|
+
//! Ported from zeroclaw's `auto_compact_history` which uses the LLM itself
|
|
7
|
+
//! to generate a summary of older conversation history.
|
|
8
|
+
|
|
9
|
+
use enact_core::callable::Callable;
|
|
10
|
+
|
|
11
|
+
/// A message in the conversation history.
|
|
12
|
+
#[derive(Debug, Clone)]
|
|
13
|
+
pub struct HistoryMessage {
|
|
14
|
+
/// Role: "system", "user", "assistant", "tool"
|
|
15
|
+
pub role: String,
|
|
16
|
+
/// Message content
|
|
17
|
+
pub content: String,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
impl HistoryMessage {
|
|
21
|
+
pub fn system(content: impl Into<String>) -> Self {
|
|
22
|
+
Self {
|
|
23
|
+
role: "system".to_string(),
|
|
24
|
+
content: content.into(),
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pub fn user(content: impl Into<String>) -> Self {
|
|
29
|
+
Self {
|
|
30
|
+
role: "user".to_string(),
|
|
31
|
+
content: content.into(),
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pub fn assistant(content: impl Into<String>) -> Self {
|
|
36
|
+
Self {
|
|
37
|
+
role: "assistant".to_string(),
|
|
38
|
+
content: content.into(),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pub fn tool_result(name: &str, content: impl Into<String>) -> Self {
|
|
43
|
+
Self {
|
|
44
|
+
role: "tool".to_string(),
|
|
45
|
+
content: format!("[{}]: {}", name, content.into()),
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// Check if compaction is needed based on message count.
|
|
51
|
+
pub fn needs_compaction(history: &[HistoryMessage], threshold: usize) -> bool {
|
|
52
|
+
history.len() > threshold
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Compact the conversation history by summarizing older messages.
|
|
56
|
+
///
|
|
57
|
+
/// Keeps the system message (index 0) and the `keep_recent` most recent messages.
|
|
58
|
+
/// Everything in between is summarized into a single "context summary" message.
|
|
59
|
+
///
|
|
60
|
+
/// # Arguments
|
|
61
|
+
/// * `history` — mutable reference to the message history
|
|
62
|
+
/// * `summarizer` — a Callable used to generate the summary (typically the LLM itself)
|
|
63
|
+
/// * `keep_recent` — how many recent messages to preserve verbatim
|
|
64
|
+
///
|
|
65
|
+
/// # Returns
|
|
66
|
+
/// `true` if compaction was performed, `false` if history was too short.
|
|
67
|
+
pub async fn compact_history(
|
|
68
|
+
history: &mut Vec<HistoryMessage>,
|
|
69
|
+
summarizer: &dyn Callable,
|
|
70
|
+
keep_recent: usize,
|
|
71
|
+
) -> anyhow::Result<bool> {
|
|
72
|
+
// Need at least: system + some old messages + keep_recent
|
|
73
|
+
if history.len() <= keep_recent + 2 {
|
|
74
|
+
return Ok(false);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Split: [system] [old messages to summarize] [recent messages to keep]
|
|
78
|
+
let split_point = history.len() - keep_recent;
|
|
79
|
+
|
|
80
|
+
// Build a transcript of the old messages (skip system at index 0)
|
|
81
|
+
let old_messages = &history[1..split_point];
|
|
82
|
+
|
|
83
|
+
if old_messages.is_empty() {
|
|
84
|
+
return Ok(false);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let transcript = old_messages
|
|
88
|
+
.iter()
|
|
89
|
+
.map(|m| format!("{}: {}", m.role, m.content))
|
|
90
|
+
.collect::<Vec<_>>()
|
|
91
|
+
.join("\n");
|
|
92
|
+
|
|
93
|
+
// Ask the LLM to summarize the old conversation
|
|
94
|
+
let summary_prompt = format!(
|
|
95
|
+
"Summarize the following conversation history into a concise context summary. \
|
|
96
|
+
Preserve key facts, decisions, tool results, and any state that would be needed \
|
|
97
|
+
to continue the conversation. Be concise but complete.\n\n\
|
|
98
|
+
CONVERSATION:\n{}\n\n\
|
|
99
|
+
SUMMARY:",
|
|
100
|
+
transcript
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
let summary = summarizer.run(&summary_prompt).await?;
|
|
104
|
+
|
|
105
|
+
tracing::info!(
|
|
106
|
+
old_messages = old_messages.len(),
|
|
107
|
+
summary_len = summary.len(),
|
|
108
|
+
"Compacted conversation history"
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Reconstruct history: [system] [summary] [recent messages]
|
|
112
|
+
let system_msg = history[0].clone();
|
|
113
|
+
let recent_messages: Vec<HistoryMessage> = history[split_point..].to_vec();
|
|
114
|
+
|
|
115
|
+
history.clear();
|
|
116
|
+
history.push(system_msg);
|
|
117
|
+
history.push(HistoryMessage {
|
|
118
|
+
role: "system".to_string(),
|
|
119
|
+
content: format!(
|
|
120
|
+
"[Context Summary from earlier conversation]\n{}",
|
|
121
|
+
summary
|
|
122
|
+
),
|
|
123
|
+
});
|
|
124
|
+
history.extend(recent_messages);
|
|
125
|
+
|
|
126
|
+
Ok(true)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[cfg(test)]
|
|
130
|
+
mod tests {
|
|
131
|
+
use super::*;
|
|
132
|
+
|
|
133
|
+
#[test]
|
|
134
|
+
fn test_needs_compaction() {
|
|
135
|
+
let history: Vec<HistoryMessage> = (0..50)
|
|
136
|
+
.map(|i| HistoryMessage::user(format!("msg {}", i)))
|
|
137
|
+
.collect();
|
|
138
|
+
|
|
139
|
+
assert!(needs_compaction(&history, 40));
|
|
140
|
+
assert!(!needs_compaction(&history, 50));
|
|
141
|
+
assert!(!needs_compaction(&history, 100));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#[test]
|
|
145
|
+
fn test_history_message_constructors() {
|
|
146
|
+
let sys = HistoryMessage::system("You are helpful");
|
|
147
|
+
assert_eq!(sys.role, "system");
|
|
148
|
+
|
|
149
|
+
let user = HistoryMessage::user("Hello");
|
|
150
|
+
assert_eq!(user.role, "user");
|
|
151
|
+
|
|
152
|
+
let asst = HistoryMessage::assistant("Hi there");
|
|
153
|
+
assert_eq!(asst.role, "assistant");
|
|
154
|
+
|
|
155
|
+
let tool = HistoryMessage::tool_result("search", "found 5 results");
|
|
156
|
+
assert_eq!(tool.role, "tool");
|
|
157
|
+
assert!(tool.content.contains("[search]"));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#[tokio::test]
|
|
161
|
+
async fn test_compact_short_history_is_noop() {
|
|
162
|
+
use async_trait::async_trait;
|
|
163
|
+
|
|
164
|
+
struct MockSummarizer;
|
|
165
|
+
|
|
166
|
+
#[async_trait]
|
|
167
|
+
impl Callable for MockSummarizer {
|
|
168
|
+
fn name(&self) -> &str {
|
|
169
|
+
"mock"
|
|
170
|
+
}
|
|
171
|
+
async fn run(&self, _input: &str) -> anyhow::Result<String> {
|
|
172
|
+
Ok("summary".to_string())
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let mut history = vec![
|
|
177
|
+
HistoryMessage::system("system"),
|
|
178
|
+
HistoryMessage::user("hello"),
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
let result = compact_history(&mut history, &MockSummarizer, 10).await.unwrap();
|
|
182
|
+
assert!(!result, "Should not compact when history is short");
|
|
183
|
+
assert_eq!(history.len(), 2);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#[tokio::test]
|
|
187
|
+
async fn test_compact_long_history() {
|
|
188
|
+
use async_trait::async_trait;
|
|
189
|
+
|
|
190
|
+
struct MockSummarizer;
|
|
191
|
+
|
|
192
|
+
#[async_trait]
|
|
193
|
+
impl Callable for MockSummarizer {
|
|
194
|
+
fn name(&self) -> &str {
|
|
195
|
+
"mock"
|
|
196
|
+
}
|
|
197
|
+
async fn run(&self, _input: &str) -> anyhow::Result<String> {
|
|
198
|
+
Ok("Summarized: user asked about Rust, assistant explained ownership.".to_string())
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let mut history = vec![HistoryMessage::system("Be helpful")];
|
|
203
|
+
// Add 30 old messages
|
|
204
|
+
for i in 0..30 {
|
|
205
|
+
history.push(HistoryMessage::user(format!("question {}", i)));
|
|
206
|
+
history.push(HistoryMessage::assistant(format!("answer {}", i)));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let original_len = history.len(); // 61
|
|
210
|
+
|
|
211
|
+
let result = compact_history(&mut history, &MockSummarizer, 5).await.unwrap();
|
|
212
|
+
assert!(result, "Should have compacted");
|
|
213
|
+
|
|
214
|
+
// Should now be: system + summary + 5 recent = 7
|
|
215
|
+
assert_eq!(history.len(), 7);
|
|
216
|
+
assert!(history.len() < original_len);
|
|
217
|
+
|
|
218
|
+
// First should still be system
|
|
219
|
+
assert_eq!(history[0].role, "system");
|
|
220
|
+
assert_eq!(history[0].content, "Be helpful");
|
|
221
|
+
|
|
222
|
+
// Second should be the context summary
|
|
223
|
+
assert!(history[1].content.contains("[Context Summary"));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//! Runner configuration
|
|
2
|
+
//!
|
|
3
|
+
//! Defines `RunnerConfig` — the knobs that control the robust agent loop.
|
|
4
|
+
//! Ported from zeroclaw's iteration limits, compaction thresholds, and retry policies.
|
|
5
|
+
|
|
6
|
+
use std::time::Duration;
|
|
7
|
+
|
|
8
|
+
/// Configuration for the `AgentRunner` loop.
|
|
9
|
+
///
|
|
10
|
+
/// Controls iteration limits, context compaction, retry behavior,
|
|
11
|
+
/// and checkpointing intervals.
|
|
12
|
+
#[derive(Debug, Clone)]
|
|
13
|
+
pub struct RunnerConfig {
|
|
14
|
+
/// Maximum number of tool-call iterations before the loop terminates.
|
|
15
|
+
/// Prevents runaway executions.
|
|
16
|
+
/// Default: 25 (zeroclaw uses configurable `max_tool_iterations`)
|
|
17
|
+
pub max_iterations: usize,
|
|
18
|
+
|
|
19
|
+
/// Maximum wall-clock duration for the entire run.
|
|
20
|
+
/// Default: 10 minutes
|
|
21
|
+
pub max_duration: Duration,
|
|
22
|
+
|
|
23
|
+
/// Number of messages in history before auto-compaction triggers.
|
|
24
|
+
/// When exceeded, older messages are summarized into a single context message.
|
|
25
|
+
/// Default: 40 messages
|
|
26
|
+
pub compaction_threshold: usize,
|
|
27
|
+
|
|
28
|
+
/// How many messages to keep verbatim after compaction.
|
|
29
|
+
/// The rest are summarized.
|
|
30
|
+
/// Default: 10 (keep the 10 most recent messages)
|
|
31
|
+
pub compaction_keep_recent: usize,
|
|
32
|
+
|
|
33
|
+
/// Retry configuration for transient errors.
|
|
34
|
+
pub retry: RetryConfig,
|
|
35
|
+
|
|
36
|
+
/// Checkpoint every N steps. `None` disables periodic checkpointing.
|
|
37
|
+
/// Default: Some(5)
|
|
38
|
+
pub checkpoint_interval: Option<usize>,
|
|
39
|
+
|
|
40
|
+
/// Whether to emit verbose stream events for each iteration.
|
|
41
|
+
/// Default: true
|
|
42
|
+
pub emit_events: bool,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Retry configuration for transient errors.
|
|
46
|
+
#[derive(Debug, Clone)]
|
|
47
|
+
pub struct RetryConfig {
|
|
48
|
+
/// Maximum number of retries for a single operation.
|
|
49
|
+
/// Default: 3
|
|
50
|
+
pub max_retries: u32,
|
|
51
|
+
|
|
52
|
+
/// Initial delay before the first retry.
|
|
53
|
+
/// Default: 1 second
|
|
54
|
+
pub initial_delay: Duration,
|
|
55
|
+
|
|
56
|
+
/// Maximum delay between retries (caps exponential growth).
|
|
57
|
+
/// Default: 30 seconds
|
|
58
|
+
pub max_delay: Duration,
|
|
59
|
+
|
|
60
|
+
/// Multiplier for exponential backoff.
|
|
61
|
+
/// Default: 2.0
|
|
62
|
+
pub backoff_multiplier: f64,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
impl Default for RunnerConfig {
|
|
66
|
+
fn default() -> Self {
|
|
67
|
+
Self {
|
|
68
|
+
max_iterations: 25,
|
|
69
|
+
max_duration: Duration::from_secs(600),
|
|
70
|
+
compaction_threshold: 40,
|
|
71
|
+
compaction_keep_recent: 10,
|
|
72
|
+
retry: RetryConfig::default(),
|
|
73
|
+
checkpoint_interval: Some(5),
|
|
74
|
+
emit_events: true,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
impl Default for RetryConfig {
|
|
80
|
+
fn default() -> Self {
|
|
81
|
+
Self {
|
|
82
|
+
max_retries: 3,
|
|
83
|
+
initial_delay: Duration::from_secs(1),
|
|
84
|
+
max_delay: Duration::from_secs(30),
|
|
85
|
+
backoff_multiplier: 2.0,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
impl RunnerConfig {
|
|
91
|
+
/// Create a config tuned for short, interactive sessions.
|
|
92
|
+
pub fn interactive() -> Self {
|
|
93
|
+
Self {
|
|
94
|
+
max_iterations: 10,
|
|
95
|
+
max_duration: Duration::from_secs(120),
|
|
96
|
+
compaction_threshold: 20,
|
|
97
|
+
compaction_keep_recent: 6,
|
|
98
|
+
checkpoint_interval: None,
|
|
99
|
+
..Default::default()
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Create a config tuned for long-running background agents.
|
|
104
|
+
pub fn long_running() -> Self {
|
|
105
|
+
Self {
|
|
106
|
+
max_iterations: 100,
|
|
107
|
+
max_duration: Duration::from_secs(3600),
|
|
108
|
+
compaction_threshold: 60,
|
|
109
|
+
compaction_keep_recent: 15,
|
|
110
|
+
checkpoint_interval: Some(10),
|
|
111
|
+
retry: RetryConfig {
|
|
112
|
+
max_retries: 5,
|
|
113
|
+
..Default::default()
|
|
114
|
+
},
|
|
115
|
+
..Default::default()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|