trace-agent-server 0.0.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.
@@ -0,0 +1,363 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from trace_agent_server.models import Event, Run
6
+
7
+
8
+ STATE_BY_EVENT_TYPE = {
9
+ "run.started": "initialized",
10
+ "guardrail.input_checked": "guardrail_checked",
11
+ "guardrail.output_checked": "guardrail_checked",
12
+ "guardrail.blocked": "guardrail_blocked",
13
+ "model.requested": "waiting_model",
14
+ "model.responded": "model_responded",
15
+ "tool.requested": "waiting_tool",
16
+ "tool.started": "tool_running",
17
+ "tool.succeeded": "tool_succeeded",
18
+ "tool.failed": "tool_failed",
19
+ "command.started": "command_running",
20
+ "command.succeeded": "command_succeeded",
21
+ "command.failed": "command_failed",
22
+ "file.read": "file_read",
23
+ "file.written": "file_written",
24
+ "patch.applied": "patch_applied",
25
+ "artifact.created": "artifact_created",
26
+ "artifact.updated": "artifact_updated",
27
+ "run.completed": "completed",
28
+ "run.failed": "failed",
29
+ }
30
+
31
+
32
+ def _token_usage_template() -> dict[str, int]:
33
+ return {
34
+ "prompt_tokens": 0,
35
+ "completion_tokens": 0,
36
+ "total_tokens": 0,
37
+ "reasoning_tokens": 0,
38
+ }
39
+
40
+
41
+ def _registered_tool_count(events: list[Event], run: Run) -> int:
42
+ registered_tools = run.run_metadata.get("registered_tools", [])
43
+ if registered_tools:
44
+ return len(registered_tools)
45
+
46
+ for event in reversed(events):
47
+ tool_names = event.event_metadata.get("tool_names")
48
+ if isinstance(tool_names, list) and tool_names:
49
+ return len(tool_names)
50
+ tool_count = event.event_metadata.get("registered_tool_count")
51
+ if isinstance(tool_count, int):
52
+ return tool_count
53
+ return 0
54
+
55
+
56
+ def build_execution_metadata(run: Run, events: list[Event]) -> dict[str, Any]:
57
+ last_event = events[-1] if events else None
58
+ duration_ms: int | None = None
59
+ if run.ended_at is not None:
60
+ duration_ms = max(0, int((run.ended_at - run.started_at).total_seconds() * 1000))
61
+ elif last_event is not None:
62
+ duration_ms = max(0, int((last_event.timestamp - run.started_at).total_seconds() * 1000))
63
+
64
+ token_usage = _token_usage_template()
65
+ for event in events:
66
+ if event.type != "model.responded":
67
+ continue
68
+ usage = event.event_metadata.get("usage", {})
69
+ if not isinstance(usage, dict):
70
+ continue
71
+ token_usage["prompt_tokens"] += int(usage.get("prompt_tokens") or 0)
72
+ token_usage["completion_tokens"] += int(usage.get("completion_tokens") or 0)
73
+ token_usage["total_tokens"] += int(usage.get("total_tokens") or 0)
74
+ completion_details = usage.get("completion_tokens_details", {})
75
+ if isinstance(completion_details, dict):
76
+ token_usage["reasoning_tokens"] += int(completion_details.get("reasoning_tokens") or 0)
77
+
78
+ current_state = run.status
79
+ if run.status == "running" and last_event is not None:
80
+ current_state = STATE_BY_EVENT_TYPE.get(last_event.type, run.status)
81
+
82
+ return {
83
+ "duration_ms": duration_ms,
84
+ "total_event_count": len(events),
85
+ "model_turn_count": sum(1 for event in events if event.type == "model.requested"),
86
+ "assistant_turn_count": sum(1 for event in events if event.type == "model.responded"),
87
+ "tool_request_count": sum(1 for event in events if event.type == "tool.requested"),
88
+ "tool_success_count": sum(1 for event in events if event.type == "tool.succeeded"),
89
+ "tool_failure_count": sum(1 for event in events if event.type == "tool.failed"),
90
+ "command_count": sum(1 for event in events if event.type in {"command.succeeded", "command.failed"}),
91
+ "command_failure_count": sum(1 for event in events if event.type == "command.failed"),
92
+ "file_read_count": sum(1 for event in events if event.type == "file.read"),
93
+ "file_write_count": sum(1 for event in events if event.type == "file.written"),
94
+ "patch_count": sum(1 for event in events if event.type == "patch.applied"),
95
+ "registered_tool_count": _registered_tool_count(events, run),
96
+ "current_state": current_state,
97
+ "last_event_type": last_event.type if last_event is not None else None,
98
+ "last_event_actor": last_event.actor if last_event is not None else None,
99
+ "last_event_at": last_event.timestamp if last_event is not None else None,
100
+ "token_usage": token_usage,
101
+ }
102
+
103
+
104
+ def build_timeline_groups(events: list[Event]) -> list[dict[str, Any]]:
105
+ grouped: dict[str, list[Event]] = {}
106
+ for event in events:
107
+ key = event.step_id or f"seq-{event.seq}"
108
+ grouped.setdefault(key, []).append(event)
109
+
110
+ groups: list[dict[str, Any]] = []
111
+ for step_id, group_events in grouped.items():
112
+ last_event = group_events[-1]
113
+ step_type = "run"
114
+ if any(event.tool_name for event in group_events):
115
+ step_type = "tool"
116
+ elif any(event.type.startswith("command.") for event in group_events):
117
+ step_type = "command"
118
+ elif any(event.type.startswith("file.") for event in group_events):
119
+ step_type = "file"
120
+ elif any(event.type == "patch.applied" for event in group_events):
121
+ step_type = "patch"
122
+
123
+ groups.append(
124
+ {
125
+ "step_id": step_id,
126
+ "label": last_event.tool_name or str(last_event.event_metadata.get("path") or last_event.event_metadata.get("command") or last_event.type),
127
+ "status": last_event.status or "observed",
128
+ "step_type": step_type,
129
+ "tool_name": last_event.tool_name,
130
+ "call_id": next(
131
+ (
132
+ event.event_metadata.get("call_id")
133
+ for event in group_events
134
+ if isinstance(event.event_metadata.get("call_id"), str)
135
+ ),
136
+ None,
137
+ ),
138
+ "parent_step_id": next((event.parent_step_id for event in group_events if event.parent_step_id), None),
139
+ "event_count": len(group_events),
140
+ "started_at": group_events[0].timestamp,
141
+ "ended_at": group_events[-1].timestamp,
142
+ "duration_ms": max(
143
+ 0,
144
+ int((group_events[-1].timestamp - group_events[0].timestamp).total_seconds() * 1000),
145
+ )
146
+ if len(group_events) > 1
147
+ else group_events[-1].duration_ms,
148
+ "events": group_events,
149
+ }
150
+ )
151
+
152
+ groups.sort(key=lambda item: item["events"][0].seq if item["events"] else 0)
153
+ return groups
154
+
155
+
156
+ def build_tool_chain(events: list[Event]) -> list[dict[str, Any]]:
157
+ chain: dict[str, dict[str, Any]] = {}
158
+ order: list[str] = []
159
+
160
+ def get_key(event: Event) -> str:
161
+ call_id = event.event_metadata.get("call_id")
162
+ if isinstance(call_id, str) and call_id:
163
+ return call_id
164
+ if event.step_id:
165
+ return event.step_id
166
+ return f"event-{event.id}"
167
+
168
+ for event in events:
169
+ if event.type not in {"tool.requested", "tool.started", "tool.succeeded", "tool.failed", "artifact.created"}:
170
+ continue
171
+ if event.type == "artifact.created" and not event.tool_name and not event.step_id:
172
+ continue
173
+
174
+ key = get_key(event)
175
+ if key not in chain:
176
+ chain[key] = {
177
+ "call_id": event.event_metadata.get("call_id") if isinstance(event.event_metadata.get("call_id"), str) else None,
178
+ "step_id": event.step_id or key,
179
+ "parent_step_id": event.parent_step_id,
180
+ "tool_name": event.tool_name or "unknown tool",
181
+ "status": event.status or "observed",
182
+ "event_ids": [],
183
+ "requested_at": None,
184
+ "started_at": None,
185
+ "completed_at": None,
186
+ "duration_ms": None,
187
+ "provider": event.provider,
188
+ "model": event.model,
189
+ "args_summary": None,
190
+ "output_summary": None,
191
+ "error_summary": None,
192
+ "error_code": None,
193
+ "artifact_count": 0,
194
+ "_first_seq": event.seq,
195
+ }
196
+ order.append(key)
197
+
198
+ item = chain[key]
199
+ item["event_ids"].append(event.id)
200
+ item["tool_name"] = event.tool_name or item["tool_name"]
201
+ item["provider"] = event.provider or item["provider"]
202
+ item["model"] = event.model or item["model"]
203
+ item["parent_step_id"] = event.parent_step_id or item["parent_step_id"]
204
+
205
+ if event.type == "tool.requested":
206
+ item["requested_at"] = event.timestamp
207
+ item["status"] = "requested"
208
+ item["args_summary"] = event.summary
209
+ elif event.type == "tool.started":
210
+ item["started_at"] = event.timestamp
211
+ item["status"] = "running"
212
+ item["args_summary"] = event.event_metadata.get("args_summary") or item["args_summary"]
213
+ elif event.type == "tool.succeeded":
214
+ item["completed_at"] = event.timestamp
215
+ item["duration_ms"] = event.duration_ms
216
+ item["status"] = "succeeded"
217
+ item["output_summary"] = event.summary
218
+ elif event.type == "tool.failed":
219
+ item["completed_at"] = event.timestamp
220
+ item["duration_ms"] = event.duration_ms
221
+ item["status"] = "failed"
222
+ item["error_summary"] = event.summary
223
+ item["error_code"] = event.error_code
224
+ elif event.type == "artifact.created":
225
+ item["artifact_count"] += max(1, len(event.artifact_refs))
226
+
227
+ ordered = sorted((chain[key] for key in order), key=lambda item: item["_first_seq"])
228
+ for item in ordered:
229
+ item.pop("_first_seq", None)
230
+ return ordered
231
+
232
+
233
+ def build_command_chain(events: list[Event]) -> list[dict[str, Any]]:
234
+ chain: dict[str, dict[str, Any]] = {}
235
+ order: list[str] = []
236
+
237
+ def get_key(event: Event) -> str:
238
+ command_id = event.event_metadata.get("command_id")
239
+ if isinstance(command_id, str) and command_id:
240
+ return command_id
241
+ if event.step_id:
242
+ return event.step_id
243
+ return f"command-event-{event.id}"
244
+
245
+ for event in events:
246
+ if event.type not in {"command.started", "command.succeeded", "command.failed"}:
247
+ continue
248
+
249
+ key = get_key(event)
250
+ if key not in chain:
251
+ chain[key] = {
252
+ "command_id": event.event_metadata.get("command_id") if isinstance(event.event_metadata.get("command_id"), str) else None,
253
+ "step_id": event.step_id or key,
254
+ "parent_step_id": event.parent_step_id,
255
+ "command": str(event.event_metadata.get("command") or "unknown command"),
256
+ "status": event.status or "observed",
257
+ "event_ids": [],
258
+ "started_at": None,
259
+ "completed_at": None,
260
+ "duration_ms": None,
261
+ "cwd": event.event_metadata.get("cwd"),
262
+ "exit_code": event.event_metadata.get("exit_code"),
263
+ "output_summary": None,
264
+ "stdout_summary": None,
265
+ "stderr_summary": None,
266
+ "error_summary": None,
267
+ "error_code": None,
268
+ "_first_seq": event.seq,
269
+ }
270
+ order.append(key)
271
+
272
+ item = chain[key]
273
+ item["event_ids"].append(event.id)
274
+ item["parent_step_id"] = event.parent_step_id or item["parent_step_id"]
275
+ item["cwd"] = event.event_metadata.get("cwd") or item["cwd"]
276
+ item["exit_code"] = event.event_metadata.get("exit_code") if event.event_metadata.get("exit_code") is not None else item["exit_code"]
277
+ item["command"] = str(event.event_metadata.get("command") or item["command"])
278
+ item["output_summary"] = event.event_metadata.get("output_summary") or item["output_summary"]
279
+ item["stdout_summary"] = event.event_metadata.get("stdout_summary") or item["stdout_summary"]
280
+ item["stderr_summary"] = event.event_metadata.get("stderr_summary") or item["stderr_summary"]
281
+
282
+ if event.type == "command.started":
283
+ item["started_at"] = event.timestamp
284
+ item["status"] = "running"
285
+ elif event.type == "command.succeeded":
286
+ item["completed_at"] = event.timestamp
287
+ item["duration_ms"] = event.duration_ms
288
+ item["status"] = "succeeded"
289
+ item["output_summary"] = event.event_metadata.get("output_summary") or event.summary
290
+ elif event.type == "command.failed":
291
+ item["completed_at"] = event.timestamp
292
+ item["duration_ms"] = event.duration_ms
293
+ item["status"] = "failed"
294
+ item["error_summary"] = event.summary
295
+ item["error_code"] = event.error_code
296
+
297
+ ordered = sorted((chain[key] for key in order), key=lambda item: item["_first_seq"])
298
+ for item in ordered:
299
+ item.pop("_first_seq", None)
300
+ return ordered
301
+
302
+
303
+ def build_file_activity(events: list[Event]) -> list[dict[str, Any]]:
304
+ items: list[dict[str, Any]] = []
305
+ for event in events:
306
+ if event.type not in {"file.read", "file.written"}:
307
+ continue
308
+ items.append(
309
+ {
310
+ "step_id": event.step_id or f"file-{event.id}",
311
+ "parent_step_id": event.parent_step_id,
312
+ "event_id": event.id,
313
+ "event_type": event.type,
314
+ "path": str(event.event_metadata.get("path") or "unknown"),
315
+ "status": event.status or "observed",
316
+ "change_type": event.event_metadata.get("change_type"),
317
+ "summary": event.summary,
318
+ "content_hash": event.event_metadata.get("content_hash"),
319
+ "before_hash": event.event_metadata.get("before_hash"),
320
+ "after_hash": event.event_metadata.get("after_hash"),
321
+ "diff_summary": event.event_metadata.get("diff_summary"),
322
+ "line_additions": event.event_metadata.get("line_additions"),
323
+ "line_deletions": event.event_metadata.get("line_deletions"),
324
+ "extension": event.event_metadata.get("extension"),
325
+ "size_bytes": event.event_metadata.get("size_bytes"),
326
+ "timestamp": event.timestamp,
327
+ "_first_seq": event.seq,
328
+ }
329
+ )
330
+
331
+ items.sort(key=lambda item: item["_first_seq"])
332
+ for item in items:
333
+ item.pop("_first_seq", None)
334
+ return items
335
+
336
+
337
+ def build_patch_activity(events: list[Event]) -> list[dict[str, Any]]:
338
+ items: list[dict[str, Any]] = []
339
+ for event in events:
340
+ if event.type != "patch.applied":
341
+ continue
342
+ items.append(
343
+ {
344
+ "step_id": event.step_id or f"patch-{event.id}",
345
+ "parent_step_id": event.parent_step_id,
346
+ "event_id": event.id,
347
+ "path": str(event.event_metadata.get("path") or "unknown"),
348
+ "status": event.status or "observed",
349
+ "summary": event.summary,
350
+ "before_hash": event.event_metadata.get("before_hash"),
351
+ "after_hash": event.event_metadata.get("after_hash"),
352
+ "diff_summary": event.event_metadata.get("diff_summary"),
353
+ "line_additions": event.event_metadata.get("line_additions"),
354
+ "line_deletions": event.event_metadata.get("line_deletions"),
355
+ "timestamp": event.timestamp,
356
+ "_first_seq": event.seq,
357
+ }
358
+ )
359
+
360
+ items.sort(key=lambda item: item["_first_seq"])
361
+ for item in items:
362
+ item.pop("_first_seq", None)
363
+ return items
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: trace-agent-server
3
+ Version: 0.0.1
4
+ Summary: TraceAgent FastAPI backend server.
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: trace-agent-sdk
7
+ Requires-Dist: fastapi<1.0.0,>=0.115.0
8
+ Requires-Dist: httpx<1.0.0,>=0.27.0
9
+ Requires-Dist: openai<2.0.0,>=1.52.0
10
+ Requires-Dist: pydantic<3.0.0,>=2.8.0
11
+ Requires-Dist: pydantic-settings<3.0.0,>=2.3.0
12
+ Requires-Dist: sqlalchemy<3.0.0,>=2.0.32
13
+ Requires-Dist: uvicorn[standard]<1.0.0,>=0.30.0
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest<9.0.0,>=8.3.2; extra == "dev"
16
+ Requires-Dist: pytest-cov<7.0.0,>=6.0.0; extra == "dev"
17
+ Provides-Extra: providers
18
+ Requires-Dist: anthropic>=0.42.0; extra == "providers"
19
+ Requires-Dist: google-generativeai>=0.8.0; extra == "providers"
20
+ Provides-Extra: langchain
21
+ Requires-Dist: trace-agent-langchain; extra == "langchain"
@@ -0,0 +1,25 @@
1
+ trace_agent_server/__init__.py,sha256=8OtsAoPv1fSWUJk_A6kAxK5A8XkP2yezekBA82aql3M,163
2
+ trace_agent_server/cli.py,sha256=WGoYgzV8ySTPn5vZgjtEu3c0lMdqrBBnNvXMBQ-y7iU,846
3
+ trace_agent_server/config.py,sha256=SMN2dXroF_1yqZpGumsK2UrCTPwyZpmkeBFVkJLrzfk,1825
4
+ trace_agent_server/db.py,sha256=p3FOQbg43ydzCIoqwZokw1aKut7qmgEX-ZvmIapgh80,862
5
+ trace_agent_server/main.py,sha256=ZNBU3U_omxlY-45rAQkTdtuw2PpF7rjidex_F8Kfkdc,2661
6
+ trace_agent_server/models.py,sha256=nMMgt7GzDcd39YSFdQ5IYdjziT_FTPw_xHeVu5mSaQY,7390
7
+ trace_agent_server/schemas.py,sha256=wICi9ckgnNYHGwipDCelO199LuQ_EmDG_j_JAf7OmSQ,14099
8
+ trace_agent_server/api/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
9
+ trace_agent_server/api/runs.py,sha256=tHiWTXGolpS6R55Og1pIYhs9J8m3-DdicT8jqxd-kL0,48921
10
+ trace_agent_server/services/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
11
+ trace_agent_server/services/analytics.py,sha256=yFO5dubvXZABy0usMXCD9KFl2i2sy-iZlPPK4DOb3H4,11919
12
+ trace_agent_server/services/audit_guardrails.py,sha256=mH8qwnzBNunwaRqkItZPW188jo4x42tKKiJNBQx8IGo,11498
13
+ trace_agent_server/services/compare.py,sha256=rU4h4PlfgkM_FPh_WY3TSKZiYBT5-zSOi3Cn7pZl3Pk,37427
14
+ trace_agent_server/services/dashboard.py,sha256=nKzG6YFp4GOX99rJBSMXBc7BJmrOLMipJIVySNhXBpY,4425
15
+ trace_agent_server/services/events.py,sha256=sxRJEn5XXpz6QHauQiYgew1kYXnpsvxxN-c316_wRdw,3433
16
+ trace_agent_server/services/explanation.py,sha256=fRRmj049Qd6OxQ7TW6TJ4nKnmQ_U9dua1EtKMp0liHs,9702
17
+ trace_agent_server/services/graph.py,sha256=Gh3zftczcH9MfUd9njIT45Baye1DE1nNC7lLW-jFPQk,3629
18
+ trace_agent_server/services/observability.py,sha256=SNiQsFe7qP1r3n_F9dNUX-QQLrcQ0tkwbkCsX7kAaB4,5529
19
+ trace_agent_server/services/provider.py,sha256=7ju-Yego3P41ymagEE_0-hmUb7GBjyArJrhv45jhiZk,20654
20
+ trace_agent_server/services/run_summary.py,sha256=_-ZODRjQ0tY9kqqraFP6Q_bh-WWH4pbXkHMWEileQXU,15712
21
+ trace_agent_server-0.0.1.dist-info/METADATA,sha256=0BMGEYX_lbi0X2ppoeds5Lso7aXcJ5pf6Ro0B4Mg5Es,805
22
+ trace_agent_server-0.0.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
23
+ trace_agent_server-0.0.1.dist-info/entry_points.txt,sha256=F1tD0og_Z7am4b_XBNQq4qCAyS-ym6H1N9ent9aaFp0,67
24
+ trace_agent_server-0.0.1.dist-info/top_level.txt,sha256=JILkkFzbLfzlFmsbVBlBA2hOjFlJKbKgcWNIeG-IkM0,19
25
+ trace_agent_server-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ trace-agent-server = trace_agent_server.cli:main
@@ -0,0 +1 @@
1
+ trace_agent_server