legate 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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +345 -0
- data/bin/legate +13 -0
- data/examples/00_quickstart.rb +51 -0
- data/examples/01_simple_agent.rb +105 -0
- data/examples/02_multi_tool_agent.rb +140 -0
- data/examples/03_custom_tool.rb +93 -0
- data/examples/04_agent_instructions.rb +84 -0
- data/examples/05_state_and_sessions.rb +91 -0
- data/examples/06_callbacks.rb +186 -0
- data/examples/07_async_jobs.rb +112 -0
- data/examples/08_loop_agent.rb +197 -0
- data/examples/09_sequential_workflow.rb +40 -0
- data/examples/10_parallel_workflow.rb +34 -0
- data/examples/11_agent_delegation.rb +24 -0
- data/examples/12_http_client_tool.rb +156 -0
- data/examples/13_authentication.rb +220 -0
- data/examples/14_mcp_client.rb +154 -0
- data/examples/15_mcp_server.rb +79 -0
- data/examples/16_webhooks.rb +91 -0
- data/examples/README_sequential_agents.md +164 -0
- data/examples/advanced/auth/cookie_auth_tool.rb +146 -0
- data/examples/advanced/auth/custom_auth_flows_example.rb +626 -0
- data/examples/advanced/auth/excon_middleware.rb +317 -0
- data/examples/advanced/auth/excon_middleware_auth.rb +399 -0
- data/examples/advanced/auth/fiber_auth_example.rb +281 -0
- data/examples/advanced/auth/fiber_oidc_example.rb +403 -0
- data/examples/advanced/auth/httpbin_bearer_tool.rb +159 -0
- data/examples/advanced/auth/oauth2_auth.rb +419 -0
- data/examples/advanced/auth/oidc_auth.rb +514 -0
- data/examples/advanced/auth/openweather_api.rb +251 -0
- data/examples/advanced/auth/openweather_tool.rb +153 -0
- data/examples/advanced/auth/query_param_middleware_test.rb +138 -0
- data/examples/advanced/auth/service_account.rb +135 -0
- data/examples/advanced/auth/test_with_httpbin.rb +202 -0
- data/examples/advanced/auth/token_lifecycle_example.rb +428 -0
- data/examples/advanced/callback_monitoring.rb +679 -0
- data/examples/advanced/mas/fixed_delegation_example.rb +191 -0
- data/examples/advanced/mas/loop_workflow.rb +28 -0
- data/examples/advanced/mas/mock_planner.rb +77 -0
- data/examples/advanced/mas/proper_delegation_example.rb +276 -0
- data/examples/advanced/mcp/legate_mcp_server_resource_example.rb +182 -0
- data/examples/advanced/mcp/mcp_resource_server_example.rb +309 -0
- data/examples/advanced/mcp/mcp_server_async.rb +76 -0
- data/examples/advanced/mcp/mcp_server_async_tools.rb +122 -0
- data/examples/advanced/mcp/mcp_server_legate_agent.rb +95 -0
- data/examples/advanced/mcp/mcp_server_rack.rb +89 -0
- data/examples/advanced/random_calculator.rb +104 -0
- data/examples/advanced/sleep_agent.rb +153 -0
- data/examples/advanced/webhooks/webhook_e2e_runner.rb +110 -0
- data/examples/advanced/webhooks/webhook_receiver_agent.rb +58 -0
- data/examples/advanced/workflows/task_refinement_loop_agent.rb +278 -0
- data/examples/advanced/workflows/travel_planner_auto_sequential.rb +444 -0
- data/examples/advanced/workflows/travel_planner_parallel.rb +656 -0
- data/examples/advanced/workflows/travel_planner_sequential.rb +512 -0
- data/examples/tools/oauth2_example.rb +136 -0
- data/examples/tools/sleepy_tool.rb +42 -0
- data/lib/legate/activity_log.rb +71 -0
- data/lib/legate/agent.rb +959 -0
- data/lib/legate/agent_code_generator.rb +185 -0
- data/lib/legate/agent_definition.rb +812 -0
- data/lib/legate/agentic/decision.rb +49 -0
- data/lib/legate/agentic/loop.rb +134 -0
- data/lib/legate/agentic.rb +5 -0
- data/lib/legate/agents/loop_agent.rb +248 -0
- data/lib/legate/agents/parallel_agent.rb +163 -0
- data/lib/legate/agents/sequential_agent.rb +190 -0
- data/lib/legate/agents.rb +14 -0
- data/lib/legate/auth/config.rb +148 -0
- data/lib/legate/auth/coordinator.rb +218 -0
- data/lib/legate/auth/coordinators/oauth2_coordinator.rb +99 -0
- data/lib/legate/auth/coordinators/oidc_coordinator.rb +68 -0
- data/lib/legate/auth/coordinators/service_account_coordinator.rb +122 -0
- data/lib/legate/auth/credential.rb +157 -0
- data/lib/legate/auth/encryption.rb +108 -0
- data/lib/legate/auth/error.rb +94 -0
- data/lib/legate/auth/exchanged_credential.rb +180 -0
- data/lib/legate/auth/excon_middleware.rb +285 -0
- data/lib/legate/auth/http_client_utils.rb +364 -0
- data/lib/legate/auth/manager.rb +531 -0
- data/lib/legate/auth/manager_store.rb +394 -0
- data/lib/legate/auth/middleware_factory.rb +290 -0
- data/lib/legate/auth/runner.rb +279 -0
- data/lib/legate/auth/scheme.rb +125 -0
- data/lib/legate/auth/schemes/api_key.rb +212 -0
- data/lib/legate/auth/schemes/google_service_account.rb +108 -0
- data/lib/legate/auth/schemes/http_bearer.rb +98 -0
- data/lib/legate/auth/schemes/oauth2.rb +396 -0
- data/lib/legate/auth/schemes/openid_connect.rb +346 -0
- data/lib/legate/auth/schemes/service_account.rb +388 -0
- data/lib/legate/auth/schemes.rb +40 -0
- data/lib/legate/auth/token_manager.rb +362 -0
- data/lib/legate/auth/token_store.rb +86 -0
- data/lib/legate/auth/tool_context_extension.rb +97 -0
- data/lib/legate/auth/tool_integration.rb +188 -0
- data/lib/legate/auth/url_guard.rb +81 -0
- data/lib/legate/auth.rb +453 -0
- data/lib/legate/callbacks/callback_context.rb +71 -0
- data/lib/legate/cli/agent_commands.rb +950 -0
- data/lib/legate/cli/auth_commands.rb +520 -0
- data/lib/legate/cli/base_command.rb +24 -0
- data/lib/legate/cli/deployment_commands.rb +934 -0
- data/lib/legate/cli/output_helper.rb +108 -0
- data/lib/legate/cli/session_commands.rb +138 -0
- data/lib/legate/cli/skaffold_commands.rb +223 -0
- data/lib/legate/cli/tool_commands.rb +261 -0
- data/lib/legate/cli/web_commands.rb +182 -0
- data/lib/legate/cli.rb +40 -0
- data/lib/legate/configuration/webhooks.rb +113 -0
- data/lib/legate/configuration.rb +39 -0
- data/lib/legate/definition_store.rb +23 -0
- data/lib/legate/errors.rb +118 -0
- data/lib/legate/event.rb +161 -0
- data/lib/legate/gemini_ai_beta_patch.rb +39 -0
- data/lib/legate/generators/agent_generator.rb +412 -0
- data/lib/legate/generators/code_validator.rb +48 -0
- data/lib/legate/generators/legate/install_generator.rb +35 -0
- data/lib/legate/generators/legate/templates/create_legate_tables.rb.tt +36 -0
- data/lib/legate/generators/legate/templates/initializer.rb +18 -0
- data/lib/legate/generators/runtime_tool_loader.rb +76 -0
- data/lib/legate/generators/tool_generator.rb +408 -0
- data/lib/legate/generators.rb +11 -0
- data/lib/legate/global_definition_registry.rb +506 -0
- data/lib/legate/global_tool_manager.rb +135 -0
- data/lib/legate/llm/adapter.rb +69 -0
- data/lib/legate/llm/gemini.rb +172 -0
- data/lib/legate/llm/ollama.rb +80 -0
- data/lib/legate/llm.rb +34 -0
- data/lib/legate/mcp/client.rb +320 -0
- data/lib/legate/mcp/connection/sse.rb +292 -0
- data/lib/legate/mcp/connection/stdio.rb +273 -0
- data/lib/legate/mcp/connection_manager.rb +103 -0
- data/lib/legate/mcp/server/legate_agent_adapter.rb +170 -0
- data/lib/legate/mcp/server/legate_direct_agent_adapter.rb +140 -0
- data/lib/legate/mcp/server/legate_tool_adapter.rb +119 -0
- data/lib/legate/mcp/tool_wrapper.rb +138 -0
- data/lib/legate/mcp/util/schema_converter.rb +134 -0
- data/lib/legate/mcp.rb +23 -0
- data/lib/legate/plan_executor.rb +375 -0
- data/lib/legate/planner.rb +839 -0
- data/lib/legate/rails/railtie.rb +43 -0
- data/lib/legate/rails.rb +9 -0
- data/lib/legate/redaction.rb +32 -0
- data/lib/legate/session.rb +299 -0
- data/lib/legate/session_service/active_record.rb +300 -0
- data/lib/legate/session_service/base.rb +68 -0
- data/lib/legate/session_service/event_broadcast.rb +74 -0
- data/lib/legate/session_service/in_memory.rb +188 -0
- data/lib/legate/tool/metadata_dsl.rb +122 -0
- data/lib/legate/tool.rb +276 -0
- data/lib/legate/tool_code_generator.rb +103 -0
- data/lib/legate/tool_context.rb +350 -0
- data/lib/legate/tool_loader.rb +39 -0
- data/lib/legate/tool_registry.rb +73 -0
- data/lib/legate/tool_result.rb +61 -0
- data/lib/legate/tools/agent_tool.rb +187 -0
- data/lib/legate/tools/base/http_client.rb +319 -0
- data/lib/legate/tools/base/safe_url.rb +56 -0
- data/lib/legate/tools/base_async_job_tool.rb +91 -0
- data/lib/legate/tools/calculator.rb +89 -0
- data/lib/legate/tools/cat_facts.rb +81 -0
- data/lib/legate/tools/check_job_status_tool.rb +48 -0
- data/lib/legate/tools/current_time_tool.rb +64 -0
- data/lib/legate/tools/echo.rb +43 -0
- data/lib/legate/tools/http_request_tool.rb +105 -0
- data/lib/legate/tools/random_number_tool.rb +64 -0
- data/lib/legate/tools/read_webpage_tool.rb +92 -0
- data/lib/legate/tools/sleepy_tool.rb +74 -0
- data/lib/legate/tools/webhook_tool.rb +146 -0
- data/lib/legate/version.rb +5 -0
- data/lib/legate/web/app.rb +984 -0
- data/lib/legate/web/public/css/main.css +4980 -0
- data/lib/legate/web/public/images/favicon-256.png +0 -0
- data/lib/legate/web/public/images/favicon-32.png +0 -0
- data/lib/legate/web/public/images/legate-logo-dark.png +0 -0
- data/lib/legate/web/public/images/legate-logo-light.png +0 -0
- data/lib/legate/web/public/js/legate.js +616 -0
- data/lib/legate/web/public/styles/main.scss +4402 -0
- data/lib/legate/web/routes/agent_authentication_routes.rb +530 -0
- data/lib/legate/web/routes/agent_definition_routes.rb +803 -0
- data/lib/legate/web/routes/agent_generator_routes.rb +80 -0
- data/lib/legate/web/routes/agent_interaction_routes.rb +734 -0
- data/lib/legate/web/routes/agent_runtime_routes.rb +323 -0
- data/lib/legate/web/routes/api_routes.rb +56 -0
- data/lib/legate/web/routes/authentication_routes.rb +1541 -0
- data/lib/legate/web/routes/core_routes.rb +111 -0
- data/lib/legate/web/routes/documentation_routes.rb +220 -0
- data/lib/legate/web/routes/tool_generator_routes.rb +81 -0
- data/lib/legate/web/routes/tools_ui_routes.rb +207 -0
- data/lib/legate/web/sass_compiler.rb +73 -0
- data/lib/legate/web/views/_active_session_info.slim +25 -0
- data/lib/legate/web/views/_activity_list.slim +55 -0
- data/lib/legate/web/views/_agent_card.slim +56 -0
- data/lib/legate/web/views/_agent_generator_modal.slim +382 -0
- data/lib/legate/web/views/_agent_status_controls.slim +71 -0
- data/lib/legate/web/views/_agent_tool_table.slim +74 -0
- data/lib/legate/web/views/_chat_message.slim +95 -0
- data/lib/legate/web/views/_display_agent_configuration.slim +26 -0
- data/lib/legate/web/views/_display_agent_description.slim +11 -0
- data/lib/legate/web/views/_display_agent_fallback.slim +15 -0
- data/lib/legate/web/views/_display_agent_hierarchy.slim +93 -0
- data/lib/legate/web/views/_display_agent_instruction.slim +17 -0
- data/lib/legate/web/views/_display_agent_mcp.slim +13 -0
- data/lib/legate/web/views/_display_agent_model.slim +17 -0
- data/lib/legate/web/views/_display_agent_name.slim +42 -0
- data/lib/legate/web/views/_display_agent_output_key.slim +26 -0
- data/lib/legate/web/views/_display_agent_type.slim +65 -0
- data/lib/legate/web/views/_edit_agent_configuration.slim +74 -0
- data/lib/legate/web/views/_edit_agent_description.slim +16 -0
- data/lib/legate/web/views/_edit_agent_fallback.slim +25 -0
- data/lib/legate/web/views/_edit_agent_hierarchy.slim +98 -0
- data/lib/legate/web/views/_edit_agent_instruction.slim +49 -0
- data/lib/legate/web/views/_edit_agent_mcp.slim +33 -0
- data/lib/legate/web/views/_edit_agent_model.slim +23 -0
- data/lib/legate/web/views/_edit_agent_output_key.slim +36 -0
- data/lib/legate/web/views/_edit_agent_tools.slim +40 -0
- data/lib/legate/web/views/_edit_agent_type.slim +67 -0
- data/lib/legate/web/views/_session_error.slim +4 -0
- data/lib/legate/web/views/_skeleton.slim +69 -0
- data/lib/legate/web/views/_tool_card.slim +9 -0
- data/lib/legate/web/views/_tool_generator_modal.slim +311 -0
- data/lib/legate/web/views/agent.slim +436 -0
- data/lib/legate/web/views/agent_auth.slim +562 -0
- data/lib/legate/web/views/agents.slim +369 -0
- data/lib/legate/web/views/auth.slim +112 -0
- data/lib/legate/web/views/auth_credential_detail.slim +327 -0
- data/lib/legate/web/views/auth_credentials.slim +261 -0
- data/lib/legate/web/views/auth_debug.slim +94 -0
- data/lib/legate/web/views/auth_mapping_detail.slim +151 -0
- data/lib/legate/web/views/auth_mapping_new.slim +123 -0
- data/lib/legate/web/views/auth_mappings.slim +120 -0
- data/lib/legate/web/views/auth_scheme_detail.slim +274 -0
- data/lib/legate/web/views/auth_schemes.slim +259 -0
- data/lib/legate/web/views/auth_test.slim +418 -0
- data/lib/legate/web/views/chat.slim +192 -0
- data/lib/legate/web/views/docs_index.slim +105 -0
- data/lib/legate/web/views/docs_show.slim +105 -0
- data/lib/legate/web/views/error_404.slim +5 -0
- data/lib/legate/web/views/index.slim +148 -0
- data/lib/legate/web/views/layout.slim +144 -0
- data/lib/legate/web/views/tool_detail.slim +87 -0
- data/lib/legate/web/views/tools.slim +50 -0
- data/lib/legate/web/webhook_listener.rb +367 -0
- data/lib/legate/web.rb +9 -0
- data/lib/legate.rb +220 -0
- data/public/docs/advanced/callbacks.md +828 -0
- data/public/docs/advanced/mcp_schema_conversion.md +59 -0
- data/public/docs/authentication/api_reference/config.md +210 -0
- data/public/docs/authentication/api_reference/credential.md +246 -0
- data/public/docs/authentication/api_reference/encryption.md +218 -0
- data/public/docs/authentication/api_reference/exchanged_credential.md +271 -0
- data/public/docs/authentication/api_reference/excon_middleware.md +175 -0
- data/public/docs/authentication/api_reference/index.md +30 -0
- data/public/docs/authentication/api_reference/scheme.md +250 -0
- data/public/docs/authentication/api_reference/schemes/api_key.md +175 -0
- data/public/docs/authentication/api_reference/schemes/google_service_account.md +221 -0
- data/public/docs/authentication/api_reference/schemes/http_bearer.md +169 -0
- data/public/docs/authentication/api_reference/schemes/oauth2.md +343 -0
- data/public/docs/authentication/api_reference/schemes/oidc.md +73 -0
- data/public/docs/authentication/api_reference/schemes/openid_connect.md +311 -0
- data/public/docs/authentication/api_reference/schemes/service_account.md +287 -0
- data/public/docs/authentication/api_reference/token_manager.md +221 -0
- data/public/docs/authentication/api_reference/token_store.md +146 -0
- data/public/docs/authentication/api_reference/tool_context_extension.md +166 -0
- data/public/docs/authentication/guides/api_key.md +190 -0
- data/public/docs/authentication/guides/bearer.md +172 -0
- data/public/docs/authentication/guides/configuration.md +255 -0
- data/public/docs/authentication/guides/custom_flow.md +523 -0
- data/public/docs/authentication/guides/index.md +24 -0
- data/public/docs/authentication/guides/migration.md +435 -0
- data/public/docs/authentication/guides/oauth2.md +252 -0
- data/public/docs/authentication/guides/oidc.md +241 -0
- data/public/docs/authentication/guides/overview.md +155 -0
- data/public/docs/authentication/guides/secure_storage.md +301 -0
- data/public/docs/authentication/guides/service_account.md +228 -0
- data/public/docs/authentication/guides/token_lifecycle.md +295 -0
- data/public/docs/authentication/guides/web_ui_integration.md +504 -0
- data/public/docs/authentication/index.md +58 -0
- data/public/docs/authentication/troubleshooting/credential_storage.md +550 -0
- data/public/docs/authentication/troubleshooting/environment_variables.md +540 -0
- data/public/docs/authentication/troubleshooting/index.md +11 -0
- data/public/docs/authentication/troubleshooting/oauth2_issues.md +220 -0
- data/public/docs/authentication/troubleshooting/oidc_issues.md +412 -0
- data/public/docs/authentication/troubleshooting/token_refresh.md +338 -0
- data/public/docs/cli/legate_cli_usage.md +363 -0
- data/public/docs/core_concepts/legate_agent_lifecycle.md +124 -0
- data/public/docs/core_concepts/legate_architecture_overview.md +110 -0
- data/public/docs/core_concepts/legate_configuration.md +116 -0
- data/public/docs/core_concepts/legate_definition_store.md +102 -0
- data/public/docs/core_concepts/legate_planner.md +94 -0
- data/public/docs/core_concepts/legate_session_service.md +104 -0
- data/public/docs/error_handling/legate_error_handling.md +122 -0
- data/public/docs/examples.md +199 -0
- data/public/docs/getting_started.md +111 -0
- data/public/docs/guides/agentic_agents.md +137 -0
- data/public/docs/guides/ai_code_generators.md +437 -0
- data/public/docs/guides/auto_loading.md +326 -0
- data/public/docs/guides/configuring_agent_webhooks.md +219 -0
- data/public/docs/guides/http_client_usage.md +264 -0
- data/public/docs/guides/llm_providers.md +137 -0
- data/public/docs/guides/mcp_client_integration.md +232 -0
- data/public/docs/guides/mcp_server_exposure.md +206 -0
- data/public/docs/guides/rails_integration.md +128 -0
- data/public/docs/guides/sending_outbound_webhooks.md +227 -0
- data/public/docs/guides/streaming.md +112 -0
- data/public/docs/guides/webhooks.md +288 -0
- data/public/docs/introduction.md +51 -0
- data/public/docs/multi_agent_systems/advanced_features.md +57 -0
- data/public/docs/multi_agent_systems/agent_delegation.md +190 -0
- data/public/docs/multi_agent_systems/agent_hierarchy.md +49 -0
- data/public/docs/multi_agent_systems/state_management.md +47 -0
- data/public/docs/multi_agent_systems/workflow_agents.md +72 -0
- data/public/docs/tools/legate_built_in_tools.md +332 -0
- data/public/docs/tools/legate_tools_and_registry.md +263 -0
- data/public/docs/web_ui/legate_web_ui.md +137 -0
- metadata +823 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# File: lib/legate/session_service/base.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Legate
|
|
5
|
+
module SessionService
|
|
6
|
+
# Base class for session services
|
|
7
|
+
class Base
|
|
8
|
+
# Returns whether this service persists state
|
|
9
|
+
# @return [Boolean] true if state is persisted, false otherwise
|
|
10
|
+
def persistent?
|
|
11
|
+
false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Saves scoped state
|
|
15
|
+
# @param scope [String] The scope of the state ('user', 'app', or 'temp')
|
|
16
|
+
# @param key [String] The key to save
|
|
17
|
+
# @param value [Object] The value to save
|
|
18
|
+
# @raise [NotImplementedError] Must be implemented by subclasses
|
|
19
|
+
def save_scoped_state(scope, key, value)
|
|
20
|
+
raise NotImplementedError, "#{self.class} must implement #save_scoped_state"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Loads scoped state
|
|
24
|
+
# @param scope [String] The scope of the state ('user', 'app', or 'temp')
|
|
25
|
+
# @param key [String] The key to load
|
|
26
|
+
# @return [Object, nil] The loaded value or nil if not found
|
|
27
|
+
# @raise [NotImplementedError] Must be implemented by subclasses
|
|
28
|
+
def load_scoped_state(scope, key)
|
|
29
|
+
raise NotImplementedError, "#{self.class} must implement #load_scoped_state"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Clears scoped state
|
|
33
|
+
# @param scope [String] The scope of the state ('user', 'app', or 'temp')
|
|
34
|
+
# @param key [String] The key to clear
|
|
35
|
+
# @raise [NotImplementedError] Must be implemented by subclasses
|
|
36
|
+
def clear_scoped_state(scope, key)
|
|
37
|
+
raise NotImplementedError, "#{self.class} must implement #clear_scoped_state"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def append_event(session_id:, event:)
|
|
41
|
+
raise NotImplementedError, "#{self.class.name} must implement #append_event."
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Sets a key-value pair in the state associated with the session.
|
|
45
|
+
# This typically involves finding the session and then calling the session object's
|
|
46
|
+
# own state management methods (e.g., session.set_state(key, value)).
|
|
47
|
+
# @param session_id [String] The ID of the session.
|
|
48
|
+
# @param key [Symbol] The key for the state entry (should not have service-level prefixes like user: or app:).
|
|
49
|
+
# @param value [Object] The value to store.
|
|
50
|
+
# @return [void]
|
|
51
|
+
# @raise [NotImplementedError] If the subclass does not implement this method.
|
|
52
|
+
def set_state(session_id:, key:, value:)
|
|
53
|
+
raise NotImplementedError, "#{self.class.name} must implement #set_state."
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Retrieves a value from the state associated with the session.
|
|
57
|
+
# This typically involves finding the session and then calling the session object's
|
|
58
|
+
# own state management methods (e.g., session.get_state(key)).
|
|
59
|
+
# @param session_id [String] The ID of the session.
|
|
60
|
+
# @param key [Symbol] The key for the state entry (should not have service-level prefixes).
|
|
61
|
+
# @return [Object, nil] The value if found, or nil.
|
|
62
|
+
# @raise [NotImplementedError] If the subclass does not implement this method.
|
|
63
|
+
def get_state(session_id:, key:)
|
|
64
|
+
raise NotImplementedError, "#{self.class.name} must implement #get_state."
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# File: lib/legate/session_service/event_broadcast.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'concurrent'
|
|
5
|
+
|
|
6
|
+
module Legate
|
|
7
|
+
module SessionService
|
|
8
|
+
# Session-scoped pub/sub for streaming agent events (R3).
|
|
9
|
+
#
|
|
10
|
+
# A session service includes this mixin and calls {#broadcast_event} right
|
|
11
|
+
# after persisting an event in #append_event. Consumers (a Sinatra SSE
|
|
12
|
+
# response, a CLI, a test) {#subscribe} to a session_id and receive each
|
|
13
|
+
# event as it is appended, then {#unsubscribe} when done.
|
|
14
|
+
#
|
|
15
|
+
# Delivery is synchronous and in-order on the appending thread; a subscriber
|
|
16
|
+
# that raises is isolated (logged, never breaks persistence or other
|
|
17
|
+
# subscribers). Subscribers are keyed by session_id, so concurrent runs on
|
|
18
|
+
# different sessions never cross.
|
|
19
|
+
module EventBroadcast
|
|
20
|
+
# Guards the one-time creation of the subscriber registry. Shared across
|
|
21
|
+
# instances, but only contended on each instance's very first subscribe —
|
|
22
|
+
# negligible, and it avoids depending on the including class's #initialize.
|
|
23
|
+
INIT_LOCK = Mutex.new
|
|
24
|
+
private_constant :INIT_LOCK
|
|
25
|
+
|
|
26
|
+
# @param session_id [String]
|
|
27
|
+
# @yieldparam event [Legate::Event] each event appended to this session
|
|
28
|
+
# @return [Object] an opaque handle to pass to {#unsubscribe}
|
|
29
|
+
def subscribe(session_id, &listener)
|
|
30
|
+
raise ArgumentError, 'subscribe requires a block' unless listener
|
|
31
|
+
|
|
32
|
+
key = session_id.to_s
|
|
33
|
+
subscribers = event_subscribers.compute_if_absent(key) { Concurrent::Array.new }
|
|
34
|
+
subscribers << listener
|
|
35
|
+
[key, listener]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Removes a subscription created by {#subscribe}. Safe to call twice / with nil.
|
|
39
|
+
# @param handle [Object] the value returned by {#subscribe}
|
|
40
|
+
def unsubscribe(handle)
|
|
41
|
+
return unless handle.is_a?(Array)
|
|
42
|
+
|
|
43
|
+
key, listener = handle
|
|
44
|
+
subscribers = event_subscribers[key]
|
|
45
|
+
return unless subscribers
|
|
46
|
+
|
|
47
|
+
subscribers.delete(listener)
|
|
48
|
+
event_subscribers.delete(key) if subscribers.empty?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Notifies subscribers of `session_id` that `event` was appended.
|
|
52
|
+
# @return [void]
|
|
53
|
+
def broadcast_event(session_id, event)
|
|
54
|
+
subscribers = event_subscribers[session_id.to_s]
|
|
55
|
+
return if subscribers.nil? || subscribers.empty?
|
|
56
|
+
|
|
57
|
+
subscribers.each do |listener|
|
|
58
|
+
listener.call(event)
|
|
59
|
+
rescue StandardError => e
|
|
60
|
+
Legate.logger.error("EventBroadcast: subscriber raised #{e.class}: #{e.message}")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# Thread-safe lazy registry: the fast path is lock-free; the first caller
|
|
67
|
+
# per instance takes INIT_LOCK so concurrent first-subscribers can't each
|
|
68
|
+
# create (and orphan) a separate map.
|
|
69
|
+
def event_subscribers
|
|
70
|
+
@event_subscribers || INIT_LOCK.synchronize { @event_subscribers ||= Concurrent::Map.new }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# File: lib/legate/session_service/in_memory.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'concurrent'
|
|
5
|
+
require_relative 'base'
|
|
6
|
+
require_relative 'event_broadcast'
|
|
7
|
+
require_relative '../session'
|
|
8
|
+
require_relative '../event'
|
|
9
|
+
|
|
10
|
+
module Legate
|
|
11
|
+
module SessionService
|
|
12
|
+
# Stores sessions entirely in memory. Data is lost on application restart.
|
|
13
|
+
# Useful for local development, testing, and simple use cases.
|
|
14
|
+
class InMemory < Base
|
|
15
|
+
include EventBroadcast
|
|
16
|
+
|
|
17
|
+
attr_reader :sessions, :scoped_states
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
@sessions = Concurrent::Map.new
|
|
21
|
+
@scoped_states = Concurrent::Map.new
|
|
22
|
+
Legate.logger.info('InMemorySessionService initialized.')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def persistent?
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Creates a new session in memory.
|
|
30
|
+
# @param app_name [String] Identifier for the agent application.
|
|
31
|
+
# @param user_id [String] Identifier for the user initiating the session.
|
|
32
|
+
# @param session_id [String, nil] Optional explicit session id (defaults to a generated UUID).
|
|
33
|
+
# @param initial_state [Hash] Optional initial data for the session state.
|
|
34
|
+
# @return [Legate::Session] The newly created session object.
|
|
35
|
+
def create_session(app_name:, user_id:, session_id: nil, initial_state: {})
|
|
36
|
+
# Fix: Ensure keys are symbols before passing to Session constructor
|
|
37
|
+
symbolized_state = initial_state.transform_keys { |k|
|
|
38
|
+
begin
|
|
39
|
+
k.to_sym
|
|
40
|
+
rescue StandardError
|
|
41
|
+
k
|
|
42
|
+
end
|
|
43
|
+
}
|
|
44
|
+
session = Legate::Session.new(
|
|
45
|
+
app_name: app_name,
|
|
46
|
+
user_id: user_id,
|
|
47
|
+
id: session_id,
|
|
48
|
+
initial_state: symbolized_state,
|
|
49
|
+
session_service: self
|
|
50
|
+
)
|
|
51
|
+
@sessions[session.id] = session
|
|
52
|
+
Legate.logger.info("Created session: #{session.id} for app:#{app_name}, user:#{user_id}")
|
|
53
|
+
session
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Retrieves a session from memory by its ID.
|
|
57
|
+
# @param session_id [String] The unique ID of the session to retrieve.
|
|
58
|
+
# @return [Legate::Session, nil] The session object if found, otherwise nil.
|
|
59
|
+
def get_session(session_id:)
|
|
60
|
+
session = @sessions[session_id]
|
|
61
|
+
if session
|
|
62
|
+
Legate.logger.debug("Retrieved session: #{session_id}")
|
|
63
|
+
else
|
|
64
|
+
Legate.logger.warn("Session not found: #{session_id}")
|
|
65
|
+
end
|
|
66
|
+
session
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# --- DEPRECATED ---
|
|
70
|
+
# Saves the session state (in memory this just means it's already updated).
|
|
71
|
+
# In a persistent store, this would write changes. Here, we just update the timestamp.
|
|
72
|
+
# NOTE: Events and state updates should be done via #append_event for atomicity.
|
|
73
|
+
# This method mainly exists for interface compatibility if needed but should be avoided.
|
|
74
|
+
# @param session [Legate::Session] The session object to "save".
|
|
75
|
+
# @return [Boolean] True if the session exists in memory.
|
|
76
|
+
def save_session(session:)
|
|
77
|
+
if @sessions.key?(session.id)
|
|
78
|
+
session.updated_at = Time.now.utc # Ensure timestamp reflects save attempt
|
|
79
|
+
Legate.logger.warn('InMemorySessionService#save_session called (likely unnecessary). Use append_event.')
|
|
80
|
+
true
|
|
81
|
+
else
|
|
82
|
+
Legate.logger.error("Attempted to save non-existent session: #{session.id}")
|
|
83
|
+
false
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# --- REVISED METHOD ---
|
|
88
|
+
# Appends an event to a session and merges state updates from the event's state_delta.
|
|
89
|
+
# This should be the primary way to modify a session during a turn.
|
|
90
|
+
# @param session_id [String] The ID of the session to update.
|
|
91
|
+
# @param event [Legate::Event] The event to append. Must be an instance of Legate::Event.
|
|
92
|
+
# @return [Boolean] True if successful, false if session not found or event is invalid.
|
|
93
|
+
def append_event(session_id:, event:)
|
|
94
|
+
session = get_session(session_id: session_id)
|
|
95
|
+
return false unless session
|
|
96
|
+
|
|
97
|
+
session.add_event(event)
|
|
98
|
+
broadcast_event(session_id, event) # notify any streaming subscribers (R3)
|
|
99
|
+
true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Deletes a session from memory.
|
|
103
|
+
# @param session_id [String] The ID of the session to delete.
|
|
104
|
+
# @return [Boolean] True if a session was deleted, false otherwise.
|
|
105
|
+
def delete_session(session_id:)
|
|
106
|
+
deleted_session = @sessions.delete(session_id)
|
|
107
|
+
if deleted_session
|
|
108
|
+
Legate.logger.info("Deleted session: #{session_id}")
|
|
109
|
+
true
|
|
110
|
+
else
|
|
111
|
+
Legate.logger.warn("Attempted to delete non-existent session: #{session_id}")
|
|
112
|
+
false
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Lists sessions (in this implementation, just returns all session objects).
|
|
117
|
+
# Filtering could be added later if needed.
|
|
118
|
+
# @param app_name [String, nil] Optional filter by app name.
|
|
119
|
+
# @param user_id [String, nil] Optional filter by user ID.
|
|
120
|
+
# @return [Array<Legate::Session>] An array of session objects matching filters.
|
|
121
|
+
def list_sessions(app_name: nil, user_id: nil)
|
|
122
|
+
filtered = @sessions.values # Get all session objects
|
|
123
|
+
filtered.select! { |s| s.app_name == app_name } if app_name
|
|
124
|
+
filtered.select! { |s| s.user_id == user_id } if user_id
|
|
125
|
+
Legate.logger.debug("Listing #{filtered.count} sessions.")
|
|
126
|
+
filtered
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def save_scoped_state(scope, key, value)
|
|
130
|
+
state_key = "#{scope}:#{key}"
|
|
131
|
+
@scoped_states[state_key] = value
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def load_scoped_state(scope, key)
|
|
135
|
+
state_key = "#{scope}:#{key}"
|
|
136
|
+
@scoped_states[state_key]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def clear_scoped_state(scope, key)
|
|
140
|
+
if key == '*'
|
|
141
|
+
# Clear all states for the given scope
|
|
142
|
+
@scoped_states.keys.each do |state_key|
|
|
143
|
+
@scoped_states.delete(state_key) if state_key.start_with?("#{scope}:")
|
|
144
|
+
end
|
|
145
|
+
else
|
|
146
|
+
state_key = "#{scope}:#{key}"
|
|
147
|
+
@scoped_states.delete(state_key)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Sets a key-value pair in the state associated with the session.
|
|
152
|
+
# Delegates to the Legate::Session instance's set_state method.
|
|
153
|
+
# @param session_id [String] The ID of the session.
|
|
154
|
+
# @param key [Symbol] The key for the state entry.
|
|
155
|
+
# @param value [Object] The value to store.
|
|
156
|
+
# @return [void]
|
|
157
|
+
def set_state(session_id:, key:, value:)
|
|
158
|
+
session = get_session(session_id: session_id)
|
|
159
|
+
if session
|
|
160
|
+
begin
|
|
161
|
+
session.set_state(key, value) # Legate::Session#set_state handles its own logging
|
|
162
|
+
rescue Legate::SerializationError => e # Catch potential serialization errors from session.set_state
|
|
163
|
+
Legate.logger.error("InMemorySessionService: Error setting state for session '#{session_id}', key '#{key}': #{e.message}")
|
|
164
|
+
# Depending on desired behavior, could re-raise or just log
|
|
165
|
+
end
|
|
166
|
+
else
|
|
167
|
+
Legate.logger.warn("InMemorySessionService: Session not found '#{session_id}' when trying to set state for key '#{key}'.")
|
|
168
|
+
end
|
|
169
|
+
nil # Return void consistent with base
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Retrieves a value from the state associated with the session.
|
|
173
|
+
# Delegates to the Legate::Session instance's get_state method.
|
|
174
|
+
# @param session_id [String] The ID of the session.
|
|
175
|
+
# @param key [Symbol] The key for the state entry.
|
|
176
|
+
# @return [Object, nil] The value if found, or nil.
|
|
177
|
+
def get_state(session_id:, key:)
|
|
178
|
+
session = get_session(session_id: session_id)
|
|
179
|
+
if session
|
|
180
|
+
session.get_state(key) # Legate::Session#get_state handles its own logic
|
|
181
|
+
else
|
|
182
|
+
Legate.logger.warn("InMemorySessionService: Session not found '#{session_id}' when trying to get state for key '#{key}'.")
|
|
183
|
+
nil
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legate
|
|
4
|
+
class Tool
|
|
5
|
+
# Module to provide a more concise DSL for defining tool metadata.
|
|
6
|
+
module MetadataDsl
|
|
7
|
+
def self.included(base)
|
|
8
|
+
base.extend ClassMethods
|
|
9
|
+
|
|
10
|
+
# Define class instance variable accessors on the base class singleton
|
|
11
|
+
# These are primarily for the *new* DSL
|
|
12
|
+
class << base
|
|
13
|
+
# Replaced attr_accessor with manual methods to handle cache invalidation
|
|
14
|
+
# attr_accessor :explicit_tool_name, :description, :parameters_definition
|
|
15
|
+
|
|
16
|
+
attr_reader :explicit_tool_name, :description, :parameters_definition
|
|
17
|
+
|
|
18
|
+
def explicit_tool_name=(value)
|
|
19
|
+
@explicit_tool_name = value
|
|
20
|
+
@_tool_metadata_cache = nil # Invalidate cache
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def description=(value)
|
|
24
|
+
@description = value
|
|
25
|
+
@_tool_metadata_cache = nil # Invalidate cache
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def parameters_definition=(value)
|
|
29
|
+
@parameters_definition = value
|
|
30
|
+
@_tool_metadata_cache = nil # Invalidate cache
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Initialize with default values to ensure methods don't fail on nil
|
|
34
|
+
# Note: These are instance variables of the singleton class (class instance variables)
|
|
35
|
+
def initialize_dsl_storage
|
|
36
|
+
@explicit_tool_name ||= nil
|
|
37
|
+
@description ||= nil # DSL description storage
|
|
38
|
+
@parameters_definition ||= {} # DSL parameters storage
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
module ClassMethods
|
|
44
|
+
# DSL method for setting description
|
|
45
|
+
def tool_description(text)
|
|
46
|
+
initialize_dsl_storage # Ensure vars exist
|
|
47
|
+
self.description = text.to_s
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# DSL method for defining a parameter
|
|
51
|
+
def parameter(name, options = {})
|
|
52
|
+
initialize_dsl_storage # Ensure hash exists
|
|
53
|
+
raise ArgumentError, 'Parameter name must be a Symbol' unless name.is_a?(Symbol)
|
|
54
|
+
|
|
55
|
+
@parameters_definition[name] = options
|
|
56
|
+
@_tool_metadata_cache = nil # Invalidate cache on modification
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get the inferred name (logic unchanged)
|
|
60
|
+
def inferred_name
|
|
61
|
+
class_name_str = Module.instance_method(:name).bind(self).call
|
|
62
|
+
return nil unless class_name_str && !class_name_str.empty? && class_name_str.is_a?(String)
|
|
63
|
+
return nil if class_name_str.start_with?('#<Class:')
|
|
64
|
+
|
|
65
|
+
inferred = class_name_str.split('::').last
|
|
66
|
+
return nil if inferred.nil? || inferred.empty?
|
|
67
|
+
|
|
68
|
+
inferred = inferred.dup
|
|
69
|
+
inferred.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
70
|
+
inferred.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
|
71
|
+
inferred.tr!('-', '_')
|
|
72
|
+
inferred.downcase!
|
|
73
|
+
inferred.to_sym
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Get the final tool name with priority:
|
|
77
|
+
# 1. DSL's explicit_tool_name
|
|
78
|
+
# 2. define_metadata's @tool_name
|
|
79
|
+
# 3. Inferred name
|
|
80
|
+
def effective_tool_name
|
|
81
|
+
initialize_dsl_storage # Ensure @explicit_tool_name exists
|
|
82
|
+
explicit_dsl = explicit_tool_name
|
|
83
|
+
return explicit_dsl if explicit_dsl && explicit_dsl != :''
|
|
84
|
+
|
|
85
|
+
# Check define_metadata's variable if explicit DSL name wasn't set
|
|
86
|
+
# Use instance_variable_get as @tool_name is not directly accessible via reader here
|
|
87
|
+
if instance_variable_defined?(:@tool_name)
|
|
88
|
+
explicit_old = instance_variable_get(:@tool_name)
|
|
89
|
+
return explicit_old if explicit_old && explicit_old != :''
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Fallback to inferred name
|
|
93
|
+
inferred_name
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Retrieve consolidated metadata, preferring DSL values but falling back to define_metadata values.
|
|
97
|
+
# Cached for performance.
|
|
98
|
+
def tool_metadata
|
|
99
|
+
@_tool_metadata_cache ||= begin
|
|
100
|
+
initialize_dsl_storage # Ensure DSL variables exist
|
|
101
|
+
|
|
102
|
+
# Get description: Prefer DSL, fallback to define_metadata's @description
|
|
103
|
+
dsl_desc = description
|
|
104
|
+
old_desc = instance_variable_get(:@description) if instance_variable_defined?(:@description) && dsl_desc.nil?
|
|
105
|
+
final_desc = dsl_desc || old_desc
|
|
106
|
+
|
|
107
|
+
# Get parameters: Prefer DSL, fallback to define_metadata's @parameters_definition
|
|
108
|
+
dsl_params = parameters_definition
|
|
109
|
+
old_params = instance_variable_get(:@parameters_definition) if instance_variable_defined?(:@parameters_definition) && (dsl_params.nil? || dsl_params.empty?)
|
|
110
|
+
final_params = dsl_params && !dsl_params.empty? ? dsl_params : (old_params || {})
|
|
111
|
+
|
|
112
|
+
{
|
|
113
|
+
name: effective_tool_name,
|
|
114
|
+
description: final_desc,
|
|
115
|
+
parameters: final_params
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end # End ClassMethods
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|