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,2086 @@
|
|
|
1
|
+
//! Execution & Telemetry IDs - Graph-centric observability identifiers
|
|
2
|
+
//!
|
|
3
|
+
//! This module defines the canonical ID types for the enact-core runtime,
|
|
4
|
+
//! aligned with the Execution/Step/Artifact hierarchy.
|
|
5
|
+
//!
|
|
6
|
+
//! ## ID Format
|
|
7
|
+
//! All IDs use prefixed KSUID (K-Sortable Unique IDentifier):
|
|
8
|
+
//! - `exec_[27 alphanumeric chars]` - ExecutionId
|
|
9
|
+
//! - `step_[27 alphanumeric chars]` - StepId
|
|
10
|
+
//! - `artifact_[27 alphanumeric chars]` - ArtifactId
|
|
11
|
+
//!
|
|
12
|
+
//! ## Hierarchy
|
|
13
|
+
//! ```text
|
|
14
|
+
//! ExecutionId (The Run)
|
|
15
|
+
//! ├── StepId (Type: ToolNode) -> Artifacts
|
|
16
|
+
//! ├── StepId (Type: LlmNode) -> Artifacts
|
|
17
|
+
//! └── StepId (Type: GraphNode "Sub-Agent")
|
|
18
|
+
//! └── ExecutionId (Nested run)
|
|
19
|
+
//! └── StepId...
|
|
20
|
+
//! ```
|
|
21
|
+
//!
|
|
22
|
+
//! @see docs/TECHNICAL/01-EXECUTION-TELEMETRY.md
|
|
23
|
+
|
|
24
|
+
use serde::{Deserialize, Serialize};
|
|
25
|
+
use std::fmt;
|
|
26
|
+
use svix_ksuid::{Ksuid, KsuidLike};
|
|
27
|
+
|
|
28
|
+
/// Generate a new KSUID
|
|
29
|
+
fn new_ksuid() -> String {
|
|
30
|
+
Ksuid::new(None, None).to_string()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// ExecutionId - One run of a blueprint (Agent/Graph/Workflow)
|
|
35
|
+
// =============================================================================
|
|
36
|
+
|
|
37
|
+
/// ExecutionId - Identifies one run of a blueprint
|
|
38
|
+
///
|
|
39
|
+
/// This is the primary unit of billing and audit.
|
|
40
|
+
/// Produces an append-only stream of ExecutionEvents.
|
|
41
|
+
///
|
|
42
|
+
/// Format: `exec_[27-char KSUID]`
|
|
43
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
44
|
+
pub struct ExecutionId(String);
|
|
45
|
+
|
|
46
|
+
impl ExecutionId {
|
|
47
|
+
/// Create a new ExecutionId with auto-generated KSUID
|
|
48
|
+
pub fn new() -> Self {
|
|
49
|
+
Self(format!("exec_{}", new_ksuid()))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Create from an existing string (useful for deserialization)
|
|
53
|
+
pub fn from_string(s: impl Into<String>) -> Self {
|
|
54
|
+
Self(s.into())
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Get the ID as a string slice
|
|
58
|
+
pub fn as_str(&self) -> &str {
|
|
59
|
+
&self.0
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
impl Default for ExecutionId {
|
|
64
|
+
fn default() -> Self {
|
|
65
|
+
Self::new()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
impl fmt::Display for ExecutionId {
|
|
70
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
71
|
+
write!(f, "{}", self.0)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
impl From<String> for ExecutionId {
|
|
76
|
+
fn from(s: String) -> Self {
|
|
77
|
+
Self(s)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
impl From<&str> for ExecutionId {
|
|
82
|
+
fn from(s: &str) -> Self {
|
|
83
|
+
Self(s.to_string())
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// RunId - Alias for ExecutionId
|
|
88
|
+
pub type RunId = ExecutionId;
|
|
89
|
+
|
|
90
|
+
// =============================================================================
|
|
91
|
+
// StepId - A distinct action within an execution
|
|
92
|
+
// =============================================================================
|
|
93
|
+
|
|
94
|
+
/// StepId - Identifies a distinct action within an execution
|
|
95
|
+
///
|
|
96
|
+
/// In TUI/CLI, appears as a "Sub-Agent" or "Action" performing work.
|
|
97
|
+
/// Internally, this is a graph node execution.
|
|
98
|
+
///
|
|
99
|
+
/// Format: `step_[27-char KSUID]`
|
|
100
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
101
|
+
pub struct StepId(String);
|
|
102
|
+
|
|
103
|
+
impl StepId {
|
|
104
|
+
/// Create a new StepId with auto-generated KSUID
|
|
105
|
+
pub fn new() -> Self {
|
|
106
|
+
Self(format!("step_{}", new_ksuid()))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Create from an existing string
|
|
110
|
+
pub fn from_string(s: impl Into<String>) -> Self {
|
|
111
|
+
Self(s.into())
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Get the ID as a string slice
|
|
115
|
+
pub fn as_str(&self) -> &str {
|
|
116
|
+
&self.0
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
impl Default for StepId {
|
|
121
|
+
fn default() -> Self {
|
|
122
|
+
Self::new()
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
impl fmt::Display for StepId {
|
|
127
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
128
|
+
write!(f, "{}", self.0)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
impl From<String> for StepId {
|
|
133
|
+
fn from(s: String) -> Self {
|
|
134
|
+
Self(s)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
impl From<&str> for StepId {
|
|
139
|
+
fn from(s: &str) -> Self {
|
|
140
|
+
Self(s.to_string())
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// NodeId - Alias for StepId
|
|
145
|
+
pub type NodeId = StepId;
|
|
146
|
+
|
|
147
|
+
// =============================================================================
|
|
148
|
+
// GraphId - Graph definition identifier (design-time)
|
|
149
|
+
// =============================================================================
|
|
150
|
+
|
|
151
|
+
/// GraphId - Graph definition identifier
|
|
152
|
+
///
|
|
153
|
+
/// Identifies a static execution blueprint (Nodes + Edges).
|
|
154
|
+
/// Versioned and immutable once published.
|
|
155
|
+
///
|
|
156
|
+
/// Format: `graph_[27-char KSUID]`
|
|
157
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
158
|
+
pub struct GraphId(String);
|
|
159
|
+
|
|
160
|
+
impl GraphId {
|
|
161
|
+
pub fn new() -> Self {
|
|
162
|
+
Self(format!("graph_{}", new_ksuid()))
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
pub fn from_string(s: impl Into<String>) -> Self {
|
|
166
|
+
Self(s.into())
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
pub fn as_str(&self) -> &str {
|
|
170
|
+
&self.0
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
impl Default for GraphId {
|
|
175
|
+
fn default() -> Self {
|
|
176
|
+
Self::new()
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
impl fmt::Display for GraphId {
|
|
181
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
182
|
+
write!(f, "{}", self.0)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// =============================================================================
|
|
187
|
+
// ArtifactId - Data produced by a step
|
|
188
|
+
// =============================================================================
|
|
189
|
+
|
|
190
|
+
/// ArtifactId - Identifies persisted output produced by a step
|
|
191
|
+
///
|
|
192
|
+
/// Examples: Generated Code, PDF Report, Search JSON, Image.
|
|
193
|
+
///
|
|
194
|
+
/// Format: `artifact_[27-char KSUID]`
|
|
195
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
196
|
+
pub struct ArtifactId(String);
|
|
197
|
+
|
|
198
|
+
impl ArtifactId {
|
|
199
|
+
pub fn new() -> Self {
|
|
200
|
+
Self(format!("artifact_{}", new_ksuid()))
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
pub fn from_string(s: impl Into<String>) -> Self {
|
|
204
|
+
Self(s.into())
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
pub fn as_str(&self) -> &str {
|
|
208
|
+
&self.0
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
impl Default for ArtifactId {
|
|
213
|
+
fn default() -> Self {
|
|
214
|
+
Self::new()
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
impl fmt::Display for ArtifactId {
|
|
219
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
220
|
+
write!(f, "{}", self.0)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// =============================================================================
|
|
225
|
+
// TenantId / UserId - Multi-tenant context
|
|
226
|
+
// =============================================================================
|
|
227
|
+
|
|
228
|
+
/// TenantId - Multi-tenant isolation identifier
|
|
229
|
+
///
|
|
230
|
+
/// Format: `tenant_[27-char KSUID]`
|
|
231
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
232
|
+
pub struct TenantId(String);
|
|
233
|
+
|
|
234
|
+
impl TenantId {
|
|
235
|
+
pub fn new() -> Self {
|
|
236
|
+
Self(format!("tenant_{}", new_ksuid()))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
pub fn from_string(s: impl Into<String>) -> Self {
|
|
240
|
+
Self(s.into())
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
pub fn as_str(&self) -> &str {
|
|
244
|
+
&self.0
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
impl Default for TenantId {
|
|
249
|
+
fn default() -> Self {
|
|
250
|
+
Self::new()
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
impl fmt::Display for TenantId {
|
|
255
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
256
|
+
write!(f, "{}", self.0)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
impl From<String> for TenantId {
|
|
261
|
+
fn from(s: String) -> Self {
|
|
262
|
+
Self(s)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
impl From<&str> for TenantId {
|
|
267
|
+
fn from(s: &str) -> Self {
|
|
268
|
+
Self(s.to_string())
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/// UserId - User identifier
|
|
273
|
+
///
|
|
274
|
+
/// Format: `user_[27-char KSUID]`
|
|
275
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
276
|
+
pub struct UserId(String);
|
|
277
|
+
|
|
278
|
+
impl UserId {
|
|
279
|
+
pub fn new() -> Self {
|
|
280
|
+
Self(format!("user_{}", new_ksuid()))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
pub fn from_string(s: impl Into<String>) -> Self {
|
|
284
|
+
Self(s.into())
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
pub fn as_str(&self) -> &str {
|
|
288
|
+
&self.0
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
impl Default for UserId {
|
|
293
|
+
fn default() -> Self {
|
|
294
|
+
Self::new()
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
impl fmt::Display for UserId {
|
|
299
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
300
|
+
write!(f, "{}", self.0)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
impl From<String> for UserId {
|
|
305
|
+
fn from(s: String) -> Self {
|
|
306
|
+
Self(s)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
impl From<&str> for UserId {
|
|
311
|
+
fn from(s: &str) -> Self {
|
|
312
|
+
Self(s.to_string())
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// =============================================================================
|
|
317
|
+
// StepType - What kind of step this is
|
|
318
|
+
// =============================================================================
|
|
319
|
+
|
|
320
|
+
/// StepType - Defines what the step *is* (internal: NodeType)
|
|
321
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
322
|
+
#[serde(rename_all = "PascalCase")]
|
|
323
|
+
pub enum StepType {
|
|
324
|
+
/// LLM call step
|
|
325
|
+
LlmNode,
|
|
326
|
+
/// Sub-agent / sub-graph execution
|
|
327
|
+
GraphNode,
|
|
328
|
+
/// Tool execution
|
|
329
|
+
ToolNode,
|
|
330
|
+
/// Deterministic code function
|
|
331
|
+
FunctionNode,
|
|
332
|
+
/// Routing decision node
|
|
333
|
+
RouterNode,
|
|
334
|
+
/// Conditional branch node
|
|
335
|
+
BranchNode,
|
|
336
|
+
/// Loop iteration node
|
|
337
|
+
LoopNode,
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
impl fmt::Display for StepType {
|
|
341
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
342
|
+
match self {
|
|
343
|
+
StepType::LlmNode => write!(f, "LlmNode"),
|
|
344
|
+
StepType::GraphNode => write!(f, "GraphNode"),
|
|
345
|
+
StepType::ToolNode => write!(f, "ToolNode"),
|
|
346
|
+
StepType::FunctionNode => write!(f, "FunctionNode"),
|
|
347
|
+
StepType::RouterNode => write!(f, "RouterNode"),
|
|
348
|
+
StepType::BranchNode => write!(f, "BranchNode"),
|
|
349
|
+
StepType::LoopNode => write!(f, "LoopNode"),
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// =============================================================================
|
|
355
|
+
// CallableType - What kind of callable was invoked
|
|
356
|
+
// =============================================================================
|
|
357
|
+
|
|
358
|
+
/// CallableType - The type of callable that was invoked
|
|
359
|
+
///
|
|
360
|
+
/// Used for billing, traceability, and audit trails. Unlike `name` which can change,
|
|
361
|
+
/// `callable_id` + `callable_type` provide stable identifiers for cost attribution.
|
|
362
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
363
|
+
#[serde(rename_all = "snake_case")]
|
|
364
|
+
pub enum CallableType {
|
|
365
|
+
/// Single LLM call, no history
|
|
366
|
+
Completion,
|
|
367
|
+
/// Multi-turn conversation
|
|
368
|
+
Chat,
|
|
369
|
+
/// Goal-driven with tool use
|
|
370
|
+
Agent,
|
|
371
|
+
/// Compiled graph execution
|
|
372
|
+
Workflow,
|
|
373
|
+
/// Ephemeral tasks (title gen, summarization)
|
|
374
|
+
Background,
|
|
375
|
+
/// Callable that invokes other callables
|
|
376
|
+
Composite,
|
|
377
|
+
/// Tool execution (wraps a tool as callable)
|
|
378
|
+
Tool,
|
|
379
|
+
/// Custom/user-defined callable
|
|
380
|
+
Custom,
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
impl fmt::Display for CallableType {
|
|
384
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
385
|
+
match self {
|
|
386
|
+
CallableType::Completion => write!(f, "completion"),
|
|
387
|
+
CallableType::Chat => write!(f, "chat"),
|
|
388
|
+
CallableType::Agent => write!(f, "agent"),
|
|
389
|
+
CallableType::Workflow => write!(f, "workflow"),
|
|
390
|
+
CallableType::Background => write!(f, "background"),
|
|
391
|
+
CallableType::Composite => write!(f, "composite"),
|
|
392
|
+
CallableType::Tool => write!(f, "tool"),
|
|
393
|
+
CallableType::Custom => write!(f, "custom"),
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
impl Default for CallableType {
|
|
399
|
+
fn default() -> Self {
|
|
400
|
+
CallableType::Agent
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// =============================================================================
|
|
405
|
+
// Parent Linkage - Polymorphic root for causal tracing
|
|
406
|
+
// =============================================================================
|
|
407
|
+
|
|
408
|
+
/// ParentType - What kind of entity triggered this execution
|
|
409
|
+
///
|
|
410
|
+
/// Instead of many nullable root fields, we use a single parent pointer
|
|
411
|
+
/// to trace causal origin.
|
|
412
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
413
|
+
#[serde(rename_all = "snake_case")]
|
|
414
|
+
pub enum ParentType {
|
|
415
|
+
/// User chatted in UI (e.g., msg_1234)
|
|
416
|
+
UserMessage,
|
|
417
|
+
/// Scheduled job (e.g., evt_cron_daily)
|
|
418
|
+
ScheduleEvent,
|
|
419
|
+
/// A parent agent invoked a sub-agent (e.g., step_abc_789)
|
|
420
|
+
StepExecution,
|
|
421
|
+
/// External system trigger (e.g., wh_stripe_hook)
|
|
422
|
+
Webhook,
|
|
423
|
+
/// Agent-to-agent request
|
|
424
|
+
A2aRequest,
|
|
425
|
+
/// System-initiated (startup, recovery)
|
|
426
|
+
System,
|
|
427
|
+
/// Assistant message in a thread
|
|
428
|
+
AssistantMessage,
|
|
429
|
+
/// Start of a thread (no parent)
|
|
430
|
+
ThreadStart,
|
|
431
|
+
/// Result of a tool execution
|
|
432
|
+
ToolResult,
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
impl fmt::Display for ParentType {
|
|
436
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
437
|
+
match self {
|
|
438
|
+
ParentType::UserMessage => write!(f, "user_message"),
|
|
439
|
+
ParentType::ScheduleEvent => write!(f, "schedule_event"),
|
|
440
|
+
ParentType::StepExecution => write!(f, "step_execution"),
|
|
441
|
+
ParentType::Webhook => write!(f, "webhook"),
|
|
442
|
+
ParentType::A2aRequest => write!(f, "a2a_request"),
|
|
443
|
+
ParentType::System => write!(f, "system"),
|
|
444
|
+
ParentType::AssistantMessage => write!(f, "assistant_message"),
|
|
445
|
+
ParentType::ThreadStart => write!(f, "thread_start"),
|
|
446
|
+
ParentType::ToolResult => write!(f, "tool_result"),
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/// ParentLink - Traces causal origin of an execution
|
|
452
|
+
///
|
|
453
|
+
/// Every execution has a parent link that describes what triggered it.
|
|
454
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
455
|
+
pub struct ParentLink {
|
|
456
|
+
/// The ID of the triggering entity
|
|
457
|
+
pub parent_id: String,
|
|
458
|
+
/// What kind of entity triggered this
|
|
459
|
+
pub parent_type: ParentType,
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
impl ParentLink {
|
|
463
|
+
/// Create a new ParentLink
|
|
464
|
+
pub fn new(parent_id: impl Into<String>, parent_type: ParentType) -> Self {
|
|
465
|
+
Self {
|
|
466
|
+
parent_id: parent_id.into(),
|
|
467
|
+
parent_type,
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/// Create a ParentLink for a user message trigger
|
|
472
|
+
pub fn from_user_message(message_id: impl Into<String>) -> Self {
|
|
473
|
+
Self::new(message_id, ParentType::UserMessage)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/// Create a ParentLink for a step execution (sub-agent) trigger
|
|
477
|
+
pub fn from_step(step_id: &StepId) -> Self {
|
|
478
|
+
Self::new(step_id.as_str(), ParentType::StepExecution)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/// Create a ParentLink for a parent execution (nested execution)
|
|
482
|
+
pub fn execution(execution_id: ExecutionId) -> Self {
|
|
483
|
+
Self::new(execution_id.as_str(), ParentType::StepExecution)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/// Create a ParentLink for a system trigger
|
|
487
|
+
pub fn system() -> Self {
|
|
488
|
+
Self::new("system", ParentType::System)
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// =============================================================================
|
|
493
|
+
// StepSource - Tracks why/how a step was created
|
|
494
|
+
// =============================================================================
|
|
495
|
+
|
|
496
|
+
/// StepSourceType - Why/how a step was created
|
|
497
|
+
///
|
|
498
|
+
/// Used to track the origin of each step for audit trails and replay.
|
|
499
|
+
/// Follows the same pattern as ParentType but at the step level.
|
|
500
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
501
|
+
#[serde(rename_all = "snake_case")]
|
|
502
|
+
pub enum StepSourceType {
|
|
503
|
+
/// Part of initial compiled graph (default)
|
|
504
|
+
InitialPlan,
|
|
505
|
+
/// Discovered during agentic execution
|
|
506
|
+
Discovered,
|
|
507
|
+
/// Retry of a failed step
|
|
508
|
+
Retry,
|
|
509
|
+
/// From user guidance via inbox
|
|
510
|
+
UserGuidance,
|
|
511
|
+
/// From tool result suggesting new work
|
|
512
|
+
ToolResult,
|
|
513
|
+
/// From LLM output suggesting new work
|
|
514
|
+
LlmOutput,
|
|
515
|
+
/// From external agent (A2A)
|
|
516
|
+
A2aMessage,
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
impl fmt::Display for StepSourceType {
|
|
520
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
521
|
+
match self {
|
|
522
|
+
StepSourceType::InitialPlan => write!(f, "initial_plan"),
|
|
523
|
+
StepSourceType::Discovered => write!(f, "discovered"),
|
|
524
|
+
StepSourceType::Retry => write!(f, "retry"),
|
|
525
|
+
StepSourceType::UserGuidance => write!(f, "user_guidance"),
|
|
526
|
+
StepSourceType::ToolResult => write!(f, "tool_result"),
|
|
527
|
+
StepSourceType::LlmOutput => write!(f, "llm_output"),
|
|
528
|
+
StepSourceType::A2aMessage => write!(f, "a2a_message"),
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
impl Default for StepSourceType {
|
|
534
|
+
fn default() -> Self {
|
|
535
|
+
StepSourceType::InitialPlan
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/// StepSource - Tracks the origin of a step
|
|
540
|
+
///
|
|
541
|
+
/// Every step can optionally track why it was created, enabling:
|
|
542
|
+
/// - Full audit trails for discovered steps
|
|
543
|
+
/// - Replay of agentic discovery sequences
|
|
544
|
+
/// - Understanding step chains and dependencies
|
|
545
|
+
///
|
|
546
|
+
/// @see packages/enact-schemas/src/execution.schemas.ts - stepSourceSchema
|
|
547
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
548
|
+
#[serde(rename_all = "camelCase")]
|
|
549
|
+
pub struct StepSource {
|
|
550
|
+
/// Type of source (why was this step created)
|
|
551
|
+
pub source_type: StepSourceType,
|
|
552
|
+
/// ID of the step/entity that triggered this step (if any)
|
|
553
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
554
|
+
pub triggered_by: Option<String>,
|
|
555
|
+
/// Human-readable reason for step creation
|
|
556
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
557
|
+
pub reason: Option<String>,
|
|
558
|
+
/// Discovery depth (for discovered steps - how deep in discovery chain)
|
|
559
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
560
|
+
pub depth: Option<u32>,
|
|
561
|
+
/// How this step was spawned (for spawned steps)
|
|
562
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
563
|
+
pub spawn_mode: Option<SpawnMode>,
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
impl StepSource {
|
|
567
|
+
/// Create a new StepSource
|
|
568
|
+
pub fn new(source_type: StepSourceType) -> Self {
|
|
569
|
+
Self {
|
|
570
|
+
source_type,
|
|
571
|
+
triggered_by: None,
|
|
572
|
+
reason: None,
|
|
573
|
+
depth: None,
|
|
574
|
+
spawn_mode: None,
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/// Create a source for initial plan steps
|
|
579
|
+
pub fn initial_plan() -> Self {
|
|
580
|
+
Self::new(StepSourceType::InitialPlan)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/// Create a source for discovered steps
|
|
584
|
+
pub fn discovered(triggered_by: &StepId, reason: impl Into<String>, depth: u32) -> Self {
|
|
585
|
+
Self {
|
|
586
|
+
source_type: StepSourceType::Discovered,
|
|
587
|
+
triggered_by: Some(triggered_by.as_str().to_string()),
|
|
588
|
+
reason: Some(reason.into()),
|
|
589
|
+
depth: Some(depth),
|
|
590
|
+
spawn_mode: None,
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/// Create a source for retry steps
|
|
595
|
+
pub fn retry(original_step_id: &StepId) -> Self {
|
|
596
|
+
Self {
|
|
597
|
+
source_type: StepSourceType::Retry,
|
|
598
|
+
triggered_by: Some(original_step_id.as_str().to_string()),
|
|
599
|
+
reason: None,
|
|
600
|
+
depth: None,
|
|
601
|
+
spawn_mode: None,
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/// Create a source for user guidance steps
|
|
606
|
+
pub fn user_guidance(message_id: impl Into<String>, reason: impl Into<String>) -> Self {
|
|
607
|
+
Self {
|
|
608
|
+
source_type: StepSourceType::UserGuidance,
|
|
609
|
+
triggered_by: Some(message_id.into()),
|
|
610
|
+
reason: Some(reason.into()),
|
|
611
|
+
depth: None,
|
|
612
|
+
spawn_mode: None,
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/// Create a source for LLM output discovery
|
|
617
|
+
pub fn llm_output(step_id: &StepId, reason: impl Into<String>, depth: u32) -> Self {
|
|
618
|
+
Self {
|
|
619
|
+
source_type: StepSourceType::LlmOutput,
|
|
620
|
+
triggered_by: Some(step_id.as_str().to_string()),
|
|
621
|
+
reason: Some(reason.into()),
|
|
622
|
+
depth: Some(depth),
|
|
623
|
+
spawn_mode: None,
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/// Create a source for tool result discovery
|
|
628
|
+
pub fn tool_result(step_id: &StepId, reason: impl Into<String>, depth: u32) -> Self {
|
|
629
|
+
Self {
|
|
630
|
+
source_type: StepSourceType::ToolResult,
|
|
631
|
+
triggered_by: Some(step_id.as_str().to_string()),
|
|
632
|
+
reason: Some(reason.into()),
|
|
633
|
+
depth: Some(depth),
|
|
634
|
+
spawn_mode: None,
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/// Set the spawn_mode field
|
|
639
|
+
pub fn with_spawn_mode(mut self, spawn_mode: SpawnMode) -> Self {
|
|
640
|
+
self.spawn_mode = Some(spawn_mode);
|
|
641
|
+
self
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/// Set the triggered_by field
|
|
645
|
+
pub fn with_triggered_by(mut self, triggered_by: impl Into<String>) -> Self {
|
|
646
|
+
self.triggered_by = Some(triggered_by.into());
|
|
647
|
+
self
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/// Set the reason field
|
|
651
|
+
pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
|
|
652
|
+
self.reason = Some(reason.into());
|
|
653
|
+
self
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/// Set the depth field
|
|
657
|
+
pub fn with_depth(mut self, depth: u32) -> Self {
|
|
658
|
+
self.depth = Some(depth);
|
|
659
|
+
self
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
impl Default for StepSource {
|
|
664
|
+
fn default() -> Self {
|
|
665
|
+
Self::initial_plan()
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// =============================================================================
|
|
670
|
+
// SpawnMode - Execution Isolation Control
|
|
671
|
+
// =============================================================================
|
|
672
|
+
|
|
673
|
+
/// SpawnMode - How a callable spawns child work
|
|
674
|
+
///
|
|
675
|
+
/// Controls execution isolation, inbox routing, and cancellation behavior.
|
|
676
|
+
/// Every callable can spawn children - SpawnMode determines HOW.
|
|
677
|
+
///
|
|
678
|
+
/// @see packages/enact-schemas/src/execution.schemas.ts - spawnModeSchema
|
|
679
|
+
/// @see docs/TECHNICAL/32-SPAWN-MODE.md
|
|
680
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
681
|
+
#[serde(tag = "mode", rename_all = "snake_case")]
|
|
682
|
+
pub enum SpawnMode {
|
|
683
|
+
/// Child work joins parent's pending_steps queue (same ExecutionId)
|
|
684
|
+
/// - Same execution context
|
|
685
|
+
/// - Same inbox
|
|
686
|
+
/// - Same policies
|
|
687
|
+
Inline,
|
|
688
|
+
|
|
689
|
+
/// Child work runs as isolated execution (new ExecutionId with ParentLink)
|
|
690
|
+
Child {
|
|
691
|
+
/// Run in background (don't block parent)
|
|
692
|
+
#[serde(default)]
|
|
693
|
+
background: bool,
|
|
694
|
+
|
|
695
|
+
/// Inherit parent's inbox (forward messages)
|
|
696
|
+
#[serde(default)]
|
|
697
|
+
inherit_inbox: bool,
|
|
698
|
+
|
|
699
|
+
/// Override policies for child (JSON object)
|
|
700
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
701
|
+
policies: Option<serde_json::Value>,
|
|
702
|
+
},
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
impl Default for SpawnMode {
|
|
706
|
+
fn default() -> Self {
|
|
707
|
+
SpawnMode::Inline
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
impl SpawnMode {
|
|
712
|
+
/// Create inline spawn mode (default)
|
|
713
|
+
pub fn inline() -> Self {
|
|
714
|
+
SpawnMode::Inline
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/// Create child spawn mode with options
|
|
718
|
+
pub fn child(background: bool, inherit_inbox: bool) -> Self {
|
|
719
|
+
SpawnMode::Child {
|
|
720
|
+
background,
|
|
721
|
+
inherit_inbox,
|
|
722
|
+
policies: None,
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/// Create child spawn mode with custom policies
|
|
727
|
+
pub fn child_with_policies(
|
|
728
|
+
background: bool,
|
|
729
|
+
inherit_inbox: bool,
|
|
730
|
+
policies: serde_json::Value,
|
|
731
|
+
) -> Self {
|
|
732
|
+
SpawnMode::Child {
|
|
733
|
+
background,
|
|
734
|
+
inherit_inbox,
|
|
735
|
+
policies: Some(policies),
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/// Check if this is inline mode
|
|
740
|
+
pub fn is_inline(&self) -> bool {
|
|
741
|
+
matches!(self, SpawnMode::Inline)
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/// Check if this is child mode
|
|
745
|
+
pub fn is_child(&self) -> bool {
|
|
746
|
+
matches!(self, SpawnMode::Child { .. })
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/// Check if child should run in background
|
|
750
|
+
pub fn is_background(&self) -> bool {
|
|
751
|
+
matches!(self, SpawnMode::Child { background: true, .. })
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/// Check if child should inherit parent's inbox
|
|
755
|
+
pub fn inherits_inbox(&self) -> bool {
|
|
756
|
+
matches!(self, SpawnMode::Child { inherit_inbox: true, .. })
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
impl fmt::Display for SpawnMode {
|
|
761
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
762
|
+
match self {
|
|
763
|
+
SpawnMode::Inline => write!(f, "inline"),
|
|
764
|
+
SpawnMode::Child { background, inherit_inbox, .. } => {
|
|
765
|
+
write!(f, "child(background={}, inherit_inbox={})", background, inherit_inbox)
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// =============================================================================
|
|
772
|
+
// CancellationPolicy - Child Execution Lifecycle
|
|
773
|
+
// =============================================================================
|
|
774
|
+
|
|
775
|
+
/// CancellationPolicy - What happens to children when parent is cancelled
|
|
776
|
+
///
|
|
777
|
+
/// Controls the lifecycle relationship between parent and child executions.
|
|
778
|
+
///
|
|
779
|
+
/// @see packages/enact-schemas/src/execution.schemas.ts - cancellationPolicySchema
|
|
780
|
+
/// @see docs/TECHNICAL/32-SPAWN-MODE.md
|
|
781
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
782
|
+
#[serde(rename_all = "snake_case")]
|
|
783
|
+
pub enum CancellationPolicy {
|
|
784
|
+
/// Cancel all children when parent cancelled (default)
|
|
785
|
+
CascadeCancel,
|
|
786
|
+
|
|
787
|
+
/// Let children complete, parent waits for them
|
|
788
|
+
WaitForChildren,
|
|
789
|
+
|
|
790
|
+
/// Detach children (they continue independently)
|
|
791
|
+
Detach,
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
impl Default for CancellationPolicy {
|
|
795
|
+
fn default() -> Self {
|
|
796
|
+
CancellationPolicy::CascadeCancel
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
impl fmt::Display for CancellationPolicy {
|
|
801
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
802
|
+
match self {
|
|
803
|
+
CancellationPolicy::CascadeCancel => write!(f, "cascade_cancel"),
|
|
804
|
+
CancellationPolicy::WaitForChildren => write!(f, "wait_for_children"),
|
|
805
|
+
CancellationPolicy::Detach => write!(f, "detach"),
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// =============================================================================
|
|
811
|
+
// ThreadId / MessageId - Conversation hierarchy
|
|
812
|
+
// =============================================================================
|
|
813
|
+
|
|
814
|
+
/// ThreadId - Conversation thread identifier
|
|
815
|
+
///
|
|
816
|
+
/// Groups messages into a single conversation context.
|
|
817
|
+
///
|
|
818
|
+
/// Format: `thread_[27-char KSUID]`
|
|
819
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
820
|
+
pub struct ThreadId(String);
|
|
821
|
+
|
|
822
|
+
impl ThreadId {
|
|
823
|
+
pub fn new() -> Self {
|
|
824
|
+
Self(format!("thread_{}", new_ksuid()))
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
pub fn from_string(s: impl Into<String>) -> Self {
|
|
828
|
+
Self(s.into())
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
pub fn as_str(&self) -> &str {
|
|
832
|
+
&self.0
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
impl Default for ThreadId {
|
|
837
|
+
fn default() -> Self {
|
|
838
|
+
Self::new()
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
impl fmt::Display for ThreadId {
|
|
843
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
844
|
+
write!(f, "{}", self.0)
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
impl From<String> for ThreadId {
|
|
849
|
+
fn from(s: String) -> Self {
|
|
850
|
+
Self(s)
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
impl From<&str> for ThreadId {
|
|
855
|
+
fn from(s: &str) -> Self {
|
|
856
|
+
Self(s.to_string())
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/// MessageId - Individual message identifier
|
|
861
|
+
///
|
|
862
|
+
/// Identifies a single message within a thread.
|
|
863
|
+
///
|
|
864
|
+
/// Format: `msg_[27-char KSUID]`
|
|
865
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
866
|
+
pub struct MessageId(String);
|
|
867
|
+
|
|
868
|
+
impl MessageId {
|
|
869
|
+
pub fn new() -> Self {
|
|
870
|
+
Self(format!("msg_{}", new_ksuid()))
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
pub fn from_string(s: impl Into<String>) -> Self {
|
|
874
|
+
Self(s.into())
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
pub fn as_str(&self) -> &str {
|
|
878
|
+
&self.0
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
impl Default for MessageId {
|
|
883
|
+
fn default() -> Self {
|
|
884
|
+
Self::new()
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
impl fmt::Display for MessageId {
|
|
889
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
890
|
+
write!(f, "{}", self.0)
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
impl From<String> for MessageId {
|
|
895
|
+
fn from(s: String) -> Self {
|
|
896
|
+
Self(s)
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
impl From<&str> for MessageId {
|
|
901
|
+
fn from(s: &str) -> Self {
|
|
902
|
+
Self(s.to_string())
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// =============================================================================
|
|
907
|
+
// ID Prefix Constants
|
|
908
|
+
// =============================================================================
|
|
909
|
+
|
|
910
|
+
/// ID prefix constants for validation and generation
|
|
911
|
+
pub mod prefixes {
|
|
912
|
+
pub const EXECUTION: &str = "exec_";
|
|
913
|
+
pub const STEP: &str = "step_";
|
|
914
|
+
pub const ARTIFACT: &str = "artifact_";
|
|
915
|
+
pub const GRAPH: &str = "graph_";
|
|
916
|
+
pub const TENANT: &str = "tenant_";
|
|
917
|
+
pub const USER: &str = "user_";
|
|
918
|
+
pub const EVENT: &str = "evt_";
|
|
919
|
+
pub const DECISION: &str = "dec_";
|
|
920
|
+
pub const CONTROL: &str = "ctrl_";
|
|
921
|
+
pub const THREAD: &str = "thread_";
|
|
922
|
+
pub const MESSAGE: &str = "msg_";
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
#[cfg(test)]
|
|
926
|
+
mod tests {
|
|
927
|
+
use super::*;
|
|
928
|
+
use std::collections::HashSet;
|
|
929
|
+
|
|
930
|
+
// =========================================================================
|
|
931
|
+
// ExecutionId Tests
|
|
932
|
+
// =========================================================================
|
|
933
|
+
|
|
934
|
+
#[test]
|
|
935
|
+
fn test_execution_id_new_has_correct_prefix() {
|
|
936
|
+
let id = ExecutionId::new();
|
|
937
|
+
assert!(
|
|
938
|
+
id.as_str().starts_with("exec_"),
|
|
939
|
+
"ExecutionId should start with 'exec_'"
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
#[test]
|
|
944
|
+
fn test_execution_id_new_unique() {
|
|
945
|
+
let id1 = ExecutionId::new();
|
|
946
|
+
let id2 = ExecutionId::new();
|
|
947
|
+
assert_ne!(id1, id2, "Each new ExecutionId should be unique");
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
#[test]
|
|
951
|
+
fn test_execution_id_from_string() {
|
|
952
|
+
let id = ExecutionId::from_string("exec_custom123");
|
|
953
|
+
assert_eq!(id.as_str(), "exec_custom123");
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
#[test]
|
|
957
|
+
fn test_execution_id_from_string_owned() {
|
|
958
|
+
let id = ExecutionId::from_string(String::from("exec_owned"));
|
|
959
|
+
assert_eq!(id.as_str(), "exec_owned");
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
#[test]
|
|
963
|
+
fn test_execution_id_display() {
|
|
964
|
+
let id = ExecutionId::from_string("exec_display_test");
|
|
965
|
+
assert_eq!(format!("{}", id), "exec_display_test");
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
#[test]
|
|
969
|
+
fn test_execution_id_default() {
|
|
970
|
+
let id = ExecutionId::default();
|
|
971
|
+
assert!(id.as_str().starts_with("exec_"));
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
#[test]
|
|
975
|
+
fn test_execution_id_from_string_trait() {
|
|
976
|
+
let id: ExecutionId = String::from("exec_from_string").into();
|
|
977
|
+
assert_eq!(id.as_str(), "exec_from_string");
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
#[test]
|
|
981
|
+
fn test_execution_id_from_str_trait() {
|
|
982
|
+
let id: ExecutionId = "exec_from_str".into();
|
|
983
|
+
assert_eq!(id.as_str(), "exec_from_str");
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
#[test]
|
|
987
|
+
fn test_execution_id_clone() {
|
|
988
|
+
let id1 = ExecutionId::new();
|
|
989
|
+
let id2 = id1.clone();
|
|
990
|
+
assert_eq!(id1, id2);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
#[test]
|
|
994
|
+
fn test_execution_id_hash() {
|
|
995
|
+
let id1 = ExecutionId::from_string("exec_hash_test");
|
|
996
|
+
let id2 = ExecutionId::from_string("exec_hash_test");
|
|
997
|
+
let mut set = HashSet::new();
|
|
998
|
+
set.insert(id1);
|
|
999
|
+
assert!(set.contains(&id2));
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
#[test]
|
|
1003
|
+
fn test_execution_id_debug() {
|
|
1004
|
+
let id = ExecutionId::from_string("exec_debug");
|
|
1005
|
+
let debug_str = format!("{:?}", id);
|
|
1006
|
+
assert!(debug_str.contains("exec_debug"));
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
#[test]
|
|
1010
|
+
fn test_execution_id_serde() {
|
|
1011
|
+
let id = ExecutionId::from_string("exec_serde_test");
|
|
1012
|
+
let serialized = serde_json::to_string(&id).unwrap();
|
|
1013
|
+
let deserialized: ExecutionId = serde_json::from_str(&serialized).unwrap();
|
|
1014
|
+
assert_eq!(id, deserialized);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// =========================================================================
|
|
1018
|
+
// StepId Tests
|
|
1019
|
+
// =========================================================================
|
|
1020
|
+
|
|
1021
|
+
#[test]
|
|
1022
|
+
fn test_step_id_new_has_correct_prefix() {
|
|
1023
|
+
let id = StepId::new();
|
|
1024
|
+
assert!(
|
|
1025
|
+
id.as_str().starts_with("step_"),
|
|
1026
|
+
"StepId should start with 'step_'"
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
#[test]
|
|
1031
|
+
fn test_step_id_new_unique() {
|
|
1032
|
+
let id1 = StepId::new();
|
|
1033
|
+
let id2 = StepId::new();
|
|
1034
|
+
assert_ne!(id1, id2);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
#[test]
|
|
1038
|
+
fn test_step_id_from_string() {
|
|
1039
|
+
let id = StepId::from_string("step_custom");
|
|
1040
|
+
assert_eq!(id.as_str(), "step_custom");
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
#[test]
|
|
1044
|
+
fn test_step_id_display() {
|
|
1045
|
+
let id = StepId::from_string("step_display");
|
|
1046
|
+
assert_eq!(format!("{}", id), "step_display");
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
#[test]
|
|
1050
|
+
fn test_step_id_default() {
|
|
1051
|
+
let id = StepId::default();
|
|
1052
|
+
assert!(id.as_str().starts_with("step_"));
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
#[test]
|
|
1056
|
+
fn test_step_id_from_traits() {
|
|
1057
|
+
let id1: StepId = String::from("step_string").into();
|
|
1058
|
+
let id2: StepId = "step_str".into();
|
|
1059
|
+
assert_eq!(id1.as_str(), "step_string");
|
|
1060
|
+
assert_eq!(id2.as_str(), "step_str");
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
#[test]
|
|
1064
|
+
fn test_step_id_hash() {
|
|
1065
|
+
let id = StepId::from_string("step_hash");
|
|
1066
|
+
let mut set = HashSet::new();
|
|
1067
|
+
set.insert(id.clone());
|
|
1068
|
+
assert!(set.contains(&id));
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
#[test]
|
|
1072
|
+
fn test_step_id_serde() {
|
|
1073
|
+
let id = StepId::from_string("step_serde");
|
|
1074
|
+
let json = serde_json::to_string(&id).unwrap();
|
|
1075
|
+
let parsed: StepId = serde_json::from_str(&json).unwrap();
|
|
1076
|
+
assert_eq!(id, parsed);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// =========================================================================
|
|
1080
|
+
// GraphId Tests
|
|
1081
|
+
// =========================================================================
|
|
1082
|
+
|
|
1083
|
+
#[test]
|
|
1084
|
+
fn test_graph_id_new_has_correct_prefix() {
|
|
1085
|
+
let id = GraphId::new();
|
|
1086
|
+
assert!(id.as_str().starts_with("graph_"));
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
#[test]
|
|
1090
|
+
fn test_graph_id_new_unique() {
|
|
1091
|
+
let id1 = GraphId::new();
|
|
1092
|
+
let id2 = GraphId::new();
|
|
1093
|
+
assert_ne!(id1, id2);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
#[test]
|
|
1097
|
+
fn test_graph_id_from_string() {
|
|
1098
|
+
let id = GraphId::from_string("graph_custom");
|
|
1099
|
+
assert_eq!(id.as_str(), "graph_custom");
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
#[test]
|
|
1103
|
+
fn test_graph_id_display() {
|
|
1104
|
+
let id = GraphId::from_string("graph_display");
|
|
1105
|
+
assert_eq!(format!("{}", id), "graph_display");
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
#[test]
|
|
1109
|
+
fn test_graph_id_default() {
|
|
1110
|
+
let id = GraphId::default();
|
|
1111
|
+
assert!(id.as_str().starts_with("graph_"));
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
#[test]
|
|
1115
|
+
fn test_graph_id_serde() {
|
|
1116
|
+
let id = GraphId::from_string("graph_serde");
|
|
1117
|
+
let json = serde_json::to_string(&id).unwrap();
|
|
1118
|
+
let parsed: GraphId = serde_json::from_str(&json).unwrap();
|
|
1119
|
+
assert_eq!(id, parsed);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// =========================================================================
|
|
1123
|
+
// ArtifactId Tests
|
|
1124
|
+
// =========================================================================
|
|
1125
|
+
|
|
1126
|
+
#[test]
|
|
1127
|
+
fn test_artifact_id_new_has_correct_prefix() {
|
|
1128
|
+
let id = ArtifactId::new();
|
|
1129
|
+
assert!(id.as_str().starts_with("artifact_"));
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
#[test]
|
|
1133
|
+
fn test_artifact_id_new_unique() {
|
|
1134
|
+
let id1 = ArtifactId::new();
|
|
1135
|
+
let id2 = ArtifactId::new();
|
|
1136
|
+
assert_ne!(id1, id2);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
#[test]
|
|
1140
|
+
fn test_artifact_id_from_string() {
|
|
1141
|
+
let id = ArtifactId::from_string("artifact_custom");
|
|
1142
|
+
assert_eq!(id.as_str(), "artifact_custom");
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
#[test]
|
|
1146
|
+
fn test_artifact_id_display() {
|
|
1147
|
+
let id = ArtifactId::from_string("artifact_display");
|
|
1148
|
+
assert_eq!(format!("{}", id), "artifact_display");
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
#[test]
|
|
1152
|
+
fn test_artifact_id_default() {
|
|
1153
|
+
let id = ArtifactId::default();
|
|
1154
|
+
assert!(id.as_str().starts_with("artifact_"));
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
#[test]
|
|
1158
|
+
fn test_artifact_id_serde() {
|
|
1159
|
+
let id = ArtifactId::from_string("artifact_serde");
|
|
1160
|
+
let json = serde_json::to_string(&id).unwrap();
|
|
1161
|
+
let parsed: ArtifactId = serde_json::from_str(&json).unwrap();
|
|
1162
|
+
assert_eq!(id, parsed);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// =========================================================================
|
|
1166
|
+
// TenantId Tests
|
|
1167
|
+
// =========================================================================
|
|
1168
|
+
|
|
1169
|
+
#[test]
|
|
1170
|
+
fn test_tenant_id_new_has_correct_prefix() {
|
|
1171
|
+
let id = TenantId::new();
|
|
1172
|
+
assert!(id.as_str().starts_with("tenant_"));
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
#[test]
|
|
1176
|
+
fn test_tenant_id_new_unique() {
|
|
1177
|
+
let id1 = TenantId::new();
|
|
1178
|
+
let id2 = TenantId::new();
|
|
1179
|
+
assert_ne!(id1, id2);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
#[test]
|
|
1183
|
+
fn test_tenant_id_from_string() {
|
|
1184
|
+
let id = TenantId::from_string("tenant_custom");
|
|
1185
|
+
assert_eq!(id.as_str(), "tenant_custom");
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
#[test]
|
|
1189
|
+
fn test_tenant_id_display() {
|
|
1190
|
+
let id = TenantId::from_string("tenant_display");
|
|
1191
|
+
assert_eq!(format!("{}", id), "tenant_display");
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
#[test]
|
|
1195
|
+
fn test_tenant_id_default() {
|
|
1196
|
+
let id = TenantId::default();
|
|
1197
|
+
assert!(id.as_str().starts_with("tenant_"));
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
#[test]
|
|
1201
|
+
fn test_tenant_id_from_traits() {
|
|
1202
|
+
let id1: TenantId = String::from("tenant_string").into();
|
|
1203
|
+
let id2: TenantId = "tenant_str".into();
|
|
1204
|
+
assert_eq!(id1.as_str(), "tenant_string");
|
|
1205
|
+
assert_eq!(id2.as_str(), "tenant_str");
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
#[test]
|
|
1209
|
+
fn test_tenant_id_serde() {
|
|
1210
|
+
let id = TenantId::from_string("tenant_serde");
|
|
1211
|
+
let json = serde_json::to_string(&id).unwrap();
|
|
1212
|
+
let parsed: TenantId = serde_json::from_str(&json).unwrap();
|
|
1213
|
+
assert_eq!(id, parsed);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// =========================================================================
|
|
1217
|
+
// UserId Tests
|
|
1218
|
+
// =========================================================================
|
|
1219
|
+
|
|
1220
|
+
#[test]
|
|
1221
|
+
fn test_user_id_new_has_correct_prefix() {
|
|
1222
|
+
let id = UserId::new();
|
|
1223
|
+
assert!(id.as_str().starts_with("user_"));
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
#[test]
|
|
1227
|
+
fn test_user_id_new_unique() {
|
|
1228
|
+
let id1 = UserId::new();
|
|
1229
|
+
let id2 = UserId::new();
|
|
1230
|
+
assert_ne!(id1, id2);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
#[test]
|
|
1234
|
+
fn test_user_id_from_string() {
|
|
1235
|
+
let id = UserId::from_string("user_custom");
|
|
1236
|
+
assert_eq!(id.as_str(), "user_custom");
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
#[test]
|
|
1240
|
+
fn test_user_id_display() {
|
|
1241
|
+
let id = UserId::from_string("user_display");
|
|
1242
|
+
assert_eq!(format!("{}", id), "user_display");
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
#[test]
|
|
1246
|
+
fn test_user_id_default() {
|
|
1247
|
+
let id = UserId::default();
|
|
1248
|
+
assert!(id.as_str().starts_with("user_"));
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
#[test]
|
|
1252
|
+
fn test_user_id_from_traits() {
|
|
1253
|
+
let id1: UserId = String::from("user_string").into();
|
|
1254
|
+
let id2: UserId = "user_str".into();
|
|
1255
|
+
assert_eq!(id1.as_str(), "user_string");
|
|
1256
|
+
assert_eq!(id2.as_str(), "user_str");
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
#[test]
|
|
1260
|
+
fn test_user_id_serde() {
|
|
1261
|
+
let id = UserId::from_string("user_serde");
|
|
1262
|
+
let json = serde_json::to_string(&id).unwrap();
|
|
1263
|
+
let parsed: UserId = serde_json::from_str(&json).unwrap();
|
|
1264
|
+
assert_eq!(id, parsed);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// =========================================================================
|
|
1268
|
+
// StepType Tests
|
|
1269
|
+
// =========================================================================
|
|
1270
|
+
|
|
1271
|
+
#[test]
|
|
1272
|
+
fn test_step_type_display_llm_node() {
|
|
1273
|
+
assert_eq!(format!("{}", StepType::LlmNode), "LlmNode");
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
#[test]
|
|
1277
|
+
fn test_step_type_display_graph_node() {
|
|
1278
|
+
assert_eq!(format!("{}", StepType::GraphNode), "GraphNode");
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
#[test]
|
|
1282
|
+
fn test_step_type_display_tool_node() {
|
|
1283
|
+
assert_eq!(format!("{}", StepType::ToolNode), "ToolNode");
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
#[test]
|
|
1287
|
+
fn test_step_type_display_function_node() {
|
|
1288
|
+
assert_eq!(format!("{}", StepType::FunctionNode), "FunctionNode");
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
#[test]
|
|
1292
|
+
fn test_step_type_display_router_node() {
|
|
1293
|
+
assert_eq!(format!("{}", StepType::RouterNode), "RouterNode");
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
#[test]
|
|
1297
|
+
fn test_step_type_display_branch_node() {
|
|
1298
|
+
assert_eq!(format!("{}", StepType::BranchNode), "BranchNode");
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
#[test]
|
|
1302
|
+
fn test_step_type_display_loop_node() {
|
|
1303
|
+
assert_eq!(format!("{}", StepType::LoopNode), "LoopNode");
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
#[test]
|
|
1307
|
+
fn test_step_type_serde_llm_node() {
|
|
1308
|
+
let step = StepType::LlmNode;
|
|
1309
|
+
let json = serde_json::to_string(&step).unwrap();
|
|
1310
|
+
assert_eq!(json, "\"LlmNode\"");
|
|
1311
|
+
let parsed: StepType = serde_json::from_str(&json).unwrap();
|
|
1312
|
+
assert_eq!(parsed, StepType::LlmNode);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
#[test]
|
|
1316
|
+
fn test_step_type_serde_all_variants() {
|
|
1317
|
+
let variants = vec![
|
|
1318
|
+
StepType::LlmNode,
|
|
1319
|
+
StepType::GraphNode,
|
|
1320
|
+
StepType::ToolNode,
|
|
1321
|
+
StepType::FunctionNode,
|
|
1322
|
+
StepType::RouterNode,
|
|
1323
|
+
StepType::BranchNode,
|
|
1324
|
+
StepType::LoopNode,
|
|
1325
|
+
];
|
|
1326
|
+
for variant in variants {
|
|
1327
|
+
let json = serde_json::to_string(&variant).unwrap();
|
|
1328
|
+
let parsed: StepType = serde_json::from_str(&json).unwrap();
|
|
1329
|
+
assert_eq!(parsed, variant);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
#[test]
|
|
1334
|
+
fn test_step_type_equality() {
|
|
1335
|
+
assert_eq!(StepType::LlmNode, StepType::LlmNode);
|
|
1336
|
+
assert_ne!(StepType::LlmNode, StepType::ToolNode);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
#[test]
|
|
1340
|
+
fn test_step_type_clone() {
|
|
1341
|
+
let step = StepType::GraphNode;
|
|
1342
|
+
let cloned = step.clone();
|
|
1343
|
+
assert_eq!(step, cloned);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// =========================================================================
|
|
1347
|
+
// CallableType Tests (for billing/traceability)
|
|
1348
|
+
// =========================================================================
|
|
1349
|
+
|
|
1350
|
+
#[test]
|
|
1351
|
+
fn test_callable_type_display_all() {
|
|
1352
|
+
assert_eq!(format!("{}", CallableType::Completion), "completion");
|
|
1353
|
+
assert_eq!(format!("{}", CallableType::Chat), "chat");
|
|
1354
|
+
assert_eq!(format!("{}", CallableType::Agent), "agent");
|
|
1355
|
+
assert_eq!(format!("{}", CallableType::Workflow), "workflow");
|
|
1356
|
+
assert_eq!(format!("{}", CallableType::Background), "background");
|
|
1357
|
+
assert_eq!(format!("{}", CallableType::Composite), "composite");
|
|
1358
|
+
assert_eq!(format!("{}", CallableType::Tool), "tool");
|
|
1359
|
+
assert_eq!(format!("{}", CallableType::Custom), "custom");
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
#[test]
|
|
1363
|
+
fn test_callable_type_default() {
|
|
1364
|
+
let default_type = CallableType::default();
|
|
1365
|
+
assert_eq!(default_type, CallableType::Agent);
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
#[test]
|
|
1369
|
+
fn test_callable_type_serde_all_variants() {
|
|
1370
|
+
let variants = vec![
|
|
1371
|
+
CallableType::Completion,
|
|
1372
|
+
CallableType::Chat,
|
|
1373
|
+
CallableType::Agent,
|
|
1374
|
+
CallableType::Workflow,
|
|
1375
|
+
CallableType::Background,
|
|
1376
|
+
CallableType::Composite,
|
|
1377
|
+
CallableType::Tool,
|
|
1378
|
+
CallableType::Custom,
|
|
1379
|
+
];
|
|
1380
|
+
for variant in variants {
|
|
1381
|
+
let json = serde_json::to_string(&variant).unwrap();
|
|
1382
|
+
let parsed: CallableType = serde_json::from_str(&json).unwrap();
|
|
1383
|
+
assert_eq!(parsed, variant);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
#[test]
|
|
1388
|
+
fn test_callable_type_serde_snake_case() {
|
|
1389
|
+
// Verify snake_case serialization
|
|
1390
|
+
let json = serde_json::to_string(&CallableType::Background).unwrap();
|
|
1391
|
+
assert_eq!(json, "\"background\"");
|
|
1392
|
+
|
|
1393
|
+
let json = serde_json::to_string(&CallableType::Workflow).unwrap();
|
|
1394
|
+
assert_eq!(json, "\"workflow\"");
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
#[test]
|
|
1398
|
+
fn test_callable_type_equality() {
|
|
1399
|
+
assert_eq!(CallableType::Agent, CallableType::Agent);
|
|
1400
|
+
assert_ne!(CallableType::Agent, CallableType::Chat);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
#[test]
|
|
1404
|
+
fn test_callable_type_clone() {
|
|
1405
|
+
let callable = CallableType::Workflow;
|
|
1406
|
+
let cloned = callable.clone();
|
|
1407
|
+
assert_eq!(callable, cloned);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
#[test]
|
|
1411
|
+
fn test_callable_type_hash() {
|
|
1412
|
+
use std::collections::HashSet;
|
|
1413
|
+
let mut set = HashSet::new();
|
|
1414
|
+
set.insert(CallableType::Agent);
|
|
1415
|
+
set.insert(CallableType::Chat);
|
|
1416
|
+
set.insert(CallableType::Agent); // duplicate
|
|
1417
|
+
assert_eq!(set.len(), 2);
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
// =========================================================================
|
|
1421
|
+
// ParentType Tests
|
|
1422
|
+
// =========================================================================
|
|
1423
|
+
|
|
1424
|
+
#[test]
|
|
1425
|
+
fn test_parent_type_display_user_message() {
|
|
1426
|
+
assert_eq!(format!("{}", ParentType::UserMessage), "user_message");
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
#[test]
|
|
1430
|
+
fn test_parent_type_display_schedule_event() {
|
|
1431
|
+
assert_eq!(format!("{}", ParentType::ScheduleEvent), "schedule_event");
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
#[test]
|
|
1435
|
+
fn test_parent_type_display_step_execution() {
|
|
1436
|
+
assert_eq!(format!("{}", ParentType::StepExecution), "step_execution");
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
#[test]
|
|
1440
|
+
fn test_parent_type_display_webhook() {
|
|
1441
|
+
assert_eq!(format!("{}", ParentType::Webhook), "webhook");
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
#[test]
|
|
1445
|
+
fn test_parent_type_display_a2a_request() {
|
|
1446
|
+
assert_eq!(format!("{}", ParentType::A2aRequest), "a2a_request");
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
#[test]
|
|
1450
|
+
fn test_parent_type_display_system() {
|
|
1451
|
+
assert_eq!(format!("{}", ParentType::System), "system");
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
#[test]
|
|
1455
|
+
fn test_parent_type_serde_all_variants() {
|
|
1456
|
+
let variants = vec![
|
|
1457
|
+
ParentType::UserMessage,
|
|
1458
|
+
ParentType::ScheduleEvent,
|
|
1459
|
+
ParentType::StepExecution,
|
|
1460
|
+
ParentType::Webhook,
|
|
1461
|
+
ParentType::A2aRequest,
|
|
1462
|
+
ParentType::System,
|
|
1463
|
+
];
|
|
1464
|
+
for variant in variants {
|
|
1465
|
+
let json = serde_json::to_string(&variant).unwrap();
|
|
1466
|
+
let parsed: ParentType = serde_json::from_str(&json).unwrap();
|
|
1467
|
+
assert_eq!(parsed, variant);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
#[test]
|
|
1472
|
+
fn test_parent_type_equality() {
|
|
1473
|
+
assert_eq!(ParentType::System, ParentType::System);
|
|
1474
|
+
assert_ne!(ParentType::System, ParentType::Webhook);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
// =========================================================================
|
|
1478
|
+
// ParentLink Tests
|
|
1479
|
+
// =========================================================================
|
|
1480
|
+
|
|
1481
|
+
#[test]
|
|
1482
|
+
fn test_parent_link_new() {
|
|
1483
|
+
let link = ParentLink::new("msg_123", ParentType::UserMessage);
|
|
1484
|
+
assert_eq!(link.parent_id, "msg_123");
|
|
1485
|
+
assert_eq!(link.parent_type, ParentType::UserMessage);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
#[test]
|
|
1489
|
+
fn test_parent_link_new_with_string() {
|
|
1490
|
+
let link = ParentLink::new(String::from("wh_456"), ParentType::Webhook);
|
|
1491
|
+
assert_eq!(link.parent_id, "wh_456");
|
|
1492
|
+
assert_eq!(link.parent_type, ParentType::Webhook);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
#[test]
|
|
1496
|
+
fn test_parent_link_from_user_message() {
|
|
1497
|
+
let link = ParentLink::from_user_message("msg_user_789");
|
|
1498
|
+
assert_eq!(link.parent_id, "msg_user_789");
|
|
1499
|
+
assert_eq!(link.parent_type, ParentType::UserMessage);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
#[test]
|
|
1503
|
+
fn test_parent_link_from_step() {
|
|
1504
|
+
let step_id = StepId::from_string("step_abc123");
|
|
1505
|
+
let link = ParentLink::from_step(&step_id);
|
|
1506
|
+
assert_eq!(link.parent_id, "step_abc123");
|
|
1507
|
+
assert_eq!(link.parent_type, ParentType::StepExecution);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
#[test]
|
|
1511
|
+
fn test_parent_link_execution() {
|
|
1512
|
+
let exec_id = ExecutionId::from_string("exec_parent");
|
|
1513
|
+
let link = ParentLink::execution(exec_id);
|
|
1514
|
+
assert_eq!(link.parent_id, "exec_parent");
|
|
1515
|
+
assert_eq!(link.parent_type, ParentType::StepExecution);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
#[test]
|
|
1519
|
+
fn test_parent_link_system() {
|
|
1520
|
+
let link = ParentLink::system();
|
|
1521
|
+
assert_eq!(link.parent_id, "system");
|
|
1522
|
+
assert_eq!(link.parent_type, ParentType::System);
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
#[test]
|
|
1526
|
+
fn test_parent_link_serde() {
|
|
1527
|
+
let link = ParentLink::new("test_id", ParentType::A2aRequest);
|
|
1528
|
+
let json = serde_json::to_string(&link).unwrap();
|
|
1529
|
+
let parsed: ParentLink = serde_json::from_str(&json).unwrap();
|
|
1530
|
+
assert_eq!(link, parsed);
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
#[test]
|
|
1534
|
+
fn test_parent_link_equality() {
|
|
1535
|
+
let link1 = ParentLink::new("same_id", ParentType::System);
|
|
1536
|
+
let link2 = ParentLink::new("same_id", ParentType::System);
|
|
1537
|
+
let link3 = ParentLink::new("different_id", ParentType::System);
|
|
1538
|
+
assert_eq!(link1, link2);
|
|
1539
|
+
assert_ne!(link1, link3);
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
#[test]
|
|
1543
|
+
fn test_parent_link_clone() {
|
|
1544
|
+
let link = ParentLink::from_user_message("msg_clone");
|
|
1545
|
+
let cloned = link.clone();
|
|
1546
|
+
assert_eq!(link, cloned);
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// =========================================================================
|
|
1550
|
+
// Prefixes Tests
|
|
1551
|
+
// =========================================================================
|
|
1552
|
+
|
|
1553
|
+
#[test]
|
|
1554
|
+
fn test_prefix_execution() {
|
|
1555
|
+
assert_eq!(prefixes::EXECUTION, "exec_");
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
#[test]
|
|
1559
|
+
fn test_prefix_step() {
|
|
1560
|
+
assert_eq!(prefixes::STEP, "step_");
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
#[test]
|
|
1564
|
+
fn test_prefix_artifact() {
|
|
1565
|
+
assert_eq!(prefixes::ARTIFACT, "artifact_");
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
#[test]
|
|
1569
|
+
fn test_prefix_graph() {
|
|
1570
|
+
assert_eq!(prefixes::GRAPH, "graph_");
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
#[test]
|
|
1574
|
+
fn test_prefix_tenant() {
|
|
1575
|
+
assert_eq!(prefixes::TENANT, "tenant_");
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
#[test]
|
|
1579
|
+
fn test_prefix_user() {
|
|
1580
|
+
assert_eq!(prefixes::USER, "user_");
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
#[test]
|
|
1584
|
+
fn test_prefix_event() {
|
|
1585
|
+
assert_eq!(prefixes::EVENT, "evt_");
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
#[test]
|
|
1589
|
+
fn test_prefix_decision() {
|
|
1590
|
+
assert_eq!(prefixes::DECISION, "dec_");
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
#[test]
|
|
1594
|
+
fn test_prefix_control() {
|
|
1595
|
+
assert_eq!(prefixes::CONTROL, "ctrl_");
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// =========================================================================
|
|
1599
|
+
// Type Alias Tests
|
|
1600
|
+
// =========================================================================
|
|
1601
|
+
|
|
1602
|
+
#[test]
|
|
1603
|
+
fn test_run_id_alias() {
|
|
1604
|
+
let exec_id = ExecutionId::from_string("exec_alias");
|
|
1605
|
+
let run_id: RunId = exec_id.clone();
|
|
1606
|
+
assert_eq!(exec_id.as_str(), run_id.as_str());
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
#[test]
|
|
1610
|
+
fn test_node_id_alias() {
|
|
1611
|
+
let step_id = StepId::from_string("step_alias");
|
|
1612
|
+
let node_id: NodeId = step_id.clone();
|
|
1613
|
+
assert_eq!(step_id.as_str(), node_id.as_str());
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// =========================================================================
|
|
1617
|
+
// KSUID Format Tests
|
|
1618
|
+
// =========================================================================
|
|
1619
|
+
|
|
1620
|
+
#[test]
|
|
1621
|
+
fn test_execution_id_ksuid_length() {
|
|
1622
|
+
let id = ExecutionId::new();
|
|
1623
|
+
// exec_ (5 chars) + KSUID (27 chars) = 32 chars
|
|
1624
|
+
assert_eq!(id.as_str().len(), 32);
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
#[test]
|
|
1628
|
+
fn test_step_id_ksuid_length() {
|
|
1629
|
+
let id = StepId::new();
|
|
1630
|
+
// step_ (5 chars) + KSUID (27 chars) = 32 chars
|
|
1631
|
+
assert_eq!(id.as_str().len(), 32);
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
#[test]
|
|
1635
|
+
fn test_graph_id_ksuid_length() {
|
|
1636
|
+
let id = GraphId::new();
|
|
1637
|
+
// graph_ (6 chars) + KSUID (27 chars) = 33 chars
|
|
1638
|
+
assert_eq!(id.as_str().len(), 33);
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
#[test]
|
|
1642
|
+
fn test_artifact_id_ksuid_length() {
|
|
1643
|
+
let id = ArtifactId::new();
|
|
1644
|
+
// artifact_ (9 chars) + KSUID (27 chars) = 36 chars
|
|
1645
|
+
assert_eq!(id.as_str().len(), 36);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
#[test]
|
|
1649
|
+
fn test_tenant_id_ksuid_length() {
|
|
1650
|
+
let id = TenantId::new();
|
|
1651
|
+
// tenant_ (7 chars) + KSUID (27 chars) = 34 chars
|
|
1652
|
+
assert_eq!(id.as_str().len(), 34);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
#[test]
|
|
1656
|
+
fn test_user_id_ksuid_length() {
|
|
1657
|
+
let id = UserId::new();
|
|
1658
|
+
// user_ (5 chars) + KSUID (27 chars) = 32 chars
|
|
1659
|
+
assert_eq!(id.as_str().len(), 32);
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
// =========================================================================
|
|
1663
|
+
// Sortability Tests (KSUIDs are time-sorted)
|
|
1664
|
+
// =========================================================================
|
|
1665
|
+
|
|
1666
|
+
#[test]
|
|
1667
|
+
fn test_execution_ids_sortable() {
|
|
1668
|
+
// Generate multiple IDs quickly and verify they're all unique
|
|
1669
|
+
let mut ids: Vec<ExecutionId> = (0..10).map(|_| ExecutionId::new()).collect();
|
|
1670
|
+
let original_count = ids.len();
|
|
1671
|
+
|
|
1672
|
+
// Deduplicate to check uniqueness
|
|
1673
|
+
ids.sort_by(|a, b| a.as_str().cmp(b.as_str()));
|
|
1674
|
+
ids.dedup_by(|a, b| a.as_str() == b.as_str());
|
|
1675
|
+
assert_eq!(
|
|
1676
|
+
ids.len(),
|
|
1677
|
+
original_count,
|
|
1678
|
+
"All generated IDs should be unique"
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
#[test]
|
|
1683
|
+
fn test_ksuid_contains_alphanumeric() {
|
|
1684
|
+
let id = ExecutionId::new();
|
|
1685
|
+
let ksuid_part = &id.as_str()[5..]; // After "exec_"
|
|
1686
|
+
assert!(ksuid_part.chars().all(|c| c.is_ascii_alphanumeric()));
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
// =========================================================================
|
|
1690
|
+
// ThreadId Tests
|
|
1691
|
+
// =========================================================================
|
|
1692
|
+
|
|
1693
|
+
#[test]
|
|
1694
|
+
fn test_thread_id_new_has_correct_prefix() {
|
|
1695
|
+
let id = ThreadId::new();
|
|
1696
|
+
assert!(
|
|
1697
|
+
id.as_str().starts_with("thread_"),
|
|
1698
|
+
"ThreadId should start with 'thread_'"
|
|
1699
|
+
);
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
#[test]
|
|
1703
|
+
fn test_thread_id_new_unique() {
|
|
1704
|
+
let id1 = ThreadId::new();
|
|
1705
|
+
let id2 = ThreadId::new();
|
|
1706
|
+
assert_ne!(id1, id2, "Each new ThreadId should be unique");
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
#[test]
|
|
1710
|
+
fn test_thread_id_from_string() {
|
|
1711
|
+
let id = ThreadId::from_string("thread_custom123");
|
|
1712
|
+
assert_eq!(id.as_str(), "thread_custom123");
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
#[test]
|
|
1716
|
+
fn test_thread_id_display() {
|
|
1717
|
+
let id = ThreadId::from_string("thread_display_test");
|
|
1718
|
+
assert_eq!(format!("{}", id), "thread_display_test");
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
#[test]
|
|
1722
|
+
fn test_thread_id_default() {
|
|
1723
|
+
let id = ThreadId::default();
|
|
1724
|
+
assert!(id.as_str().starts_with("thread_"));
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
#[test]
|
|
1728
|
+
fn test_thread_id_from_traits() {
|
|
1729
|
+
let id1: ThreadId = String::from("thread_string").into();
|
|
1730
|
+
let id2: ThreadId = "thread_str".into();
|
|
1731
|
+
assert_eq!(id1.as_str(), "thread_string");
|
|
1732
|
+
assert_eq!(id2.as_str(), "thread_str");
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
#[test]
|
|
1736
|
+
fn test_thread_id_hash() {
|
|
1737
|
+
let id = ThreadId::from_string("thread_hash");
|
|
1738
|
+
let mut set = HashSet::new();
|
|
1739
|
+
set.insert(id.clone());
|
|
1740
|
+
assert!(set.contains(&id));
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
#[test]
|
|
1744
|
+
fn test_thread_id_serde() {
|
|
1745
|
+
let id = ThreadId::from_string("thread_serde");
|
|
1746
|
+
let json = serde_json::to_string(&id).unwrap();
|
|
1747
|
+
let parsed: ThreadId = serde_json::from_str(&json).unwrap();
|
|
1748
|
+
assert_eq!(id, parsed);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
#[test]
|
|
1752
|
+
fn test_thread_id_ksuid_length() {
|
|
1753
|
+
let id = ThreadId::new();
|
|
1754
|
+
// thread_ (7 chars) + KSUID (27 chars) = 34 chars
|
|
1755
|
+
assert_eq!(id.as_str().len(), 34);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
// =========================================================================
|
|
1759
|
+
// MessageId Tests
|
|
1760
|
+
// =========================================================================
|
|
1761
|
+
|
|
1762
|
+
#[test]
|
|
1763
|
+
fn test_message_id_new_has_correct_prefix() {
|
|
1764
|
+
let id = MessageId::new();
|
|
1765
|
+
assert!(
|
|
1766
|
+
id.as_str().starts_with("msg_"),
|
|
1767
|
+
"MessageId should start with 'msg_'"
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
#[test]
|
|
1772
|
+
fn test_message_id_new_unique() {
|
|
1773
|
+
let id1 = MessageId::new();
|
|
1774
|
+
let id2 = MessageId::new();
|
|
1775
|
+
assert_ne!(id1, id2, "Each new MessageId should be unique");
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
#[test]
|
|
1779
|
+
fn test_message_id_from_string() {
|
|
1780
|
+
let id = MessageId::from_string("msg_custom123");
|
|
1781
|
+
assert_eq!(id.as_str(), "msg_custom123");
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
#[test]
|
|
1785
|
+
fn test_message_id_display() {
|
|
1786
|
+
let id = MessageId::from_string("msg_display_test");
|
|
1787
|
+
assert_eq!(format!("{}", id), "msg_display_test");
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
#[test]
|
|
1791
|
+
fn test_message_id_default() {
|
|
1792
|
+
let id = MessageId::default();
|
|
1793
|
+
assert!(id.as_str().starts_with("msg_"));
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
#[test]
|
|
1797
|
+
fn test_message_id_from_traits() {
|
|
1798
|
+
let id1: MessageId = String::from("msg_string").into();
|
|
1799
|
+
let id2: MessageId = "msg_str".into();
|
|
1800
|
+
assert_eq!(id1.as_str(), "msg_string");
|
|
1801
|
+
assert_eq!(id2.as_str(), "msg_str");
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
#[test]
|
|
1805
|
+
fn test_message_id_hash() {
|
|
1806
|
+
let id = MessageId::from_string("msg_hash");
|
|
1807
|
+
let mut set = HashSet::new();
|
|
1808
|
+
set.insert(id.clone());
|
|
1809
|
+
assert!(set.contains(&id));
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
#[test]
|
|
1813
|
+
fn test_message_id_serde() {
|
|
1814
|
+
let id = MessageId::from_string("msg_serde");
|
|
1815
|
+
let json = serde_json::to_string(&id).unwrap();
|
|
1816
|
+
let parsed: MessageId = serde_json::from_str(&json).unwrap();
|
|
1817
|
+
assert_eq!(id, parsed);
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
#[test]
|
|
1821
|
+
fn test_message_id_ksuid_length() {
|
|
1822
|
+
let id = MessageId::new();
|
|
1823
|
+
// msg_ (4 chars) + KSUID (27 chars) = 31 chars
|
|
1824
|
+
assert_eq!(id.as_str().len(), 31);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
// =========================================================================
|
|
1828
|
+
// Prefix Tests for new IDs
|
|
1829
|
+
// =========================================================================
|
|
1830
|
+
|
|
1831
|
+
#[test]
|
|
1832
|
+
fn test_prefix_thread() {
|
|
1833
|
+
assert_eq!(prefixes::THREAD, "thread_");
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
#[test]
|
|
1837
|
+
fn test_prefix_message() {
|
|
1838
|
+
assert_eq!(prefixes::MESSAGE, "msg_");
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
// =========================================================================
|
|
1842
|
+
// SpawnMode Tests
|
|
1843
|
+
// =========================================================================
|
|
1844
|
+
|
|
1845
|
+
#[test]
|
|
1846
|
+
fn test_spawn_mode_inline_default() {
|
|
1847
|
+
// Inline is not the default since we use Option<SpawnMode>
|
|
1848
|
+
// but we can still test the variant
|
|
1849
|
+
let mode = SpawnMode::Inline;
|
|
1850
|
+
assert_eq!(mode, SpawnMode::Inline);
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
#[test]
|
|
1854
|
+
fn test_spawn_mode_child_default_fields() {
|
|
1855
|
+
let mode = SpawnMode::Child {
|
|
1856
|
+
background: false,
|
|
1857
|
+
inherit_inbox: false,
|
|
1858
|
+
policies: None,
|
|
1859
|
+
};
|
|
1860
|
+
if let SpawnMode::Child { background, inherit_inbox, policies } = mode {
|
|
1861
|
+
assert!(!background, "default background should be false");
|
|
1862
|
+
assert!(!inherit_inbox, "default inherit_inbox should be false");
|
|
1863
|
+
assert!(policies.is_none(), "default policies should be None");
|
|
1864
|
+
} else {
|
|
1865
|
+
panic!("Expected SpawnMode::Child");
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
#[test]
|
|
1870
|
+
fn test_spawn_mode_child_with_inherit_inbox() {
|
|
1871
|
+
let mode = SpawnMode::Child {
|
|
1872
|
+
background: false,
|
|
1873
|
+
inherit_inbox: true,
|
|
1874
|
+
policies: None,
|
|
1875
|
+
};
|
|
1876
|
+
if let SpawnMode::Child { inherit_inbox, .. } = mode {
|
|
1877
|
+
assert!(inherit_inbox, "inherit_inbox should be true");
|
|
1878
|
+
} else {
|
|
1879
|
+
panic!("Expected SpawnMode::Child");
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
#[test]
|
|
1884
|
+
fn test_spawn_mode_child_background() {
|
|
1885
|
+
let mode = SpawnMode::Child {
|
|
1886
|
+
background: true,
|
|
1887
|
+
inherit_inbox: false,
|
|
1888
|
+
policies: None,
|
|
1889
|
+
};
|
|
1890
|
+
if let SpawnMode::Child { background, .. } = mode {
|
|
1891
|
+
assert!(background, "background should be true");
|
|
1892
|
+
} else {
|
|
1893
|
+
panic!("Expected SpawnMode::Child");
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
#[test]
|
|
1898
|
+
fn test_spawn_mode_serde_inline() {
|
|
1899
|
+
let mode = SpawnMode::Inline;
|
|
1900
|
+
let json = serde_json::to_string(&mode).unwrap();
|
|
1901
|
+
assert!(json.contains("\"mode\":\"inline\""), "Inline should serialize with mode tag");
|
|
1902
|
+
let parsed: SpawnMode = serde_json::from_str(&json).unwrap();
|
|
1903
|
+
assert_eq!(mode, parsed);
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
#[test]
|
|
1907
|
+
fn test_spawn_mode_serde_child() {
|
|
1908
|
+
let mode = SpawnMode::Child {
|
|
1909
|
+
background: true,
|
|
1910
|
+
inherit_inbox: true,
|
|
1911
|
+
policies: None,
|
|
1912
|
+
};
|
|
1913
|
+
let json = serde_json::to_string(&mode).unwrap();
|
|
1914
|
+
assert!(json.contains("\"mode\":\"child\""), "Child should serialize with mode tag");
|
|
1915
|
+
assert!(json.contains("\"background\":true"), "background should be serialized");
|
|
1916
|
+
assert!(json.contains("\"inherit_inbox\":true"), "inherit_inbox should be serialized");
|
|
1917
|
+
let parsed: SpawnMode = serde_json::from_str(&json).unwrap();
|
|
1918
|
+
assert_eq!(mode, parsed);
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
#[test]
|
|
1922
|
+
fn test_spawn_mode_serde_child_with_policies() {
|
|
1923
|
+
let policies = serde_json::json!({
|
|
1924
|
+
"max_steps": 100,
|
|
1925
|
+
"max_tokens": 10000
|
|
1926
|
+
});
|
|
1927
|
+
let mode = SpawnMode::Child {
|
|
1928
|
+
background: false,
|
|
1929
|
+
inherit_inbox: true,
|
|
1930
|
+
policies: Some(policies.clone()),
|
|
1931
|
+
};
|
|
1932
|
+
let json = serde_json::to_string(&mode).unwrap();
|
|
1933
|
+
let parsed: SpawnMode = serde_json::from_str(&json).unwrap();
|
|
1934
|
+
assert_eq!(mode, parsed);
|
|
1935
|
+
if let SpawnMode::Child { policies: Some(p), .. } = parsed {
|
|
1936
|
+
assert_eq!(p["max_steps"], 100);
|
|
1937
|
+
} else {
|
|
1938
|
+
panic!("Expected SpawnMode::Child with policies");
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
#[test]
|
|
1943
|
+
fn test_spawn_mode_equality() {
|
|
1944
|
+
let mode1 = SpawnMode::Inline;
|
|
1945
|
+
let mode2 = SpawnMode::Inline;
|
|
1946
|
+
let mode3 = SpawnMode::Child {
|
|
1947
|
+
background: false,
|
|
1948
|
+
inherit_inbox: false,
|
|
1949
|
+
policies: None,
|
|
1950
|
+
};
|
|
1951
|
+
assert_eq!(mode1, mode2);
|
|
1952
|
+
assert_ne!(mode1, mode3);
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
#[test]
|
|
1956
|
+
fn test_spawn_mode_clone() {
|
|
1957
|
+
let mode = SpawnMode::Child {
|
|
1958
|
+
background: true,
|
|
1959
|
+
inherit_inbox: true,
|
|
1960
|
+
policies: Some(serde_json::json!({"key": "value"})),
|
|
1961
|
+
};
|
|
1962
|
+
let cloned = mode.clone();
|
|
1963
|
+
assert_eq!(mode, cloned);
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// =========================================================================
|
|
1967
|
+
// CancellationPolicy Tests
|
|
1968
|
+
// =========================================================================
|
|
1969
|
+
|
|
1970
|
+
#[test]
|
|
1971
|
+
fn test_cancellation_policy_default() {
|
|
1972
|
+
let policy = CancellationPolicy::default();
|
|
1973
|
+
assert_eq!(policy, CancellationPolicy::CascadeCancel);
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
#[test]
|
|
1977
|
+
fn test_cancellation_policy_variants() {
|
|
1978
|
+
let cascade = CancellationPolicy::CascadeCancel;
|
|
1979
|
+
let wait = CancellationPolicy::WaitForChildren;
|
|
1980
|
+
let detach = CancellationPolicy::Detach;
|
|
1981
|
+
|
|
1982
|
+
assert_ne!(cascade, wait);
|
|
1983
|
+
assert_ne!(wait, detach);
|
|
1984
|
+
assert_ne!(cascade, detach);
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
#[test]
|
|
1988
|
+
fn test_cancellation_policy_serde_cascade() {
|
|
1989
|
+
let policy = CancellationPolicy::CascadeCancel;
|
|
1990
|
+
let json = serde_json::to_string(&policy).unwrap();
|
|
1991
|
+
assert_eq!(json, "\"cascade_cancel\"");
|
|
1992
|
+
let parsed: CancellationPolicy = serde_json::from_str(&json).unwrap();
|
|
1993
|
+
assert_eq!(policy, parsed);
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
#[test]
|
|
1997
|
+
fn test_cancellation_policy_serde_wait() {
|
|
1998
|
+
let policy = CancellationPolicy::WaitForChildren;
|
|
1999
|
+
let json = serde_json::to_string(&policy).unwrap();
|
|
2000
|
+
assert_eq!(json, "\"wait_for_children\"");
|
|
2001
|
+
let parsed: CancellationPolicy = serde_json::from_str(&json).unwrap();
|
|
2002
|
+
assert_eq!(policy, parsed);
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
#[test]
|
|
2006
|
+
fn test_cancellation_policy_serde_detach() {
|
|
2007
|
+
let policy = CancellationPolicy::Detach;
|
|
2008
|
+
let json = serde_json::to_string(&policy).unwrap();
|
|
2009
|
+
assert_eq!(json, "\"detach\"");
|
|
2010
|
+
let parsed: CancellationPolicy = serde_json::from_str(&json).unwrap();
|
|
2011
|
+
assert_eq!(policy, parsed);
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
#[test]
|
|
2015
|
+
fn test_cancellation_policy_all_variants_serde() {
|
|
2016
|
+
let variants = vec![
|
|
2017
|
+
CancellationPolicy::CascadeCancel,
|
|
2018
|
+
CancellationPolicy::WaitForChildren,
|
|
2019
|
+
CancellationPolicy::Detach,
|
|
2020
|
+
];
|
|
2021
|
+
for variant in variants {
|
|
2022
|
+
let json = serde_json::to_string(&variant).unwrap();
|
|
2023
|
+
let parsed: CancellationPolicy = serde_json::from_str(&json).unwrap();
|
|
2024
|
+
assert_eq!(parsed, variant);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
#[test]
|
|
2029
|
+
fn test_cancellation_policy_clone() {
|
|
2030
|
+
let policy = CancellationPolicy::WaitForChildren;
|
|
2031
|
+
let cloned = policy.clone();
|
|
2032
|
+
assert_eq!(policy, cloned);
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
#[test]
|
|
2036
|
+
fn test_cancellation_policy_hash() {
|
|
2037
|
+
let policy = CancellationPolicy::Detach;
|
|
2038
|
+
let mut set = HashSet::new();
|
|
2039
|
+
set.insert(policy.clone());
|
|
2040
|
+
assert!(set.contains(&policy));
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
// =========================================================================
|
|
2044
|
+
// StepSource with SpawnMode Tests
|
|
2045
|
+
// =========================================================================
|
|
2046
|
+
|
|
2047
|
+
#[test]
|
|
2048
|
+
fn test_step_source_with_spawn_mode() {
|
|
2049
|
+
let source = StepSource {
|
|
2050
|
+
source_type: StepSourceType::Discovered,
|
|
2051
|
+
triggered_by: Some("step_parent".to_string()),
|
|
2052
|
+
reason: Some("test spawn".to_string()),
|
|
2053
|
+
depth: Some(1),
|
|
2054
|
+
spawn_mode: Some(SpawnMode::Child {
|
|
2055
|
+
background: false,
|
|
2056
|
+
inherit_inbox: true,
|
|
2057
|
+
policies: None,
|
|
2058
|
+
}),
|
|
2059
|
+
};
|
|
2060
|
+
assert!(source.spawn_mode.is_some());
|
|
2061
|
+
if let Some(SpawnMode::Child { inherit_inbox, .. }) = source.spawn_mode {
|
|
2062
|
+
assert!(inherit_inbox);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
#[test]
|
|
2067
|
+
fn test_step_source_discovered_default_spawn_mode() {
|
|
2068
|
+
let spawner = StepId::new();
|
|
2069
|
+
let source = StepSource::discovered(&spawner, "test reason", 1);
|
|
2070
|
+
assert!(source.spawn_mode.is_none(), "discovered() should default to None spawn_mode");
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
#[test]
|
|
2074
|
+
fn test_step_source_serde_with_spawn_mode() {
|
|
2075
|
+
let source = StepSource {
|
|
2076
|
+
source_type: StepSourceType::Discovered,
|
|
2077
|
+
triggered_by: Some("step_123".to_string()),
|
|
2078
|
+
reason: Some("test reason".to_string()),
|
|
2079
|
+
depth: Some(2),
|
|
2080
|
+
spawn_mode: Some(SpawnMode::Inline),
|
|
2081
|
+
};
|
|
2082
|
+
let json = serde_json::to_string(&source).unwrap();
|
|
2083
|
+
let parsed: StepSource = serde_json::from_str(&json).unwrap();
|
|
2084
|
+
assert_eq!(source, parsed);
|
|
2085
|
+
}
|
|
2086
|
+
}
|