idun-agent-engine 0.4.0__py3-none-any.whl → 0.4.2__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.
Files changed (51) hide show
  1. idun_agent_engine/_version.py +1 -1
  2. idun_agent_engine/agent/adk/adk.py +7 -4
  3. idun_agent_engine/agent/haystack/__init__.py +0 -2
  4. idun_agent_engine/agent/haystack/haystack.py +9 -5
  5. idun_agent_engine/agent/langgraph/langgraph.py +10 -13
  6. idun_agent_engine/core/config_builder.py +33 -13
  7. idun_agent_engine/guardrails/guardrails_hub/guardrails_hub.py +52 -9
  8. idun_agent_engine/mcp/__init__.py +2 -2
  9. idun_agent_engine/mcp/helpers.py +53 -15
  10. idun_agent_engine/mcp/registry.py +5 -5
  11. idun_agent_engine/observability/base.py +11 -2
  12. idun_agent_engine/observability/gcp_trace/gcp_trace_handler.py +3 -1
  13. idun_agent_engine/observability/langfuse/langfuse_handler.py +1 -3
  14. idun_agent_engine/server/dependencies.py +7 -2
  15. idun_agent_engine/server/lifespan.py +2 -7
  16. idun_agent_engine/server/routers/agent.py +2 -1
  17. idun_agent_engine/server/routers/base.py +7 -5
  18. idun_agent_engine/telemetry/__init__.py +0 -1
  19. idun_agent_engine/telemetry/config.py +0 -1
  20. idun_agent_engine/telemetry/telemetry.py +3 -4
  21. idun_agent_engine/templates/correction.py +4 -7
  22. idun_agent_engine/templates/deep_research.py +1 -0
  23. idun_agent_engine/templates/translation.py +4 -4
  24. {idun_agent_engine-0.4.0.dist-info → idun_agent_engine-0.4.2.dist-info}/METADATA +2 -2
  25. idun_agent_engine-0.4.2.dist-info/RECORD +86 -0
  26. idun_platform_cli/groups/agent/package.py +4 -1
  27. idun_platform_cli/groups/agent/serve.py +2 -0
  28. idun_platform_cli/groups/init.py +2 -0
  29. idun_platform_cli/telemetry.py +55 -0
  30. idun_platform_cli/tui/css/create_agent.py +137 -14
  31. idun_platform_cli/tui/css/main.py +7 -10
  32. idun_platform_cli/tui/main.py +3 -3
  33. idun_platform_cli/tui/schemas/create_agent.py +8 -4
  34. idun_platform_cli/tui/screens/create_agent.py +186 -20
  35. idun_platform_cli/tui/utils/config.py +23 -2
  36. idun_platform_cli/tui/validators/guardrails.py +20 -6
  37. idun_platform_cli/tui/validators/mcps.py +9 -6
  38. idun_platform_cli/tui/widgets/__init__.py +8 -4
  39. idun_platform_cli/tui/widgets/chat_widget.py +155 -0
  40. idun_platform_cli/tui/widgets/guardrails_widget.py +12 -4
  41. idun_platform_cli/tui/widgets/identity_widget.py +28 -10
  42. idun_platform_cli/tui/widgets/mcps_widget.py +113 -25
  43. idun_platform_cli/tui/widgets/memory_widget.py +194 -0
  44. idun_platform_cli/tui/widgets/observability_widget.py +12 -14
  45. idun_platform_cli/tui/widgets/serve_widget.py +50 -47
  46. idun_agent_engine/agent/haystack/haystack_model.py +0 -13
  47. idun_agent_engine/guardrails/guardrails_hub/utils.py +0 -1
  48. idun_agent_engine/server/routers/agui.py +0 -47
  49. idun_agent_engine-0.4.0.dist-info/RECORD +0 -86
  50. {idun_agent_engine-0.4.0.dist-info → idun_agent_engine-0.4.2.dist-info}/WHEEL +0 -0
  51. {idun_agent_engine-0.4.0.dist-info → idun_agent_engine-0.4.2.dist-info}/entry_points.txt +0 -0
@@ -59,14 +59,14 @@ class IdunApp(App):
59
59
  )
60
60
 
61
61
  yield Static("IDUN AGENT PLATFORM", classes="platform")
62
- yield Static("Deploy, guard and monitor any agent", classes="tagline")
63
- yield Static("Built with ❤️ by Idun Group", classes="built-by")
62
+ yield Static("Deploy, guard and monitor your agents.", classes="tagline")
63
+ yield Static("Built with 💜 by Idun Group", classes="built-by")
64
64
  yield Static(f"v{__version__}", classes="version")
65
65
  with Horizontal(classes="link-container"):
66
66
  yield Link("⭐️ Github", url=self.REPO, classes="links")
67
67
  yield Link("📚 Docs", url=self.DOCS, classes="links")
68
+ yield Link("🌐 Website", url=self.DOCS, classes="links")
68
69
 
69
- yield Static("What do you want to do?", classes="question_prompt")
70
70
  yield MainPageActions()
71
71
  yield Footer()
72
72
 
@@ -21,7 +21,7 @@ agent:
21
21
  # we need to map the graph_definition, pipeline_definition, agent_definition fields based on framework
22
22
  AGENT_SOURCE_KEY_MAPPING: dict[str, str] = dict(
23
23
  {
24
- "HAYSTACK": "pipeline_definition",
24
+ "HAYSTACK": "component_definition",
25
25
  "LANGGRAPH": "graph_definition",
26
26
  "ADK": "agent",
27
27
  }
@@ -35,21 +35,25 @@ class TUIAgentConfig(BaseModel):
35
35
  graph_definition: str
36
36
 
37
37
  @field_validator("*", mode="after")
38
- def validate_not_null(cls, value: str | Any | None) -> str:
38
+ def validate_not_null(cls, value: str | Any | None) -> str: # noqa: N805
39
39
  if value is None or value == "":
40
40
  raise ValueError("Cannot have empty fields!")
41
41
  return value
42
42
 
43
43
  def to_engine_config(self) -> dict[str, Any]:
44
+ sanitized_name = self.name.replace("-", "_").replace(" ", "_")
45
+
44
46
  agent_config = {
45
- "name": self.name,
47
+ "name": sanitized_name if self.framework == "ADK" else self.name,
46
48
  AGENT_SOURCE_KEY_MAPPING[self.framework]: self.graph_definition,
47
49
  }
48
50
 
49
51
  if self.framework == "ADK":
50
- agent_config["app_name"] = self.name.replace("-", "_").replace(" ", "_")
52
+ agent_config["app_name"] = sanitized_name
51
53
  agent_config["session_service"] = {"type": "in_memory"}
52
54
  agent_config["memory_service"] = {"type": "in_memory"}
55
+ elif self.framework == "HAYSTACK":
56
+ agent_config["component_type"] = "pipeline"
53
57
 
54
58
  return {
55
59
  "server": {"api": {"port": self.port}},
@@ -7,15 +7,17 @@ from textual.app import ComposeResult
7
7
  from textual.containers import Container, Horizontal, Vertical
8
8
  from textual.reactive import reactive
9
9
  from textual.screen import Screen
10
- from textual.widgets import Button, Footer, Label, RichLog, Static
10
+ from textual.widgets import Button, Label, RichLog, Static
11
11
 
12
12
  from idun_platform_cli.tui.css.create_agent import CREATE_AGENT_CSS
13
13
  from idun_platform_cli.tui.schemas.create_agent import TUIAgentConfig
14
14
  from idun_platform_cli.tui.utils.config import ConfigManager
15
15
  from idun_platform_cli.tui.widgets import (
16
+ ChatWidget,
16
17
  GuardrailsWidget,
17
18
  IdentityWidget,
18
19
  MCPsWidget,
20
+ MemoryWidget,
19
21
  ObservabilityWidget,
20
22
  ServeWidget,
21
23
  )
@@ -32,10 +34,12 @@ class CreateAgentScreen(Screen):
32
34
  active_section = reactive("identity")
33
35
  nav_panes = [
34
36
  "nav-identity",
37
+ "nav-memory",
35
38
  "nav-observability",
36
39
  "nav-guardrails",
37
40
  "nav-mcps",
38
41
  "nav-serve",
42
+ "nav-chat",
39
43
  ]
40
44
  current_nav_index = 0
41
45
  focus_on_nav = True # Track if focus is on nav or content
@@ -48,6 +52,39 @@ class CreateAgentScreen(Screen):
48
52
  self.server_process = None
49
53
  self.server_running = False
50
54
 
55
+ def on_unmount(self) -> None:
56
+ if self.server_process:
57
+ import os
58
+ import signal
59
+ import subprocess
60
+
61
+ try:
62
+ pgid = os.getpgid(self.server_process.pid)
63
+ os.killpg(pgid, signal.SIGKILL)
64
+ except (ProcessLookupError, OSError, AttributeError, PermissionError):
65
+ try:
66
+ self.server_process.kill()
67
+ except:
68
+ pass
69
+
70
+ try:
71
+ config = self.config_manager.load_config()
72
+ if config:
73
+ port = config.get("server", {}).get("api", {}).get("port", 8008)
74
+ pids = (
75
+ subprocess.check_output(["lsof", "-ti", f":{port}"], text=True)
76
+ .strip()
77
+ .split("\n")
78
+ )
79
+ for pid in pids:
80
+ if pid:
81
+ try:
82
+ os.kill(int(pid), signal.SIGKILL)
83
+ except:
84
+ pass
85
+ except:
86
+ pass
87
+
51
88
  def watch_active_section(self, new_section: str) -> None:
52
89
  for section_id, widget in self.widgets_map.items():
53
90
  if section_id == new_section:
@@ -67,12 +104,20 @@ class CreateAgentScreen(Screen):
67
104
  severity="warning",
68
105
  )
69
106
 
107
+ if new_section == "chat":
108
+ config = self.config_manager.load_config()
109
+ chat_widget = self.widgets_map.get("chat")
110
+ if chat_widget and config:
111
+ chat_widget.load_config(config)
112
+
70
113
  for pane_id in [
71
114
  "nav-identity",
115
+ "nav-memory",
72
116
  "nav-observability",
73
117
  "nav-guardrails",
74
118
  "nav-mcps",
75
119
  "nav-serve",
120
+ "nav-chat",
76
121
  ]:
77
122
  pane = self.query_one(f"#{pane_id}")
78
123
  if pane_id == f"nav-{new_section}":
@@ -101,6 +146,18 @@ class CreateAgentScreen(Screen):
101
146
  nav_identity.can_focus = True
102
147
  yield nav_identity
103
148
 
149
+ nav_memory = Vertical(
150
+ Label(
151
+ "Configure agent\ncheckpointing",
152
+ id="nav-memory-label",
153
+ ),
154
+ classes="nav-pane",
155
+ id="nav-memory",
156
+ )
157
+ nav_memory.border_title = "Memory"
158
+ nav_memory.can_focus = True
159
+ yield nav_memory
160
+
104
161
  nav_observability = Vertical(
105
162
  Label(
106
163
  "Setup monitoring\nand tracing",
@@ -140,6 +197,15 @@ class CreateAgentScreen(Screen):
140
197
  nav_serve.can_focus = True
141
198
  yield nav_serve
142
199
 
200
+ nav_chat = Vertical(
201
+ Label("Chat with your\nrunning agent"),
202
+ classes="nav-pane",
203
+ id="nav-chat",
204
+ )
205
+ nav_chat.border_title = "Chat"
206
+ nav_chat.can_focus = True
207
+ yield nav_chat
208
+
143
209
  with Horizontal(classes="action-buttons"):
144
210
  yield Button("Back", id="back_button", classes="action-btn")
145
211
  yield Button("Next", id="next_button", classes="action-btn")
@@ -147,6 +213,9 @@ class CreateAgentScreen(Screen):
147
213
  with Vertical(classes="content-area"):
148
214
  identity = IdentityWidget(id="widget-identity", classes="section")
149
215
 
216
+ memory = MemoryWidget(id="widget-memory", classes="section")
217
+ memory.border_title = "Memory & Checkpointing"
218
+
150
219
  observability = ObservabilityWidget(
151
220
  id="widget-observability", classes="section"
152
221
  )
@@ -161,26 +230,38 @@ class CreateAgentScreen(Screen):
161
230
  serve = ServeWidget(id="widget-serve", classes="section")
162
231
  serve.border_title = "Validate & Run"
163
232
 
233
+ chat = ChatWidget(id="widget-chat", classes="section")
234
+ chat.border_title = "Chat"
235
+
164
236
  self.widgets_map = {
165
237
  "identity": identity,
238
+ "memory": memory,
166
239
  "observability": observability,
167
240
  "guardrails": guardrails,
168
241
  "mcps": mcps,
169
242
  "serve": serve,
243
+ "chat": chat,
170
244
  }
171
245
 
246
+ memory.display = False
172
247
  observability.display = False
173
248
  guardrails.display = False
174
249
  mcps.display = False
175
250
  serve.display = False
251
+ chat.display = False
176
252
 
177
253
  yield identity
254
+ yield memory
178
255
  yield observability
179
256
  yield guardrails
180
257
  yield mcps
181
258
  yield serve
259
+ yield chat
182
260
 
183
- footer = Static("💡 Press Next to save section", classes="custom-footer")
261
+ footer = Static(
262
+ "💡 Press Next to save section | Press Ctrl+Q to exit",
263
+ classes="custom-footer",
264
+ )
184
265
  yield footer
185
266
 
186
267
  def on_mount(self) -> None:
@@ -201,7 +282,7 @@ class CreateAgentScreen(Screen):
201
282
  if active_widget:
202
283
  try:
203
284
  focusable = active_widget.query(
204
- "Input, OptionList, DirectoryTree, Button"
285
+ "Input, OptionList, DirectoryTree, Button, RadioSet"
205
286
  ).first()
206
287
  if focusable:
207
288
  focusable.focus()
@@ -288,6 +369,8 @@ class CreateAgentScreen(Screen):
288
369
  return
289
370
 
290
371
  if section == "identity":
372
+ if not widget.validate():
373
+ return
291
374
  data = widget.get_data()
292
375
  if data is None:
293
376
  self.notify("Please complete all required fields", severity="error")
@@ -307,13 +390,26 @@ class CreateAgentScreen(Screen):
307
390
  return
308
391
  self.validated_sections.add("identity")
309
392
  self._update_nav_checkmark("identity")
310
- except ValidationError as e:
393
+ except ValidationError:
311
394
  self.notify(
312
- f"Validation error: {str(e)}",
395
+ "Error validating Identity: make sure all fields are correct.",
313
396
  severity="error",
314
397
  )
315
398
  return
316
399
 
400
+ elif section == "memory":
401
+ data = widget.get_data()
402
+ if data is not None:
403
+ success, msg = self.config_manager.save_partial("memory", data)
404
+ if not success:
405
+ self.notify("Memory configuration is invalid", severity="error")
406
+ return
407
+ self.validated_sections.add("memory")
408
+ self._update_nav_checkmark("memory")
409
+ else:
410
+ self.notify("Please configure checkpoint settings", severity="error")
411
+ return
412
+
317
413
  elif section == "observability":
318
414
  data = widget.get_data()
319
415
  if data is None:
@@ -356,15 +452,20 @@ class CreateAgentScreen(Screen):
356
452
 
357
453
  validated_servers, msg = validate_mcp_servers(data)
358
454
  if validated_servers is None:
359
- error_msg = str(msg).replace("[", "").replace("]", "")
360
- self.notify(f"MCP validation failed: {error_msg[:200]}", severity="error")
455
+ self.notify(
456
+ "Error validating MCPs: make sure all fields are correct.",
457
+ severity="error",
458
+ )
361
459
  return
362
460
 
363
461
  success, save_msg = self.config_manager.save_partial(
364
462
  "mcp_servers", validated_servers
365
463
  )
366
464
  if not success:
367
- self.notify(f"Failed to save: {save_msg}", severity="error")
465
+ self.notify(
466
+ "Error saving MCPs: make sure all fields are correct.",
467
+ severity="error",
468
+ )
368
469
  return
369
470
 
370
471
  self.validated_sections.add("mcps")
@@ -378,19 +479,74 @@ class CreateAgentScreen(Screen):
378
479
  section = self.nav_panes[self.current_nav_index].replace("nav-", "")
379
480
  self.active_section = section
380
481
 
381
- elif event.button.id == "validate_run_button":
482
+ elif event.button.id == "save_exit_button":
483
+ if self.server_running and self.server_process:
484
+ self.notify("Kill Server before exiting", severity="warning")
485
+ return
486
+ self.notify("Configuration saved. Exiting...", severity="information")
487
+ self.app.exit()
488
+
489
+ elif event.button.id == "save_run_button":
382
490
  if self.server_running and self.server_process:
383
- self.server_process.terminate()
491
+ import os
492
+ import signal
493
+ import subprocess
494
+
495
+ rich_log = self.query_one("#server_logs", RichLog)
496
+ rich_log.write("\n[yellow]Stopping server...[/yellow]")
497
+
498
+ try:
499
+ pgid = os.getpgid(self.server_process.pid)
500
+ os.killpg(pgid, signal.SIGTERM)
501
+ try:
502
+ self.server_process.wait(timeout=3)
503
+ except subprocess.TimeoutExpired:
504
+ os.killpg(pgid, signal.SIGKILL)
505
+ self.server_process.wait(timeout=1)
506
+ except (ProcessLookupError, OSError, PermissionError):
507
+ try:
508
+ self.server_process.terminate()
509
+ self.server_process.wait(timeout=2)
510
+ except subprocess.TimeoutExpired:
511
+ self.server_process.kill()
512
+ self.server_process.wait(timeout=1)
513
+ except:
514
+ pass
515
+
516
+ config = self.config_manager.load_config()
517
+ if config:
518
+ port = config.get("server", {}).get("api", {}).get("port", 8008)
519
+ try:
520
+ subprocess.run(
521
+ ["lsof", "-ti", f":{port}"],
522
+ capture_output=True,
523
+ text=True,
524
+ check=True,
525
+ )
526
+ pids = (
527
+ subprocess.check_output(
528
+ ["lsof", "-ti", f":{port}"], text=True
529
+ )
530
+ .strip()
531
+ .split("\n")
532
+ )
533
+ for pid in pids:
534
+ if pid:
535
+ try:
536
+ os.kill(int(pid), signal.SIGKILL)
537
+ except:
538
+ pass
539
+ except:
540
+ pass
541
+
384
542
  self.server_process = None
385
543
  self.server_running = False
386
544
 
387
- button = self.query_one("#validate_run_button", Button)
388
- button.label = "Validate & Run"
545
+ button = self.query_one("#save_run_button", Button)
546
+ button.label = "Save and Run"
389
547
  button.remove_class("kill-mode")
390
548
 
391
- rich_log = self.query_one("#server_logs", RichLog)
392
549
  rich_log.write("\n[red]Server stopped[/red]")
393
-
394
550
  self.notify("Server stopped", severity="information")
395
551
  return
396
552
 
@@ -424,11 +580,18 @@ class CreateAgentScreen(Screen):
424
580
 
425
581
  try:
426
582
  process = subprocess.Popen(
427
- ["idun", "agent", "serve", "--source=file", f"--path={config_path}"],
583
+ [
584
+ "idun",
585
+ "agent",
586
+ "serve",
587
+ "--source=file",
588
+ f"--path={config_path}",
589
+ ],
428
590
  stdout=subprocess.PIPE,
429
591
  stderr=subprocess.STDOUT,
430
592
  text=True,
431
593
  bufsize=1,
594
+ start_new_session=True,
432
595
  )
433
596
 
434
597
  fd = process.stdout.fileno()
@@ -438,14 +601,17 @@ class CreateAgentScreen(Screen):
438
601
  self.server_process = process
439
602
  self.server_running = True
440
603
 
441
- button = self.query_one("#validate_run_button", Button)
604
+ button = self.query_one("#save_run_button", Button)
442
605
  button.label = "Kill Server"
443
606
  button.add_class("kill-mode")
444
607
 
445
608
  self.run_worker(self._stream_logs(process), exclusive=True)
446
609
 
447
- except Exception as e:
448
- self.notify(f"Failed to start server: {e}", severity="error")
610
+ except Exception:
611
+ self.notify(
612
+ "Failed to start server. Check your configuration.",
613
+ severity="error",
614
+ )
449
615
 
450
616
  async def _stream_logs(self, process) -> None:
451
617
  import asyncio
@@ -469,8 +635,8 @@ class CreateAgentScreen(Screen):
469
635
 
470
636
  self.server_running = False
471
637
  self.server_process = None
472
- button = self.query_one("#validate_run_button", Button)
473
- button.label = "Validate & Run"
638
+ button = self.query_one("#save_run_button", Button)
639
+ button.label = "Save and Run"
474
640
  button.remove_class("kill-mode")
475
641
  rich_log.write("\n[yellow]Server exited[/yellow]")
476
642
  break
@@ -7,7 +7,6 @@ from idun_agent_schema.engine.observability_v2 import ObservabilityConfig
7
7
  from pydantic import ValidationError
8
8
 
9
9
  from idun_platform_cli.tui.schemas.create_agent import (
10
- AGENT_SOURCE_KEY_MAPPING,
11
10
  TUIAgentConfig,
12
11
  )
13
12
 
@@ -128,6 +127,28 @@ class ConfigManager:
128
127
  existing_config["mcp_servers"] = mcp_servers_list
129
128
  else:
130
129
  existing_config["mcp_servers"] = data
130
+ elif section == "memory":
131
+ from idun_agent_schema.engine.langgraph import CheckpointConfig
132
+
133
+ if "agent" not in existing_config:
134
+ return False, "Agent configuration not found. Save identity first."
135
+
136
+ agent_type = existing_config.get("agent", {}).get("type")
137
+ if agent_type != "LANGGRAPH":
138
+ return (
139
+ True,
140
+ "Checkpoint configuration skipped for non-LANGGRAPH agents",
141
+ )
142
+
143
+ if isinstance(data, CheckpointConfig):
144
+ checkpoint_dict = data.model_dump(by_alias=False, mode="json")
145
+
146
+ if "config" not in existing_config["agent"]:
147
+ existing_config["agent"]["config"] = {}
148
+
149
+ existing_config["agent"]["config"]["checkpointer"] = checkpoint_dict
150
+ else:
151
+ return False, "Invalid checkpoint configuration type"
131
152
  else:
132
153
  existing_config[section] = data
133
154
 
@@ -157,5 +178,5 @@ class ConfigManager:
157
178
  with self.agent_path.open("r") as f:
158
179
  return yaml.safe_load(f) or {}
159
180
 
160
- except Exception as e:
181
+ except Exception:
161
182
  return {}
@@ -1,12 +1,12 @@
1
1
  """Guardrails validation logic."""
2
2
 
3
3
  from idun_agent_schema.engine.guardrails_v2 import (
4
- GuardrailConfigId,
4
+ BanListConfig,
5
5
  BiasCheckConfig,
6
- ToxicLanguageConfig,
7
6
  CompetitionCheckConfig,
8
- BanListConfig,
9
7
  DetectPIIConfig,
8
+ GuardrailConfigId,
9
+ ToxicLanguageConfig,
10
10
  )
11
11
 
12
12
 
@@ -16,14 +16,22 @@ def validate_guardrail(guardrail_id: str, config: dict) -> tuple[any, str]:
16
16
  case "bias_check":
17
17
  threshold = float(config.get("threshold", 0.5))
18
18
  validated = BiasCheckConfig(
19
- config_id=GuardrailConfigId.BIAS_CHECK, threshold=threshold
19
+ config_id=GuardrailConfigId.BIAS_CHECK,
20
+ api_key=config.get("api_key", ""),
21
+ reject_message=config.get("reject_message", "Bias detected"),
22
+ threshold=threshold,
20
23
  )
21
24
  return validated, "ok"
22
25
 
23
26
  case "toxic_language":
24
27
  threshold = float(config.get("threshold", 0.5))
25
28
  validated = ToxicLanguageConfig(
26
- config_id=GuardrailConfigId.TOXIC_LANGUAGE, threshold=threshold
29
+ config_id=GuardrailConfigId.TOXIC_LANGUAGE,
30
+ api_key=config.get("api_key", ""),
31
+ reject_message=config.get(
32
+ "reject_message", "Toxic language detected"
33
+ ),
34
+ threshold=threshold,
27
35
  )
28
36
  return validated, "ok"
29
37
 
@@ -35,6 +43,8 @@ def validate_guardrail(guardrail_id: str, config: dict) -> tuple[any, str]:
35
43
  ]
36
44
  validated = CompetitionCheckConfig(
37
45
  config_id=GuardrailConfigId.COMPETITION_CHECK,
46
+ api_key=config.get("api_key", ""),
47
+ reject_message=config.get("reject_message", "Competitor mentioned"),
38
48
  competitors=competitors,
39
49
  )
40
50
  return validated, "ok"
@@ -73,4 +83,8 @@ def validate_guardrail(guardrail_id: str, config: dict) -> tuple[any, str]:
73
83
  return None, f"Unknown guardrail type: {guardrail_id}"
74
84
 
75
85
  except Exception as e:
76
- return None, f"Validation error for {guardrail_id}: {str(e)}"
86
+ error_msg = str(e)
87
+ if len(error_msg) > 100:
88
+ error_msg = error_msg[:100] + "..."
89
+ error_msg = error_msg.replace("<", "").replace(">", "")
90
+ return None, f"Validation error for {guardrail_id}: {error_msg}"
@@ -30,12 +30,15 @@ def validate_mcp_servers(
30
30
  None,
31
31
  f"Server '{name}': command is required for stdio transport",
32
32
  )
33
- elif transport in ["sse", "streamable_http", "websocket"]:
34
- if not server_data.get("url"):
35
- return (
36
- None,
37
- f"Server '{name}': url is required for {transport} transport",
38
- )
33
+ elif transport in [
34
+ "sse",
35
+ "streamable_http",
36
+ "websocket",
37
+ ] and not server_data.get("url"):
38
+ return (
39
+ None,
40
+ f"Server '{name}': url is required for {transport} transport",
41
+ )
39
42
 
40
43
  args = server_data.get("args", [])
41
44
  if isinstance(args, str):
@@ -1,15 +1,19 @@
1
1
  """Widget components for the agent configuration screens."""
2
2
 
3
- from .identity_widget import IdentityWidget
4
- from .observability_widget import ObservabilityWidget
3
+ from .chat_widget import ChatWidget
5
4
  from .guardrails_widget import GuardrailsWidget
5
+ from .identity_widget import IdentityWidget
6
6
  from .mcps_widget import MCPsWidget
7
+ from .memory_widget import MemoryWidget
8
+ from .observability_widget import ObservabilityWidget
7
9
  from .serve_widget import ServeWidget
8
10
 
9
11
  __all__ = [
10
- "IdentityWidget",
11
- "ObservabilityWidget",
12
+ "ChatWidget",
12
13
  "GuardrailsWidget",
14
+ "IdentityWidget",
13
15
  "MCPsWidget",
16
+ "MemoryWidget",
17
+ "ObservabilityWidget",
14
18
  "ServeWidget",
15
19
  ]