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,546 @@
|
|
|
1
|
+
//! Filesystem Artifact Store - Local storage with zstd compression
|
|
2
|
+
//!
|
|
3
|
+
//! This implementation stores artifacts on the local filesystem with:
|
|
4
|
+
//! - zstd compression for space efficiency
|
|
5
|
+
//! - SHA-256 content hashing for integrity
|
|
6
|
+
//! - Metadata stored as JSON sidecar files
|
|
7
|
+
//!
|
|
8
|
+
//! ## Directory Structure
|
|
9
|
+
//!
|
|
10
|
+
//! ```text
|
|
11
|
+
//! base_path/
|
|
12
|
+
//! {execution_id}/
|
|
13
|
+
//! {artifact_id}.zst # Compressed content
|
|
14
|
+
//! {artifact_id}.meta.json # Metadata
|
|
15
|
+
//! ```
|
|
16
|
+
|
|
17
|
+
use super::metadata::{ArtifactMetadata, CompressionType};
|
|
18
|
+
use super::store::{
|
|
19
|
+
ArtifactStore, ArtifactStoreError, GetArtifactResponse,
|
|
20
|
+
ListArtifactsQuery, PutArtifactRequest, PutArtifactResponse,
|
|
21
|
+
};
|
|
22
|
+
use crate::kernel::ids::{ArtifactId, ExecutionId};
|
|
23
|
+
use async_trait::async_trait;
|
|
24
|
+
use sha2::{Digest, Sha256};
|
|
25
|
+
use std::io::{Read, Write};
|
|
26
|
+
use std::path::{Path, PathBuf};
|
|
27
|
+
use tokio::fs;
|
|
28
|
+
|
|
29
|
+
/// Filesystem-based artifact store
|
|
30
|
+
///
|
|
31
|
+
/// Stores artifacts on local disk with optional compression.
|
|
32
|
+
/// Uses zstd for compression by default.
|
|
33
|
+
pub struct FilesystemArtifactStore {
|
|
34
|
+
/// Base directory for artifact storage
|
|
35
|
+
base_path: PathBuf,
|
|
36
|
+
/// Compression level (1-22, default 3)
|
|
37
|
+
compression_level: i32,
|
|
38
|
+
/// Whether compression is enabled
|
|
39
|
+
compression_enabled: bool,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
impl FilesystemArtifactStore {
|
|
43
|
+
/// Create a new filesystem artifact store
|
|
44
|
+
pub fn new(base_path: impl Into<PathBuf>) -> Self {
|
|
45
|
+
Self {
|
|
46
|
+
base_path: base_path.into(),
|
|
47
|
+
compression_level: 3, // Default zstd level
|
|
48
|
+
compression_enabled: true,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Set compression level (1-22, higher = better compression but slower)
|
|
53
|
+
pub fn with_compression_level(mut self, level: i32) -> Self {
|
|
54
|
+
self.compression_level = level.clamp(1, 22);
|
|
55
|
+
self
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Disable compression
|
|
59
|
+
pub fn without_compression(mut self) -> Self {
|
|
60
|
+
self.compression_enabled = false;
|
|
61
|
+
self
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Get the path for an execution's artifact directory
|
|
65
|
+
fn execution_path(&self, execution_id: &ExecutionId) -> PathBuf {
|
|
66
|
+
self.base_path.join(execution_id.as_str())
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Get the path for an artifact's content file
|
|
70
|
+
fn artifact_content_path(&self, execution_id: &ExecutionId, artifact_id: &ArtifactId) -> PathBuf {
|
|
71
|
+
let ext = if self.compression_enabled { ".zst" } else { "" };
|
|
72
|
+
self.execution_path(execution_id)
|
|
73
|
+
.join(format!("{}{}", artifact_id.as_str(), ext))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Get the path for an artifact's metadata file
|
|
77
|
+
fn artifact_metadata_path(&self, execution_id: &ExecutionId, artifact_id: &ArtifactId) -> PathBuf {
|
|
78
|
+
self.execution_path(execution_id)
|
|
79
|
+
.join(format!("{}.meta.json", artifact_id.as_str()))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Compress content using zstd
|
|
83
|
+
fn compress(&self, data: &[u8]) -> Result<Vec<u8>, ArtifactStoreError> {
|
|
84
|
+
if !self.compression_enabled {
|
|
85
|
+
return Ok(data.to_vec());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Use pure Rust zstd encoder
|
|
89
|
+
let mut encoder = zstd_encoder(self.compression_level)?;
|
|
90
|
+
encoder.write_all(data).map_err(|e| {
|
|
91
|
+
ArtifactStoreError::Compression(format!("Failed to write to encoder: {}", e))
|
|
92
|
+
})?;
|
|
93
|
+
encoder.finish().map_err(|e| {
|
|
94
|
+
ArtifactStoreError::Compression(format!("Failed to finish compression: {}", e))
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Decompress content using zstd
|
|
99
|
+
fn decompress(&self, data: &[u8]) -> Result<Vec<u8>, ArtifactStoreError> {
|
|
100
|
+
if !self.compression_enabled {
|
|
101
|
+
return Ok(data.to_vec());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let mut decoder = zstd_decoder(data)?;
|
|
105
|
+
let mut result = Vec::new();
|
|
106
|
+
decoder.read_to_end(&mut result).map_err(|e| {
|
|
107
|
+
ArtifactStoreError::Compression(format!("Failed to decompress: {}", e))
|
|
108
|
+
})?;
|
|
109
|
+
Ok(result)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Calculate SHA-256 hash of content
|
|
113
|
+
fn hash_content(data: &[u8]) -> String {
|
|
114
|
+
let mut hasher = Sha256::new();
|
|
115
|
+
hasher.update(data);
|
|
116
|
+
format!("{:x}", hasher.finalize())
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Load metadata from file
|
|
120
|
+
async fn load_metadata(&self, path: &Path) -> Result<ArtifactMetadata, ArtifactStoreError> {
|
|
121
|
+
let content = fs::read_to_string(path).await?;
|
|
122
|
+
let metadata: ArtifactMetadata = serde_json::from_str(&content)?;
|
|
123
|
+
Ok(metadata)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// Save metadata to file
|
|
127
|
+
async fn save_metadata(&self, path: &Path, metadata: &ArtifactMetadata) -> Result<(), ArtifactStoreError> {
|
|
128
|
+
let content = serde_json::to_string_pretty(metadata)?;
|
|
129
|
+
fs::write(path, content).await?;
|
|
130
|
+
Ok(())
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// =============================================================================
|
|
135
|
+
// Compression Helpers (Simple implementation without external zstd crate)
|
|
136
|
+
// =============================================================================
|
|
137
|
+
|
|
138
|
+
/// Create a zstd encoder
|
|
139
|
+
/// This is a simple wrapper that uses the flate2 crate as a fallback
|
|
140
|
+
/// In production, you would use the zstd crate directly
|
|
141
|
+
fn zstd_encoder(level: i32) -> Result<ZstdEncoder, ArtifactStoreError> {
|
|
142
|
+
Ok(ZstdEncoder::new(level))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// Create a zstd decoder
|
|
146
|
+
fn zstd_decoder(data: &[u8]) -> Result<ZstdDecoder, ArtifactStoreError> {
|
|
147
|
+
Ok(ZstdDecoder::new(data))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// Simple zstd-like encoder using miniz_oxide for compression
|
|
151
|
+
/// In production, replace with actual zstd crate
|
|
152
|
+
struct ZstdEncoder {
|
|
153
|
+
level: i32,
|
|
154
|
+
buffer: Vec<u8>,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
impl ZstdEncoder {
|
|
158
|
+
fn new(level: i32) -> Self {
|
|
159
|
+
Self {
|
|
160
|
+
level,
|
|
161
|
+
buffer: Vec::new(),
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
fn finish(self) -> Result<Vec<u8>, std::io::Error> {
|
|
166
|
+
// Simple compression using miniz_oxide (deflate)
|
|
167
|
+
// In production, use the zstd crate for actual zstd compression
|
|
168
|
+
|
|
169
|
+
// For now, we use a simple framing format:
|
|
170
|
+
// [4 bytes: original length][compressed data]
|
|
171
|
+
let original_len = self.buffer.len() as u32;
|
|
172
|
+
let compressed = miniz_oxide::deflate::compress_to_vec(&self.buffer, self.level as u8);
|
|
173
|
+
|
|
174
|
+
let mut result = Vec::with_capacity(4 + compressed.len());
|
|
175
|
+
result.extend_from_slice(&original_len.to_le_bytes());
|
|
176
|
+
result.extend_from_slice(&compressed);
|
|
177
|
+
Ok(result)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
impl Write for ZstdEncoder {
|
|
182
|
+
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
183
|
+
self.buffer.extend_from_slice(buf);
|
|
184
|
+
Ok(buf.len())
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fn flush(&mut self) -> std::io::Result<()> {
|
|
188
|
+
Ok(())
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Simple zstd-like decoder
|
|
193
|
+
struct ZstdDecoder {
|
|
194
|
+
data: Vec<u8>,
|
|
195
|
+
position: usize,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
impl ZstdDecoder {
|
|
199
|
+
fn new(data: &[u8]) -> Self {
|
|
200
|
+
Self {
|
|
201
|
+
data: data.to_vec(),
|
|
202
|
+
position: 0,
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
impl Read for ZstdDecoder {
|
|
208
|
+
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
|
209
|
+
if self.position == 0 && self.data.len() > 4 {
|
|
210
|
+
// First read - decompress
|
|
211
|
+
let _original_len = u32::from_le_bytes([
|
|
212
|
+
self.data[0], self.data[1], self.data[2], self.data[3]
|
|
213
|
+
]) as usize;
|
|
214
|
+
|
|
215
|
+
let decompressed = miniz_oxide::inflate::decompress_to_vec(&self.data[4..])
|
|
216
|
+
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, format!("{:?}", e)))?;
|
|
217
|
+
|
|
218
|
+
self.data = decompressed;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let remaining = self.data.len() - self.position;
|
|
222
|
+
let to_read = std::cmp::min(remaining, buf.len());
|
|
223
|
+
|
|
224
|
+
if to_read > 0 {
|
|
225
|
+
buf[..to_read].copy_from_slice(&self.data[self.position..self.position + to_read]);
|
|
226
|
+
self.position += to_read;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
Ok(to_read)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// =============================================================================
|
|
234
|
+
// ArtifactStore Implementation
|
|
235
|
+
// =============================================================================
|
|
236
|
+
|
|
237
|
+
#[async_trait]
|
|
238
|
+
impl ArtifactStore for FilesystemArtifactStore {
|
|
239
|
+
async fn put(&self, request: PutArtifactRequest) -> Result<PutArtifactResponse, ArtifactStoreError> {
|
|
240
|
+
let artifact_id = ArtifactId::new();
|
|
241
|
+
let original_size = request.content.len() as u64;
|
|
242
|
+
|
|
243
|
+
// Ensure execution directory exists
|
|
244
|
+
let exec_path = self.execution_path(&request.execution_id);
|
|
245
|
+
fs::create_dir_all(&exec_path).await?;
|
|
246
|
+
|
|
247
|
+
// Compress content
|
|
248
|
+
let compressed = self.compress(&request.content)?;
|
|
249
|
+
let compressed_size = compressed.len() as u64;
|
|
250
|
+
|
|
251
|
+
// Calculate content hash
|
|
252
|
+
let content_hash = Self::hash_content(&request.content);
|
|
253
|
+
|
|
254
|
+
// Create metadata
|
|
255
|
+
let content_path = self.artifact_content_path(&request.execution_id, &artifact_id);
|
|
256
|
+
let metadata = ArtifactMetadata::new(
|
|
257
|
+
artifact_id.clone(),
|
|
258
|
+
request.execution_id.clone(),
|
|
259
|
+
request.step_id,
|
|
260
|
+
request.name,
|
|
261
|
+
request.artifact_type,
|
|
262
|
+
)
|
|
263
|
+
.with_original_size(original_size)
|
|
264
|
+
.with_compressed_size(compressed_size)
|
|
265
|
+
.with_compression(if self.compression_enabled { CompressionType::Zstd } else { CompressionType::None })
|
|
266
|
+
.with_content_hash(content_hash)
|
|
267
|
+
.with_storage_uri(content_path.to_string_lossy().to_string())
|
|
268
|
+
.with_content_type(request.content_type.unwrap_or_else(|| request.artifact_type.default_content_type().to_string()));
|
|
269
|
+
|
|
270
|
+
// Write content file
|
|
271
|
+
fs::write(&content_path, &compressed).await?;
|
|
272
|
+
|
|
273
|
+
// Write metadata file
|
|
274
|
+
let metadata_path = self.artifact_metadata_path(&request.execution_id, &artifact_id);
|
|
275
|
+
self.save_metadata(&metadata_path, &metadata).await?;
|
|
276
|
+
|
|
277
|
+
Ok(PutArtifactResponse {
|
|
278
|
+
artifact_id,
|
|
279
|
+
metadata,
|
|
280
|
+
compressed_size,
|
|
281
|
+
original_size,
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async fn get(&self, artifact_id: &ArtifactId) -> Result<GetArtifactResponse, ArtifactStoreError> {
|
|
286
|
+
// We need to find the execution ID from metadata
|
|
287
|
+
// Search all execution directories
|
|
288
|
+
let mut entries = fs::read_dir(&self.base_path).await?;
|
|
289
|
+
|
|
290
|
+
while let Some(entry) = entries.next_entry().await? {
|
|
291
|
+
if entry.file_type().await?.is_dir() {
|
|
292
|
+
let exec_id = ExecutionId::from(entry.file_name().to_string_lossy().as_ref());
|
|
293
|
+
let metadata_path = self.artifact_metadata_path(&exec_id, artifact_id);
|
|
294
|
+
|
|
295
|
+
if metadata_path.exists() {
|
|
296
|
+
let metadata = self.load_metadata(&metadata_path).await?;
|
|
297
|
+
let content_path = self.artifact_content_path(&exec_id, artifact_id);
|
|
298
|
+
|
|
299
|
+
let compressed = fs::read(&content_path).await?;
|
|
300
|
+
let content = self.decompress(&compressed)?;
|
|
301
|
+
|
|
302
|
+
return Ok(GetArtifactResponse { metadata, content });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
Err(ArtifactStoreError::NotFound(artifact_id.clone()))
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async fn exists(&self, artifact_id: &ArtifactId) -> Result<bool, ArtifactStoreError> {
|
|
311
|
+
// Search all execution directories
|
|
312
|
+
let mut entries = fs::read_dir(&self.base_path).await?;
|
|
313
|
+
|
|
314
|
+
while let Some(entry) = entries.next_entry().await? {
|
|
315
|
+
if entry.file_type().await?.is_dir() {
|
|
316
|
+
let exec_id = ExecutionId::from(entry.file_name().to_string_lossy().as_ref());
|
|
317
|
+
let metadata_path = self.artifact_metadata_path(&exec_id, artifact_id);
|
|
318
|
+
|
|
319
|
+
if metadata_path.exists() {
|
|
320
|
+
return Ok(true);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
Ok(false)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async fn delete(&self, artifact_id: &ArtifactId) -> Result<(), ArtifactStoreError> {
|
|
329
|
+
// Search all execution directories
|
|
330
|
+
let mut entries = fs::read_dir(&self.base_path).await?;
|
|
331
|
+
|
|
332
|
+
while let Some(entry) = entries.next_entry().await? {
|
|
333
|
+
if entry.file_type().await?.is_dir() {
|
|
334
|
+
let exec_id = ExecutionId::from(entry.file_name().to_string_lossy().as_ref());
|
|
335
|
+
let metadata_path = self.artifact_metadata_path(&exec_id, artifact_id);
|
|
336
|
+
|
|
337
|
+
if metadata_path.exists() {
|
|
338
|
+
let content_path = self.artifact_content_path(&exec_id, artifact_id);
|
|
339
|
+
|
|
340
|
+
// Delete both files
|
|
341
|
+
if content_path.exists() {
|
|
342
|
+
fs::remove_file(&content_path).await?;
|
|
343
|
+
}
|
|
344
|
+
fs::remove_file(&metadata_path).await?;
|
|
345
|
+
|
|
346
|
+
return Ok(());
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
Err(ArtifactStoreError::NotFound(artifact_id.clone()))
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async fn list(&self, query: ListArtifactsQuery) -> Result<Vec<ArtifactMetadata>, ArtifactStoreError> {
|
|
355
|
+
let mut results = Vec::new();
|
|
356
|
+
|
|
357
|
+
// If execution_id is specified, only search that directory
|
|
358
|
+
let exec_dirs = if let Some(ref exec_id) = query.execution_id {
|
|
359
|
+
vec![self.execution_path(exec_id)]
|
|
360
|
+
} else {
|
|
361
|
+
// Search all execution directories
|
|
362
|
+
let mut dirs = Vec::new();
|
|
363
|
+
let mut entries = fs::read_dir(&self.base_path).await?;
|
|
364
|
+
while let Some(entry) = entries.next_entry().await? {
|
|
365
|
+
if entry.file_type().await?.is_dir() {
|
|
366
|
+
dirs.push(entry.path());
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
dirs
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
for exec_path in exec_dirs {
|
|
373
|
+
if !exec_path.exists() {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
let mut entries = fs::read_dir(&exec_path).await?;
|
|
378
|
+
while let Some(entry) = entries.next_entry().await? {
|
|
379
|
+
let path = entry.path();
|
|
380
|
+
if path.extension().map(|e| e == "json").unwrap_or(false)
|
|
381
|
+
&& path.to_string_lossy().contains(".meta.")
|
|
382
|
+
{
|
|
383
|
+
if let Ok(metadata) = self.load_metadata(&path).await {
|
|
384
|
+
// Apply filters
|
|
385
|
+
if let Some(ref step_id) = query.step_id {
|
|
386
|
+
if metadata.step_id != *step_id {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if let Some(ref artifact_type) = query.artifact_type {
|
|
391
|
+
if metadata.artifact_type != *artifact_type {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
results.push(metadata);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Sort by creation time
|
|
402
|
+
results.sort_by(|a, b| a.created_at.cmp(&b.created_at));
|
|
403
|
+
|
|
404
|
+
// Apply pagination
|
|
405
|
+
if let Some(offset) = query.offset {
|
|
406
|
+
results = results.into_iter().skip(offset).collect();
|
|
407
|
+
}
|
|
408
|
+
if let Some(limit) = query.limit {
|
|
409
|
+
results.truncate(limit);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
Ok(results)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async fn get_metadata(&self, artifact_id: &ArtifactId) -> Result<ArtifactMetadata, ArtifactStoreError> {
|
|
416
|
+
// Search all execution directories
|
|
417
|
+
let mut entries = fs::read_dir(&self.base_path).await?;
|
|
418
|
+
|
|
419
|
+
while let Some(entry) = entries.next_entry().await? {
|
|
420
|
+
if entry.file_type().await?.is_dir() {
|
|
421
|
+
let exec_id = ExecutionId::from(entry.file_name().to_string_lossy().as_ref());
|
|
422
|
+
let metadata_path = self.artifact_metadata_path(&exec_id, artifact_id);
|
|
423
|
+
|
|
424
|
+
if metadata_path.exists() {
|
|
425
|
+
return self.load_metadata(&metadata_path).await;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
Err(ArtifactStoreError::NotFound(artifact_id.clone()))
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async fn get_execution_size(&self, execution_id: &ExecutionId) -> Result<u64, ArtifactStoreError> {
|
|
434
|
+
let exec_path = self.execution_path(execution_id);
|
|
435
|
+
|
|
436
|
+
if !exec_path.exists() {
|
|
437
|
+
return Ok(0);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
let mut total: u64 = 0;
|
|
441
|
+
let mut entries = fs::read_dir(&exec_path).await?;
|
|
442
|
+
|
|
443
|
+
while let Some(entry) = entries.next_entry().await? {
|
|
444
|
+
if let Ok(metadata) = entry.metadata().await {
|
|
445
|
+
total += metadata.len();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
Ok(total)
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// =============================================================================
|
|
454
|
+
// Tests
|
|
455
|
+
// =============================================================================
|
|
456
|
+
|
|
457
|
+
#[cfg(test)]
|
|
458
|
+
mod tests {
|
|
459
|
+
use super::*;
|
|
460
|
+
use super::super::metadata::ArtifactType;
|
|
461
|
+
use crate::kernel::ids::StepId;
|
|
462
|
+
use tempfile::TempDir;
|
|
463
|
+
|
|
464
|
+
#[tokio::test]
|
|
465
|
+
async fn test_filesystem_store_put_get() {
|
|
466
|
+
let temp_dir = TempDir::new().unwrap();
|
|
467
|
+
let store = FilesystemArtifactStore::new(temp_dir.path());
|
|
468
|
+
|
|
469
|
+
let exec_id = ExecutionId::new();
|
|
470
|
+
let step_id = StepId::new();
|
|
471
|
+
let content = b"Hello, World! This is a test artifact.".to_vec();
|
|
472
|
+
|
|
473
|
+
let request = PutArtifactRequest::new(
|
|
474
|
+
exec_id.clone(),
|
|
475
|
+
step_id,
|
|
476
|
+
"test.txt",
|
|
477
|
+
ArtifactType::Text,
|
|
478
|
+
content.clone(),
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
let response = store.put(request).await.unwrap();
|
|
482
|
+
assert!(response.artifact_id.as_str().starts_with("artifact_"));
|
|
483
|
+
assert!(response.compressed_size > 0);
|
|
484
|
+
assert_eq!(response.original_size, content.len() as u64);
|
|
485
|
+
|
|
486
|
+
// Retrieve and verify
|
|
487
|
+
let get_response = store.get(&response.artifact_id).await.unwrap();
|
|
488
|
+
assert_eq!(get_response.content, content);
|
|
489
|
+
assert_eq!(get_response.metadata.name, "test.txt");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
#[tokio::test]
|
|
493
|
+
async fn test_filesystem_store_compression() {
|
|
494
|
+
let temp_dir = TempDir::new().unwrap();
|
|
495
|
+
let store = FilesystemArtifactStore::new(temp_dir.path());
|
|
496
|
+
|
|
497
|
+
let exec_id = ExecutionId::new();
|
|
498
|
+
let step_id = StepId::new();
|
|
499
|
+
|
|
500
|
+
// Create repetitive content that compresses well
|
|
501
|
+
let content = "Hello, World! ".repeat(1000).into_bytes();
|
|
502
|
+
|
|
503
|
+
let request = PutArtifactRequest::new(
|
|
504
|
+
exec_id,
|
|
505
|
+
step_id,
|
|
506
|
+
"repetitive.txt",
|
|
507
|
+
ArtifactType::Text,
|
|
508
|
+
content.clone(),
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
let response = store.put(request).await.unwrap();
|
|
512
|
+
|
|
513
|
+
// Compressed should be smaller than original for repetitive data
|
|
514
|
+
assert!(response.compressed_size < response.original_size);
|
|
515
|
+
|
|
516
|
+
// Verify content is preserved
|
|
517
|
+
let get_response = store.get(&response.artifact_id).await.unwrap();
|
|
518
|
+
assert_eq!(get_response.content, content);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
#[tokio::test]
|
|
522
|
+
async fn test_filesystem_store_list() {
|
|
523
|
+
let temp_dir = TempDir::new().unwrap();
|
|
524
|
+
let store = FilesystemArtifactStore::new(temp_dir.path());
|
|
525
|
+
|
|
526
|
+
let exec_id = ExecutionId::new();
|
|
527
|
+
let step_id = StepId::new();
|
|
528
|
+
|
|
529
|
+
// Store multiple artifacts
|
|
530
|
+
for i in 0..3 {
|
|
531
|
+
let request = PutArtifactRequest::new(
|
|
532
|
+
exec_id.clone(),
|
|
533
|
+
step_id.clone(),
|
|
534
|
+
format!("file{}.txt", i),
|
|
535
|
+
ArtifactType::Text,
|
|
536
|
+
format!("Content {}", i).into_bytes(),
|
|
537
|
+
);
|
|
538
|
+
store.put(request).await.unwrap();
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// List all for execution
|
|
542
|
+
let query = ListArtifactsQuery::for_execution(exec_id);
|
|
543
|
+
let results = store.list(query).await.unwrap();
|
|
544
|
+
assert_eq!(results.len(), 3);
|
|
545
|
+
}
|
|
546
|
+
}
|