tokenjam 0.2.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 (86) hide show
  1. tokenjam/__init__.py +1 -0
  2. tokenjam/api/__init__.py +0 -0
  3. tokenjam/api/app.py +104 -0
  4. tokenjam/api/deps.py +18 -0
  5. tokenjam/api/middleware.py +28 -0
  6. tokenjam/api/routes/__init__.py +0 -0
  7. tokenjam/api/routes/agents.py +33 -0
  8. tokenjam/api/routes/alerts.py +77 -0
  9. tokenjam/api/routes/budget.py +96 -0
  10. tokenjam/api/routes/cost.py +43 -0
  11. tokenjam/api/routes/drift.py +63 -0
  12. tokenjam/api/routes/logs.py +511 -0
  13. tokenjam/api/routes/metrics.py +81 -0
  14. tokenjam/api/routes/otlp.py +63 -0
  15. tokenjam/api/routes/spans.py +202 -0
  16. tokenjam/api/routes/status.py +84 -0
  17. tokenjam/api/routes/tools.py +22 -0
  18. tokenjam/api/routes/traces.py +92 -0
  19. tokenjam/cli/__init__.py +0 -0
  20. tokenjam/cli/cmd_alerts.py +94 -0
  21. tokenjam/cli/cmd_budget.py +119 -0
  22. tokenjam/cli/cmd_cost.py +90 -0
  23. tokenjam/cli/cmd_demo.py +82 -0
  24. tokenjam/cli/cmd_doctor.py +173 -0
  25. tokenjam/cli/cmd_drift.py +238 -0
  26. tokenjam/cli/cmd_export.py +200 -0
  27. tokenjam/cli/cmd_mcp.py +78 -0
  28. tokenjam/cli/cmd_onboard.py +779 -0
  29. tokenjam/cli/cmd_serve.py +85 -0
  30. tokenjam/cli/cmd_status.py +153 -0
  31. tokenjam/cli/cmd_stop.py +87 -0
  32. tokenjam/cli/cmd_tools.py +45 -0
  33. tokenjam/cli/cmd_traces.py +161 -0
  34. tokenjam/cli/cmd_uninstall.py +159 -0
  35. tokenjam/cli/main.py +110 -0
  36. tokenjam/core/__init__.py +0 -0
  37. tokenjam/core/alerts.py +619 -0
  38. tokenjam/core/api_backend.py +235 -0
  39. tokenjam/core/config.py +360 -0
  40. tokenjam/core/cost.py +102 -0
  41. tokenjam/core/db.py +718 -0
  42. tokenjam/core/drift.py +256 -0
  43. tokenjam/core/ingest.py +265 -0
  44. tokenjam/core/models.py +225 -0
  45. tokenjam/core/pricing.py +54 -0
  46. tokenjam/core/retention.py +21 -0
  47. tokenjam/core/schema_validator.py +156 -0
  48. tokenjam/demo/__init__.py +0 -0
  49. tokenjam/demo/env.py +96 -0
  50. tokenjam/mcp/__init__.py +0 -0
  51. tokenjam/mcp/server.py +1067 -0
  52. tokenjam/otel/__init__.py +0 -0
  53. tokenjam/otel/exporters.py +26 -0
  54. tokenjam/otel/provider.py +207 -0
  55. tokenjam/otel/semconv.py +144 -0
  56. tokenjam/pricing/models.toml +70 -0
  57. tokenjam/py.typed +0 -0
  58. tokenjam/sdk/__init__.py +21 -0
  59. tokenjam/sdk/agent.py +206 -0
  60. tokenjam/sdk/bootstrap.py +120 -0
  61. tokenjam/sdk/http_exporter.py +109 -0
  62. tokenjam/sdk/integrations/__init__.py +0 -0
  63. tokenjam/sdk/integrations/anthropic.py +200 -0
  64. tokenjam/sdk/integrations/autogen.py +97 -0
  65. tokenjam/sdk/integrations/base.py +27 -0
  66. tokenjam/sdk/integrations/bedrock.py +103 -0
  67. tokenjam/sdk/integrations/crewai.py +96 -0
  68. tokenjam/sdk/integrations/gemini.py +131 -0
  69. tokenjam/sdk/integrations/langchain.py +156 -0
  70. tokenjam/sdk/integrations/langgraph.py +101 -0
  71. tokenjam/sdk/integrations/litellm.py +323 -0
  72. tokenjam/sdk/integrations/llamaindex.py +52 -0
  73. tokenjam/sdk/integrations/nemoclaw.py +139 -0
  74. tokenjam/sdk/integrations/openai.py +159 -0
  75. tokenjam/sdk/integrations/openai_agents_sdk.py +47 -0
  76. tokenjam/sdk/transport.py +98 -0
  77. tokenjam/ui/index.html +1213 -0
  78. tokenjam/utils/__init__.py +0 -0
  79. tokenjam/utils/formatting.py +43 -0
  80. tokenjam/utils/ids.py +15 -0
  81. tokenjam/utils/time_parse.py +54 -0
  82. tokenjam-0.2.0.dist-info/METADATA +622 -0
  83. tokenjam-0.2.0.dist-info/RECORD +86 -0
  84. tokenjam-0.2.0.dist-info/WHEEL +4 -0
  85. tokenjam-0.2.0.dist-info/entry_points.txt +2 -0
  86. tokenjam-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,47 @@
1
+ """
2
+ OpenAI Agents SDK integration.
3
+
4
+ OpenAI Agents SDK has native OTel support. This module configures it
5
+ to export traces to tj serve via the SDK's official set_trace_processors() API.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from typing import TYPE_CHECKING
11
+
12
+ from tokenjam.core.config import load_config
13
+
14
+ if TYPE_CHECKING:
15
+ from tokenjam.core.config import TjConfig
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def patch_openai_agents(config: TjConfig | None = None) -> None:
21
+ """
22
+ Configure OpenAI Agents SDK's built-in OTel support to export to tj serve.
23
+ Uses the SDK's official set_trace_processors() API.
24
+ """
25
+ if config is None:
26
+ config = load_config()
27
+
28
+ try:
29
+ from agents import set_trace_processors
30
+ from agents.tracing.processors import BatchTraceProcessor
31
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
32
+ except ImportError:
33
+ logger.warning(
34
+ "openai-agents SDK not installed — "
35
+ "run: pip install openai-agents"
36
+ )
37
+ return
38
+
39
+ endpoint = f"http://{config.api.host}:{config.api.port}/api/v1/spans"
40
+ headers: dict[str, str] = {}
41
+ if config.security.ingest_secret:
42
+ headers["Authorization"] = f"Bearer {config.security.ingest_secret}"
43
+
44
+ exporter = OTLPSpanExporter(endpoint=endpoint, headers=headers)
45
+ processor = BatchTraceProcessor(exporter)
46
+ set_trace_processors([processor])
47
+ logger.debug("OpenAI Agents SDK integration installed (via native OTel)")
@@ -0,0 +1,98 @@
1
+ """
2
+ Span transport layer. Delivers spans via HTTP POST to the local tj serve
3
+ instance (used when tj serve is a separate process, or by the TypeScript SDK).
4
+
5
+ The transport is initialised once when the first @watch() call is made.
6
+ Subsequent calls reuse the same transport.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import time
12
+
13
+ import httpx
14
+
15
+ from tokenjam.core.config import TjConfig
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ _MAX_BUFFER = 1000
20
+ _MAX_RETRIES = 3
21
+ _BASE_DELAY = 2.0 # seconds
22
+
23
+
24
+ class HttpTransport:
25
+ """
26
+ Posts spans to POST /api/v1/spans on the local tj serve instance.
27
+
28
+ Buffers up to 1000 spans if tj serve is not reachable.
29
+ Retries with exponential backoff (max 3 attempts, 2s base delay).
30
+ Drops buffered spans on process exit with a log warning.
31
+ """
32
+
33
+ def __init__(self, config: TjConfig):
34
+ self.endpoint = (
35
+ f"http://{config.api.host}:{config.api.port}/api/v1/spans"
36
+ )
37
+ self.secret = config.security.ingest_secret
38
+ self._buffer: list[dict] = []
39
+
40
+ def send(self, spans: list[dict]) -> bool:
41
+ """
42
+ POST spans to tj serve.
43
+ Returns True on success, False on failure (spans are buffered).
44
+ """
45
+ # Add new spans to buffer
46
+ self._buffer.extend(spans)
47
+ if len(self._buffer) > _MAX_BUFFER:
48
+ dropped = len(self._buffer) - _MAX_BUFFER
49
+ self._buffer = self._buffer[-_MAX_BUFFER:]
50
+ logger.warning("Dropped %d buffered spans (buffer full)", dropped)
51
+
52
+ headers: dict[str, str] = {"Content-Type": "application/json"}
53
+ if self.secret:
54
+ headers["Authorization"] = f"Bearer {self.secret}"
55
+
56
+ payload = self._buffer.copy()
57
+ for attempt in range(_MAX_RETRIES):
58
+ try:
59
+ resp = httpx.post(
60
+ self.endpoint,
61
+ json=payload,
62
+ headers=headers,
63
+ timeout=5.0,
64
+ )
65
+ if resp.status_code < 300:
66
+ self._buffer.clear()
67
+ return True
68
+ logger.warning(
69
+ "tj serve returned %d on attempt %d",
70
+ resp.status_code, attempt + 1,
71
+ )
72
+ except (httpx.ConnectError, httpx.TimeoutException) as exc:
73
+ logger.debug(
74
+ "tj serve unreachable (attempt %d/%d): %s",
75
+ attempt + 1, _MAX_RETRIES, exc,
76
+ )
77
+ if attempt < _MAX_RETRIES - 1:
78
+ time.sleep(_BASE_DELAY * (2 ** attempt))
79
+
80
+ logger.warning(
81
+ "Failed to send %d spans after %d attempts; buffered for retry",
82
+ len(payload), _MAX_RETRIES,
83
+ )
84
+ return False
85
+
86
+ @property
87
+ def buffered_count(self) -> int:
88
+ return len(self._buffer)
89
+
90
+ def flush(self) -> None:
91
+ """Attempt to send all buffered spans. Called on shutdown."""
92
+ if not self._buffer:
93
+ return
94
+ if not self.send([]):
95
+ logger.warning(
96
+ "Dropping %d buffered spans on shutdown", len(self._buffer),
97
+ )
98
+ self._buffer.clear()