@elizaos/python 2.0.0-alpha.10 → 2.0.0-alpha.11
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/elizaos/__init__.py +0 -1
- package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +70 -15
- package/elizaos/advanced_capabilities/actions/send_message.py +9 -184
- package/elizaos/advanced_memory/memory_service.py +15 -17
- package/elizaos/advanced_planning/planning_service.py +26 -14
- package/elizaos/basic_capabilities/providers/actions.py +118 -29
- package/elizaos/basic_capabilities/providers/character.py +19 -21
- package/elizaos/basic_capabilities/providers/current_time.py +7 -4
- package/elizaos/basic_capabilities/providers/time.py +7 -4
- package/elizaos/bootstrap/__init__.py +21 -2
- package/elizaos/bootstrap/actions/schedule_follow_up.py +65 -7
- package/elizaos/bootstrap/actions/send_message.py +162 -15
- package/elizaos/bootstrap/autonomy/service.py +230 -28
- package/elizaos/bootstrap/providers/actions.py +118 -27
- package/elizaos/bootstrap/providers/agent_settings.py +1 -0
- package/elizaos/bootstrap/providers/attachments.py +1 -0
- package/elizaos/bootstrap/providers/capabilities.py +1 -0
- package/elizaos/bootstrap/providers/character.py +1 -0
- package/elizaos/bootstrap/providers/choice.py +1 -0
- package/elizaos/bootstrap/providers/contacts.py +1 -0
- package/elizaos/bootstrap/providers/current_time.py +8 -2
- package/elizaos/bootstrap/providers/entities.py +1 -0
- package/elizaos/bootstrap/providers/evaluators.py +1 -0
- package/elizaos/bootstrap/providers/facts.py +1 -0
- package/elizaos/bootstrap/providers/follow_ups.py +1 -0
- package/elizaos/bootstrap/providers/knowledge.py +1 -0
- package/elizaos/bootstrap/providers/providers_list.py +1 -0
- package/elizaos/bootstrap/providers/relationships.py +1 -0
- package/elizaos/bootstrap/providers/roles.py +1 -0
- package/elizaos/bootstrap/providers/settings.py +1 -0
- package/elizaos/bootstrap/providers/time.py +8 -4
- package/elizaos/bootstrap/providers/world.py +1 -0
- package/elizaos/deterministic.py +193 -0
- package/elizaos/generated/__init__.py +1 -0
- package/elizaos/generated/action_docs.py +3181 -0
- package/elizaos/generated/spec_helpers.py +175 -0
- package/elizaos/media/mime.py +2 -2
- package/elizaos/media/search.py +23 -23
- package/elizaos/runtime.py +152 -39
- package/elizaos/services/message_service.py +2 -6
- package/elizaos/types/components.py +2 -2
- package/elizaos/types/generated/__init__.py +12 -0
- package/elizaos/types/generated/eliza/v1/agent_pb2.py +63 -0
- package/elizaos/types/generated/eliza/v1/agent_pb2.pyi +161 -0
- package/elizaos/types/generated/eliza/v1/components_pb2.py +65 -0
- package/elizaos/types/generated/eliza/v1/components_pb2.pyi +160 -0
- package/elizaos/types/generated/eliza/v1/database_pb2.py +78 -0
- package/elizaos/types/generated/eliza/v1/database_pb2.pyi +305 -0
- package/elizaos/types/generated/eliza/v1/environment_pb2.py +58 -0
- package/elizaos/types/generated/eliza/v1/environment_pb2.pyi +135 -0
- package/elizaos/types/generated/eliza/v1/events_pb2.py +82 -0
- package/elizaos/types/generated/eliza/v1/events_pb2.pyi +322 -0
- package/elizaos/types/generated/eliza/v1/ipc_pb2.py +113 -0
- package/elizaos/types/generated/eliza/v1/ipc_pb2.pyi +367 -0
- package/elizaos/types/generated/eliza/v1/knowledge_pb2.py +41 -0
- package/elizaos/types/generated/eliza/v1/knowledge_pb2.pyi +26 -0
- package/elizaos/types/generated/eliza/v1/memory_pb2.py +55 -0
- package/elizaos/types/generated/eliza/v1/memory_pb2.pyi +111 -0
- package/elizaos/types/generated/eliza/v1/message_service_pb2.py +48 -0
- package/elizaos/types/generated/eliza/v1/message_service_pb2.pyi +69 -0
- package/elizaos/types/generated/eliza/v1/messaging_pb2.py +51 -0
- package/elizaos/types/generated/eliza/v1/messaging_pb2.pyi +97 -0
- package/elizaos/types/generated/eliza/v1/model_pb2.py +84 -0
- package/elizaos/types/generated/eliza/v1/model_pb2.pyi +280 -0
- package/elizaos/types/generated/eliza/v1/payment_pb2.py +44 -0
- package/elizaos/types/generated/eliza/v1/payment_pb2.pyi +70 -0
- package/elizaos/types/generated/eliza/v1/plugin_pb2.py +68 -0
- package/elizaos/types/generated/eliza/v1/plugin_pb2.pyi +145 -0
- package/elizaos/types/generated/eliza/v1/primitives_pb2.py +48 -0
- package/elizaos/types/generated/eliza/v1/primitives_pb2.pyi +92 -0
- package/elizaos/types/generated/eliza/v1/prompts_pb2.py +52 -0
- package/elizaos/types/generated/eliza/v1/prompts_pb2.pyi +74 -0
- package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.py +211 -0
- package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.pyi +1296 -0
- package/elizaos/types/generated/eliza/v1/service_pb2.py +42 -0
- package/elizaos/types/generated/eliza/v1/service_pb2.pyi +69 -0
- package/elizaos/types/generated/eliza/v1/settings_pb2.py +58 -0
- package/elizaos/types/generated/eliza/v1/settings_pb2.pyi +85 -0
- package/elizaos/types/generated/eliza/v1/state_pb2.py +60 -0
- package/elizaos/types/generated/eliza/v1/state_pb2.pyi +114 -0
- package/elizaos/types/generated/eliza/v1/task_pb2.py +42 -0
- package/elizaos/types/generated/eliza/v1/task_pb2.pyi +58 -0
- package/elizaos/types/generated/eliza/v1/tee_pb2.py +52 -0
- package/elizaos/types/generated/eliza/v1/tee_pb2.pyi +90 -0
- package/elizaos/types/generated/eliza/v1/testing_pb2.py +39 -0
- package/elizaos/types/generated/eliza/v1/testing_pb2.pyi +23 -0
- package/elizaos/types/model.py +3 -0
- package/elizaos/types/runtime.py +1 -1
- package/package.json +3 -2
- package/tests/test_action_parameters.py +2 -3
- package/tests/test_advanced_memory_behavior.py +0 -2
- package/tests/test_advanced_memory_flag.py +0 -2
- package/tests/test_advanced_planning_behavior.py +11 -5
- package/tests/test_autonomy.py +11 -1
- package/tests/test_runtime.py +8 -17
- package/tests/test_schedule_follow_up_action.py +260 -0
- package/tests/test_send_message_action_targets.py +114 -0
- package/tests/test_settings_crypto.py +0 -2
- package/uv.lock +1565 -0
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import contextlib
|
|
4
|
+
from typing import TYPE_CHECKING, TypeVar, cast
|
|
4
5
|
|
|
5
6
|
from google.protobuf.json_format import MessageToDict
|
|
6
7
|
|
|
7
8
|
from elizaos.action_docs import get_canonical_action_example_calls
|
|
9
|
+
from elizaos.deterministic import (
|
|
10
|
+
build_conversation_seed,
|
|
11
|
+
build_deterministic_seed,
|
|
12
|
+
deterministic_int,
|
|
13
|
+
)
|
|
8
14
|
from elizaos.generated.spec_helpers import require_provider_spec
|
|
9
15
|
from elizaos.types import Provider, ProviderResult
|
|
10
16
|
from elizaos.types.components import ActionExample
|
|
@@ -23,10 +29,6 @@ if TYPE_CHECKING:
|
|
|
23
29
|
_spec = require_provider_spec("ACTIONS")
|
|
24
30
|
|
|
25
31
|
|
|
26
|
-
def format_action_names(actions: list[Action]) -> str:
|
|
27
|
-
return ", ".join(action.name for action in actions)
|
|
28
|
-
|
|
29
|
-
|
|
30
32
|
def _format_parameter_type(schema: ActionParameterSchema) -> str:
|
|
31
33
|
if schema.type == "number" and (schema.minimum is not None or schema.maximum is not None):
|
|
32
34
|
min_val = schema.minimum if schema.minimum is not None else "∞"
|
|
@@ -35,9 +37,10 @@ def _format_parameter_type(schema: ActionParameterSchema) -> str:
|
|
|
35
37
|
return schema.type
|
|
36
38
|
|
|
37
39
|
|
|
38
|
-
def _get_param_schema(param: ActionParameter) ->
|
|
40
|
+
def _get_param_schema(param: ActionParameter) -> ActionParameterSchema | None:
|
|
39
41
|
"""Get schema from ActionParameter, handling both Pydantic and protobuf variants."""
|
|
40
|
-
|
|
42
|
+
schema = getattr(param, "schema_def", None) or getattr(param, "schema", None)
|
|
43
|
+
return cast("ActionParameterSchema | None", schema)
|
|
41
44
|
|
|
42
45
|
|
|
43
46
|
def _format_action_parameters(parameters: list[ActionParameter]) -> str:
|
|
@@ -64,9 +67,23 @@ def _format_action_parameters(parameters: list[ActionParameter]) -> str:
|
|
|
64
67
|
return "\n".join(lines)
|
|
65
68
|
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
T = TypeVar("T")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _deterministic_shuffle(items: list[T], seed: str, surface: str = "shuffle") -> list[T]:
|
|
74
|
+
shuffled = list(items)
|
|
75
|
+
for i in range(len(shuffled) - 1, 0, -1):
|
|
76
|
+
j = deterministic_int(seed, f"{surface}:{i}", i + 1)
|
|
77
|
+
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
|
|
78
|
+
return shuffled
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def format_actions(actions: list[Action], seed: str | None = None) -> str:
|
|
82
|
+
deterministic_seed = seed or build_deterministic_seed(
|
|
83
|
+
["actions-format", ",".join(action.name for action in actions)]
|
|
84
|
+
)
|
|
68
85
|
lines: list[str] = []
|
|
69
|
-
for action in actions:
|
|
86
|
+
for action in _deterministic_shuffle(actions, deterministic_seed, "actions"):
|
|
70
87
|
line = f"- **{action.name}**: {action.description or 'No description'}"
|
|
71
88
|
if action.parameters:
|
|
72
89
|
params_text = _format_action_parameters(action.parameters)
|
|
@@ -83,7 +100,24 @@ def _replace_name_placeholders(text: str) -> str:
|
|
|
83
100
|
return text
|
|
84
101
|
|
|
85
102
|
|
|
86
|
-
def
|
|
103
|
+
def _replace_name_placeholders_seeded(text: str, seed: str, example_index: int) -> str:
|
|
104
|
+
names = ["Alex", "Jordan", "Sam", "Taylor", "Riley"]
|
|
105
|
+
output = text
|
|
106
|
+
for placeholder_index in range(1, 6):
|
|
107
|
+
name_index = deterministic_int(
|
|
108
|
+
seed,
|
|
109
|
+
f"example:{example_index}:name:{placeholder_index}",
|
|
110
|
+
len(names),
|
|
111
|
+
)
|
|
112
|
+
output = output.replace(f"{{{{name{placeholder_index}}}}}", names[name_index])
|
|
113
|
+
return output
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def format_action_examples(
|
|
117
|
+
actions: list[Action],
|
|
118
|
+
max_examples: int = 10,
|
|
119
|
+
seed: str | None = None,
|
|
120
|
+
) -> str:
|
|
87
121
|
"""
|
|
88
122
|
Format a deterministic subset of action examples for prompt context.
|
|
89
123
|
|
|
@@ -92,27 +126,60 @@ def format_action_examples(actions: list[Action], max_examples: int = 10) -> str
|
|
|
92
126
|
if max_examples <= 0:
|
|
93
127
|
return ""
|
|
94
128
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
examples.append(ex)
|
|
102
|
-
if len(examples) >= max_examples:
|
|
103
|
-
break
|
|
104
|
-
if len(examples) >= max_examples:
|
|
105
|
-
break
|
|
106
|
-
|
|
107
|
-
if not examples:
|
|
129
|
+
actions_with_examples = [
|
|
130
|
+
action
|
|
131
|
+
for action in actions
|
|
132
|
+
if action.examples and isinstance(action.examples, list) and len(action.examples) > 0
|
|
133
|
+
]
|
|
134
|
+
if not actions_with_examples:
|
|
108
135
|
return ""
|
|
109
136
|
|
|
137
|
+
examples_copy: list[list[list[ActionExample]]] = [
|
|
138
|
+
[example for example in (action.examples or []) if isinstance(example, list) and example]
|
|
139
|
+
for action in actions_with_examples
|
|
140
|
+
]
|
|
141
|
+
available_action_indices = [
|
|
142
|
+
idx for idx, action_examples in enumerate(examples_copy) if action_examples
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
selection_seed = seed or build_deterministic_seed(
|
|
146
|
+
[
|
|
147
|
+
"action-examples",
|
|
148
|
+
",".join(action.name for action in actions_with_examples),
|
|
149
|
+
max_examples,
|
|
150
|
+
]
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
selected_examples: list[list[ActionExample]] = []
|
|
154
|
+
iteration = 0
|
|
155
|
+
while len(selected_examples) < max_examples and available_action_indices:
|
|
156
|
+
random_index = deterministic_int(
|
|
157
|
+
selection_seed,
|
|
158
|
+
f"action-index:{iteration}",
|
|
159
|
+
len(available_action_indices),
|
|
160
|
+
)
|
|
161
|
+
action_index = available_action_indices[random_index]
|
|
162
|
+
action_examples = examples_copy[action_index]
|
|
163
|
+
|
|
164
|
+
example_index = deterministic_int(
|
|
165
|
+
selection_seed,
|
|
166
|
+
f"example-index:{iteration}",
|
|
167
|
+
len(action_examples),
|
|
168
|
+
)
|
|
169
|
+
selected_examples.append(action_examples.pop(example_index))
|
|
170
|
+
iteration += 1
|
|
171
|
+
|
|
172
|
+
if not action_examples:
|
|
173
|
+
available_action_indices.pop(random_index)
|
|
174
|
+
|
|
110
175
|
blocks: list[str] = []
|
|
111
|
-
for ex in
|
|
176
|
+
for example_index, ex in enumerate(selected_examples):
|
|
112
177
|
lines: list[str] = []
|
|
113
178
|
for msg in ex:
|
|
114
179
|
msg_text = msg.content.text if msg.content and msg.content.text else ""
|
|
115
|
-
lines.append(
|
|
180
|
+
lines.append(
|
|
181
|
+
f"{msg.name}: {_replace_name_placeholders_seeded(msg_text, selection_seed, example_index)}"
|
|
182
|
+
)
|
|
116
183
|
blocks.append("\n".join(lines))
|
|
117
184
|
|
|
118
185
|
return "\n\n".join(blocks)
|
|
@@ -182,6 +249,17 @@ def format_action_call_examples(actions: list[Action], max_examples: int = 5) ->
|
|
|
182
249
|
return "\n\n".join(blocks)
|
|
183
250
|
|
|
184
251
|
|
|
252
|
+
def format_action_names(actions: list[Action], seed: str | None = None) -> str:
|
|
253
|
+
if not actions:
|
|
254
|
+
return ""
|
|
255
|
+
|
|
256
|
+
deterministic_seed = seed or build_deterministic_seed(
|
|
257
|
+
["action-names", ",".join(action.name for action in actions)]
|
|
258
|
+
)
|
|
259
|
+
shuffled = _deterministic_shuffle(actions, deterministic_seed, "actions")
|
|
260
|
+
return ", ".join(action.name for action in shuffled)
|
|
261
|
+
|
|
262
|
+
|
|
185
263
|
async def get_actions(
|
|
186
264
|
runtime: IAgentRuntime,
|
|
187
265
|
message: Memory,
|
|
@@ -193,16 +271,27 @@ async def get_actions(
|
|
|
193
271
|
# Support both validate and validate_fn for backwards compatibility
|
|
194
272
|
validate_fn = getattr(action, "validate", None) or getattr(action, "validate_fn", None)
|
|
195
273
|
if validate_fn:
|
|
196
|
-
|
|
274
|
+
try:
|
|
275
|
+
is_valid = await validate_fn(runtime, message, state)
|
|
276
|
+
except Exception:
|
|
277
|
+
if hasattr(runtime, "logger"):
|
|
278
|
+
with contextlib.suppress(Exception):
|
|
279
|
+
runtime.logger.warning(
|
|
280
|
+
f"Action validation failed for {action.name}; excluding from prompt"
|
|
281
|
+
)
|
|
282
|
+
is_valid = False
|
|
197
283
|
if is_valid:
|
|
198
284
|
validated_actions.append(action)
|
|
199
285
|
else:
|
|
200
286
|
# If no validation function, include the action
|
|
201
287
|
validated_actions.append(action)
|
|
202
288
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
289
|
+
action_seed = build_conversation_seed(runtime, message, state, "provider:actions")
|
|
290
|
+
action_names = format_action_names(validated_actions, seed=f"{action_seed}:names")
|
|
291
|
+
actions_text = format_actions(validated_actions, seed=f"{action_seed}:descriptions")
|
|
292
|
+
examples_text = format_action_examples(
|
|
293
|
+
validated_actions, max_examples=10, seed=f"{action_seed}:examples"
|
|
294
|
+
)
|
|
206
295
|
call_examples_text = format_action_call_examples(validated_actions, max_examples=5)
|
|
207
296
|
|
|
208
297
|
text_parts: list[str] = [f"Possible response actions: {action_names}"]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Iterable
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
6
|
from elizaos.generated.spec_helpers import require_provider_spec
|
|
@@ -27,6 +28,19 @@ def _resolve_name_list(items: list[str], name: str) -> list[str]:
|
|
|
27
28
|
return [_resolve_name(s, name) for s in items]
|
|
28
29
|
|
|
29
30
|
|
|
31
|
+
def _coerce_text_list(value: object) -> list[str]:
|
|
32
|
+
"""Normalize protobuf repeated fields and plain Python values to list[str]."""
|
|
33
|
+
if value is None:
|
|
34
|
+
return []
|
|
35
|
+
if isinstance(value, str):
|
|
36
|
+
return [value]
|
|
37
|
+
if isinstance(value, (list, tuple)):
|
|
38
|
+
return [str(item) for item in value]
|
|
39
|
+
if isinstance(value, Iterable):
|
|
40
|
+
return [str(item) for item in value]
|
|
41
|
+
return [str(value)]
|
|
42
|
+
|
|
43
|
+
|
|
30
44
|
async def get_character_context(
|
|
31
45
|
runtime: IAgentRuntime,
|
|
32
46
|
message: Memory,
|
|
@@ -47,11 +61,7 @@ async def get_character_context(
|
|
|
47
61
|
sections.append(f"\n## Bio\n{bio_text}")
|
|
48
62
|
|
|
49
63
|
if character.adjectives:
|
|
50
|
-
adjectives = (
|
|
51
|
-
character.adjectives
|
|
52
|
-
if isinstance(character.adjectives, list)
|
|
53
|
-
else [character.adjectives]
|
|
54
|
-
)
|
|
64
|
+
adjectives = _coerce_text_list(character.adjectives)
|
|
55
65
|
resolved_adjectives = _resolve_name_list(adjectives, agent_name)
|
|
56
66
|
sections.append(f"\n## Personality Traits\n{', '.join(resolved_adjectives)}")
|
|
57
67
|
|
|
@@ -65,34 +75,22 @@ async def get_character_context(
|
|
|
65
75
|
sections.append(f"\n## Background\n{lore_text}")
|
|
66
76
|
|
|
67
77
|
if character.topics:
|
|
68
|
-
topics =
|
|
78
|
+
topics = _coerce_text_list(character.topics)
|
|
69
79
|
resolved_topics = _resolve_name_list(topics, agent_name)
|
|
70
80
|
sections.append(f"\n## Knowledge Areas\n{', '.join(resolved_topics)}")
|
|
71
81
|
|
|
72
82
|
if character.style:
|
|
73
83
|
style_sections: list[str] = []
|
|
74
84
|
if character.style.all:
|
|
75
|
-
all_style = (
|
|
76
|
-
character.style.all
|
|
77
|
-
if isinstance(character.style.all, list)
|
|
78
|
-
else [character.style.all]
|
|
79
|
-
)
|
|
85
|
+
all_style = _coerce_text_list(character.style.all)
|
|
80
86
|
resolved_all = _resolve_name_list(all_style, agent_name)
|
|
81
87
|
style_sections.append(f"General: {', '.join(resolved_all)}")
|
|
82
88
|
if character.style.chat:
|
|
83
|
-
chat_style = (
|
|
84
|
-
character.style.chat
|
|
85
|
-
if isinstance(character.style.chat, list)
|
|
86
|
-
else [character.style.chat]
|
|
87
|
-
)
|
|
89
|
+
chat_style = _coerce_text_list(character.style.chat)
|
|
88
90
|
resolved_chat = _resolve_name_list(chat_style, agent_name)
|
|
89
91
|
style_sections.append(f"Chat: {', '.join(resolved_chat)}")
|
|
90
92
|
if character.style.post:
|
|
91
|
-
post_style = (
|
|
92
|
-
character.style.post
|
|
93
|
-
if isinstance(character.style.post, list)
|
|
94
|
-
else [character.style.post]
|
|
95
|
-
)
|
|
93
|
+
post_style = _coerce_text_list(character.style.post)
|
|
96
94
|
resolved_post = _resolve_name_list(post_style, agent_name)
|
|
97
95
|
style_sections.append(f"Posts: {', '.join(resolved_post)}")
|
|
98
96
|
if style_sections:
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from datetime import UTC, datetime
|
|
4
3
|
from typing import TYPE_CHECKING
|
|
5
4
|
|
|
5
|
+
from elizaos.deterministic import get_prompt_reference_datetime
|
|
6
6
|
from elizaos.generated.spec_helpers import require_provider_spec
|
|
7
7
|
from elizaos.types import Provider, ProviderResult
|
|
8
8
|
|
|
@@ -18,9 +18,12 @@ async def get_current_time_context(
|
|
|
18
18
|
message: Memory,
|
|
19
19
|
state: State | None = None,
|
|
20
20
|
) -> ProviderResult:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
now = get_prompt_reference_datetime(
|
|
22
|
+
runtime,
|
|
23
|
+
message,
|
|
24
|
+
state,
|
|
25
|
+
"provider:current_time",
|
|
26
|
+
)
|
|
24
27
|
|
|
25
28
|
iso_timestamp = now.isoformat()
|
|
26
29
|
human_readable = now.strftime("%A, %B %d, %Y at %H:%M:%S UTC")
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from datetime import UTC, datetime
|
|
4
3
|
from typing import TYPE_CHECKING
|
|
5
4
|
|
|
5
|
+
from elizaos.deterministic import get_prompt_reference_datetime
|
|
6
6
|
from elizaos.generated.spec_helpers import require_provider_spec
|
|
7
7
|
from elizaos.types import Provider, ProviderResult
|
|
8
8
|
|
|
@@ -18,9 +18,12 @@ async def get_time_context(
|
|
|
18
18
|
message: Memory,
|
|
19
19
|
state: State | None = None,
|
|
20
20
|
) -> ProviderResult:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
now = get_prompt_reference_datetime(
|
|
22
|
+
runtime,
|
|
23
|
+
message,
|
|
24
|
+
state,
|
|
25
|
+
"provider:time",
|
|
26
|
+
)
|
|
24
27
|
iso_string = now.isoformat()
|
|
25
28
|
timestamp_ms = int(now.timestamp() * 1000)
|
|
26
29
|
human_readable = now.strftime("%A, %B %d, %Y at %H:%M:%S UTC")
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
"""elizaOS Bootstrap Plugin - Python implementation."""
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .plugin import bootstrap_plugin, create_bootstrap_plugin
|
|
9
|
+
from .types import CapabilityConfig
|
|
5
10
|
|
|
6
11
|
__version__ = "2.0.0-alpha.0"
|
|
7
12
|
__all__ = [
|
|
@@ -10,3 +15,17 @@ __all__ = [
|
|
|
10
15
|
"CapabilityConfig",
|
|
11
16
|
"__version__",
|
|
12
17
|
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def __getattr__(name: str) -> object:
|
|
21
|
+
if name in {"bootstrap_plugin", "create_bootstrap_plugin"}:
|
|
22
|
+
from .plugin import bootstrap_plugin, create_bootstrap_plugin
|
|
23
|
+
|
|
24
|
+
if name == "bootstrap_plugin":
|
|
25
|
+
return bootstrap_plugin
|
|
26
|
+
return create_bootstrap_plugin
|
|
27
|
+
if name == "CapabilityConfig":
|
|
28
|
+
from .types import CapabilityConfig
|
|
29
|
+
|
|
30
|
+
return CapabilityConfig
|
|
31
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
@@ -3,8 +3,10 @@ from __future__ import annotations
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
|
+
from uuid import UUID as StdUUID
|
|
6
7
|
|
|
7
8
|
from elizaos.bootstrap.utils.xml import parse_key_value_xml
|
|
9
|
+
from elizaos.deterministic import get_prompt_reference_datetime
|
|
8
10
|
from elizaos.generated.spec_helpers import require_action_spec
|
|
9
11
|
from elizaos.prompts import SCHEDULE_FOLLOW_UP_TEMPLATE
|
|
10
12
|
from elizaos.types import (
|
|
@@ -48,6 +50,25 @@ def _convert_spec_examples() -> list[list[ActionExample]]:
|
|
|
48
50
|
return []
|
|
49
51
|
|
|
50
52
|
|
|
53
|
+
def _normalize_priority(raw_priority: str) -> str:
|
|
54
|
+
normalized = raw_priority.strip().lower()
|
|
55
|
+
if normalized in {"high", "medium", "low"}:
|
|
56
|
+
return normalized
|
|
57
|
+
return "medium"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _coerce_uuid(value: object | None) -> StdUUID | None:
|
|
61
|
+
if value is None:
|
|
62
|
+
return None
|
|
63
|
+
text = str(value).strip()
|
|
64
|
+
if not text:
|
|
65
|
+
return None
|
|
66
|
+
try:
|
|
67
|
+
return StdUUID(text)
|
|
68
|
+
except ValueError:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
51
72
|
@dataclass
|
|
52
73
|
class ScheduleFollowUpAction:
|
|
53
74
|
name: str = _spec["name"]
|
|
@@ -93,7 +114,12 @@ class ScheduleFollowUpAction:
|
|
|
93
114
|
)
|
|
94
115
|
|
|
95
116
|
state = await runtime.compose_state(message, ["RECENT_MESSAGES", "ENTITIES"])
|
|
96
|
-
state.values["currentDateTime"] =
|
|
117
|
+
state.values["currentDateTime"] = get_prompt_reference_datetime(
|
|
118
|
+
runtime,
|
|
119
|
+
message,
|
|
120
|
+
state,
|
|
121
|
+
"action:schedule_follow_up",
|
|
122
|
+
).isoformat()
|
|
97
123
|
|
|
98
124
|
prompt = runtime.compose_prompt_from_state(
|
|
99
125
|
state=state,
|
|
@@ -114,14 +140,46 @@ class ScheduleFollowUpAction:
|
|
|
114
140
|
contact_name = str(parsed.get("contactName", ""))
|
|
115
141
|
scheduled_at_str = str(parsed.get("scheduledAt", ""))
|
|
116
142
|
reason = str(parsed.get("reason", "Follow-up"))
|
|
117
|
-
priority = str(parsed.get("priority", "medium"))
|
|
143
|
+
priority = _normalize_priority(str(parsed.get("priority", "medium")))
|
|
118
144
|
follow_up_message = str(parsed.get("message", ""))
|
|
145
|
+
parsed_entity_id = _coerce_uuid(parsed.get("entityId"))
|
|
146
|
+
message_entity_id = _coerce_uuid(message.entity_id)
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
scheduled_at = datetime.fromisoformat(scheduled_at_str.replace("Z", "+00:00"))
|
|
150
|
+
except ValueError:
|
|
151
|
+
return ActionResult(
|
|
152
|
+
text="Could not parse the follow-up date/time",
|
|
153
|
+
success=False,
|
|
154
|
+
values={"error": True},
|
|
155
|
+
data={"error": "Invalid follow-up datetime"},
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
entity_id_uuid = parsed_entity_id or message_entity_id
|
|
159
|
+
if entity_id_uuid is None and contact_name:
|
|
160
|
+
contacts = await rolodex_service.search_contacts(search_term=contact_name)
|
|
161
|
+
if contacts:
|
|
162
|
+
entity_id_uuid = contacts[0].entity_id
|
|
119
163
|
|
|
120
|
-
|
|
164
|
+
if entity_id_uuid is None:
|
|
165
|
+
return ActionResult(
|
|
166
|
+
text=f"Could not determine which contact to schedule for ({contact_name}).",
|
|
167
|
+
success=False,
|
|
168
|
+
values={"error": True},
|
|
169
|
+
data={"error": "Missing contact entity id"},
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
contact = await rolodex_service.get_contact(entity_id_uuid)
|
|
173
|
+
if contact is None:
|
|
174
|
+
return ActionResult(
|
|
175
|
+
text=f"Contact '{contact_name}' was not found in the rolodex.",
|
|
176
|
+
success=False,
|
|
177
|
+
values={"error": True},
|
|
178
|
+
data={"error": "Contact not found"},
|
|
179
|
+
)
|
|
121
180
|
|
|
122
|
-
entity_id = message.entity_id
|
|
123
181
|
await follow_up_service.schedule_follow_up(
|
|
124
|
-
entity_id=
|
|
182
|
+
entity_id=entity_id_uuid,
|
|
125
183
|
scheduled_at=scheduled_at,
|
|
126
184
|
reason=reason,
|
|
127
185
|
priority=priority,
|
|
@@ -137,11 +195,11 @@ class ScheduleFollowUpAction:
|
|
|
137
195
|
text=response_text,
|
|
138
196
|
success=True,
|
|
139
197
|
values={
|
|
140
|
-
"contactId": str(
|
|
198
|
+
"contactId": str(entity_id_uuid),
|
|
141
199
|
"scheduledAt": scheduled_at.isoformat(),
|
|
142
200
|
},
|
|
143
201
|
data={
|
|
144
|
-
"contactId": str(
|
|
202
|
+
"contactId": str(entity_id_uuid),
|
|
145
203
|
"contactName": contact_name,
|
|
146
204
|
"scheduledAt": scheduled_at.isoformat(),
|
|
147
205
|
"reason": reason,
|