idun-agent-engine 0.4.0__py3-none-any.whl → 0.4.1__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.
@@ -25,6 +25,7 @@ class IdentityWidget(Widget):
25
25
  def __init__(self, *args, **kwargs):
26
26
  super().__init__(*args, **kwargs)
27
27
  self.selected_file_path = ""
28
+ self.selected_variable = ""
28
29
 
29
30
  def compose(self) -> ComposeResult:
30
31
  agent_info_section = Horizontal(
@@ -115,7 +116,7 @@ class IdentityWidget(Widget):
115
116
  import ast
116
117
 
117
118
  try:
118
- with open(file_path, "r") as f:
119
+ with open(file_path) as f:
119
120
  tree = ast.parse(f.read())
120
121
 
121
122
  variables = []
@@ -127,21 +128,28 @@ class IdentityWidget(Widget):
127
128
 
128
129
  var_list = self.query_one("#variable_list", OptionList)
129
130
  var_list.clear_options()
131
+ self.selected_variable = ""
130
132
 
131
133
  if variables:
132
134
  for var in variables:
133
135
  var_list.add_option(Option(var, id=var))
134
- var_list.highlighted = 0
135
136
  else:
136
137
  var_list.add_option(Option("No variables found", id="none"))
137
138
 
138
- except Exception as e:
139
- self.app.notify(f"Error parsing file: {str(e)}", severity="error")
139
+ except Exception:
140
+ self.app.notify(
141
+ "Error parsing file. Make sure it's a valid Python file.",
142
+ severity="error",
143
+ )
140
144
 
141
145
  def on_option_list_option_highlighted(
142
146
  self, event: OptionList.OptionHighlighted
143
147
  ) -> None:
144
148
  if event.option_list.id == "variable_list":
149
+ var_list = self.query_one("#variable_list", OptionList)
150
+ if var_list.highlighted is not None:
151
+ variable_option = var_list.get_option_at_index(var_list.highlighted)
152
+ self.selected_variable = str(variable_option.id)
145
153
  self._update_full_definition()
146
154
  elif event.option_list.id == "framework_select":
147
155
  self._update_section_labels()
@@ -149,7 +157,9 @@ class IdentityWidget(Widget):
149
157
  def _update_section_labels(self) -> None:
150
158
  framework_select = self.query_one("#framework_select", OptionList)
151
159
  if framework_select.highlighted is not None:
152
- framework_option = framework_select.get_option_at_index(framework_select.highlighted)
160
+ framework_option = framework_select.get_option_at_index(
161
+ framework_select.highlighted
162
+ )
153
163
  framework = str(framework_option.id)
154
164
 
155
165
  graph_section = self.query_one(".graph-definition-section", Vertical)
@@ -192,14 +202,22 @@ class IdentityWidget(Widget):
192
202
  self.app.notify("Agent name and port are required!", severity="error")
193
203
  return False
194
204
 
195
- var_list = self.query_one("#variable_list", OptionList)
196
- var_index = var_list.highlighted
205
+ if not self.selected_file_path:
206
+ self.query_one("#identity_error", Static).update(
207
+ "Please select a Python file"
208
+ )
209
+ self.app.notify(
210
+ "Graph definition incomplete! Select a file.", severity="error"
211
+ )
212
+ return False
197
213
 
198
- if not self.selected_file_path or var_index is None:
214
+ if not self.selected_variable or self.selected_variable == "none":
199
215
  self.query_one("#identity_error", Static).update(
200
- "Please select a file and variable"
216
+ "Please select a variable from the list"
217
+ )
218
+ self.app.notify(
219
+ "Graph definition incomplete! Select a variable.", severity="error"
201
220
  )
202
- self.app.notify("Graph definition incomplete!", severity="error")
203
221
  return False
204
222
 
205
223
  return True
@@ -223,8 +223,8 @@ class MCPsWidget(Widget):
223
223
 
224
224
  servers_data.append(server_config)
225
225
 
226
- except Exception as e:
227
- self.app.notify(f"Error reading server {server_id + 1}: {str(e)}", severity="error")
226
+ except Exception:
227
+ self.app.notify(f"Error reading MCP server {server_id + 1}: check your configuration.", severity="error")
228
228
  return None
229
229
 
230
230
  return servers_data
@@ -0,0 +1,195 @@
1
+ """Memory and checkpoint configuration widget."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from pydantic import ValidationError
8
+ from textual.app import ComposeResult
9
+ from textual.containers import Horizontal, Vertical
10
+ from textual.css.query import NoMatches
11
+ from textual.reactive import reactive
12
+ from textual.widget import Widget
13
+ from textual.widgets import Input, RadioButton, RadioSet, Static
14
+
15
+ if TYPE_CHECKING:
16
+ from idun_agent_schema.engine.langgraph import CheckpointConfig
17
+
18
+
19
+ class MemoryWidget(Widget):
20
+ selected_type = reactive("memory")
21
+
22
+ def compose(self) -> ComposeResult:
23
+ main_section = Vertical(classes="memory-main")
24
+ main_section.border_title = "Checkpoint Configuration"
25
+
26
+ with main_section, Horizontal(classes="field-row framework-row"):
27
+ yield Static("Type:", classes="field-label")
28
+ with RadioSet(id="checkpoint_type_select"):
29
+ yield RadioButton("In-Memory", id="memory", value=True)
30
+ yield RadioButton("SQLite", id="sqlite")
31
+ yield RadioButton("PostgreSQL", id="postgres")
32
+
33
+ config_container = Vertical(
34
+ classes="checkpoint-config-container",
35
+ id="checkpoint_config",
36
+ )
37
+ yield config_container
38
+
39
+ def on_mount(self) -> None:
40
+ self._update_checkpoint_config()
41
+
42
+ def on_radio_set_changed(self, event: RadioSet.Changed) -> None:
43
+ if event.radio_set.id == "checkpoint_type_select":
44
+ self.selected_type = str(event.pressed.id)
45
+ self._update_checkpoint_config()
46
+
47
+ def _update_checkpoint_config(self) -> None:
48
+ try:
49
+ config_container = self.query_one("#checkpoint_config", Vertical)
50
+ config_container.remove_children()
51
+
52
+ if self.selected_type == "memory":
53
+ pass
54
+ elif self.selected_type == "sqlite":
55
+ self._render_sqlite_config(config_container)
56
+ elif self.selected_type == "postgres":
57
+ self._render_postgres_config(config_container)
58
+ except Exception:
59
+ pass
60
+
61
+ def _render_sqlite_config(self, container: Vertical) -> None:
62
+ config_section = Vertical(
63
+ Horizontal(
64
+ Static("DB URL:", classes="field-label"),
65
+ Input(
66
+ placeholder="sqlite:///./checkpoints.db",
67
+ id="sqlite_db_url",
68
+ classes="field-input",
69
+ ),
70
+ classes="field-row",
71
+ ),
72
+ classes="checkpoint-fields-section",
73
+ )
74
+ config_section.border_title = "SQLite Configuration"
75
+ container.mount(config_section)
76
+
77
+ def _render_postgres_config(self, container: Vertical) -> None:
78
+ config_section = Vertical(
79
+ Horizontal(
80
+ Static("DB URL:", classes="field-label"),
81
+ Input(
82
+ placeholder="postgresql://user:pass@localhost:5432/db",
83
+ id="postgres_db_url",
84
+ classes="field-input",
85
+ ),
86
+ classes="field-row",
87
+ ),
88
+ classes="checkpoint-fields-section",
89
+ )
90
+ config_section.border_title = "PostgreSQL Configuration"
91
+ container.mount(config_section)
92
+
93
+ def get_data(self) -> CheckpointConfig | None:
94
+ from idun_agent_schema.engine.langgraph import (
95
+ CheckpointConfig,
96
+ InMemoryCheckpointConfig,
97
+ PostgresCheckpointConfig,
98
+ SqliteCheckpointConfig,
99
+ )
100
+
101
+ try:
102
+ radio_set = self.query_one("#checkpoint_type_select", RadioSet)
103
+
104
+ checkpoint_type = "memory"
105
+ if radio_set.pressed_button:
106
+ checkpoint_type = str(radio_set.pressed_button.id)
107
+
108
+ if checkpoint_type == "memory":
109
+ return InMemoryCheckpointConfig(type="memory")
110
+
111
+ elif checkpoint_type == "sqlite":
112
+ try:
113
+ db_url_input = self.query_one("#sqlite_db_url", Input)
114
+ db_url = db_url_input.value.strip()
115
+ except NoMatches:
116
+ self.app.notify(
117
+ "Configuration error. Please reselect checkpoint type.",
118
+ severity="error",
119
+ )
120
+ return None
121
+
122
+ if not db_url:
123
+ self.app.notify(
124
+ "SQLite DB URL is required",
125
+ severity="error",
126
+ )
127
+ return None
128
+
129
+ if not db_url.startswith("sqlite:///"):
130
+ self.app.notify(
131
+ "SQLite URL must start with 'sqlite:///'",
132
+ severity="error",
133
+ )
134
+ return None
135
+
136
+ try:
137
+ return SqliteCheckpointConfig(type="sqlite", db_url=db_url)
138
+ except ValidationError:
139
+ self.app.notify(
140
+ "Invalid SQLite configuration. Check your URL format.",
141
+ severity="error",
142
+ )
143
+ return None
144
+
145
+ elif checkpoint_type == "postgres":
146
+ try:
147
+ db_url_input = self.query_one("#postgres_db_url", Input)
148
+ db_url = db_url_input.value.strip()
149
+ except NoMatches:
150
+ self.app.notify(
151
+ "Configuration error. Please reselect checkpoint type.",
152
+ severity="error",
153
+ )
154
+ return None
155
+
156
+ if not db_url:
157
+ self.app.notify(
158
+ "PostgreSQL DB URL is required",
159
+ severity="error",
160
+ )
161
+ return None
162
+
163
+ if not (
164
+ db_url.startswith("postgresql://")
165
+ or db_url.startswith("postgres://")
166
+ ):
167
+ self.app.notify(
168
+ "PostgreSQL URL must start with 'postgresql://' or 'postgres://'",
169
+ severity="error",
170
+ )
171
+ return None
172
+
173
+ try:
174
+ return PostgresCheckpointConfig(type="postgres", db_url=db_url)
175
+ except ValidationError:
176
+ self.app.notify(
177
+ "Invalid PostgreSQL configuration. Check your URL format.",
178
+ severity="error",
179
+ )
180
+ return None
181
+
182
+ except NoMatches:
183
+ self.app.notify(
184
+ "Error reading checkpoint configuration",
185
+ severity="error",
186
+ )
187
+ return None
188
+ except Exception:
189
+ self.app.notify(
190
+ "Error validating checkpoint configuration",
191
+ severity="error",
192
+ )
193
+ return None
194
+
195
+ return None
@@ -4,7 +4,7 @@ from idun_agent_schema.engine.observability_v2 import (
4
4
  )
5
5
  from textual.app import ComposeResult
6
6
  from textual.containers import Horizontal, Vertical
7
- from textual.widgets import Static, Input, Switch, RadioSet, RadioButton
7
+ from textual.widgets import Static, Input, RadioSet, RadioButton, Switch
8
8
  from textual.widget import Widget
9
9
  from textual.reactive import reactive
10
10
 
@@ -12,21 +12,18 @@ from idun_platform_cli.tui.validators.observability import validate_observabilit
12
12
 
13
13
 
14
14
  class ObservabilityWidget(Widget):
15
- selected_provider = reactive("LANGFUSE")
15
+ selected_provider = reactive("OFF")
16
16
 
17
17
  def compose(self) -> ComposeResult:
18
18
  main_section = Vertical(classes="observability-main")
19
19
  main_section.border_title = "Observability"
20
20
 
21
21
  with main_section:
22
- with Horizontal(classes="field-row"):
23
- yield Static("Enabled:", classes="field-label")
24
- yield Switch(value=True, id="enabled_toggle")
25
-
26
22
  with Horizontal(classes="field-row framework-row"):
27
23
  yield Static("Provider:", classes="field-label")
28
24
  with RadioSet(id="provider_select"):
29
- yield RadioButton("LANGFUSE", id="LANGFUSE", value=True)
25
+ yield RadioButton("Off", id="OFF", value=True)
26
+ yield RadioButton("LANGFUSE", id="LANGFUSE")
30
27
  yield RadioButton("PHOENIX", id="PHOENIX")
31
28
  yield RadioButton("GCP LOGGING", id="GCP_LOGGING")
32
29
  yield RadioButton("GCP TRACE", id="GCP_TRACE")
@@ -49,7 +46,9 @@ class ObservabilityWidget(Widget):
49
46
  config_container = self.query_one("#provider_config", Vertical)
50
47
  config_container.remove_children()
51
48
 
52
- if self.selected_provider == "LANGFUSE":
49
+ if self.selected_provider == "OFF":
50
+ pass
51
+ elif self.selected_provider == "LANGFUSE":
53
52
  self._render_langfuse_config(config_container)
54
53
  elif self.selected_provider == "PHOENIX":
55
54
  self._render_phoenix_config(config_container)
@@ -305,16 +304,15 @@ class ObservabilityWidget(Widget):
305
304
  def get_data(self) -> ObservabilityConfig | None:
306
305
  radio_set = self.query_one("#provider_select", RadioSet)
307
306
 
308
- provider = ObservabilityProvider.LANGFUSE
307
+ provider = "OFF"
309
308
  if radio_set.pressed_button:
310
309
  provider = str(radio_set.pressed_button.id)
311
310
 
312
- enabled = self.query_one("#enabled_toggle", Switch).value
313
- config = {}
314
-
315
- if not enabled:
311
+ if provider == "OFF":
316
312
  return None
317
313
 
314
+ config = {}
315
+
318
316
  match provider:
319
317
  case "LANGFUSE":
320
318
  config = {
@@ -1,7 +1,10 @@
1
1
  """Serve configuration widget."""
2
2
 
3
+ from typing import Any
4
+
5
+ from rich.syntax import Syntax
3
6
  from textual.app import ComposeResult
4
- from textual.containers import Vertical
7
+ from textual.containers import Horizontal, Vertical
5
8
  from textual.widget import Widget
6
9
  from textual.widgets import Button, RichLog, Static
7
10
 
@@ -14,23 +17,20 @@ class ServeWidget(Widget):
14
17
  self.shell_id = None
15
18
 
16
19
  def compose(self) -> ComposeResult:
17
- summary_container = Vertical(classes="serve-summary")
18
- summary_container.border_title = "Configuration Summary"
19
-
20
- with summary_container:
21
- agent_box = Vertical(classes="summary-box", id="agent_summary")
22
- agent_box.border_title = "Agent"
23
- with agent_box:
24
- yield Static("Loading...", id="agent_summary_text")
20
+ yaml_container = Vertical(classes="serve-yaml-display")
21
+ yaml_container.border_title = "Agent Configuration"
25
22
 
26
- obs_box = Vertical(classes="summary-box", id="obs_summary")
27
- obs_box.border_title = "Observability"
28
- with obs_box:
29
- yield Static("Loading...", id="obs_summary_text")
23
+ with yaml_container:
24
+ yield Static("Loading configuration...", id="yaml_content")
30
25
 
31
- yield Button(
32
- "Validate & Run", id="validate_run_button", classes="validate-run-btn"
33
- )
26
+ button_container = Horizontal(classes="serve-button-container")
27
+ with button_container:
28
+ yield Button(
29
+ "Save and Exit", id="save_exit_button", classes="validate-run-btn"
30
+ )
31
+ yield Button(
32
+ "Save and Run", id="save_run_button", classes="validate-run-btn"
33
+ )
34
34
 
35
35
  logs_container = Vertical(classes="serve-logs", id="logs_container")
36
36
  logs_container.border_title = "Server Logs"
@@ -40,37 +40,41 @@ class ServeWidget(Widget):
40
40
 
41
41
  def load_config(self, config: dict) -> None:
42
42
  self.config_data = config
43
- self._update_summary()
44
-
45
- def _update_summary(self) -> None:
46
- from idun_platform_cli.tui.schemas.create_agent import AGENT_SOURCE_KEY_MAPPING
47
-
48
- agent_info = self.config_data.get("agent", {})
49
- agent_config = agent_info.get("config", {})
50
- agent_type = agent_info.get("type")
51
- agent_name = agent_config.get("name")
52
-
53
- server_info = self.config_data.get("server", {})
54
- port = server_info.get("api", {}).get("port")
55
-
56
- graph_def_key = AGENT_SOURCE_KEY_MAPPING.get(agent_type, "graph_definition")
57
- graph_def = agent_config.get(graph_def_key)
58
-
59
- agent_text = f"Name: {agent_name}\nFramework: {agent_type}\nPort: {port}\nGraph: {graph_def}"
60
-
61
- self.query_one("#agent_summary_text", Static).update(agent_text)
62
-
63
- obs_list = self.config_data.get("observability", [])
64
- if obs_list:
65
- obs_data = obs_list[0]
66
- provider = obs_data.get("provider")
67
- enabled = obs_data.get("enabled", False)
68
- status = "Enabled" if enabled else "Disabled"
69
- obs_text = f"Provider: {provider}\nStatus: {status}"
70
- else:
71
- obs_text = "Not configured"
72
-
73
- self.query_one("#obs_summary_text", Static).update(obs_text)
43
+ self._update_yaml_display()
44
+
45
+ def _update_yaml_display(self) -> None:
46
+ import yaml
47
+
48
+ if not self.config_data:
49
+ self.query_one("#yaml_content", Static).update(
50
+ "[yellow]No configuration loaded yet.[/yellow]\n"
51
+ "[dim]Complete the previous sections to generate configuration.[/dim]"
52
+ )
53
+ return
54
+
55
+ try:
56
+ yaml_string = yaml.dump(
57
+ self.config_data,
58
+ default_flow_style=False,
59
+ sort_keys=False,
60
+ allow_unicode=True
61
+ )
62
+
63
+ syntax = Syntax(
64
+ yaml_string,
65
+ "yaml",
66
+ theme="monokai",
67
+ line_numbers=True,
68
+ word_wrap=False,
69
+ indent_guides=True,
70
+ background_color="default"
71
+ )
72
+
73
+ self.query_one("#yaml_content", Static).update(syntax)
74
+
75
+ except Exception as e:
76
+ error_msg = f"[red]Error displaying configuration:[/red]\n{str(e)}"
77
+ self.query_one("#yaml_content", Static).update(error_msg)
74
78
 
75
79
  def get_agent_name(self) -> str:
76
80
  agent_info = self.config_data.get("agent", {})