fenix-mcp 0.7.0__py3-none-any.whl → 1.1.0__py3-none-any.whl
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.
- fenix_mcp/__init__.py +1 -1
- fenix_mcp/application/tools/initialize.py +15 -20
- fenix_mcp/application/tools/intelligence.py +71 -75
- fenix_mcp/application/tools/knowledge.py +284 -313
- fenix_mcp/application/tools/productivity.py +51 -51
- fenix_mcp/application/tools/user_config.py +32 -32
- fenix_mcp/domain/knowledge.py +43 -48
- fenix_mcp/domain/user_config.py +10 -2
- fenix_mcp/infrastructure/fenix_api/client.py +28 -44
- {fenix_mcp-0.7.0.dist-info → fenix_mcp-1.1.0.dist-info}/METADATA +1 -1
- {fenix_mcp-0.7.0.dist-info → fenix_mcp-1.1.0.dist-info}/RECORD +14 -14
- {fenix_mcp-0.7.0.dist-info → fenix_mcp-1.1.0.dist-info}/WHEEL +0 -0
- {fenix_mcp-0.7.0.dist-info → fenix_mcp-1.1.0.dist-info}/entry_points.txt +0 -0
- {fenix_mcp-0.7.0.dist-info → fenix_mcp-1.1.0.dist-info}/top_level.txt +0 -0
fenix_mcp/__init__.py
CHANGED
|
@@ -21,34 +21,29 @@ class InitializeAction(str, Enum):
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class InitializeRequest(ToolRequest):
|
|
24
|
-
action: InitializeAction = Field(
|
|
25
|
-
description="Operação de inicialização a executar."
|
|
26
|
-
)
|
|
24
|
+
action: InitializeAction = Field(description="Initialization operation to execute.")
|
|
27
25
|
include_user_docs: bool = Field(
|
|
28
26
|
default=True,
|
|
29
27
|
description=(
|
|
30
|
-
"
|
|
31
|
-
"(apenas para ação init)."
|
|
28
|
+
"Include personal documents during initialization (only for init action)."
|
|
32
29
|
),
|
|
33
30
|
)
|
|
34
31
|
limit: int = Field(
|
|
35
32
|
default=50,
|
|
36
33
|
ge=1,
|
|
37
34
|
le=200,
|
|
38
|
-
description=("
|
|
35
|
+
description=("Maximum number of core/personal documents to load."),
|
|
39
36
|
)
|
|
40
37
|
answers: Optional[List[str]] = Field(
|
|
41
38
|
default=None,
|
|
42
|
-
description=(
|
|
43
|
-
"Lista com 9 respostas textuais para processar o setup personalizado."
|
|
44
|
-
),
|
|
39
|
+
description=("List of 9 text answers to process the personalized setup."),
|
|
45
40
|
)
|
|
46
41
|
|
|
47
42
|
|
|
48
43
|
class InitializeTool(Tool):
|
|
49
44
|
name = "initialize"
|
|
50
45
|
description = (
|
|
51
|
-
"
|
|
46
|
+
"Initializes the Fenix Cloud environment or processes the personalized setup."
|
|
52
47
|
)
|
|
53
48
|
request_model = InitializeRequest
|
|
54
49
|
|
|
@@ -61,7 +56,7 @@ class InitializeTool(Tool):
|
|
|
61
56
|
return await self._handle_init(payload)
|
|
62
57
|
if payload.action is InitializeAction.SETUP:
|
|
63
58
|
return await self._handle_setup(payload)
|
|
64
|
-
return text("❌
|
|
59
|
+
return text("❌ Unknown initialization action.")
|
|
65
60
|
|
|
66
61
|
async def _handle_init(self, payload: InitializeRequest):
|
|
67
62
|
try:
|
|
@@ -72,8 +67,8 @@ class InitializeTool(Tool):
|
|
|
72
67
|
except Exception as exc: # pragma: no cover - defensive
|
|
73
68
|
self._context.logger.error("Initialize failed: %s", exc)
|
|
74
69
|
return text(
|
|
75
|
-
"❌
|
|
76
|
-
"
|
|
70
|
+
"❌ Failed to load initialization data. "
|
|
71
|
+
"Verify that the token has API access."
|
|
77
72
|
)
|
|
78
73
|
|
|
79
74
|
if (
|
|
@@ -82,7 +77,7 @@ class InitializeTool(Tool):
|
|
|
82
77
|
and not data.profile
|
|
83
78
|
):
|
|
84
79
|
return text(
|
|
85
|
-
"⚠️
|
|
80
|
+
"⚠️ Could not load documents or profile. Confirm the token and, if this is your first access, use `initialize action=setup` to answer the initial questionnaire."
|
|
86
81
|
)
|
|
87
82
|
|
|
88
83
|
payload_dict = {
|
|
@@ -99,7 +94,7 @@ class InitializeTool(Tool):
|
|
|
99
94
|
tenant_info = profile.get("tenant") or {}
|
|
100
95
|
team_info = profile.get("team") or {}
|
|
101
96
|
|
|
102
|
-
context_lines = ["📋 **
|
|
97
|
+
context_lines = ["📋 **User Context**"]
|
|
103
98
|
if user_info.get("id"):
|
|
104
99
|
context_lines.append(f"- **user_id**: `{user_info['id']}`")
|
|
105
100
|
if user_info.get("name"):
|
|
@@ -115,7 +110,7 @@ class InitializeTool(Tool):
|
|
|
115
110
|
|
|
116
111
|
message_lines = context_lines + [
|
|
117
112
|
"",
|
|
118
|
-
"📦 **
|
|
113
|
+
"📦 **Complete initialization data**",
|
|
119
114
|
"```json",
|
|
120
115
|
json.dumps(payload_dict, ensure_ascii=False, indent=2),
|
|
121
116
|
"```",
|
|
@@ -138,11 +133,11 @@ class InitializeTool(Tool):
|
|
|
138
133
|
return text(f"❌ {validation_error}")
|
|
139
134
|
|
|
140
135
|
summary_lines = [
|
|
141
|
-
"📝 **
|
|
136
|
+
"📝 **Personalized setup received!**",
|
|
142
137
|
"",
|
|
143
|
-
"
|
|
138
|
+
"Your answers have been registered. I will suggest documents, rules and routines based on this information.",
|
|
144
139
|
"",
|
|
145
|
-
"
|
|
140
|
+
"Answer summary:",
|
|
146
141
|
]
|
|
147
142
|
for idx, answer in enumerate(answers, start=1):
|
|
148
143
|
summary_lines.append(f"{idx}. {answer.strip()}")
|
|
@@ -150,7 +145,7 @@ class InitializeTool(Tool):
|
|
|
150
145
|
summary_lines.extend(
|
|
151
146
|
[
|
|
152
147
|
"",
|
|
153
|
-
"
|
|
148
|
+
"You can now request specific content, for example:",
|
|
154
149
|
"- `productivity action=todo_create ...`",
|
|
155
150
|
"- `knowledge action=mode_list`",
|
|
156
151
|
]
|
|
@@ -32,17 +32,17 @@ class IntelligenceAction(str, Enum):
|
|
|
32
32
|
|
|
33
33
|
SMART_CREATE = (
|
|
34
34
|
"memory_smart_create",
|
|
35
|
-
"
|
|
35
|
+
"Creates intelligent memories with similarity analysis.",
|
|
36
36
|
)
|
|
37
|
-
QUERY = ("memory_query", "
|
|
38
|
-
SIMILARITY = ("memory_similarity", "
|
|
37
|
+
QUERY = ("memory_query", "Lists memories applying filters and text search.")
|
|
38
|
+
SIMILARITY = ("memory_similarity", "Finds memories similar to a base content.")
|
|
39
39
|
CONSOLIDATE = (
|
|
40
40
|
"memory_consolidate",
|
|
41
|
-
"
|
|
41
|
+
"Consolidates multiple memories into a primary one.",
|
|
42
42
|
)
|
|
43
|
-
UPDATE = ("memory_update", "
|
|
44
|
-
DELETE = ("memory_delete", "
|
|
45
|
-
HELP = ("memory_help", "
|
|
43
|
+
UPDATE = ("memory_update", "Updates fields of an existing memory.")
|
|
44
|
+
DELETE = ("memory_delete", "Removes a memory by ID.")
|
|
45
|
+
HELP = ("memory_help", "Shows supported actions and their uses.")
|
|
46
46
|
|
|
47
47
|
@classmethod
|
|
48
48
|
def choices(cls) -> List[str]:
|
|
@@ -51,7 +51,7 @@ class IntelligenceAction(str, Enum):
|
|
|
51
51
|
@classmethod
|
|
52
52
|
def formatted_help(cls) -> str:
|
|
53
53
|
lines = [
|
|
54
|
-
"| **
|
|
54
|
+
"| **Action** | **Description** |",
|
|
55
55
|
"| --- | --- |",
|
|
56
56
|
]
|
|
57
57
|
for member in cls:
|
|
@@ -60,7 +60,7 @@ class IntelligenceAction(str, Enum):
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
ACTION_FIELD_DESCRIPTION = (
|
|
63
|
-
"
|
|
63
|
+
"Intelligence action to execute. Use one of the values: "
|
|
64
64
|
+ ", ".join(
|
|
65
65
|
f"`{member.value}` ({member.description.rstrip('.')})."
|
|
66
66
|
for member in IntelligenceAction
|
|
@@ -70,31 +70,31 @@ ACTION_FIELD_DESCRIPTION = (
|
|
|
70
70
|
|
|
71
71
|
class IntelligenceRequest(ToolRequest):
|
|
72
72
|
action: IntelligenceAction = Field(description=ACTION_FIELD_DESCRIPTION)
|
|
73
|
-
title: Optional[TitleStr] = Field(default=None, description="
|
|
73
|
+
title: Optional[TitleStr] = Field(default=None, description="Memory title.")
|
|
74
74
|
content: Optional[MarkdownStr] = Field(
|
|
75
|
-
default=None, description="
|
|
75
|
+
default=None, description="Memory content/text (Markdown)."
|
|
76
76
|
)
|
|
77
77
|
metadata: Optional[MarkdownStr] = Field(
|
|
78
78
|
default=None,
|
|
79
|
-
description="
|
|
79
|
+
description="Structured memory metadata (pipe format, compact toml, etc.).",
|
|
80
80
|
)
|
|
81
|
-
context: Optional[str] = Field(default=None, description="
|
|
82
|
-
source: Optional[str] = Field(default=None, description="
|
|
81
|
+
context: Optional[str] = Field(default=None, description="Additional context.")
|
|
82
|
+
source: Optional[str] = Field(default=None, description="Memory source.")
|
|
83
83
|
importance: Optional[str] = Field(
|
|
84
84
|
default=None,
|
|
85
|
-
description="
|
|
85
|
+
description="Memory importance level (low, medium, high, critical).",
|
|
86
86
|
)
|
|
87
87
|
include_content: bool = Field(
|
|
88
88
|
default=False,
|
|
89
|
-
description="
|
|
89
|
+
description="Return full memory content? Set true to include the full text.",
|
|
90
90
|
)
|
|
91
91
|
include_metadata: bool = Field(
|
|
92
92
|
default=False,
|
|
93
|
-
description="
|
|
93
|
+
description="Return full memory metadata? Set true to include the raw field.",
|
|
94
94
|
)
|
|
95
95
|
tags: Optional[List[TagStr]] = Field(
|
|
96
96
|
default=None,
|
|
97
|
-
description=
|
|
97
|
+
description='Memory tags. REQUIRED for create. Format: JSON array of strings, e.g.: ["tag1", "tag2"]. Do not use a single string.',
|
|
98
98
|
json_schema_extra={"example": ["tag1", "tag2", "tag3"]},
|
|
99
99
|
)
|
|
100
100
|
|
|
@@ -126,55 +126,51 @@ class IntelligenceRequest(ToolRequest):
|
|
|
126
126
|
# For any other type, convert to string and wrap in list
|
|
127
127
|
return [str(v).strip()] if str(v).strip() else None
|
|
128
128
|
|
|
129
|
-
limit: int = Field(default=20, ge=1, le=100, description="
|
|
130
|
-
offset: int = Field(default=0, ge=0, description="
|
|
131
|
-
query: Optional[str] = Field(default=None, description="
|
|
129
|
+
limit: int = Field(default=20, ge=1, le=100, description="Result limit.")
|
|
130
|
+
offset: int = Field(default=0, ge=0, description="Pagination offset.")
|
|
131
|
+
query: Optional[str] = Field(default=None, description="Search term.")
|
|
132
132
|
category: Optional[CategoryStr] = Field(
|
|
133
|
-
default=None, description="
|
|
133
|
+
default=None, description="Category for filtering."
|
|
134
134
|
)
|
|
135
135
|
date_from: Optional[DateTimeStr] = Field(
|
|
136
|
-
default=None, description="
|
|
136
|
+
default=None, description="Start date filter (ISO 8601)."
|
|
137
137
|
)
|
|
138
138
|
date_to: Optional[DateTimeStr] = Field(
|
|
139
|
-
default=None, description="
|
|
139
|
+
default=None, description="End date filter (ISO 8601)."
|
|
140
140
|
)
|
|
141
141
|
threshold: float = Field(
|
|
142
|
-
default=0.8, ge=0, le=1, description="
|
|
142
|
+
default=0.8, ge=0, le=1, description="Minimum similarity threshold."
|
|
143
143
|
)
|
|
144
144
|
max_results: int = Field(
|
|
145
|
-
default=5, ge=1, le=20, description="
|
|
145
|
+
default=5, ge=1, le=20, description="Maximum similar memories."
|
|
146
146
|
)
|
|
147
147
|
memory_ids: Optional[List[UUIDStr]] = Field(
|
|
148
|
-
default=None, description="IDs
|
|
148
|
+
default=None, description="Memory IDs for consolidation (UUIDs)."
|
|
149
149
|
)
|
|
150
|
-
strategy: str = Field(default="merge", description="
|
|
151
|
-
time_range: str = Field(
|
|
152
|
-
|
|
153
|
-
)
|
|
154
|
-
group_by: str = Field(default="category", description="Agrupamento para analytics.")
|
|
155
|
-
id: Optional[UUIDStr] = Field(default=None, description="ID da memória (UUID).")
|
|
150
|
+
strategy: str = Field(default="merge", description="Consolidation strategy.")
|
|
151
|
+
time_range: str = Field(default="month", description="Time window for analytics.")
|
|
152
|
+
group_by: str = Field(default="category", description="Grouping for analytics.")
|
|
153
|
+
id: Optional[UUIDStr] = Field(default=None, description="Memory ID (UUID).")
|
|
156
154
|
documentation_item_id: Optional[UUIDStr] = Field(
|
|
157
|
-
default=None, description="
|
|
155
|
+
default=None, description="Related documentation ID (UUID)."
|
|
158
156
|
)
|
|
159
157
|
mode_id: Optional[UUIDStr] = Field(
|
|
160
|
-
default=None, description="
|
|
158
|
+
default=None, description="Related mode ID (UUID)."
|
|
161
159
|
)
|
|
162
160
|
rule_id: Optional[UUIDStr] = Field(
|
|
163
|
-
default=None, description="
|
|
161
|
+
default=None, description="Related rule ID (UUID)."
|
|
164
162
|
)
|
|
165
163
|
work_item_id: Optional[UUIDStr] = Field(
|
|
166
|
-
default=None, description="
|
|
164
|
+
default=None, description="Related work item ID (UUID)."
|
|
167
165
|
)
|
|
168
166
|
sprint_id: Optional[UUIDStr] = Field(
|
|
169
|
-
default=None, description="
|
|
167
|
+
default=None, description="Related sprint ID (UUID)."
|
|
170
168
|
)
|
|
171
169
|
|
|
172
170
|
|
|
173
171
|
class IntelligenceTool(Tool):
|
|
174
172
|
name = "intelligence"
|
|
175
|
-
description = (
|
|
176
|
-
"Operações de inteligência do Fênix Cloud (memórias e smart operations)."
|
|
177
|
-
)
|
|
173
|
+
description = "Fenix Cloud intelligence operations (memories and smart operations)."
|
|
178
174
|
request_model = IntelligenceRequest
|
|
179
175
|
|
|
180
176
|
def __init__(self, context: AppContext):
|
|
@@ -198,19 +194,19 @@ class IntelligenceTool(Tool):
|
|
|
198
194
|
if action is IntelligenceAction.DELETE:
|
|
199
195
|
return await self._handle_delete(payload)
|
|
200
196
|
return text(
|
|
201
|
-
"❌
|
|
197
|
+
"❌ Invalid action for intelligence.\n\nChoose one of the values:\n"
|
|
202
198
|
+ "\n".join(f"- `{value}`" for value in IntelligenceAction.choices())
|
|
203
199
|
)
|
|
204
200
|
|
|
205
201
|
async def _handle_smart_create(self, payload: IntelligenceRequest):
|
|
206
202
|
if not payload.title or not payload.content:
|
|
207
|
-
return text("❌
|
|
203
|
+
return text("❌ Provide title and content to create a memory.")
|
|
208
204
|
|
|
209
205
|
if not payload.metadata or not payload.metadata.strip():
|
|
210
|
-
return text("❌
|
|
206
|
+
return text("❌ Provide metadata to create a memory.")
|
|
211
207
|
|
|
212
208
|
if not payload.source or not payload.source.strip():
|
|
213
|
-
return text("❌
|
|
209
|
+
return text("❌ Provide source to create a memory.")
|
|
214
210
|
|
|
215
211
|
try:
|
|
216
212
|
normalized_tags = _ensure_tag_sequence(payload.tags)
|
|
@@ -218,7 +214,7 @@ class IntelligenceTool(Tool):
|
|
|
218
214
|
return text(f"❌ {exc}")
|
|
219
215
|
|
|
220
216
|
if not normalized_tags or len(normalized_tags) == 0:
|
|
221
|
-
return text("❌
|
|
217
|
+
return text("❌ Provide tags to create a memory.")
|
|
222
218
|
|
|
223
219
|
memory = await self._service.smart_create_memory(
|
|
224
220
|
title=payload.title,
|
|
@@ -230,12 +226,12 @@ class IntelligenceTool(Tool):
|
|
|
230
226
|
tags=normalized_tags,
|
|
231
227
|
)
|
|
232
228
|
lines = [
|
|
233
|
-
"🧠 **
|
|
229
|
+
"🧠 **Memory created successfully!**",
|
|
234
230
|
f"ID: {memory.get('memoryId') or memory.get('id', 'N/A')}",
|
|
235
|
-
f"
|
|
236
|
-
f"
|
|
237
|
-
f"Tags: {', '.join(memory.get('tags', [])) or '
|
|
238
|
-
f"
|
|
231
|
+
f"Action: {memory.get('action') or 'created'}",
|
|
232
|
+
f"Similarity: {format_percentage(memory.get('similarity'))}",
|
|
233
|
+
f"Tags: {', '.join(memory.get('tags', [])) or 'Automatic'}",
|
|
234
|
+
f"Category: {memory.get('category') or 'Automatic'}",
|
|
239
235
|
]
|
|
240
236
|
return text("\n".join(lines))
|
|
241
237
|
|
|
@@ -258,44 +254,44 @@ class IntelligenceTool(Tool):
|
|
|
258
254
|
importance=payload.importance,
|
|
259
255
|
)
|
|
260
256
|
if not memories:
|
|
261
|
-
return text("🧠
|
|
257
|
+
return text("🧠 No memories found.")
|
|
262
258
|
body = "\n\n".join(_format_memory(mem) for mem in memories)
|
|
263
|
-
return text(f"🧠 **
|
|
259
|
+
return text(f"🧠 **Memories ({len(memories)}):**\n\n{body}")
|
|
264
260
|
|
|
265
261
|
async def _handle_similarity(self, payload: IntelligenceRequest):
|
|
266
262
|
if not payload.content:
|
|
267
|
-
return text("❌
|
|
263
|
+
return text("❌ Provide the base content to compare similarity.")
|
|
268
264
|
memories = await self._service.similar_memories(
|
|
269
265
|
content=payload.content,
|
|
270
266
|
threshold=payload.threshold,
|
|
271
267
|
max_results=payload.max_results,
|
|
272
268
|
)
|
|
273
269
|
if not memories:
|
|
274
|
-
return text("🔍
|
|
270
|
+
return text("🔍 No similar memories found.")
|
|
275
271
|
body = "\n\n".join(
|
|
276
|
-
f"🔍 **{mem.get('title', '
|
|
272
|
+
f"🔍 **{mem.get('title', 'Untitled')}**\n Similarity: {format_percentage(mem.get('finalScore'))}\n ID: {mem.get('memoryId', 'N/A')}"
|
|
277
273
|
for mem in memories
|
|
278
274
|
)
|
|
279
|
-
return text(f"🔍 **
|
|
275
|
+
return text(f"🔍 **Similar memories ({len(memories)}):**\n\n{body}")
|
|
280
276
|
|
|
281
277
|
async def _handle_consolidate(self, payload: IntelligenceRequest):
|
|
282
278
|
if not payload.memory_ids or len(payload.memory_ids) < 2:
|
|
283
|
-
return text("❌
|
|
279
|
+
return text("❌ Provide at least 2 memory IDs to consolidate.")
|
|
284
280
|
result = await self._service.consolidate_memories(
|
|
285
281
|
memory_ids=payload.memory_ids,
|
|
286
282
|
strategy=payload.strategy,
|
|
287
283
|
)
|
|
288
284
|
lines = [
|
|
289
|
-
"🔄 **
|
|
290
|
-
f"
|
|
291
|
-
f"
|
|
292
|
-
f"
|
|
285
|
+
"🔄 **Consolidation complete!**",
|
|
286
|
+
f"Primary memory: {result.get('primary_memory_id', 'N/A')}",
|
|
287
|
+
f"Consolidated: {result.get('consolidated_count', 'N/A')}",
|
|
288
|
+
f"Action executed: {result.get('action', 'N/A')}",
|
|
293
289
|
]
|
|
294
290
|
return text("\n".join(lines))
|
|
295
291
|
|
|
296
292
|
async def _handle_update(self, payload: IntelligenceRequest):
|
|
297
293
|
if not payload.id:
|
|
298
|
-
return text("❌
|
|
294
|
+
return text("❌ Provide the memory ID for update.")
|
|
299
295
|
existing = await self._service.get_memory(
|
|
300
296
|
payload.id, include_content=False, include_metadata=True
|
|
301
297
|
)
|
|
@@ -326,23 +322,23 @@ class IntelligenceTool(Tool):
|
|
|
326
322
|
return text(
|
|
327
323
|
"\n".join(
|
|
328
324
|
[
|
|
329
|
-
"✅ **
|
|
325
|
+
"✅ **Memory updated!**",
|
|
330
326
|
f"ID: {memory.get('id', payload.id)}",
|
|
331
|
-
f"
|
|
332
|
-
f"
|
|
327
|
+
f"Title: {memory.get('title', 'N/A')}",
|
|
328
|
+
f"Priority: {memory.get('priority_score', 'N/A')}",
|
|
333
329
|
]
|
|
334
330
|
)
|
|
335
331
|
)
|
|
336
332
|
|
|
337
333
|
async def _handle_delete(self, payload: IntelligenceRequest):
|
|
338
334
|
if not payload.id:
|
|
339
|
-
return text("❌
|
|
335
|
+
return text("❌ Provide the memory ID to remove.")
|
|
340
336
|
await self._service.delete_memory(payload.id)
|
|
341
|
-
return text(f"🗑️
|
|
337
|
+
return text(f"🗑️ Memory {payload.id} removed successfully.")
|
|
342
338
|
|
|
343
339
|
async def _handle_help(self):
|
|
344
340
|
return text(
|
|
345
|
-
"📚 **
|
|
341
|
+
"📚 **Available actions for intelligence**\n\n"
|
|
346
342
|
+ IntelligenceAction.formatted_help()
|
|
347
343
|
)
|
|
348
344
|
|
|
@@ -350,12 +346,12 @@ class IntelligenceTool(Tool):
|
|
|
350
346
|
def _format_memory(memory: Dict[str, Any]) -> str:
|
|
351
347
|
return "\n".join(
|
|
352
348
|
[
|
|
353
|
-
f"🧠 **{memory.get('title', '
|
|
349
|
+
f"🧠 **{memory.get('title', 'Untitled')}**",
|
|
354
350
|
f"ID: {memory.get('id', memory.get('memoryId', 'N/A'))}",
|
|
355
|
-
f"
|
|
356
|
-
f"Tags: {', '.join(memory.get('tags', [])) or '
|
|
357
|
-
f"
|
|
358
|
-
f"
|
|
351
|
+
f"Category: {memory.get('category', 'N/A')}",
|
|
352
|
+
f"Tags: {', '.join(memory.get('tags', [])) or 'None'}",
|
|
353
|
+
f"Importance: {memory.get('importance', 'N/A')}",
|
|
354
|
+
f"Accesses: {memory.get('access_count', 'N/A')}",
|
|
359
355
|
]
|
|
360
356
|
)
|
|
361
357
|
|
|
@@ -385,7 +381,7 @@ def _ensure_tag_sequence(raw: Optional[Any]) -> Optional[List[str]]:
|
|
|
385
381
|
pass
|
|
386
382
|
|
|
387
383
|
raise ValueError(
|
|
388
|
-
"
|
|
384
|
+
"The `tags` field must be sent as a JSON array, for example: "
|
|
389
385
|
'["tag1", "tag2"].'
|
|
390
386
|
)
|
|
391
387
|
return [str(raw).strip()]
|