@elizaos/python 2.0.0-alpha.10
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/LICENSE +26 -0
- package/README.md +239 -0
- package/elizaos/__init__.py +280 -0
- package/elizaos/action_docs.py +149 -0
- package/elizaos/advanced_capabilities/__init__.py +85 -0
- package/elizaos/advanced_capabilities/actions/__init__.py +54 -0
- package/elizaos/advanced_capabilities/actions/add_contact.py +139 -0
- package/elizaos/advanced_capabilities/actions/follow_room.py +151 -0
- package/elizaos/advanced_capabilities/actions/image_generation.py +148 -0
- package/elizaos/advanced_capabilities/actions/mute_room.py +164 -0
- package/elizaos/advanced_capabilities/actions/remove_contact.py +145 -0
- package/elizaos/advanced_capabilities/actions/roles.py +207 -0
- package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +154 -0
- package/elizaos/advanced_capabilities/actions/search_contacts.py +145 -0
- package/elizaos/advanced_capabilities/actions/send_message.py +187 -0
- package/elizaos/advanced_capabilities/actions/settings.py +151 -0
- package/elizaos/advanced_capabilities/actions/unfollow_room.py +164 -0
- package/elizaos/advanced_capabilities/actions/unmute_room.py +164 -0
- package/elizaos/advanced_capabilities/actions/update_contact.py +164 -0
- package/elizaos/advanced_capabilities/actions/update_entity.py +161 -0
- package/elizaos/advanced_capabilities/evaluators/__init__.py +18 -0
- package/elizaos/advanced_capabilities/evaluators/reflection.py +134 -0
- package/elizaos/advanced_capabilities/evaluators/relationship_extraction.py +203 -0
- package/elizaos/advanced_capabilities/providers/__init__.py +36 -0
- package/elizaos/advanced_capabilities/providers/agent_settings.py +60 -0
- package/elizaos/advanced_capabilities/providers/contacts.py +77 -0
- package/elizaos/advanced_capabilities/providers/facts.py +82 -0
- package/elizaos/advanced_capabilities/providers/follow_ups.py +113 -0
- package/elizaos/advanced_capabilities/providers/knowledge.py +83 -0
- package/elizaos/advanced_capabilities/providers/relationships.py +112 -0
- package/elizaos/advanced_capabilities/providers/roles.py +97 -0
- package/elizaos/advanced_capabilities/providers/settings.py +51 -0
- package/elizaos/advanced_capabilities/services/__init__.py +18 -0
- package/elizaos/advanced_capabilities/services/follow_up.py +138 -0
- package/elizaos/advanced_capabilities/services/rolodex.py +244 -0
- package/elizaos/advanced_memory/__init__.py +3 -0
- package/elizaos/advanced_memory/evaluators.py +97 -0
- package/elizaos/advanced_memory/memory_service.py +556 -0
- package/elizaos/advanced_memory/plugin.py +30 -0
- package/elizaos/advanced_memory/prompts.py +12 -0
- package/elizaos/advanced_memory/providers.py +90 -0
- package/elizaos/advanced_memory/types.py +65 -0
- package/elizaos/advanced_planning/__init__.py +10 -0
- package/elizaos/advanced_planning/actions.py +145 -0
- package/elizaos/advanced_planning/message_classifier.py +127 -0
- package/elizaos/advanced_planning/planning_service.py +712 -0
- package/elizaos/advanced_planning/plugin.py +40 -0
- package/elizaos/advanced_planning/prompts.py +4 -0
- package/elizaos/basic_capabilities/__init__.py +66 -0
- package/elizaos/basic_capabilities/actions/__init__.py +24 -0
- package/elizaos/basic_capabilities/actions/choice.py +140 -0
- package/elizaos/basic_capabilities/actions/ignore.py +66 -0
- package/elizaos/basic_capabilities/actions/none.py +56 -0
- package/elizaos/basic_capabilities/actions/reply.py +120 -0
- package/elizaos/basic_capabilities/providers/__init__.py +54 -0
- package/elizaos/basic_capabilities/providers/action_state.py +113 -0
- package/elizaos/basic_capabilities/providers/actions.py +263 -0
- package/elizaos/basic_capabilities/providers/attachments.py +76 -0
- package/elizaos/basic_capabilities/providers/capabilities.py +62 -0
- package/elizaos/basic_capabilities/providers/character.py +113 -0
- package/elizaos/basic_capabilities/providers/choice.py +73 -0
- package/elizaos/basic_capabilities/providers/context_bench.py +44 -0
- package/elizaos/basic_capabilities/providers/current_time.py +58 -0
- package/elizaos/basic_capabilities/providers/entities.py +99 -0
- package/elizaos/basic_capabilities/providers/evaluators.py +54 -0
- package/elizaos/basic_capabilities/providers/providers_list.py +55 -0
- package/elizaos/basic_capabilities/providers/recent_messages.py +85 -0
- package/elizaos/basic_capabilities/providers/time.py +45 -0
- package/elizaos/basic_capabilities/providers/world.py +93 -0
- package/elizaos/basic_capabilities/services/__init__.py +18 -0
- package/elizaos/basic_capabilities/services/embedding.py +122 -0
- package/elizaos/basic_capabilities/services/task.py +178 -0
- package/elizaos/bootstrap/__init__.py +12 -0
- package/elizaos/bootstrap/actions/__init__.py +68 -0
- package/elizaos/bootstrap/actions/add_contact.py +149 -0
- package/elizaos/bootstrap/actions/choice.py +147 -0
- package/elizaos/bootstrap/actions/follow_room.py +151 -0
- package/elizaos/bootstrap/actions/ignore.py +80 -0
- package/elizaos/bootstrap/actions/image_generation.py +135 -0
- package/elizaos/bootstrap/actions/mute_room.py +151 -0
- package/elizaos/bootstrap/actions/none.py +71 -0
- package/elizaos/bootstrap/actions/remove_contact.py +159 -0
- package/elizaos/bootstrap/actions/reply.py +140 -0
- package/elizaos/bootstrap/actions/roles.py +193 -0
- package/elizaos/bootstrap/actions/schedule_follow_up.py +164 -0
- package/elizaos/bootstrap/actions/search_contacts.py +159 -0
- package/elizaos/bootstrap/actions/send_message.py +173 -0
- package/elizaos/bootstrap/actions/settings.py +165 -0
- package/elizaos/bootstrap/actions/unfollow_room.py +151 -0
- package/elizaos/bootstrap/actions/unmute_room.py +151 -0
- package/elizaos/bootstrap/actions/update_contact.py +178 -0
- package/elizaos/bootstrap/actions/update_entity.py +175 -0
- package/elizaos/bootstrap/autonomy/__init__.py +18 -0
- package/elizaos/bootstrap/autonomy/action.py +197 -0
- package/elizaos/bootstrap/autonomy/providers.py +165 -0
- package/elizaos/bootstrap/autonomy/routes.py +171 -0
- package/elizaos/bootstrap/autonomy/service.py +562 -0
- package/elizaos/bootstrap/autonomy/types.py +18 -0
- package/elizaos/bootstrap/evaluators/__init__.py +19 -0
- package/elizaos/bootstrap/evaluators/reflection.py +118 -0
- package/elizaos/bootstrap/evaluators/relationship_extraction.py +192 -0
- package/elizaos/bootstrap/plugin.py +140 -0
- package/elizaos/bootstrap/providers/__init__.py +80 -0
- package/elizaos/bootstrap/providers/action_state.py +71 -0
- package/elizaos/bootstrap/providers/actions.py +256 -0
- package/elizaos/bootstrap/providers/agent_settings.py +63 -0
- package/elizaos/bootstrap/providers/attachments.py +76 -0
- package/elizaos/bootstrap/providers/capabilities.py +66 -0
- package/elizaos/bootstrap/providers/character.py +128 -0
- package/elizaos/bootstrap/providers/choice.py +77 -0
- package/elizaos/bootstrap/providers/contacts.py +78 -0
- package/elizaos/bootstrap/providers/context_bench.py +49 -0
- package/elizaos/bootstrap/providers/current_time.py +56 -0
- package/elizaos/bootstrap/providers/entities.py +99 -0
- package/elizaos/bootstrap/providers/evaluators.py +58 -0
- package/elizaos/bootstrap/providers/facts.py +86 -0
- package/elizaos/bootstrap/providers/follow_ups.py +116 -0
- package/elizaos/bootstrap/providers/knowledge.py +73 -0
- package/elizaos/bootstrap/providers/providers_list.py +59 -0
- package/elizaos/bootstrap/providers/recent_messages.py +85 -0
- package/elizaos/bootstrap/providers/relationships.py +106 -0
- package/elizaos/bootstrap/providers/roles.py +95 -0
- package/elizaos/bootstrap/providers/settings.py +55 -0
- package/elizaos/bootstrap/providers/time.py +45 -0
- package/elizaos/bootstrap/providers/world.py +97 -0
- package/elizaos/bootstrap/services/__init__.py +26 -0
- package/elizaos/bootstrap/services/embedding.py +122 -0
- package/elizaos/bootstrap/services/follow_up.py +138 -0
- package/elizaos/bootstrap/services/rolodex.py +244 -0
- package/elizaos/bootstrap/services/task.py +585 -0
- package/elizaos/bootstrap/types.py +54 -0
- package/elizaos/bootstrap/utils/__init__.py +7 -0
- package/elizaos/bootstrap/utils/xml.py +69 -0
- package/elizaos/character.py +149 -0
- package/elizaos/logger.py +179 -0
- package/elizaos/media/__init__.py +45 -0
- package/elizaos/media/mime.py +315 -0
- package/elizaos/media/search.py +161 -0
- package/elizaos/media/tests/__init__.py +1 -0
- package/elizaos/media/tests/test_mime.py +117 -0
- package/elizaos/media/tests/test_search.py +156 -0
- package/elizaos/plugin.py +191 -0
- package/elizaos/prompts.py +1071 -0
- package/elizaos/py.typed +0 -0
- package/elizaos/runtime.py +2572 -0
- package/elizaos/services/__init__.py +49 -0
- package/elizaos/services/hook_service.py +511 -0
- package/elizaos/services/message_service.py +1248 -0
- package/elizaos/settings.py +182 -0
- package/elizaos/streaming_context.py +159 -0
- package/elizaos/trajectory_context.py +18 -0
- package/elizaos/types/__init__.py +512 -0
- package/elizaos/types/agent.py +31 -0
- package/elizaos/types/components.py +208 -0
- package/elizaos/types/database.py +64 -0
- package/elizaos/types/environment.py +46 -0
- package/elizaos/types/events.py +47 -0
- package/elizaos/types/memory.py +45 -0
- package/elizaos/types/model.py +393 -0
- package/elizaos/types/plugin.py +188 -0
- package/elizaos/types/primitives.py +100 -0
- package/elizaos/types/runtime.py +460 -0
- package/elizaos/types/service.py +113 -0
- package/elizaos/types/service_interfaces.py +244 -0
- package/elizaos/types/state.py +188 -0
- package/elizaos/types/task.py +29 -0
- package/elizaos/utils/__init__.py +108 -0
- package/elizaos/utils/spec_examples.py +48 -0
- package/elizaos/utils/streaming.py +426 -0
- package/elizaos_atropos_shared/__init__.py +1 -0
- package/elizaos_atropos_shared/canonical_eliza.py +282 -0
- package/package.json +19 -0
- package/pyproject.toml +143 -0
- package/requirements-dev.in +11 -0
- package/requirements-dev.lock +134 -0
- package/requirements.in +9 -0
- package/requirements.lock +64 -0
- package/tests/__init__.py +0 -0
- package/tests/test_action_parameters.py +154 -0
- package/tests/test_actions_provider_examples.py +39 -0
- package/tests/test_advanced_memory_behavior.py +96 -0
- package/tests/test_advanced_memory_flag.py +30 -0
- package/tests/test_advanced_planning_behavior.py +225 -0
- package/tests/test_advanced_planning_flag.py +26 -0
- package/tests/test_autonomy.py +445 -0
- package/tests/test_bootstrap_initialize.py +37 -0
- package/tests/test_character.py +163 -0
- package/tests/test_character_provider.py +231 -0
- package/tests/test_dynamic_prompt_exec.py +561 -0
- package/tests/test_logger_redaction.py +43 -0
- package/tests/test_plugin.py +117 -0
- package/tests/test_runtime.py +422 -0
- package/tests/test_salt_production_enforcement.py +22 -0
- package/tests/test_settings_crypto.py +118 -0
- package/tests/test_streaming.py +295 -0
- package/tests/test_types.py +221 -0
- package/tests/test_uuid_parity.py +46 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from elizaos.plugin import (
|
|
4
|
+
PluginLoadError,
|
|
5
|
+
resolve_plugin_dependencies,
|
|
6
|
+
)
|
|
7
|
+
from elizaos.types import Plugin
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestResolveDependencies:
|
|
11
|
+
def test_no_dependencies(self) -> None:
|
|
12
|
+
plugins = [
|
|
13
|
+
Plugin(name="plugin-a", description="Plugin A"),
|
|
14
|
+
Plugin(name="plugin-b", description="Plugin B"),
|
|
15
|
+
]
|
|
16
|
+
result = resolve_plugin_dependencies(plugins)
|
|
17
|
+
assert len(result) == 2
|
|
18
|
+
|
|
19
|
+
def test_simple_dependency(self) -> None:
|
|
20
|
+
plugins = [
|
|
21
|
+
Plugin(
|
|
22
|
+
name="plugin-b",
|
|
23
|
+
description="Plugin B",
|
|
24
|
+
dependencies=["plugin-a"],
|
|
25
|
+
),
|
|
26
|
+
Plugin(name="plugin-a", description="Plugin A"),
|
|
27
|
+
]
|
|
28
|
+
result = resolve_plugin_dependencies(plugins)
|
|
29
|
+
assert result[0].name == "plugin-a"
|
|
30
|
+
assert result[1].name == "plugin-b"
|
|
31
|
+
|
|
32
|
+
def test_chain_dependency(self) -> None:
|
|
33
|
+
plugins = [
|
|
34
|
+
Plugin(
|
|
35
|
+
name="plugin-c",
|
|
36
|
+
description="Plugin C",
|
|
37
|
+
dependencies=["plugin-b"],
|
|
38
|
+
),
|
|
39
|
+
Plugin(
|
|
40
|
+
name="plugin-b",
|
|
41
|
+
description="Plugin B",
|
|
42
|
+
dependencies=["plugin-a"],
|
|
43
|
+
),
|
|
44
|
+
Plugin(name="plugin-a", description="Plugin A"),
|
|
45
|
+
]
|
|
46
|
+
result = resolve_plugin_dependencies(plugins)
|
|
47
|
+
names = [p.name for p in result]
|
|
48
|
+
assert names.index("plugin-a") < names.index("plugin-b")
|
|
49
|
+
assert names.index("plugin-b") < names.index("plugin-c")
|
|
50
|
+
|
|
51
|
+
def test_circular_dependency(self) -> None:
|
|
52
|
+
plugins = [
|
|
53
|
+
Plugin(
|
|
54
|
+
name="plugin-a",
|
|
55
|
+
description="Plugin A",
|
|
56
|
+
dependencies=["plugin-b"],
|
|
57
|
+
),
|
|
58
|
+
Plugin(
|
|
59
|
+
name="plugin-b",
|
|
60
|
+
description="Plugin B",
|
|
61
|
+
dependencies=["plugin-a"],
|
|
62
|
+
),
|
|
63
|
+
]
|
|
64
|
+
with pytest.raises(PluginLoadError, match="Circular dependency"):
|
|
65
|
+
resolve_plugin_dependencies(plugins)
|
|
66
|
+
|
|
67
|
+
def test_missing_dependency_handled(self) -> None:
|
|
68
|
+
plugins = [
|
|
69
|
+
Plugin(
|
|
70
|
+
name="plugin-a",
|
|
71
|
+
description="Plugin A",
|
|
72
|
+
dependencies=["external-plugin"],
|
|
73
|
+
),
|
|
74
|
+
]
|
|
75
|
+
result = resolve_plugin_dependencies(plugins)
|
|
76
|
+
assert len(result) == 1
|
|
77
|
+
|
|
78
|
+
def test_multiple_dependencies(self) -> None:
|
|
79
|
+
plugins = [
|
|
80
|
+
Plugin(
|
|
81
|
+
name="plugin-c",
|
|
82
|
+
description="Plugin C",
|
|
83
|
+
dependencies=["plugin-a", "plugin-b"],
|
|
84
|
+
),
|
|
85
|
+
Plugin(name="plugin-a", description="Plugin A"),
|
|
86
|
+
Plugin(name="plugin-b", description="Plugin B"),
|
|
87
|
+
]
|
|
88
|
+
result = resolve_plugin_dependencies(plugins)
|
|
89
|
+
names = [p.name for p in result]
|
|
90
|
+
assert names.index("plugin-a") < names.index("plugin-c")
|
|
91
|
+
assert names.index("plugin-b") < names.index("plugin-c")
|
|
92
|
+
|
|
93
|
+
def test_diamond_dependency(self) -> None:
|
|
94
|
+
plugins = [
|
|
95
|
+
Plugin(name="plugin-a", description="Plugin A"),
|
|
96
|
+
Plugin(
|
|
97
|
+
name="plugin-b",
|
|
98
|
+
description="Plugin B",
|
|
99
|
+
dependencies=["plugin-a"],
|
|
100
|
+
),
|
|
101
|
+
Plugin(
|
|
102
|
+
name="plugin-c",
|
|
103
|
+
description="Plugin C",
|
|
104
|
+
dependencies=["plugin-a"],
|
|
105
|
+
),
|
|
106
|
+
Plugin(
|
|
107
|
+
name="plugin-d",
|
|
108
|
+
description="Plugin D",
|
|
109
|
+
dependencies=["plugin-b", "plugin-c"],
|
|
110
|
+
),
|
|
111
|
+
]
|
|
112
|
+
result = resolve_plugin_dependencies(plugins)
|
|
113
|
+
names = [p.name for p in result]
|
|
114
|
+
assert names.index("plugin-a") < names.index("plugin-b")
|
|
115
|
+
assert names.index("plugin-a") < names.index("plugin-c")
|
|
116
|
+
assert names.index("plugin-b") < names.index("plugin-d")
|
|
117
|
+
assert names.index("plugin-c") < names.index("plugin-d")
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from elizaos.runtime import AgentRuntime
|
|
6
|
+
from elizaos.types import (
|
|
7
|
+
Action,
|
|
8
|
+
ActionResult,
|
|
9
|
+
Character,
|
|
10
|
+
Evaluator,
|
|
11
|
+
HandlerOptions,
|
|
12
|
+
IAgentRuntime,
|
|
13
|
+
LLMMode,
|
|
14
|
+
Memory,
|
|
15
|
+
ModelType,
|
|
16
|
+
Plugin,
|
|
17
|
+
Provider,
|
|
18
|
+
ProviderResult,
|
|
19
|
+
State,
|
|
20
|
+
as_uuid,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def character() -> Character:
|
|
26
|
+
return Character(
|
|
27
|
+
name="TestAgent",
|
|
28
|
+
bio=["A test agent for unit testing."],
|
|
29
|
+
system="You are a helpful test agent.",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.fixture
|
|
34
|
+
def runtime(character: Character) -> AgentRuntime:
|
|
35
|
+
return AgentRuntime(character=character)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TestAgentRuntimeInit:
|
|
39
|
+
def test_runtime_creation(self, character: Character) -> None:
|
|
40
|
+
runtime = AgentRuntime(character=character)
|
|
41
|
+
assert runtime.character.name == "TestAgent"
|
|
42
|
+
assert runtime.agent_id is not None
|
|
43
|
+
|
|
44
|
+
def test_runtime_with_agent_id(self, character: Character) -> None:
|
|
45
|
+
agent_id = as_uuid("12345678-1234-1234-1234-123456789012")
|
|
46
|
+
runtime = AgentRuntime(character=character, agent_id=agent_id)
|
|
47
|
+
assert runtime.agent_id == agent_id
|
|
48
|
+
|
|
49
|
+
def test_runtime_with_settings(self, character: Character) -> None:
|
|
50
|
+
runtime = AgentRuntime(
|
|
51
|
+
character=character,
|
|
52
|
+
settings={"custom_setting": "value"},
|
|
53
|
+
)
|
|
54
|
+
assert runtime.get_setting("custom_setting") == "value"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TestAgentRuntimeSettings:
|
|
58
|
+
def test_get_setting_from_runtime(self, runtime: AgentRuntime) -> None:
|
|
59
|
+
runtime.set_setting("test_key", "test_value")
|
|
60
|
+
assert runtime.get_setting("test_key") == "test_value"
|
|
61
|
+
|
|
62
|
+
@pytest.mark.skip(reason="CharacterSettings proto doesn't support arbitrary fields")
|
|
63
|
+
def test_get_setting_from_character(self) -> None:
|
|
64
|
+
character = Character(
|
|
65
|
+
name="Test",
|
|
66
|
+
bio=["Test"],
|
|
67
|
+
settings={"char_setting": "char_value"},
|
|
68
|
+
)
|
|
69
|
+
runtime = AgentRuntime(character=character)
|
|
70
|
+
assert runtime.get_setting("char_setting") == "char_value"
|
|
71
|
+
|
|
72
|
+
@pytest.mark.skip(reason="Runtime get_setting from secrets not yet implemented")
|
|
73
|
+
def test_get_setting_from_secrets(self) -> None:
|
|
74
|
+
character = Character(
|
|
75
|
+
name="Test",
|
|
76
|
+
bio=["Test"],
|
|
77
|
+
secrets={"API_KEY": "secret_key"},
|
|
78
|
+
)
|
|
79
|
+
runtime = AgentRuntime(character=character)
|
|
80
|
+
assert runtime.get_setting("API_KEY") == "secret_key"
|
|
81
|
+
|
|
82
|
+
def test_get_nonexistent_setting(self, runtime: AgentRuntime) -> None:
|
|
83
|
+
assert runtime.get_setting("nonexistent") is None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class TestAgentRuntimeProviders:
|
|
87
|
+
@pytest.mark.asyncio
|
|
88
|
+
async def test_register_provider(self, runtime: AgentRuntime) -> None:
|
|
89
|
+
async def get_data(rt: IAgentRuntime, msg: Memory, state: State) -> ProviderResult:
|
|
90
|
+
return ProviderResult(text="Provider data")
|
|
91
|
+
|
|
92
|
+
provider = Provider(
|
|
93
|
+
name="test-provider",
|
|
94
|
+
description="A test provider",
|
|
95
|
+
get=get_data,
|
|
96
|
+
)
|
|
97
|
+
runtime.register_provider(provider)
|
|
98
|
+
assert len(runtime.providers) == 1
|
|
99
|
+
assert runtime.providers[0].name == "test-provider"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class TestAgentRuntimeActions:
|
|
103
|
+
@pytest.mark.asyncio
|
|
104
|
+
async def test_register_action(self, runtime: AgentRuntime) -> None:
|
|
105
|
+
async def validate(rt: IAgentRuntime, msg: Memory, state: State | None) -> bool:
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
async def handler(
|
|
109
|
+
rt: IAgentRuntime,
|
|
110
|
+
msg: Memory,
|
|
111
|
+
state: State | None,
|
|
112
|
+
options: HandlerOptions | None,
|
|
113
|
+
callback: Any,
|
|
114
|
+
responses: list[Memory] | None,
|
|
115
|
+
) -> ActionResult | None:
|
|
116
|
+
return ActionResult(success=True)
|
|
117
|
+
|
|
118
|
+
action = Action(
|
|
119
|
+
name="TEST_ACTION",
|
|
120
|
+
description="A test action",
|
|
121
|
+
validate=validate,
|
|
122
|
+
handler=handler,
|
|
123
|
+
)
|
|
124
|
+
runtime.register_action(action)
|
|
125
|
+
assert len(runtime.actions) == 1
|
|
126
|
+
assert runtime.actions[0].name == "TEST_ACTION"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class TestAgentRuntimeEvaluators:
|
|
130
|
+
@pytest.mark.asyncio
|
|
131
|
+
async def test_register_evaluator(self, runtime: AgentRuntime) -> None:
|
|
132
|
+
async def validate(rt: IAgentRuntime, msg: Memory, state: State | None) -> bool:
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
async def handler(
|
|
136
|
+
rt: IAgentRuntime,
|
|
137
|
+
msg: Memory,
|
|
138
|
+
state: State | None,
|
|
139
|
+
options: HandlerOptions | None,
|
|
140
|
+
callback: Any,
|
|
141
|
+
responses: list[Memory] | None,
|
|
142
|
+
) -> ActionResult | None:
|
|
143
|
+
return ActionResult(success=True)
|
|
144
|
+
|
|
145
|
+
evaluator = Evaluator(
|
|
146
|
+
name="test-evaluator",
|
|
147
|
+
description="A test evaluator",
|
|
148
|
+
examples=[],
|
|
149
|
+
validate=validate,
|
|
150
|
+
handler=handler,
|
|
151
|
+
)
|
|
152
|
+
runtime.register_evaluator(evaluator)
|
|
153
|
+
assert len(runtime.evaluators) == 1
|
|
154
|
+
assert runtime.evaluators[0].name == "test-evaluator"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class TestAgentRuntimePlugins:
|
|
158
|
+
@pytest.mark.asyncio
|
|
159
|
+
async def test_register_plugin(self, runtime: AgentRuntime) -> None:
|
|
160
|
+
async def get_data(rt: IAgentRuntime, msg: Memory, state: State) -> ProviderResult:
|
|
161
|
+
return ProviderResult(text="Plugin provider data")
|
|
162
|
+
|
|
163
|
+
plugin = Plugin(
|
|
164
|
+
name="test-plugin",
|
|
165
|
+
description="A test plugin",
|
|
166
|
+
providers=[
|
|
167
|
+
Provider(
|
|
168
|
+
name="plugin-provider",
|
|
169
|
+
get=get_data,
|
|
170
|
+
)
|
|
171
|
+
],
|
|
172
|
+
)
|
|
173
|
+
await runtime.register_plugin(plugin)
|
|
174
|
+
assert len(runtime.plugins) == 1
|
|
175
|
+
assert runtime.plugins[0].name == "test-plugin"
|
|
176
|
+
assert len(runtime.providers) == 1
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class TestAgentRuntimeEvents:
|
|
180
|
+
@pytest.mark.asyncio
|
|
181
|
+
async def test_register_event_handler(self, runtime: AgentRuntime) -> None:
|
|
182
|
+
events_received: list[str] = []
|
|
183
|
+
|
|
184
|
+
async def handler(params: dict[str, object]) -> None:
|
|
185
|
+
events_received.append("event_received")
|
|
186
|
+
|
|
187
|
+
runtime.register_event("TEST_EVENT", handler)
|
|
188
|
+
await runtime.emit_event("TEST_EVENT", {"data": "test"})
|
|
189
|
+
|
|
190
|
+
assert len(events_received) == 1
|
|
191
|
+
assert events_received[0] == "event_received"
|
|
192
|
+
|
|
193
|
+
@pytest.mark.asyncio
|
|
194
|
+
async def test_multiple_event_handlers(self, runtime: AgentRuntime) -> None:
|
|
195
|
+
count = [0]
|
|
196
|
+
|
|
197
|
+
async def handler1(params: dict[str, object]) -> None:
|
|
198
|
+
count[0] += 1
|
|
199
|
+
|
|
200
|
+
async def handler2(params: dict[str, object]) -> None:
|
|
201
|
+
count[0] += 1
|
|
202
|
+
|
|
203
|
+
runtime.register_event("MULTI_EVENT", handler1)
|
|
204
|
+
runtime.register_event("MULTI_EVENT", handler2)
|
|
205
|
+
await runtime.emit_event("MULTI_EVENT", {})
|
|
206
|
+
|
|
207
|
+
assert count[0] == 2
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class TestAgentRuntimeModels:
|
|
211
|
+
@pytest.mark.asyncio
|
|
212
|
+
async def test_register_model(self, runtime: AgentRuntime) -> None:
|
|
213
|
+
async def model_handler(rt: IAgentRuntime, params: dict[str, Any]) -> Any:
|
|
214
|
+
return f"Generated: {params.get('prompt', '')}"
|
|
215
|
+
|
|
216
|
+
runtime.register_model(
|
|
217
|
+
model_type="TEXT_LARGE",
|
|
218
|
+
handler=model_handler,
|
|
219
|
+
provider="test-provider",
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
result = await runtime.use_model("TEXT_LARGE", {"prompt": "Hello"})
|
|
223
|
+
assert result == "Generated: Hello"
|
|
224
|
+
|
|
225
|
+
@pytest.mark.asyncio
|
|
226
|
+
async def test_model_priority(self, runtime: AgentRuntime) -> None:
|
|
227
|
+
async def low_priority_handler(rt: IAgentRuntime, params: dict[str, Any]) -> Any:
|
|
228
|
+
return "low"
|
|
229
|
+
|
|
230
|
+
async def high_priority_handler(rt: IAgentRuntime, params: dict[str, Any]) -> Any:
|
|
231
|
+
return "high"
|
|
232
|
+
|
|
233
|
+
runtime.register_model(
|
|
234
|
+
model_type="TEXT_LARGE",
|
|
235
|
+
handler=low_priority_handler,
|
|
236
|
+
provider="low",
|
|
237
|
+
priority=0,
|
|
238
|
+
)
|
|
239
|
+
runtime.register_model(
|
|
240
|
+
model_type="TEXT_LARGE",
|
|
241
|
+
handler=high_priority_handler,
|
|
242
|
+
provider="high",
|
|
243
|
+
priority=10,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
result = await runtime.use_model("TEXT_LARGE", {})
|
|
247
|
+
assert result == "high"
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class TestAgentRuntimeRunTracking:
|
|
251
|
+
def test_create_run_id(self, runtime: AgentRuntime) -> None:
|
|
252
|
+
run_id = runtime.create_run_id()
|
|
253
|
+
assert run_id is not None
|
|
254
|
+
assert len(run_id) == 36
|
|
255
|
+
|
|
256
|
+
def test_start_and_end_run(self, runtime: AgentRuntime) -> None:
|
|
257
|
+
room_id = as_uuid("12345678-1234-1234-1234-123456789012")
|
|
258
|
+
run_id = runtime.start_run(room_id)
|
|
259
|
+
assert run_id == runtime.get_current_run_id()
|
|
260
|
+
|
|
261
|
+
runtime.end_run()
|
|
262
|
+
new_run_id = runtime.get_current_run_id()
|
|
263
|
+
assert new_run_id != run_id
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class TestAgentRuntimeServices:
|
|
267
|
+
def test_has_service_empty(self, runtime: AgentRuntime) -> None:
|
|
268
|
+
assert runtime.has_service("test-service") is False
|
|
269
|
+
|
|
270
|
+
def test_get_service_empty(self, runtime: AgentRuntime) -> None:
|
|
271
|
+
assert runtime.get_service("test-service") is None
|
|
272
|
+
|
|
273
|
+
def test_get_registered_service_types_empty(self, runtime: AgentRuntime) -> None:
|
|
274
|
+
assert runtime.get_registered_service_types() == []
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class TestAgentRuntimeLogLevel:
|
|
278
|
+
def test_default_log_level_is_error(self, character: Character) -> None:
|
|
279
|
+
runtime = AgentRuntime(character=character)
|
|
280
|
+
assert runtime.logger is not None
|
|
281
|
+
|
|
282
|
+
def test_custom_log_level_info(self, character: Character) -> None:
|
|
283
|
+
runtime = AgentRuntime(character=character, log_level="INFO")
|
|
284
|
+
assert runtime.logger is not None
|
|
285
|
+
|
|
286
|
+
def test_custom_log_level_debug(self, character: Character) -> None:
|
|
287
|
+
runtime = AgentRuntime(character=character, log_level="DEBUG")
|
|
288
|
+
assert runtime.logger is not None
|
|
289
|
+
|
|
290
|
+
def test_custom_log_level_warning(self, character: Character) -> None:
|
|
291
|
+
runtime = AgentRuntime(character=character, log_level="WARNING")
|
|
292
|
+
assert runtime.logger is not None
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class TestAgentRuntimeLLMMode:
|
|
296
|
+
def test_default_llm_mode_is_default(self, character: Character) -> None:
|
|
297
|
+
runtime = AgentRuntime(character=character)
|
|
298
|
+
assert runtime.get_llm_mode() == LLMMode.DEFAULT
|
|
299
|
+
|
|
300
|
+
def test_constructor_option_small(self, character: Character) -> None:
|
|
301
|
+
runtime = AgentRuntime(character=character, llm_mode=LLMMode.SMALL)
|
|
302
|
+
assert runtime.get_llm_mode() == LLMMode.SMALL
|
|
303
|
+
|
|
304
|
+
def test_constructor_option_large(self, character: Character) -> None:
|
|
305
|
+
runtime = AgentRuntime(character=character, llm_mode=LLMMode.LARGE)
|
|
306
|
+
assert runtime.get_llm_mode() == LLMMode.LARGE
|
|
307
|
+
|
|
308
|
+
@pytest.mark.skip(reason="CharacterSettings proto doesn't have LLM_MODE field")
|
|
309
|
+
def test_character_setting_small(self) -> None:
|
|
310
|
+
character = Character(
|
|
311
|
+
name="Test",
|
|
312
|
+
bio=["Test"],
|
|
313
|
+
settings={"LLM_MODE": "SMALL"},
|
|
314
|
+
)
|
|
315
|
+
runtime = AgentRuntime(character=character)
|
|
316
|
+
assert runtime.get_llm_mode() == LLMMode.SMALL
|
|
317
|
+
|
|
318
|
+
@pytest.mark.skip(reason="CharacterSettings proto doesn't have LLM_MODE field")
|
|
319
|
+
def test_constructor_option_takes_precedence(self) -> None:
|
|
320
|
+
character = Character(
|
|
321
|
+
name="Test",
|
|
322
|
+
bio=["Test"],
|
|
323
|
+
settings={"LLM_MODE": "SMALL"},
|
|
324
|
+
)
|
|
325
|
+
runtime = AgentRuntime(character=character, llm_mode=LLMMode.LARGE)
|
|
326
|
+
assert runtime.get_llm_mode() == LLMMode.LARGE
|
|
327
|
+
|
|
328
|
+
@pytest.mark.skip(reason="CharacterSettings proto doesn't have LLM_MODE field")
|
|
329
|
+
def test_case_insensitive_character_setting(self) -> None:
|
|
330
|
+
character = Character(
|
|
331
|
+
name="Test",
|
|
332
|
+
bio=["Test"],
|
|
333
|
+
settings={"LLM_MODE": "small"},
|
|
334
|
+
)
|
|
335
|
+
runtime = AgentRuntime(character=character)
|
|
336
|
+
assert runtime.get_llm_mode() == LLMMode.SMALL
|
|
337
|
+
|
|
338
|
+
@pytest.mark.skip(reason="CharacterSettings proto doesn't have LLM_MODE field")
|
|
339
|
+
def test_invalid_setting_defaults_to_default(self) -> None:
|
|
340
|
+
character = Character(
|
|
341
|
+
name="Test",
|
|
342
|
+
bio=["Test"],
|
|
343
|
+
settings={"LLM_MODE": "invalid"},
|
|
344
|
+
)
|
|
345
|
+
runtime = AgentRuntime(character=character)
|
|
346
|
+
assert runtime.get_llm_mode() == LLMMode.DEFAULT
|
|
347
|
+
|
|
348
|
+
@pytest.mark.asyncio
|
|
349
|
+
async def test_use_model_override_small(self, character: Character) -> None:
|
|
350
|
+
runtime = AgentRuntime(character=character, llm_mode=LLMMode.SMALL)
|
|
351
|
+
|
|
352
|
+
async def small_handler(rt: IAgentRuntime, params: dict[str, Any]) -> Any:
|
|
353
|
+
return "small response"
|
|
354
|
+
|
|
355
|
+
async def large_handler(rt: IAgentRuntime, params: dict[str, Any]) -> Any:
|
|
356
|
+
return "large response"
|
|
357
|
+
|
|
358
|
+
runtime.register_model(ModelType.TEXT_SMALL, small_handler, "test")
|
|
359
|
+
runtime.register_model(ModelType.TEXT_LARGE, large_handler, "test")
|
|
360
|
+
|
|
361
|
+
result = await runtime.use_model(ModelType.TEXT_LARGE, {"prompt": "test"})
|
|
362
|
+
assert result == "small response"
|
|
363
|
+
|
|
364
|
+
@pytest.mark.asyncio
|
|
365
|
+
async def test_use_model_override_large(self, character: Character) -> None:
|
|
366
|
+
runtime = AgentRuntime(character=character, llm_mode=LLMMode.LARGE)
|
|
367
|
+
|
|
368
|
+
async def small_handler(rt: IAgentRuntime, params: dict[str, Any]) -> Any:
|
|
369
|
+
return "small response"
|
|
370
|
+
|
|
371
|
+
async def large_handler(rt: IAgentRuntime, params: dict[str, Any]) -> Any:
|
|
372
|
+
return "large response"
|
|
373
|
+
|
|
374
|
+
runtime.register_model(ModelType.TEXT_SMALL, small_handler, "test")
|
|
375
|
+
runtime.register_model(ModelType.TEXT_LARGE, large_handler, "test")
|
|
376
|
+
|
|
377
|
+
result = await runtime.use_model(ModelType.TEXT_SMALL, {"prompt": "test"})
|
|
378
|
+
assert result == "large response"
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class TestAgentRuntimeCheckShouldRespond:
|
|
382
|
+
def test_default_is_true(self, character: Character) -> None:
|
|
383
|
+
runtime = AgentRuntime(character=character)
|
|
384
|
+
assert runtime.is_check_should_respond_enabled() is True
|
|
385
|
+
|
|
386
|
+
def test_constructor_option_false(self, character: Character) -> None:
|
|
387
|
+
runtime = AgentRuntime(character=character, check_should_respond=False)
|
|
388
|
+
assert runtime.is_check_should_respond_enabled() is False
|
|
389
|
+
|
|
390
|
+
def test_constructor_option_true(self, character: Character) -> None:
|
|
391
|
+
runtime = AgentRuntime(character=character, check_should_respond=True)
|
|
392
|
+
assert runtime.is_check_should_respond_enabled() is True
|
|
393
|
+
|
|
394
|
+
@pytest.mark.skip(reason="CharacterSettings proto doesn't have CHECK_SHOULD_RESPOND field")
|
|
395
|
+
def test_character_setting_false(self) -> None:
|
|
396
|
+
character = Character(
|
|
397
|
+
name="Test",
|
|
398
|
+
bio=["Test"],
|
|
399
|
+
settings={"CHECK_SHOULD_RESPOND": "false"},
|
|
400
|
+
)
|
|
401
|
+
runtime = AgentRuntime(character=character)
|
|
402
|
+
assert runtime.is_check_should_respond_enabled() is False
|
|
403
|
+
|
|
404
|
+
@pytest.mark.skip(reason="CharacterSettings proto doesn't have CHECK_SHOULD_RESPOND field")
|
|
405
|
+
def test_constructor_option_takes_precedence(self) -> None:
|
|
406
|
+
character = Character(
|
|
407
|
+
name="Test",
|
|
408
|
+
bio=["Test"],
|
|
409
|
+
settings={"CHECK_SHOULD_RESPOND": "false"},
|
|
410
|
+
)
|
|
411
|
+
runtime = AgentRuntime(character=character, check_should_respond=True)
|
|
412
|
+
assert runtime.is_check_should_respond_enabled() is True
|
|
413
|
+
|
|
414
|
+
@pytest.mark.skip(reason="CharacterSettings proto doesn't have CHECK_SHOULD_RESPOND field")
|
|
415
|
+
def test_non_false_string_defaults_to_true(self) -> None:
|
|
416
|
+
character = Character(
|
|
417
|
+
name="Test",
|
|
418
|
+
bio=["Test"],
|
|
419
|
+
settings={"CHECK_SHOULD_RESPOND": "yes"},
|
|
420
|
+
)
|
|
421
|
+
runtime = AgentRuntime(character=character)
|
|
422
|
+
assert runtime.is_check_should_respond_enabled() is True
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from elizaos.settings import get_salt
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_get_salt_throws_in_production_when_default(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
9
|
+
monkeypatch.setenv("NODE_ENV", "production")
|
|
10
|
+
monkeypatch.delenv("SECRET_SALT", raising=False)
|
|
11
|
+
monkeypatch.delenv("ELIZA_ALLOW_DEFAULT_SECRET_SALT", raising=False)
|
|
12
|
+
|
|
13
|
+
with pytest.raises(RuntimeError, match="SECRET_SALT must be set"):
|
|
14
|
+
get_salt()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_get_salt_allows_override_in_production(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
18
|
+
monkeypatch.setenv("NODE_ENV", "production")
|
|
19
|
+
monkeypatch.delenv("SECRET_SALT", raising=False)
|
|
20
|
+
monkeypatch.setenv("ELIZA_ALLOW_DEFAULT_SECRET_SALT", "true")
|
|
21
|
+
|
|
22
|
+
assert get_salt() == "secretsalt"
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import secrets
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
5
|
+
from cryptography.hazmat.primitives.padding import PKCS7
|
|
6
|
+
|
|
7
|
+
from elizaos import AgentRuntime, Character
|
|
8
|
+
from elizaos.settings import (
|
|
9
|
+
_derive_key,
|
|
10
|
+
decrypt_string_value,
|
|
11
|
+
encrypt_string_value,
|
|
12
|
+
get_salt,
|
|
13
|
+
migrate_encrypted_string_value,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestSettingsCrypto:
|
|
18
|
+
def test_get_salt_default(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
19
|
+
monkeypatch.delenv("SECRET_SALT", raising=False)
|
|
20
|
+
assert get_salt() == "secretsalt"
|
|
21
|
+
|
|
22
|
+
def test_encrypt_decrypt_roundtrip(self) -> None:
|
|
23
|
+
salt = "test-salt-value"
|
|
24
|
+
plaintext = "sensitive-data"
|
|
25
|
+
|
|
26
|
+
encrypted = encrypt_string_value(plaintext, salt)
|
|
27
|
+
assert isinstance(encrypted, str)
|
|
28
|
+
assert encrypted != plaintext
|
|
29
|
+
assert ":" in encrypted
|
|
30
|
+
|
|
31
|
+
decrypted = decrypt_string_value(encrypted, salt)
|
|
32
|
+
assert decrypted == plaintext
|
|
33
|
+
|
|
34
|
+
def test_encrypt_is_idempotent_for_encrypted_values(self) -> None:
|
|
35
|
+
salt = "test-salt-value"
|
|
36
|
+
plaintext = "hello"
|
|
37
|
+
encrypted = encrypt_string_value(plaintext, salt)
|
|
38
|
+
assert isinstance(encrypted, str)
|
|
39
|
+
encrypted2 = encrypt_string_value(encrypted, salt)
|
|
40
|
+
assert encrypted2 == encrypted
|
|
41
|
+
|
|
42
|
+
def test_decrypt_non_encrypted_returns_original(self) -> None:
|
|
43
|
+
salt = "test-salt-value"
|
|
44
|
+
assert decrypt_string_value("not-encrypted", salt) == "not-encrypted"
|
|
45
|
+
|
|
46
|
+
def test_decrypt_legacy_v1_aes_cbc_value(self) -> None:
|
|
47
|
+
salt = "test-salt-value"
|
|
48
|
+
plaintext = "legacy-secret"
|
|
49
|
+
|
|
50
|
+
key = _derive_key(salt)
|
|
51
|
+
iv = secrets.token_bytes(16)
|
|
52
|
+
|
|
53
|
+
padder = PKCS7(128).padder()
|
|
54
|
+
padded = padder.update(plaintext.encode("utf-8")) + padder.finalize()
|
|
55
|
+
|
|
56
|
+
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
|
|
57
|
+
encryptor = cipher.encryptor()
|
|
58
|
+
ciphertext = encryptor.update(padded) + encryptor.finalize()
|
|
59
|
+
|
|
60
|
+
legacy = f"{iv.hex()}:{ciphertext.hex()}"
|
|
61
|
+
assert decrypt_string_value(legacy, salt) == plaintext
|
|
62
|
+
|
|
63
|
+
def test_migrate_legacy_v1_to_v2(self) -> None:
|
|
64
|
+
salt = "test-salt-value"
|
|
65
|
+
plaintext = "legacy-migrate"
|
|
66
|
+
|
|
67
|
+
key = _derive_key(salt)
|
|
68
|
+
iv = secrets.token_bytes(16)
|
|
69
|
+
padder = PKCS7(128).padder()
|
|
70
|
+
padded = padder.update(plaintext.encode("utf-8")) + padder.finalize()
|
|
71
|
+
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
|
|
72
|
+
encryptor = cipher.encryptor()
|
|
73
|
+
ciphertext = encryptor.update(padded) + encryptor.finalize()
|
|
74
|
+
legacy = f"{iv.hex()}:{ciphertext.hex()}"
|
|
75
|
+
|
|
76
|
+
migrated = migrate_encrypted_string_value(legacy, salt)
|
|
77
|
+
assert isinstance(migrated, str)
|
|
78
|
+
assert migrated.startswith("v2:")
|
|
79
|
+
assert decrypt_string_value(migrated, salt) == plaintext
|
|
80
|
+
|
|
81
|
+
@pytest.mark.skip(reason="Runtime get_setting from secrets not yet implemented")
|
|
82
|
+
def test_runtime_get_setting_decrypts_secret_strings(
|
|
83
|
+
self, monkeypatch: pytest.MonkeyPatch
|
|
84
|
+
) -> None:
|
|
85
|
+
salt = "test-salt-value"
|
|
86
|
+
monkeypatch.setenv("SECRET_SALT", salt)
|
|
87
|
+
|
|
88
|
+
encrypted_api_key = encrypt_string_value("super-secret", salt)
|
|
89
|
+
assert isinstance(encrypted_api_key, str)
|
|
90
|
+
|
|
91
|
+
character = Character(
|
|
92
|
+
name="TestAgent",
|
|
93
|
+
bio=["Test"],
|
|
94
|
+
secrets={"API_KEY": encrypted_api_key},
|
|
95
|
+
)
|
|
96
|
+
runtime = AgentRuntime(character=character)
|
|
97
|
+
assert runtime.get_setting("API_KEY") == "super-secret"
|
|
98
|
+
|
|
99
|
+
@pytest.mark.skip(reason="Runtime get_setting from secrets not yet implemented")
|
|
100
|
+
def test_runtime_get_setting_coerces_true_false_strings(
|
|
101
|
+
self, monkeypatch: pytest.MonkeyPatch
|
|
102
|
+
) -> None:
|
|
103
|
+
salt = "test-salt-value"
|
|
104
|
+
monkeypatch.setenv("SECRET_SALT", salt)
|
|
105
|
+
|
|
106
|
+
encrypted_true = encrypt_string_value("true", salt)
|
|
107
|
+
encrypted_false = encrypt_string_value("false", salt)
|
|
108
|
+
assert isinstance(encrypted_true, str)
|
|
109
|
+
assert isinstance(encrypted_false, str)
|
|
110
|
+
|
|
111
|
+
character = Character(
|
|
112
|
+
name="TestAgent",
|
|
113
|
+
bio=["Test"],
|
|
114
|
+
secrets={"FLAG_TRUE": encrypted_true, "FLAG_FALSE": encrypted_false},
|
|
115
|
+
)
|
|
116
|
+
runtime = AgentRuntime(character=character)
|
|
117
|
+
assert runtime.get_setting("FLAG_TRUE") is True
|
|
118
|
+
assert runtime.get_setting("FLAG_FALSE") is False
|