netra-zen 1.0.3__tar.gz → 1.0.4__tar.gz

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 (49) hide show
  1. {netra_zen-1.0.3/netra_zen.egg-info → netra_zen-1.0.4}/PKG-INFO +1 -1
  2. {netra_zen-1.0.3 → netra_zen-1.0.4/netra_zen.egg-info}/PKG-INFO +1 -1
  3. {netra_zen-1.0.3 → netra_zen-1.0.4}/netra_zen.egg-info/SOURCES.txt +4 -1
  4. {netra_zen-1.0.3 → netra_zen-1.0.4}/netra_zen.egg-info/top_level.txt +1 -0
  5. {netra_zen-1.0.3 → netra_zen-1.0.4}/pyproject.toml +2 -2
  6. {netra_zen-1.0.3 → netra_zen-1.0.4}/setup.py +1 -1
  7. netra_zen-1.0.4/zen/__init__.py +7 -0
  8. netra_zen-1.0.4/zen/telemetry/__init__.py +11 -0
  9. netra_zen-1.0.4/zen/telemetry/manager.py +249 -0
  10. {netra_zen-1.0.3 → netra_zen-1.0.4}/LICENSE.md +0 -0
  11. {netra_zen-1.0.3 → netra_zen-1.0.4}/MANIFEST.in +0 -0
  12. {netra_zen-1.0.3 → netra_zen-1.0.4}/README.md +0 -0
  13. {netra_zen-1.0.3 → netra_zen-1.0.4}/agent_interface/__init__.py +0 -0
  14. {netra_zen-1.0.3 → netra_zen-1.0.4}/agent_interface/base_agent.py +0 -0
  15. {netra_zen-1.0.3 → netra_zen-1.0.4}/config_example.json +0 -0
  16. {netra_zen-1.0.3 → netra_zen-1.0.4}/docs/CACHE_TOKENS_GUIDE.md +0 -0
  17. {netra_zen-1.0.3 → netra_zen-1.0.4}/docs/Cost_allocation.md +0 -0
  18. {netra_zen-1.0.3 → netra_zen-1.0.4}/docs/DOLLAR_BUDGET_USAGE_EXAMPLES.md +0 -0
  19. {netra_zen-1.0.3 → netra_zen-1.0.4}/docs/EXAMPLES.md +0 -0
  20. {netra_zen-1.0.3 → netra_zen-1.0.4}/docs/MODEL_COLUMN_GUIDE.md +0 -0
  21. {netra_zen-1.0.3 → netra_zen-1.0.4}/netra_zen.egg-info/dependency_links.txt +0 -0
  22. {netra_zen-1.0.3 → netra_zen-1.0.4}/netra_zen.egg-info/entry_points.txt +0 -0
  23. {netra_zen-1.0.3 → netra_zen-1.0.4}/netra_zen.egg-info/requires.txt +0 -0
  24. {netra_zen-1.0.3 → netra_zen-1.0.4}/prebuilt_commands_example.json +0 -0
  25. {netra_zen-1.0.3 → netra_zen-1.0.4}/requirements-dev.txt +0 -0
  26. {netra_zen-1.0.3 → netra_zen-1.0.4}/requirements.txt +0 -0
  27. {netra_zen-1.0.3 → netra_zen-1.0.4}/setup.cfg +0 -0
  28. {netra_zen-1.0.3 → netra_zen-1.0.4}/tests/__init__.py +0 -0
  29. {netra_zen-1.0.3 → netra_zen-1.0.4}/tests/test_agent_interface.py +0 -0
  30. {netra_zen-1.0.3 → netra_zen-1.0.4}/tests/test_cli_extensions.py +0 -0
  31. {netra_zen-1.0.3 → netra_zen-1.0.4}/tests/test_cli_integration.py +0 -0
  32. {netra_zen-1.0.3 → netra_zen-1.0.4}/tests/test_direct_command_execution.py +0 -0
  33. {netra_zen-1.0.3 → netra_zen-1.0.4}/tests/test_dollar_budget_enhancement.py +0 -0
  34. {netra_zen-1.0.3 → netra_zen-1.0.4}/tests/test_permission_fix_windows.py +0 -0
  35. {netra_zen-1.0.3 → netra_zen-1.0.4}/tests/test_pricing_engine.py +0 -0
  36. {netra_zen-1.0.3 → netra_zen-1.0.4}/tests/test_runner.py +0 -0
  37. {netra_zen-1.0.3 → netra_zen-1.0.4}/tests/test_workspace_detection.py +0 -0
  38. {netra_zen-1.0.3 → netra_zen-1.0.4}/tests/test_zen_commands.py +0 -0
  39. {netra_zen-1.0.3 → netra_zen-1.0.4}/tests/test_zen_integration.py +0 -0
  40. {netra_zen-1.0.3 → netra_zen-1.0.4}/tests/test_zen_metrics.py +0 -0
  41. {netra_zen-1.0.3 → netra_zen-1.0.4}/tests/test_zen_unit.py +0 -0
  42. {netra_zen-1.0.3 → netra_zen-1.0.4}/token_budget/__init__.py +0 -0
  43. {netra_zen-1.0.3 → netra_zen-1.0.4}/token_budget/budget_manager.py +0 -0
  44. {netra_zen-1.0.3 → netra_zen-1.0.4}/token_budget/models.py +0 -0
  45. {netra_zen-1.0.3 → netra_zen-1.0.4}/token_budget/visualization.py +0 -0
  46. {netra_zen-1.0.3 → netra_zen-1.0.4}/token_transparency/__init__.py +0 -0
  47. {netra_zen-1.0.3 → netra_zen-1.0.4}/token_transparency/claude_pricing_engine.py +0 -0
  48. {netra_zen-1.0.3 → netra_zen-1.0.4}/zen/telemetry/embedded_credentials.py +0 -0
  49. {netra_zen-1.0.3 → netra_zen-1.0.4}/zen_orchestrator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: netra-zen
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: Multi-instance Claude orchestrator for parallel task execution
5
5
  Home-page: https://github.com/netra-systems/zen
6
6
  Author: Systems
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: netra-zen
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: Multi-instance Claude orchestrator for parallel task execution
5
5
  Home-page: https://github.com/netra-systems/zen
6
6
  Author: Systems
@@ -41,4 +41,7 @@ token_budget/models.py
41
41
  token_budget/visualization.py
42
42
  token_transparency/__init__.py
43
43
  token_transparency/claude_pricing_engine.py
44
- zen/telemetry/embedded_credentials.py
44
+ zen/__init__.py
45
+ zen/telemetry/__init__.py
46
+ zen/telemetry/embedded_credentials.py
47
+ zen/telemetry/manager.py
@@ -1,4 +1,5 @@
1
1
  agent_interface
2
2
  token_budget
3
3
  token_transparency
4
+ zen
4
5
  zen_orchestrator
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "netra-zen"
7
- version = "1.0.3"
7
+ version = "1.0.4"
8
8
  description = "Multi-instance Claude orchestrator for parallel task execution"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -58,7 +58,7 @@ zen = "zen_orchestrator:run"
58
58
 
59
59
  [tool.setuptools]
60
60
  py-modules = ["zen_orchestrator"]
61
- packages = ["agent_interface", "token_budget", "token_transparency"]
61
+ packages = ["agent_interface", "token_budget", "token_transparency", "zen", "zen.telemetry"]
62
62
 
63
63
  [tool.setuptools.package-data]
64
64
  "*" = ["*.json", "*.yaml", "*.yml", "*.md"]
@@ -8,7 +8,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
8
8
 
9
9
  setup(
10
10
  name="netra-zen",
11
- version="1.0.3",
11
+ version="1.0.4",
12
12
  author=" Systems",
13
13
  author_email="pypi@netrasystems.ai",
14
14
  description="Multi-instance Claude orchestrator for parallel task execution",
@@ -0,0 +1,7 @@
1
+ """Zen namespace package placeholder.
2
+
3
+ This lightweight module exists so repository scripts can import
4
+ `zen.telemetry` directly without requiring the full orchestrator package.
5
+ """
6
+
7
+ __all__: list[str] = []
@@ -0,0 +1,11 @@
1
+ """Telemetry utilities exposed by the Zen package."""
2
+
3
+ from .embedded_credentials import get_embedded_credentials, get_project_id
4
+ from .manager import TelemetryManager, telemetry_manager
5
+
6
+ __all__ = [
7
+ "TelemetryManager",
8
+ "telemetry_manager",
9
+ "get_embedded_credentials",
10
+ "get_project_id",
11
+ ]
@@ -0,0 +1,249 @@
1
+ """Telemetry manager for Zen orchestrator.
2
+
3
+ Provides minimal OpenTelemetry integration that records anonymous spans with
4
+ token usage and cost metadata. If OpenTelemetry or Google Cloud libraries are
5
+ missing, the manager silently degrades to a no-op implementation.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ import logging
12
+ import os
13
+ import re
14
+ from dataclasses import asdict
15
+ from typing import Any, Dict, Optional
16
+
17
+ try:
18
+ from opentelemetry import trace
19
+ from opentelemetry.sdk.resources import Resource
20
+ from opentelemetry.sdk.trace import TracerProvider
21
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
22
+ from opentelemetry.trace import SpanKind
23
+
24
+ OPENTELEMETRY_AVAILABLE = True
25
+ except ImportError: # pragma: no cover - optional dependency
26
+ OPENTELEMETRY_AVAILABLE = False
27
+
28
+ try:
29
+ from google.cloud.trace_v2 import TraceServiceClient
30
+ from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
31
+ from google.api_core.exceptions import GoogleAPICallError # type: ignore
32
+
33
+ GCP_EXPORT_AVAILABLE = True
34
+ except ImportError: # pragma: no cover - optional dependency
35
+ GCP_EXPORT_AVAILABLE = False
36
+
37
+ class GoogleAPICallError(Exception): # type: ignore
38
+ """Fallback exception used when google-api-core is unavailable."""
39
+
40
+ pass
41
+
42
+ from .embedded_credentials import get_embedded_credentials, get_project_id
43
+
44
+ logger = logging.getLogger(__name__)
45
+
46
+
47
+ def _sanitize_tool_name(tool: str) -> str:
48
+ """Convert tool names to telemetry-safe attribute suffixes."""
49
+ safe = re.sub(r"[^a-z0-9_]+", "_", tool.lower()).strip("_")
50
+ return safe or "tool"
51
+
52
+
53
+ class _NoOpTelemetryManager:
54
+ """Fallback manager when telemetry dependencies are unavailable."""
55
+
56
+ def is_enabled(self) -> bool:
57
+ return False
58
+
59
+ def record_instance_span(self, *_, **__): # pragma: no cover - trivial
60
+ return
61
+
62
+ def shutdown(self) -> None: # pragma: no cover - trivial
63
+ return
64
+
65
+
66
+ class TelemetryManager:
67
+ """Manage OpenTelemetry setup and span emission for Zen."""
68
+
69
+ def __init__(self) -> None:
70
+ self._enabled = False
71
+ self._provider: Optional[TracerProvider] = None
72
+ self._tracer = None
73
+ self._initialize()
74
+
75
+ def _initialize(self) -> None:
76
+ if os.getenv("ZEN_TELEMETRY_DISABLED", "").lower() in {"1", "true", "yes"}:
77
+ logger.debug("Telemetry disabled via ZEN_TELEMETRY_DISABLED")
78
+ return
79
+
80
+ if not (OPENTELEMETRY_AVAILABLE and GCP_EXPORT_AVAILABLE):
81
+ logger.debug("OpenTelemetry or Google Cloud exporter not available; telemetry disabled")
82
+ return
83
+
84
+ credentials = get_embedded_credentials()
85
+ if credentials is None:
86
+ logger.debug("No telemetry credentials detected; telemetry disabled")
87
+ return
88
+
89
+ try:
90
+ project_id = get_project_id()
91
+ client = TraceServiceClient(credentials=credentials)
92
+ exporter = CloudTraceSpanExporter(project_id=project_id, client=client)
93
+
94
+ resource_attrs = {
95
+ "service.name": "zen-orchestrator",
96
+ "service.version": os.getenv("ZEN_VERSION", "1.0.3"),
97
+ "telemetry.sdk.language": "python",
98
+ "telemetry.sdk.name": "opentelemetry",
99
+ "zen.analytics.type": "community",
100
+ }
101
+
102
+ resource = Resource.create(resource_attrs)
103
+ provider = TracerProvider(resource=resource)
104
+ provider.add_span_processor(BatchSpanProcessor(exporter))
105
+
106
+ trace.set_tracer_provider(provider)
107
+ self._provider = provider
108
+ self._tracer = trace.get_tracer("zen.telemetry")
109
+ self._enabled = True
110
+ logger.info("Telemetry initialized with community credentials")
111
+ except Exception as exc: # pragma: no cover - defensive guard
112
+ logger.warning(f"Failed to initialize telemetry: {exc}")
113
+ self._enabled = False
114
+ self._provider = None
115
+ self._tracer = None
116
+
117
+ # Public API -----------------------------------------------------
118
+
119
+ def is_enabled(self) -> bool:
120
+ return self._enabled and self._tracer is not None
121
+
122
+ def record_instance_span(
123
+ self,
124
+ batch_id: str,
125
+ instance_name: str,
126
+ status: Any,
127
+ config: Any,
128
+ cost_usd: Optional[float] = None,
129
+ workspace: Optional[str] = None,
130
+ ) -> None:
131
+ if not self.is_enabled():
132
+ return
133
+
134
+ assert self._tracer is not None # mypy hint
135
+
136
+ attributes: Dict[str, Any] = {
137
+ "zen.batch.id": batch_id,
138
+ "zen.instance.name": instance_name,
139
+ "zen.instance.status": getattr(status, "status", "unknown"),
140
+ "zen.instance.success": getattr(status, "status", "") == "completed",
141
+ "zen.instance.permission_mode": getattr(config, "permission_mode", "unknown"),
142
+ "zen.instance.tool_calls": getattr(status, "tool_calls", 0),
143
+ "zen.tokens.total": getattr(status, "total_tokens", 0),
144
+ "zen.tokens.input": getattr(status, "input_tokens", 0),
145
+ "zen.tokens.output": getattr(status, "output_tokens", 0),
146
+ "zen.tokens.cache.read": getattr(status, "cache_read_tokens", 0),
147
+ "zen.tokens.cache.creation": getattr(status, "cache_creation_tokens", 0),
148
+ "zen.tokens.cached_total": getattr(status, "cached_tokens", 0),
149
+ }
150
+
151
+ start_time = getattr(status, "start_time", None)
152
+ end_time = getattr(status, "end_time", None)
153
+ if start_time and end_time:
154
+ attributes["zen.instance.duration_ms"] = int((end_time - start_time) * 1000)
155
+
156
+ command = getattr(config, "command", None) or getattr(config, "prompt", None)
157
+ if isinstance(command, str) and command.startswith("/"):
158
+ attributes["zen.instance.command_type"] = "slash"
159
+ attributes["zen.instance.command"] = command
160
+ elif isinstance(command, str):
161
+ attributes["zen.instance.command_type"] = "prompt"
162
+ else:
163
+ attributes["zen.instance.command_type"] = "unknown"
164
+
165
+ session_id = getattr(config, "session_id", None)
166
+ if session_id:
167
+ session_hash = hashlib.sha256(session_id.encode("utf-8")).hexdigest()[:16]
168
+ attributes["zen.session.hash"] = session_hash
169
+
170
+ if workspace:
171
+ workspace_hash = hashlib.sha256(workspace.encode("utf-8")).hexdigest()[:16]
172
+ attributes["zen.workspace.hash"] = workspace_hash
173
+
174
+ # Tool metadata
175
+ tool_tokens = getattr(status, "tool_tokens", {}) or {}
176
+ attributes["zen.tools.unique"] = len(tool_tokens)
177
+ total_tool_tokens = 0
178
+ for tool_name, tokens in tool_tokens.items():
179
+ sanitized = _sanitize_tool_name(tool_name)
180
+ attributes[f"zen.tools.tokens.{sanitized}"] = int(tokens)
181
+ total_tool_tokens += int(tokens)
182
+ attributes["zen.tokens.tools_total"] = total_tool_tokens
183
+
184
+ tool_details = getattr(status, "tool_details", {}) or {}
185
+ for tool_name, count in tool_details.items():
186
+ sanitized = _sanitize_tool_name(tool_name)
187
+ attributes[f"zen.tools.invocations.{sanitized}"] = int(count)
188
+
189
+ # Cost metadata
190
+ if cost_usd is not None:
191
+ attributes["zen.cost.usd_total"] = round(float(cost_usd), 6)
192
+
193
+ reported_cost = getattr(status, "total_cost_usd", None)
194
+ if reported_cost is not None:
195
+ attributes["zen.cost.usd_reported"] = round(float(reported_cost), 6)
196
+
197
+ # Derive cost components using fallback pricing (USD per million tokens)
198
+ input_tokens = getattr(status, "input_tokens", 0)
199
+ output_tokens = getattr(status, "output_tokens", 0)
200
+ cache_read_tokens = getattr(status, "cache_read_tokens", 0)
201
+ cache_creation_tokens = getattr(status, "cache_creation_tokens", 0)
202
+
203
+ input_cost = (input_tokens / 1_000_000) * 3.00
204
+ output_cost = (output_tokens / 1_000_000) * 15.00
205
+ cache_read_cost = (cache_read_tokens / 1_000_000) * (3.00 * 0.1)
206
+ cache_creation_cost = (cache_creation_tokens / 1_000_000) * (3.00 * 1.25)
207
+ tool_cost = (total_tool_tokens / 1_000_000) * 3.00
208
+
209
+ attributes.update(
210
+ {
211
+ "zen.cost.usd_input": round(input_cost, 6),
212
+ "zen.cost.usd_output": round(output_cost, 6),
213
+ "zen.cost.usd_cache_read": round(cache_read_cost, 6),
214
+ "zen.cost.usd_cache_creation": round(cache_creation_cost, 6),
215
+ "zen.cost.usd_tools": round(tool_cost, 6),
216
+ }
217
+ )
218
+
219
+ # Emit span
220
+ try:
221
+ with self._tracer.start_as_current_span(
222
+ "zen.instance", kind=SpanKind.INTERNAL
223
+ ) as span:
224
+ for key, value in attributes.items():
225
+ span.set_attribute(key, value)
226
+ except GoogleAPICallError as exc: # pragma: no cover - network failure safety
227
+ logger.warning(f"Failed to export telemetry span: {exc}")
228
+
229
+ def shutdown(self) -> None:
230
+ if not self._provider:
231
+ return
232
+ try:
233
+ if hasattr(self._provider, "force_flush"):
234
+ self._provider.force_flush()
235
+ if hasattr(self._provider, "shutdown"):
236
+ self._provider.shutdown()
237
+ except Exception as exc: # pragma: no cover
238
+ logger.debug(f"Telemetry shutdown warning: {exc}")
239
+
240
+
241
+ def _build_manager() -> TelemetryManager | _NoOpTelemetryManager:
242
+ if not (OPENTELEMETRY_AVAILABLE and GCP_EXPORT_AVAILABLE):
243
+ return _NoOpTelemetryManager()
244
+ return TelemetryManager()
245
+
246
+
247
+ telemetry_manager = _build_manager()
248
+
249
+ __all__ = ["TelemetryManager", "telemetry_manager"]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes