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