komodor-agentops 0.1.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 (82) hide show
  1. komodor_agentops/__init__.py +92 -0
  2. komodor_agentops/adapters/__init__.py +1 -0
  3. komodor_agentops/adapters/adk/__init__.py +4 -0
  4. komodor_agentops/adapters/adk/a2a_adapter.py +313 -0
  5. komodor_agentops/adapters/adk/adapter.py +453 -0
  6. komodor_agentops/adapters/agno/__init__.py +13 -0
  7. komodor_agentops/adapters/agno/a2a_adapter.py +275 -0
  8. komodor_agentops/adapters/claude_code/__init__.py +36 -0
  9. komodor_agentops/adapters/claude_code/a2a_adapter.py +658 -0
  10. komodor_agentops/adapters/claude_code/agent.py +127 -0
  11. komodor_agentops/adapters/claude_code/hook.py +260 -0
  12. komodor_agentops/adapters/claude_code/hook_payload.py +32 -0
  13. komodor_agentops/adapters/claude_code/hook_types.py +15 -0
  14. komodor_agentops/adapters/claude_code/install.py +137 -0
  15. komodor_agentops/adapters/claude_code/instrumented_query.py +110 -0
  16. komodor_agentops/adapters/claude_code/sdk_hooks.py +473 -0
  17. komodor_agentops/adapters/claude_code/session_snapshot.py +177 -0
  18. komodor_agentops/adapters/claude_code/usage.py +296 -0
  19. komodor_agentops/adapters/langchain/__init__.py +4 -0
  20. komodor_agentops/adapters/langchain/callbacks.py +168 -0
  21. komodor_agentops/adapters/langchain/context_callbacks.py +130 -0
  22. komodor_agentops/adapters/langchain/usage.py +146 -0
  23. komodor_agentops/adapters/nanobot/__init__.py +15 -0
  24. komodor_agentops/adapters/nanobot/hook.py +159 -0
  25. komodor_agentops/adapters/nanobot/patches.py +103 -0
  26. komodor_agentops/adapters/nanobot/skills.py +47 -0
  27. komodor_agentops/adapters/nanobot/telemetry.py +125 -0
  28. komodor_agentops/artifacts.py +64 -0
  29. komodor_agentops/client.py +180 -0
  30. komodor_agentops/config.py +63 -0
  31. komodor_agentops/core/__init__.py +30 -0
  32. komodor_agentops/core/agent_spec_file.py +247 -0
  33. komodor_agentops/core/auth.py +17 -0
  34. komodor_agentops/core/context.py +131 -0
  35. komodor_agentops/core/errors.py +34 -0
  36. komodor_agentops/core/events.py +89 -0
  37. komodor_agentops/core/hooks.py +47 -0
  38. komodor_agentops/core/observe.py +78 -0
  39. komodor_agentops/core/rpc.py +21 -0
  40. komodor_agentops/core/secrets.py +210 -0
  41. komodor_agentops/core/skills.py +254 -0
  42. komodor_agentops/core/transport.py +482 -0
  43. komodor_agentops/core/types.py +222 -0
  44. komodor_agentops/core/usage.py +176 -0
  45. komodor_agentops/github.py +99 -0
  46. komodor_agentops/grade_context.py +45 -0
  47. komodor_agentops/knowledge.py +134 -0
  48. komodor_agentops/observability.py +338 -0
  49. komodor_agentops/py.typed +0 -0
  50. komodor_agentops/runtime.py +206 -0
  51. komodor_agentops/schemas/agent-spec.schema.json +115 -0
  52. komodor_agentops/shipper/__init__.py +60 -0
  53. komodor_agentops/shipper/queue.py +59 -0
  54. komodor_agentops/shipper/retry.py +52 -0
  55. komodor_agentops/shipper/sender.py +139 -0
  56. komodor_agentops/worker/__init__.py +21 -0
  57. komodor_agentops/worker/_async.py +17 -0
  58. komodor_agentops/worker/a2a/__init__.py +0 -0
  59. komodor_agentops/worker/a2a/executor.py +313 -0
  60. komodor_agentops/worker/a2a/server.py +104 -0
  61. komodor_agentops/worker/a2a/streaming.py +23 -0
  62. komodor_agentops/worker/bootstrap.py +101 -0
  63. komodor_agentops/worker/builtin_use_cases.py +104 -0
  64. komodor_agentops/worker/channel/__init__.py +0 -0
  65. komodor_agentops/worker/channel/listener.py +144 -0
  66. komodor_agentops/worker/hooks/__init__.py +0 -0
  67. komodor_agentops/worker/hooks/heartbeat.py +103 -0
  68. komodor_agentops/worker/hooks/reporter.py +159 -0
  69. komodor_agentops/worker/local.py +332 -0
  70. komodor_agentops/worker/logging_bridge.py +42 -0
  71. komodor_agentops/worker/orchestrator.py +584 -0
  72. komodor_agentops/worker/result.py +57 -0
  73. komodor_agentops/worker/server.py +315 -0
  74. komodor_agentops/worker/snapshot.py +34 -0
  75. komodor_agentops/worker/stream/__init__.py +0 -0
  76. komodor_agentops/worker/stream/server.py +88 -0
  77. komodor_agentops-0.1.0.dist-info/METADATA +218 -0
  78. komodor_agentops-0.1.0.dist-info/RECORD +82 -0
  79. komodor_agentops-0.1.0.dist-info/WHEEL +5 -0
  80. komodor_agentops-0.1.0.dist-info/entry_points.txt +4 -0
  81. komodor_agentops-0.1.0.dist-info/licenses/LICENSE +53 -0
  82. komodor_agentops-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,92 @@
1
+ from agentops_rpc import (
2
+ GRADER_CAPABILITY,
3
+ REVIEW_CAPABILITY,
4
+ USE_CASE_INPUT_KEY,
5
+ A2AMessage,
6
+ A2AMessagePart,
7
+ A2ATask,
8
+ GradeContext,
9
+ GradeResult,
10
+ RunJob,
11
+ UseCase,
12
+ )
13
+ from komodor_agentops.artifacts import ArtifactClient, get_artifact_client
14
+ from komodor_agentops.client import AgentOpsClient as AgentOps
15
+ from komodor_agentops.client import get_client
16
+ from komodor_agentops.core.context import TraceContext
17
+ from komodor_agentops.core.events import AgentOpsEvent
18
+ from komodor_agentops.core.observe import observe
19
+ from komodor_agentops.core.secrets import apply_secret_to_env, get_run_secrets, get_secret
20
+ from komodor_agentops.core.skills import AgentMarkdown
21
+ from komodor_agentops.core.transport import AgentOpsClient as TransportClient
22
+ from komodor_agentops.core.types import AgentSpec
23
+ from komodor_agentops.core.usage import drain_usage, record_usage, reset_usage, usage_from_anthropic_response
24
+ from komodor_agentops.github import (
25
+ GitHubIntegrationError,
26
+ github_api_get,
27
+ github_api_patch,
28
+ github_api_post,
29
+ github_graphql,
30
+ github_installation_token,
31
+ )
32
+ from komodor_agentops.grade_context import fetch_grade_context
33
+ from komodor_agentops.worker import (
34
+ AgentOpsWorker,
35
+ collect_env_secrets,
36
+ fetch_controlplane_secrets,
37
+ load_env_files,
38
+ run_worker_once,
39
+ run_worker_once_sync,
40
+ )
41
+ from komodor_agentops.worker.a2a.streaming import stream_status_text
42
+ from komodor_agentops.worker.result import DESCRIPTIVE_OUTPUT_KEY, result_task
43
+ from komodor_agentops.worker.server import create_worker_app, event_sink_from_client, register_worker_endpoint
44
+
45
+ __all__ = [
46
+ "ArtifactClient",
47
+ "get_artifact_client",
48
+ "AgentOps",
49
+ "AgentMarkdown",
50
+ "AgentOpsEvent",
51
+ "AgentOpsWorker",
52
+ "A2AMessage",
53
+ "A2AMessagePart",
54
+ "A2ATask",
55
+ "AgentSpec",
56
+ "DESCRIPTIVE_OUTPUT_KEY",
57
+ "GRADER_CAPABILITY",
58
+ "REVIEW_CAPABILITY",
59
+ "USE_CASE_INPUT_KEY",
60
+ "GitHubIntegrationError",
61
+ "GradeContext",
62
+ "GradeResult",
63
+ "fetch_grade_context",
64
+ "RunJob",
65
+ "UseCase",
66
+ "apply_secret_to_env",
67
+ "collect_env_secrets",
68
+ "fetch_controlplane_secrets",
69
+ "TraceContext",
70
+ "TransportClient",
71
+ "create_worker_app",
72
+ "event_sink_from_client",
73
+ "get_client",
74
+ "get_run_secrets",
75
+ "github_api_get",
76
+ "github_api_patch",
77
+ "github_api_post",
78
+ "github_graphql",
79
+ "github_installation_token",
80
+ "get_secret",
81
+ "load_env_files",
82
+ "observe",
83
+ "record_usage",
84
+ "register_worker_endpoint",
85
+ "result_task",
86
+ "reset_usage",
87
+ "run_worker_once",
88
+ "run_worker_once_sync",
89
+ "drain_usage",
90
+ "stream_status_text",
91
+ "usage_from_anthropic_response",
92
+ ]
@@ -0,0 +1 @@
1
+ """Adapter modules for third-party agent frameworks."""
@@ -0,0 +1,4 @@
1
+ from komodor_agentops.adapters.adk.a2a_adapter import make_agentops_adk_interceptor, run_adk_pipeline
2
+ from komodor_agentops.adapters.adk.adapter import instrument_adk_run
3
+
4
+ __all__ = ["instrument_adk_run", "make_agentops_adk_interceptor", "run_adk_pipeline"]
@@ -0,0 +1,313 @@
1
+ from __future__ import annotations
2
+
3
+ import uuid
4
+ from typing import Any
5
+
6
+ from google.adk.runners import Runner
7
+ from google.adk.sessions import InMemorySessionService
8
+ from google.genai import types
9
+
10
+ from komodor_agentops import client as client_mod
11
+ from komodor_agentops.adapters.adk.adapter import (
12
+ instrument_adk_run,
13
+ instrument_agent_model_calls,
14
+ usage_from_adk_event,
15
+ usage_from_adk_model,
16
+ )
17
+ from komodor_agentops.core.events import AgentOpsEvent
18
+
19
+
20
+ def _extract_prompt(message: Any) -> str:
21
+ if message is None:
22
+ return ""
23
+ parts = []
24
+ for part in getattr(message, "parts", None) or []:
25
+ actual = getattr(part, "root", part)
26
+ text = getattr(actual, "text", None)
27
+ if text:
28
+ parts.append(str(text))
29
+ return " ".join(parts).strip()
30
+
31
+
32
+ def make_agentops_adk_interceptor(root_agent_name: str) -> Any:
33
+ """Return an ADK ExecuteInterceptor that emits all AgentOps hook events.
34
+
35
+ Uses per-session state keyed by context_id so concurrent requests don't
36
+ interfere. No-op when no AgentOpsClient is active.
37
+ """
38
+ # Must stay inline: tests patch sys.modules["google.adk.a2a.executor.config"]
39
+ # before calling this function to inject a fake ExecuteInterceptor.
40
+ # A top-level import would resolve before the patch is applied.
41
+ from google.adk.a2a.executor.config import ExecuteInterceptor # noqa: PLC0415
42
+
43
+ _states: dict[str, dict] = {}
44
+
45
+ async def before_agent(context: Any) -> Any:
46
+ ctx_id = context.context_id
47
+ prompt = _extract_prompt(context.message)
48
+ _states[ctx_id] = {
49
+ "prompt": prompt,
50
+ "current_subagent": None,
51
+ "subagent_span_ids": {},
52
+ "pending_tool_spans": {},
53
+ # Per-LLM-turn index (see adapter.py): bumped per model event issuing tool
54
+ # calls, stamped on each tool message so the UI can group the transcript by
55
+ # round; kept in per-session state since after_event is called once per event.
56
+ "turn": 0,
57
+ "pending_tool_turns": {},
58
+ "default_usage": usage_from_adk_model(getattr(context, "agent", None)),
59
+ }
60
+ client = client_mod._active_client
61
+ if client is not None:
62
+ await client.enqueue(
63
+ AgentOpsEvent(
64
+ run_id=ctx_id,
65
+ event_type="span.started",
66
+ span_id=ctx_id,
67
+ kind="agent",
68
+ name=root_agent_name,
69
+ hook_event_type="session.start",
70
+ )
71
+ )
72
+ if prompt:
73
+ await client.enqueue(
74
+ AgentOpsEvent(
75
+ run_id=ctx_id,
76
+ event_type="message.created",
77
+ role="user",
78
+ content=prompt,
79
+ hook_event_type="prompt.submit",
80
+ )
81
+ )
82
+ return context
83
+
84
+ async def after_event(exec_ctx: Any, a2a_event: Any, adk_event: Any) -> Any:
85
+ session_id = exec_ctx.session_id
86
+ state = _states.get(session_id)
87
+ if state is None:
88
+ return a2a_event
89
+ client = client_mod._active_client
90
+ if client is None:
91
+ return a2a_event
92
+
93
+ run_id = session_id
94
+ author: str = getattr(adk_event, "author", "") or ""
95
+ usage = usage_from_adk_event(adk_event)
96
+ if usage:
97
+ usage = {**state.get("default_usage", {}), **usage}
98
+ await client.enqueue(
99
+ AgentOpsEvent(
100
+ run_id=run_id,
101
+ event_type="token.created",
102
+ payload=usage,
103
+ )
104
+ )
105
+
106
+ if author and author != root_agent_name and author != state["current_subagent"]:
107
+ if state["current_subagent"] is not None:
108
+ await client.enqueue(
109
+ AgentOpsEvent(
110
+ run_id=run_id,
111
+ event_type="span.ended",
112
+ span_id=state["subagent_span_ids"].get(state["current_subagent"], state["current_subagent"]),
113
+ parent_span_id=run_id,
114
+ kind="agent",
115
+ name=state["current_subagent"],
116
+ hook_event_type="subagent.end",
117
+ )
118
+ )
119
+ state["current_subagent"] = author
120
+ span_id = str(uuid.uuid4())
121
+ state["subagent_span_ids"][author] = span_id
122
+ await client.enqueue(
123
+ AgentOpsEvent(
124
+ run_id=run_id,
125
+ event_type="span.started",
126
+ span_id=span_id,
127
+ parent_span_id=run_id,
128
+ kind="agent",
129
+ name=author,
130
+ hook_event_type="subagent.start",
131
+ )
132
+ )
133
+
134
+ content = getattr(adk_event, "content", None)
135
+ parts = getattr(content, "parts", None) or []
136
+ parent_span = state["subagent_span_ids"].get(state["current_subagent"] or "", run_id)
137
+ # A model event that issues tool calls is one LLM turn; all its calls share it.
138
+ if any(getattr(part, "function_call", None) is not None for part in parts):
139
+ state["turn"] += 1
140
+
141
+ for part in parts:
142
+ fc = getattr(part, "function_call", None)
143
+ fr = getattr(part, "function_response", None)
144
+ text = getattr(part, "text", None)
145
+
146
+ if fc is not None:
147
+ call_key = getattr(fc, "id", None) or fc.name
148
+ tool_span_id = str(uuid.uuid4())
149
+ state["pending_tool_spans"][call_key] = tool_span_id
150
+ state["pending_tool_turns"][call_key] = state["turn"]
151
+ args = dict(fc.args or {})
152
+ await client.enqueue(
153
+ AgentOpsEvent(
154
+ run_id=run_id,
155
+ event_type="span.started",
156
+ span_id=tool_span_id,
157
+ parent_span_id=parent_span,
158
+ kind="tool",
159
+ name=fc.name,
160
+ hook_event_type="tool.start",
161
+ payload={"args": args},
162
+ )
163
+ )
164
+ await client.message(
165
+ fc.name,
166
+ role="tool",
167
+ run_id=run_id,
168
+ message_type="tool_call",
169
+ payload={"input": args, "tool_use_id": call_key, "step": state["turn"]},
170
+ )
171
+ elif fr is not None:
172
+ call_key = getattr(fr, "id", None) or fr.name
173
+ tool_span_id = state["pending_tool_spans"].pop(call_key, str(uuid.uuid4()))
174
+ step = state["pending_tool_turns"].pop(call_key, state["turn"])
175
+ response_val = fr.response or {}
176
+ result_str = str(response_val.get("result", ""))
177
+ is_error = result_str.startswith("Error:") or "error" in response_val
178
+ await client.enqueue(
179
+ AgentOpsEvent(
180
+ run_id=run_id,
181
+ event_type="span.ended",
182
+ span_id=tool_span_id,
183
+ kind="tool",
184
+ name=fr.name,
185
+ status="error" if is_error else "ok",
186
+ hook_event_type="tool.error" if is_error else "tool.end",
187
+ payload={"response": result_str[:4096]},
188
+ )
189
+ )
190
+ await client.message(
191
+ f"{fr.name} returned",
192
+ role="tool",
193
+ run_id=run_id,
194
+ message_type="data",
195
+ payload={"output": result_str[:4096], "tool_use_id": call_key, "step": step},
196
+ )
197
+ if is_error:
198
+ await client.log(
199
+ f"Tool {fr.name} error: {result_str[:300]}",
200
+ level="ERROR",
201
+ run_id=run_id,
202
+ )
203
+ elif text:
204
+ try:
205
+ is_final = adk_event.is_final_response()
206
+ except AttributeError:
207
+ is_final = False
208
+ if is_final:
209
+ await client.enqueue(
210
+ AgentOpsEvent(
211
+ run_id=run_id,
212
+ event_type="message.created",
213
+ role="assistant",
214
+ content=text[:4096],
215
+ hook_event_type="notification",
216
+ payload={"agent": author},
217
+ )
218
+ )
219
+
220
+ return a2a_event
221
+
222
+ async def after_agent(exec_ctx: Any, final_event: Any) -> Any:
223
+ session_id = exec_ctx.session_id
224
+ state = _states.pop(session_id, {})
225
+ client = client_mod._active_client
226
+ if client is None:
227
+ return final_event
228
+
229
+ run_id = session_id
230
+ current_subagent = state.get("current_subagent")
231
+ subagent_span_ids = state.get("subagent_span_ids", {})
232
+
233
+ if current_subagent is not None:
234
+ await client.enqueue(
235
+ AgentOpsEvent(
236
+ run_id=run_id,
237
+ event_type="span.ended",
238
+ span_id=subagent_span_ids.get(current_subagent, current_subagent),
239
+ parent_span_id=run_id,
240
+ kind="agent",
241
+ name=current_subagent,
242
+ hook_event_type="subagent.end",
243
+ )
244
+ )
245
+ await client.enqueue(
246
+ AgentOpsEvent(
247
+ run_id=run_id,
248
+ event_type="span.ended",
249
+ span_id=run_id,
250
+ kind="agent",
251
+ name=root_agent_name,
252
+ hook_event_type="session.stop",
253
+ )
254
+ )
255
+ await client.flush()
256
+ return final_event
257
+
258
+ return ExecuteInterceptor(
259
+ before_agent=before_agent,
260
+ after_event=after_event,
261
+ after_agent=after_agent,
262
+ )
263
+
264
+
265
+ async def run_adk_pipeline(
266
+ pipeline: Any,
267
+ *,
268
+ run_id: str,
269
+ initial_prompt: str,
270
+ root_agent_name: str,
271
+ initial_state: dict[str, Any] | None = None,
272
+ ) -> dict[str, Any]:
273
+ """Run an ADK agent pipeline, emitting all AgentOps hook events.
274
+
275
+ Returns the final session state dict (output_key values from each sub-agent).
276
+ If no AgentOpsClient is active, events are silently dropped.
277
+ """
278
+ app_name = f"adk-{run_id}"
279
+ user_id = "worker"
280
+
281
+ # Emit an `llm` span around each model call (the run stream only sees completed turns).
282
+ instrument_agent_model_calls(pipeline, run_id=run_id, parent_span_id=run_id)
283
+
284
+ session_service = InMemorySessionService()
285
+ runner = Runner(agent=pipeline, app_name=app_name, session_service=session_service)
286
+ session = await session_service.create_session(
287
+ app_name=app_name,
288
+ user_id=user_id,
289
+ state=initial_state or {},
290
+ )
291
+ message = types.Content(
292
+ role="user",
293
+ parts=[types.Part(text=initial_prompt)],
294
+ )
295
+
296
+ async for _ in instrument_adk_run(
297
+ runner.run_async(user_id=user_id, session_id=session.id, new_message=message),
298
+ run_id=run_id,
299
+ root_agent_name=root_agent_name,
300
+ initial_prompt=initial_prompt,
301
+ default_usage=usage_from_adk_model(pipeline),
302
+ ):
303
+ pass
304
+
305
+ refreshed = await session_service.get_session(
306
+ app_name=app_name,
307
+ user_id=user_id,
308
+ session_id=session.id,
309
+ )
310
+ return refreshed.state if refreshed else {}
311
+
312
+
313
+ __all__ = ["make_agentops_adk_interceptor", "run_adk_pipeline"]