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.
- tokenjam/__init__.py +1 -0
- tokenjam/api/__init__.py +0 -0
- tokenjam/api/app.py +104 -0
- tokenjam/api/deps.py +18 -0
- tokenjam/api/middleware.py +28 -0
- tokenjam/api/routes/__init__.py +0 -0
- tokenjam/api/routes/agents.py +33 -0
- tokenjam/api/routes/alerts.py +77 -0
- tokenjam/api/routes/budget.py +96 -0
- tokenjam/api/routes/cost.py +43 -0
- tokenjam/api/routes/drift.py +63 -0
- tokenjam/api/routes/logs.py +511 -0
- tokenjam/api/routes/metrics.py +81 -0
- tokenjam/api/routes/otlp.py +63 -0
- tokenjam/api/routes/spans.py +202 -0
- tokenjam/api/routes/status.py +84 -0
- tokenjam/api/routes/tools.py +22 -0
- tokenjam/api/routes/traces.py +92 -0
- tokenjam/cli/__init__.py +0 -0
- tokenjam/cli/cmd_alerts.py +94 -0
- tokenjam/cli/cmd_budget.py +119 -0
- tokenjam/cli/cmd_cost.py +90 -0
- tokenjam/cli/cmd_demo.py +82 -0
- tokenjam/cli/cmd_doctor.py +173 -0
- tokenjam/cli/cmd_drift.py +238 -0
- tokenjam/cli/cmd_export.py +200 -0
- tokenjam/cli/cmd_mcp.py +78 -0
- tokenjam/cli/cmd_onboard.py +779 -0
- tokenjam/cli/cmd_serve.py +85 -0
- tokenjam/cli/cmd_status.py +153 -0
- tokenjam/cli/cmd_stop.py +87 -0
- tokenjam/cli/cmd_tools.py +45 -0
- tokenjam/cli/cmd_traces.py +161 -0
- tokenjam/cli/cmd_uninstall.py +159 -0
- tokenjam/cli/main.py +110 -0
- tokenjam/core/__init__.py +0 -0
- tokenjam/core/alerts.py +619 -0
- tokenjam/core/api_backend.py +235 -0
- tokenjam/core/config.py +360 -0
- tokenjam/core/cost.py +102 -0
- tokenjam/core/db.py +718 -0
- tokenjam/core/drift.py +256 -0
- tokenjam/core/ingest.py +265 -0
- tokenjam/core/models.py +225 -0
- tokenjam/core/pricing.py +54 -0
- tokenjam/core/retention.py +21 -0
- tokenjam/core/schema_validator.py +156 -0
- tokenjam/demo/__init__.py +0 -0
- tokenjam/demo/env.py +96 -0
- tokenjam/mcp/__init__.py +0 -0
- tokenjam/mcp/server.py +1067 -0
- tokenjam/otel/__init__.py +0 -0
- tokenjam/otel/exporters.py +26 -0
- tokenjam/otel/provider.py +207 -0
- tokenjam/otel/semconv.py +144 -0
- tokenjam/pricing/models.toml +70 -0
- tokenjam/py.typed +0 -0
- tokenjam/sdk/__init__.py +21 -0
- tokenjam/sdk/agent.py +206 -0
- tokenjam/sdk/bootstrap.py +120 -0
- tokenjam/sdk/http_exporter.py +109 -0
- tokenjam/sdk/integrations/__init__.py +0 -0
- tokenjam/sdk/integrations/anthropic.py +200 -0
- tokenjam/sdk/integrations/autogen.py +97 -0
- tokenjam/sdk/integrations/base.py +27 -0
- tokenjam/sdk/integrations/bedrock.py +103 -0
- tokenjam/sdk/integrations/crewai.py +96 -0
- tokenjam/sdk/integrations/gemini.py +131 -0
- tokenjam/sdk/integrations/langchain.py +156 -0
- tokenjam/sdk/integrations/langgraph.py +101 -0
- tokenjam/sdk/integrations/litellm.py +323 -0
- tokenjam/sdk/integrations/llamaindex.py +52 -0
- tokenjam/sdk/integrations/nemoclaw.py +139 -0
- tokenjam/sdk/integrations/openai.py +159 -0
- tokenjam/sdk/integrations/openai_agents_sdk.py +47 -0
- tokenjam/sdk/transport.py +98 -0
- tokenjam/ui/index.html +1213 -0
- tokenjam/utils/__init__.py +0 -0
- tokenjam/utils/formatting.py +43 -0
- tokenjam/utils/ids.py +15 -0
- tokenjam/utils/time_parse.py +54 -0
- tokenjam-0.2.0.dist-info/METADATA +622 -0
- tokenjam-0.2.0.dist-info/RECORD +86 -0
- tokenjam-0.2.0.dist-info/WHEEL +4 -0
- tokenjam-0.2.0.dist-info/entry_points.txt +2 -0
- 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()
|