idun-agent-engine 0.3.8__py3-none-any.whl → 0.4.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.
Files changed (39) hide show
  1. idun_agent_engine/_version.py +1 -1
  2. idun_agent_engine/agent/langgraph/langgraph.py +1 -1
  3. idun_agent_engine/core/app_factory.py +1 -1
  4. idun_agent_engine/core/config_builder.py +5 -6
  5. idun_agent_engine/guardrails/guardrails_hub/__init__.py +2 -2
  6. idun_agent_engine/mcp/__init__.py +18 -2
  7. idun_agent_engine/mcp/helpers.py +95 -45
  8. idun_agent_engine/mcp/registry.py +7 -1
  9. idun_agent_engine/server/lifespan.py +22 -0
  10. idun_agent_engine/telemetry/__init__.py +19 -0
  11. idun_agent_engine/telemetry/config.py +29 -0
  12. idun_agent_engine/telemetry/telemetry.py +248 -0
  13. {idun_agent_engine-0.3.8.dist-info → idun_agent_engine-0.4.0.dist-info}/METADATA +12 -8
  14. {idun_agent_engine-0.3.8.dist-info → idun_agent_engine-0.4.0.dist-info}/RECORD +39 -14
  15. idun_platform_cli/groups/init.py +23 -0
  16. idun_platform_cli/main.py +3 -0
  17. idun_platform_cli/tui/__init__.py +0 -0
  18. idun_platform_cli/tui/css/__init__.py +0 -0
  19. idun_platform_cli/tui/css/create_agent.py +789 -0
  20. idun_platform_cli/tui/css/main.py +92 -0
  21. idun_platform_cli/tui/main.py +87 -0
  22. idun_platform_cli/tui/schemas/__init__.py +0 -0
  23. idun_platform_cli/tui/schemas/create_agent.py +60 -0
  24. idun_platform_cli/tui/screens/__init__.py +0 -0
  25. idun_platform_cli/tui/screens/create_agent.py +482 -0
  26. idun_platform_cli/tui/utils/__init__.py +0 -0
  27. idun_platform_cli/tui/utils/config.py +161 -0
  28. idun_platform_cli/tui/validators/__init__.py +0 -0
  29. idun_platform_cli/tui/validators/guardrails.py +76 -0
  30. idun_platform_cli/tui/validators/mcps.py +84 -0
  31. idun_platform_cli/tui/validators/observability.py +65 -0
  32. idun_platform_cli/tui/widgets/__init__.py +15 -0
  33. idun_platform_cli/tui/widgets/guardrails_widget.py +348 -0
  34. idun_platform_cli/tui/widgets/identity_widget.py +234 -0
  35. idun_platform_cli/tui/widgets/mcps_widget.py +230 -0
  36. idun_platform_cli/tui/widgets/observability_widget.py +384 -0
  37. idun_platform_cli/tui/widgets/serve_widget.py +78 -0
  38. {idun_agent_engine-0.3.8.dist-info → idun_agent_engine-0.4.0.dist-info}/WHEEL +0 -0
  39. {idun_agent_engine-0.3.8.dist-info → idun_agent_engine-0.4.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,384 @@
1
+ from idun_agent_schema.engine.observability_v2 import (
2
+ ObservabilityConfig,
3
+ ObservabilityProvider,
4
+ )
5
+ from textual.app import ComposeResult
6
+ from textual.containers import Horizontal, Vertical
7
+ from textual.widgets import Static, Input, Switch, RadioSet, RadioButton
8
+ from textual.widget import Widget
9
+ from textual.reactive import reactive
10
+
11
+ from idun_platform_cli.tui.validators.observability import validate_observability
12
+
13
+
14
+ class ObservabilityWidget(Widget):
15
+ selected_provider = reactive("LANGFUSE")
16
+
17
+ def compose(self) -> ComposeResult:
18
+ main_section = Vertical(classes="observability-main")
19
+ main_section.border_title = "Observability"
20
+
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
+ with Horizontal(classes="field-row framework-row"):
27
+ yield Static("Provider:", classes="field-label")
28
+ with RadioSet(id="provider_select"):
29
+ yield RadioButton("LANGFUSE", id="LANGFUSE", value=True)
30
+ yield RadioButton("PHOENIX", id="PHOENIX")
31
+ yield RadioButton("GCP LOGGING", id="GCP_LOGGING")
32
+ yield RadioButton("GCP TRACE", id="GCP_TRACE")
33
+ yield RadioButton("LANGSMITH", id="LANGSMITH")
34
+
35
+ provider_config = Vertical(
36
+ classes="provider-config-container", id="provider_config"
37
+ )
38
+ yield provider_config
39
+
40
+ def on_mount(self) -> None:
41
+ self._update_provider_config()
42
+
43
+ def on_radio_set_changed(self, event: RadioSet.Changed) -> None:
44
+ if event.radio_set.id == "provider_select":
45
+ self.selected_provider = str(event.pressed.id)
46
+ self._update_provider_config()
47
+
48
+ def _update_provider_config(self) -> None:
49
+ config_container = self.query_one("#provider_config", Vertical)
50
+ config_container.remove_children()
51
+
52
+ if self.selected_provider == "LANGFUSE":
53
+ self._render_langfuse_config(config_container)
54
+ elif self.selected_provider == "PHOENIX":
55
+ self._render_phoenix_config(config_container)
56
+ elif self.selected_provider == "GCP_LOGGING":
57
+ self._render_gcp_logging_config(config_container)
58
+ elif self.selected_provider == "GCP_TRACE":
59
+ self._render_gcp_trace_config(config_container)
60
+ elif self.selected_provider == "LANGSMITH":
61
+ self._render_langsmith_config(config_container)
62
+
63
+ def _render_langfuse_config(self, container: Vertical) -> None:
64
+ config_section = Vertical(
65
+ Horizontal(
66
+ Static("Host:", classes="field-label"),
67
+ Input(
68
+ value="https://cloud.langfuse.com",
69
+ id="langfuse_host",
70
+ classes="field-input",
71
+ ),
72
+ classes="field-row",
73
+ ),
74
+ Horizontal(
75
+ Static("Public Key:", classes="field-label"),
76
+ Input(
77
+ placeholder="Enter public key",
78
+ id="langfuse_public_key",
79
+ classes="field-input",
80
+ ),
81
+ classes="field-row",
82
+ ),
83
+ Horizontal(
84
+ Static("Secret Key:", classes="field-label"),
85
+ Input(
86
+ placeholder="Enter secret key",
87
+ password=True,
88
+ id="langfuse_secret_key",
89
+ classes="field-input",
90
+ ),
91
+ classes="field-row",
92
+ ),
93
+ Horizontal(
94
+ Static("Run Name:", classes="field-label"),
95
+ Input(
96
+ placeholder="Optional run name",
97
+ id="langfuse_run_name",
98
+ classes="field-input",
99
+ ),
100
+ classes="field-row",
101
+ ),
102
+ classes="provider-fields-section",
103
+ )
104
+ config_section.border_title = "Langfuse Configuration"
105
+ container.mount(config_section)
106
+
107
+ def _render_phoenix_config(self, container: Vertical) -> None:
108
+ config_section = Vertical(
109
+ Horizontal(
110
+ Static("Endpoint:", classes="field-label"),
111
+ Input(
112
+ value="http://localhost:6006",
113
+ id="phoenix_endpoint",
114
+ classes="field-input",
115
+ ),
116
+ classes="field-row",
117
+ ),
118
+ Horizontal(
119
+ Static("Project:", classes="field-label"),
120
+ Input(
121
+ placeholder="Enter project name",
122
+ id="phoenix_project_name",
123
+ classes="field-input",
124
+ ),
125
+ classes="field-row",
126
+ ),
127
+ classes="provider-fields-section",
128
+ )
129
+ config_section.border_title = "Phoenix Configuration"
130
+ container.mount(config_section)
131
+
132
+ def _render_gcp_logging_config(self, container: Vertical) -> None:
133
+ config_section = Vertical(
134
+ Horizontal(
135
+ Static("Project ID:", classes="field-label"),
136
+ Input(
137
+ placeholder="GCP project ID",
138
+ id="gcp_log_project_id",
139
+ classes="field-input",
140
+ ),
141
+ classes="field-row",
142
+ ),
143
+ Horizontal(
144
+ Static("Region:", classes="field-label"),
145
+ Input(
146
+ placeholder="us-central1",
147
+ id="gcp_log_region",
148
+ classes="field-input",
149
+ ),
150
+ classes="field-row",
151
+ ),
152
+ Horizontal(
153
+ Static("Log Name:", classes="field-label"),
154
+ Input(
155
+ placeholder="application-log",
156
+ id="gcp_log_name",
157
+ classes="field-input",
158
+ ),
159
+ classes="field-row",
160
+ ),
161
+ Horizontal(
162
+ Static("Resource:", classes="field-label"),
163
+ Input(
164
+ placeholder="global",
165
+ id="gcp_log_resource_type",
166
+ classes="field-input",
167
+ ),
168
+ classes="field-row",
169
+ ),
170
+ Horizontal(
171
+ Static("Severity:", classes="field-label"),
172
+ Input(value="INFO", id="gcp_log_severity", classes="field-input"),
173
+ classes="field-row",
174
+ ),
175
+ Horizontal(
176
+ Static("Transport:", classes="field-label"),
177
+ Input(
178
+ value="BackgroundThread",
179
+ id="gcp_log_transport",
180
+ classes="field-input",
181
+ ),
182
+ classes="field-row",
183
+ ),
184
+ classes="provider-fields-section",
185
+ )
186
+ config_section.border_title = "GCP Logging Configuration"
187
+ container.mount(config_section)
188
+
189
+ def _render_gcp_trace_config(self, container: Vertical) -> None:
190
+ config_section = Vertical(
191
+ Horizontal(
192
+ Static("Project ID:", classes="field-label"),
193
+ Input(
194
+ placeholder="GCP project ID",
195
+ id="gcp_trace_project_id",
196
+ classes="field-input",
197
+ ),
198
+ classes="field-row",
199
+ ),
200
+ Horizontal(
201
+ Static("Region:", classes="field-label"),
202
+ Input(
203
+ placeholder="us-central1",
204
+ id="gcp_trace_region",
205
+ classes="field-input",
206
+ ),
207
+ classes="field-row",
208
+ ),
209
+ Horizontal(
210
+ Static("Trace Name:", classes="field-label"),
211
+ Input(
212
+ placeholder="Trace session",
213
+ id="gcp_trace_name",
214
+ classes="field-input",
215
+ ),
216
+ classes="field-row",
217
+ ),
218
+ Horizontal(
219
+ Static("Sample Rate:", classes="field-label"),
220
+ Input(value="1.0", id="gcp_trace_sampling_rate", classes="field-input"),
221
+ classes="field-row",
222
+ ),
223
+ Horizontal(
224
+ Static("Flush (sec):", classes="field-label"),
225
+ Input(value="5", id="gcp_trace_flush_interval", classes="field-input"),
226
+ classes="field-row",
227
+ ),
228
+ Horizontal(
229
+ Static("Ignore URLs:", classes="field-label"),
230
+ Input(
231
+ placeholder="/health,/metrics",
232
+ id="gcp_trace_ignore_urls",
233
+ classes="field-input",
234
+ ),
235
+ classes="field-row",
236
+ ),
237
+ classes="provider-fields-section",
238
+ )
239
+ config_section.border_title = "GCP Trace Configuration"
240
+ container.mount(config_section)
241
+
242
+ def _render_langsmith_config(self, container: Vertical) -> None:
243
+ config_section = Vertical(
244
+ Horizontal(
245
+ Static("API Key:", classes="field-label"),
246
+ Input(
247
+ placeholder="Enter API key",
248
+ password=True,
249
+ id="langsmith_api_key",
250
+ classes="field-input",
251
+ ),
252
+ classes="field-row",
253
+ ),
254
+ Horizontal(
255
+ Static("Project ID:", classes="field-label"),
256
+ Input(
257
+ placeholder="Project ID",
258
+ id="langsmith_project_id",
259
+ classes="field-input",
260
+ ),
261
+ classes="field-row",
262
+ ),
263
+ Horizontal(
264
+ Static("Project:", classes="field-label"),
265
+ Input(
266
+ placeholder="prod-chatbot-v1",
267
+ id="langsmith_project_name",
268
+ classes="field-input",
269
+ ),
270
+ classes="field-row",
271
+ ),
272
+ Horizontal(
273
+ Static("Endpoint:", classes="field-label"),
274
+ Input(
275
+ placeholder="https://api.smith.langchain.com",
276
+ id="langsmith_endpoint",
277
+ classes="field-input",
278
+ ),
279
+ classes="field-row",
280
+ ),
281
+ Horizontal(
282
+ Static("Trace Name:", classes="field-label"),
283
+ Input(
284
+ placeholder="Trace session",
285
+ id="langsmith_trace_name",
286
+ classes="field-input",
287
+ ),
288
+ classes="field-row",
289
+ ),
290
+ Horizontal(
291
+ Static("Tracing:", classes="field-label"),
292
+ Switch(value=False, id="langsmith_tracing_enabled"),
293
+ classes="field-row",
294
+ ),
295
+ Horizontal(
296
+ Static("Capture I/O:", classes="field-label"),
297
+ Switch(value=False, id="langsmith_capture_io"),
298
+ classes="field-row",
299
+ ),
300
+ classes="provider-fields-section",
301
+ )
302
+ config_section.border_title = "Langsmith Configuration"
303
+ container.mount(config_section)
304
+
305
+ def get_data(self) -> ObservabilityConfig | None:
306
+ radio_set = self.query_one("#provider_select", RadioSet)
307
+
308
+ provider = ObservabilityProvider.LANGFUSE
309
+ if radio_set.pressed_button:
310
+ provider = str(radio_set.pressed_button.id)
311
+
312
+ enabled = self.query_one("#enabled_toggle", Switch).value
313
+ config = {}
314
+
315
+ if not enabled:
316
+ return None
317
+
318
+ match provider:
319
+ case "LANGFUSE":
320
+ config = {
321
+ "host": self.query_one("#langfuse_host", Input).value,
322
+ "public_key": self.query_one("#langfuse_public_key", Input).value,
323
+ "secret_key": self.query_one("#langfuse_secret_key", Input).value,
324
+ "run_name": self.query_one("#langfuse_run_name", Input).value,
325
+ }
326
+ case "PHOENIX":
327
+ config = {
328
+ "collector_endpoint": self.query_one(
329
+ "#phoenix_endpoint", Input
330
+ ).value,
331
+ "project_name": self.query_one(
332
+ "#phoenix_project_name", Input
333
+ ).value,
334
+ }
335
+ case "GCP_LOGGING":
336
+ config = {
337
+ "project_id": self.query_one("#gcp_log_project_id", Input).value,
338
+ "region": self.query_one("#gcp_log_region", Input).value,
339
+ "log_name": self.query_one("#gcp_log_name", Input).value,
340
+ "resource_type": self.query_one(
341
+ "#gcp_log_resource_type", Input
342
+ ).value,
343
+ "severity": self.query_one("#gcp_log_severity", Input).value,
344
+ "transport": self.query_one("#gcp_log_transport", Input).value,
345
+ }
346
+ case "GCP_TRACE":
347
+ config = {
348
+ "project_id": self.query_one("#gcp_trace_project_id", Input).value,
349
+ "region": self.query_one("#gcp_trace_region", Input).value,
350
+ "trace_name": self.query_one("#gcp_trace_name", Input).value,
351
+ "sampling_rate": float(
352
+ self.query_one("#gcp_trace_sampling_rate", Input).value or "1.0"
353
+ ),
354
+ "flush_interval": int(
355
+ self.query_one("#gcp_trace_flush_interval", Input).value or "5"
356
+ ),
357
+ "ignore_urls": self.query_one(
358
+ "#gcp_trace_ignore_urls", Input
359
+ ).value,
360
+ }
361
+ case "LANGSMITH":
362
+ config = {
363
+ "api_key": self.query_one("#langsmith_api_key", Input).value,
364
+ "project_id": self.query_one("#langsmith_project_id", Input).value,
365
+ "project_name": self.query_one(
366
+ "#langsmith_project_name", Input
367
+ ).value,
368
+ "endpoint": self.query_one("#langsmith_endpoint", Input).value,
369
+ "trace_name": self.query_one("#langsmith_trace_name", Input).value,
370
+ "tracing_enabled": self.query_one(
371
+ "#langsmith_tracing_enabled", Switch
372
+ ).value,
373
+ "capture_inputs_outputs": self.query_one(
374
+ "#langsmith_capture_io", Switch
375
+ ).value,
376
+ }
377
+
378
+ provider = ObservabilityProvider(provider)
379
+ validated, msg = validate_observability(provider, config)
380
+ if not validated:
381
+ self.app.notify(f"error validating observability config: {msg}")
382
+ return None
383
+
384
+ return validated
@@ -0,0 +1,78 @@
1
+ """Serve configuration widget."""
2
+
3
+ from textual.app import ComposeResult
4
+ from textual.containers import Vertical
5
+ from textual.widget import Widget
6
+ from textual.widgets import Button, RichLog, Static
7
+
8
+
9
+ class ServeWidget(Widget):
10
+ def __init__(self, *args, **kwargs):
11
+ super().__init__(*args, **kwargs)
12
+ self.config_data = {}
13
+ self.server_running = False
14
+ self.shell_id = None
15
+
16
+ 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")
25
+
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")
30
+
31
+ yield Button(
32
+ "Validate & Run", id="validate_run_button", classes="validate-run-btn"
33
+ )
34
+
35
+ logs_container = Vertical(classes="serve-logs", id="logs_container")
36
+ logs_container.border_title = "Server Logs"
37
+ logs_container.display = False
38
+ with logs_container:
39
+ yield RichLog(id="server_logs", highlight=True, markup=True)
40
+
41
+ def load_config(self, config: dict) -> None:
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)
74
+
75
+ def get_agent_name(self) -> str:
76
+ agent_info = self.config_data.get("agent", {})
77
+ agent_config = agent_info.get("config", {})
78
+ return agent_config.get("name", "")