agentprof 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.
- agentprof/__init__.py +7 -0
- agentprof/adapters/__init__.py +15 -0
- agentprof/adapters/base.py +79 -0
- agentprof/adapters/claude_code/__init__.py +3 -0
- agentprof/adapters/claude_code/adapter.py +133 -0
- agentprof/adapters/claude_code/discovery.py +79 -0
- agentprof/adapters/claude_code/prompts.py +44 -0
- agentprof/adapters/claude_code/tools.py +102 -0
- agentprof/adapters/claude_code/transcript.py +264 -0
- agentprof/adapters/claude_code/tree.py +492 -0
- agentprof/adapters/codex/__init__.py +3 -0
- agentprof/adapters/codex/adapter.py +164 -0
- agentprof/adapters/codex/discovery.py +117 -0
- agentprof/adapters/codex/prompts.py +16 -0
- agentprof/adapters/codex/rollout.py +328 -0
- agentprof/adapters/codex/tools.py +97 -0
- agentprof/adapters/codex/tree.py +311 -0
- agentprof/adapters/copilot_vscode/__init__.py +3 -0
- agentprof/adapters/copilot_vscode/adapter.py +166 -0
- agentprof/adapters/copilot_vscode/debuglog.py +136 -0
- agentprof/adapters/copilot_vscode/discovery.py +153 -0
- agentprof/adapters/copilot_vscode/session.py +281 -0
- agentprof/adapters/copilot_vscode/tools.py +127 -0
- agentprof/adapters/copilot_vscode/transcript.py +176 -0
- agentprof/adapters/copilot_vscode/tree.py +325 -0
- agentprof/adapters/execution.py +64 -0
- agentprof/adapters/timestamps.py +13 -0
- agentprof/adapters/turns.py +24 -0
- agentprof/analysis/__init__.py +3 -0
- agentprof/analysis/agent_summary.py +161 -0
- agentprof/analysis/call_context.py +101 -0
- agentprof/analysis/evidence_findings.py +151 -0
- agentprof/analysis/execution.py +39 -0
- agentprof/analysis/heuristics.py +306 -0
- agentprof/analysis/pipeline.py +31 -0
- agentprof/analysis/rollup.py +196 -0
- agentprof/cli.py +141 -0
- agentprof/model.py +341 -0
- agentprof/pricing.json +23 -0
- agentprof/pricing.py +120 -0
- agentprof/registry.py +304 -0
- agentprof/server/__init__.py +3 -0
- agentprof/server/app.py +399 -0
- agentprof/server/openapi.py +31 -0
- agentprof/server/pricing_schema.py +40 -0
- agentprof/server/schemas.py +579 -0
- agentprof/server/static/assets/index-C4HEqUVf.css +1 -0
- agentprof/server/static/assets/index-ZsPn1H3d.js +58 -0
- agentprof/server/static/index.html +14 -0
- agentprof-0.1.0.dist-info/METADATA +177 -0
- agentprof-0.1.0.dist-info/RECORD +54 -0
- agentprof-0.1.0.dist-info/WHEEL +4 -0
- agentprof-0.1.0.dist-info/entry_points.txt +8 -0
- agentprof-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 epicodic
|
|
3
|
+
"""API response models and their mapping from the neutral model and registry rows."""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
from agentprof.analysis.agent_summary import AgentGroup, agent_groups, agent_ids
|
|
10
|
+
from agentprof.model import (
|
|
11
|
+
CallEvent,
|
|
12
|
+
CostMetric,
|
|
13
|
+
Diagnostics,
|
|
14
|
+
EventCallLink,
|
|
15
|
+
ExecutionEvent,
|
|
16
|
+
Finding,
|
|
17
|
+
LlmCall,
|
|
18
|
+
Metric,
|
|
19
|
+
Node,
|
|
20
|
+
NodeKind,
|
|
21
|
+
Provenance,
|
|
22
|
+
Session,
|
|
23
|
+
Tokens,
|
|
24
|
+
ToolInfo,
|
|
25
|
+
)
|
|
26
|
+
from agentprof.registry import RowState, SessionRow
|
|
27
|
+
|
|
28
|
+
_ACTIVE_KINDS = (NodeKind.TURN, NodeKind.AGENT)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class MetricOut(BaseModel):
|
|
32
|
+
value: float | None
|
|
33
|
+
provenance: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CostOut(BaseModel):
|
|
37
|
+
value: float | None
|
|
38
|
+
unit: str | None
|
|
39
|
+
usd: float | None
|
|
40
|
+
provenance: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TokensOut(BaseModel):
|
|
44
|
+
input: MetricOut
|
|
45
|
+
output: MetricOut
|
|
46
|
+
cache_read: MetricOut
|
|
47
|
+
cache_write: MetricOut
|
|
48
|
+
cache_write_5m: MetricOut
|
|
49
|
+
cache_write_1h: MetricOut
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ToolOut(BaseModel):
|
|
53
|
+
native_id: str
|
|
54
|
+
category: str
|
|
55
|
+
path: str | None
|
|
56
|
+
paths: list[str]
|
|
57
|
+
line_range: tuple[int, int] | None
|
|
58
|
+
command: str | None
|
|
59
|
+
writes_file: bool
|
|
60
|
+
target_agent_id: str | None
|
|
61
|
+
is_resume: bool
|
|
62
|
+
linked_agent_node_id: str | None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class LlmCallOut(BaseModel):
|
|
66
|
+
call_id: str | None
|
|
67
|
+
start: MetricOut
|
|
68
|
+
duration: MetricOut
|
|
69
|
+
model: str | None
|
|
70
|
+
tokens: TokensOut
|
|
71
|
+
cost: CostOut
|
|
72
|
+
in_context: bool
|
|
73
|
+
cost_parts: dict[str, CostOut]
|
|
74
|
+
price_prefix: str | None
|
|
75
|
+
gap: MetricOut
|
|
76
|
+
gap_basis: str | None
|
|
77
|
+
preceding_events: list["CallEventOut"]
|
|
78
|
+
cold_reason: str
|
|
79
|
+
cold_rewrite: bool
|
|
80
|
+
source_request_id: str | None = None
|
|
81
|
+
source_order: int | None = None
|
|
82
|
+
source_stream_id: str | None = None
|
|
83
|
+
timing_basis: Literal["request_start", "assistant_message", "usage_report", "turn_start", "unknown"] = "unknown"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class CallEventOut(BaseModel):
|
|
87
|
+
kind: str
|
|
88
|
+
event_id: str
|
|
89
|
+
start: MetricOut
|
|
90
|
+
end: MetricOut
|
|
91
|
+
duration: MetricOut
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class EventCallLinkOut(BaseModel):
|
|
95
|
+
source_request_id: str
|
|
96
|
+
relation: Literal["requested_by", "consumed_by", "next_observed_call"]
|
|
97
|
+
evidence: Literal["recorded", "observed_order"]
|
|
98
|
+
owner_id: str | None = None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ExecutionEventOut(BaseModel):
|
|
102
|
+
kind: Literal[
|
|
103
|
+
"user_input",
|
|
104
|
+
"tool_start",
|
|
105
|
+
"tool_result",
|
|
106
|
+
"delegation",
|
|
107
|
+
"child_completion",
|
|
108
|
+
"completion",
|
|
109
|
+
"message",
|
|
110
|
+
"resume",
|
|
111
|
+
"compaction",
|
|
112
|
+
]
|
|
113
|
+
event_id: str | None = None
|
|
114
|
+
subject_node_id: str | None = None
|
|
115
|
+
start: MetricOut
|
|
116
|
+
source_order: int | None = None
|
|
117
|
+
source_stream_id: str | None = None
|
|
118
|
+
success: bool | None = None
|
|
119
|
+
links: list[EventCallLinkOut]
|
|
120
|
+
execution_start: MetricOut
|
|
121
|
+
execution_end: MetricOut
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class FindingOut(BaseModel):
|
|
125
|
+
heuristic_id: str
|
|
126
|
+
node_id: str
|
|
127
|
+
severity: str
|
|
128
|
+
message: str
|
|
129
|
+
evidence: dict[str, Any]
|
|
130
|
+
estimated_avoidable_cost: CostOut
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class NodeOut(BaseModel):
|
|
134
|
+
node_id: str
|
|
135
|
+
agent_id: int | None
|
|
136
|
+
harness_agent_id: str | None
|
|
137
|
+
active: bool
|
|
138
|
+
active_descendant: bool
|
|
139
|
+
activity: str | None
|
|
140
|
+
kind: str
|
|
141
|
+
topic: str
|
|
142
|
+
model: str | None
|
|
143
|
+
success: bool | None
|
|
144
|
+
tool: ToolOut | None
|
|
145
|
+
start: MetricOut
|
|
146
|
+
end: MetricOut
|
|
147
|
+
duration: MetricOut
|
|
148
|
+
user_wait: MetricOut
|
|
149
|
+
llm_call_count: MetricOut
|
|
150
|
+
tool_call_count: MetricOut
|
|
151
|
+
tokens: TokensOut
|
|
152
|
+
tokens_total: TokensOut
|
|
153
|
+
cost_total: CostOut
|
|
154
|
+
cost_own: CostOut
|
|
155
|
+
context_peak: MetricOut
|
|
156
|
+
compactions: list[MetricOut]
|
|
157
|
+
resume_times: list[MetricOut]
|
|
158
|
+
llm_calls: list[LlmCallOut]
|
|
159
|
+
execution_events: list[ExecutionEventOut] = Field(default_factory=list)
|
|
160
|
+
findings: list[FindingOut]
|
|
161
|
+
children: list["NodeOut"]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class DiagnosticsOut(BaseModel):
|
|
165
|
+
malformed_lines: int
|
|
166
|
+
unknown_tool_ids: dict[str, int]
|
|
167
|
+
warnings: list[str]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class MalformedLineDetailOut(BaseModel):
|
|
171
|
+
source_path: str
|
|
172
|
+
line_number: int
|
|
173
|
+
excerpt: str
|
|
174
|
+
error_category: str
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class DiagnosticsDetailOut(DiagnosticsOut):
|
|
178
|
+
malformed_line_details: list[MalformedLineDetailOut]
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class SessionOut(BaseModel):
|
|
182
|
+
id: str
|
|
183
|
+
agent: str
|
|
184
|
+
title: str
|
|
185
|
+
workspace: str | None
|
|
186
|
+
mtime: float
|
|
187
|
+
sources: list[str]
|
|
188
|
+
diagnostics: DiagnosticsOut
|
|
189
|
+
root: NodeOut
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class ProjectedNodeOut(NodeOut):
|
|
193
|
+
"""A tree node in a bounded response; children may be omitted by depth."""
|
|
194
|
+
|
|
195
|
+
children: list["ProjectedNodeOut"]
|
|
196
|
+
children_omitted: bool | None = None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class ProjectedSessionOut(BaseModel):
|
|
200
|
+
"""Optional top-level groups selected with the `fields` query parameter."""
|
|
201
|
+
|
|
202
|
+
id: str | None = None
|
|
203
|
+
agent: str | None = None
|
|
204
|
+
title: str | None = None
|
|
205
|
+
workspace: str | None = None
|
|
206
|
+
mtime: float | None = None
|
|
207
|
+
sources: list[str] | None = None
|
|
208
|
+
diagnostics: DiagnosticsOut | None = None
|
|
209
|
+
root: ProjectedNodeOut | None = None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class NodeDetailOut(BaseModel):
|
|
213
|
+
node_id: str
|
|
214
|
+
prompt: str
|
|
215
|
+
result: str
|
|
216
|
+
arguments: dict[str, Any]
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class SummaryOut(BaseModel):
|
|
220
|
+
id: str
|
|
221
|
+
agent: str
|
|
222
|
+
title: str
|
|
223
|
+
workspace: str | None
|
|
224
|
+
state: str
|
|
225
|
+
mtime: float
|
|
226
|
+
start_ms: float | None
|
|
227
|
+
last_activity_ms: float | None
|
|
228
|
+
end_ms: float | None
|
|
229
|
+
file_size: int | None
|
|
230
|
+
cost_total: CostOut | None
|
|
231
|
+
error: str | None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class ApiEndpointOut(BaseModel):
|
|
235
|
+
method: str
|
|
236
|
+
path: str
|
|
237
|
+
parameters: list[str]
|
|
238
|
+
description: str
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class ApiIndexOut(BaseModel):
|
|
242
|
+
endpoints: list[ApiEndpointOut]
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class AgentSummaryRowOut(BaseModel):
|
|
246
|
+
agent_id: int
|
|
247
|
+
harness_agent_id: str | None
|
|
248
|
+
parent_agent_id: int | None
|
|
249
|
+
topic: str
|
|
250
|
+
models: list[str]
|
|
251
|
+
llm_calls: MetricOut
|
|
252
|
+
context_peak: MetricOut
|
|
253
|
+
tokens: TokensOut
|
|
254
|
+
own_cost: CostOut
|
|
255
|
+
subtree_cost: CostOut
|
|
256
|
+
start: MetricOut
|
|
257
|
+
end: MetricOut
|
|
258
|
+
cost_parts: dict[str, CostOut]
|
|
259
|
+
cache_ttl: str
|
|
260
|
+
cold_rewrites: MetricOut
|
|
261
|
+
cold_rewrite_cost: CostOut
|
|
262
|
+
longest_gap: MetricOut
|
|
263
|
+
findings_count: int
|
|
264
|
+
resume_count: int
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class AgentSummaryOut(BaseModel):
|
|
268
|
+
schema_version: int
|
|
269
|
+
id: str
|
|
270
|
+
mtime: float
|
|
271
|
+
agents: list[AgentSummaryRowOut]
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def metric_out(metric: Metric) -> MetricOut:
|
|
275
|
+
return MetricOut(value=metric.value, provenance=metric.provenance.value)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def cost_out(cost: CostMetric) -> CostOut:
|
|
279
|
+
return CostOut(value=cost.value, unit=cost.unit, usd=cost.usd, provenance=cost.provenance.value)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def tokens_out(tokens: Tokens) -> TokensOut:
|
|
283
|
+
return TokensOut(
|
|
284
|
+
input=metric_out(tokens.input),
|
|
285
|
+
output=metric_out(tokens.output),
|
|
286
|
+
cache_read=metric_out(tokens.cache_read),
|
|
287
|
+
cache_write=metric_out(tokens.cache_write),
|
|
288
|
+
cache_write_5m=metric_out(tokens.cache_write_5m),
|
|
289
|
+
cache_write_1h=metric_out(tokens.cache_write_1h),
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _tool_out(tool: ToolInfo) -> ToolOut:
|
|
294
|
+
return ToolOut(
|
|
295
|
+
native_id=tool.native_id,
|
|
296
|
+
category=tool.category.value,
|
|
297
|
+
path=tool.path,
|
|
298
|
+
paths=list(tool.paths),
|
|
299
|
+
line_range=tool.line_range,
|
|
300
|
+
command=tool.command,
|
|
301
|
+
writes_file=tool.writes_file,
|
|
302
|
+
target_agent_id=tool.target_agent_id,
|
|
303
|
+
is_resume=tool.is_resume,
|
|
304
|
+
linked_agent_node_id=tool.linked_agent_node_id,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _llm_call_out(call: LlmCall) -> LlmCallOut:
|
|
309
|
+
return LlmCallOut(
|
|
310
|
+
call_id=call.call_id,
|
|
311
|
+
start=metric_out(call.start),
|
|
312
|
+
duration=metric_out(call.duration),
|
|
313
|
+
model=call.model,
|
|
314
|
+
tokens=tokens_out(call.tokens),
|
|
315
|
+
cost=cost_out(call.cost),
|
|
316
|
+
in_context=call.in_context,
|
|
317
|
+
cost_parts={kind: cost_out(part) for kind, part in call.cost_parts.items()},
|
|
318
|
+
price_prefix=call.price_prefix,
|
|
319
|
+
gap=metric_out(call.gap),
|
|
320
|
+
gap_basis=call.gap_basis,
|
|
321
|
+
preceding_events=[_call_event_out(event) for event in call.preceding_events],
|
|
322
|
+
cold_reason=call.cold_reason,
|
|
323
|
+
cold_rewrite=call.cold_rewrite,
|
|
324
|
+
source_request_id=call.source_request_id,
|
|
325
|
+
source_order=call.source_order,
|
|
326
|
+
source_stream_id=call.source_stream_id,
|
|
327
|
+
timing_basis=call.timing_basis,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _event_call_link_out(link: EventCallLink) -> EventCallLinkOut:
|
|
332
|
+
return EventCallLinkOut(
|
|
333
|
+
source_request_id=link.source_request_id,
|
|
334
|
+
relation=link.relation,
|
|
335
|
+
evidence=link.evidence,
|
|
336
|
+
owner_id=link.owner_id,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _execution_event_out(event: ExecutionEvent) -> ExecutionEventOut:
|
|
341
|
+
return ExecutionEventOut(
|
|
342
|
+
kind=event.kind,
|
|
343
|
+
event_id=event.event_id,
|
|
344
|
+
subject_node_id=event.subject_node_id,
|
|
345
|
+
start=metric_out(event.start),
|
|
346
|
+
source_order=event.source_order,
|
|
347
|
+
source_stream_id=event.source_stream_id,
|
|
348
|
+
success=event.success,
|
|
349
|
+
links=[_event_call_link_out(link) for link in event.links],
|
|
350
|
+
execution_start=metric_out(event.execution_start),
|
|
351
|
+
execution_end=metric_out(event.execution_end),
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _call_event_out(event: CallEvent) -> CallEventOut:
|
|
356
|
+
return CallEventOut(
|
|
357
|
+
kind=event.kind,
|
|
358
|
+
event_id=event.event_id,
|
|
359
|
+
start=metric_out(event.start),
|
|
360
|
+
end=metric_out(event.end),
|
|
361
|
+
duration=metric_out(event.duration),
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def finding_out(finding: Finding) -> FindingOut:
|
|
366
|
+
return FindingOut(
|
|
367
|
+
heuristic_id=finding.heuristic_id,
|
|
368
|
+
node_id=finding.node_id,
|
|
369
|
+
severity=finding.severity,
|
|
370
|
+
message=finding.message,
|
|
371
|
+
evidence=finding.evidence,
|
|
372
|
+
estimated_avoidable_cost=cost_out(finding.estimated_avoidable_cost),
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def activity_flags(root: Node, frontier: float | None) -> dict[int, tuple[bool, bool]]:
|
|
377
|
+
"""Compute activity over the complete neutral tree for bounded projections."""
|
|
378
|
+
flags: dict[int, tuple[bool, bool]] = {}
|
|
379
|
+
|
|
380
|
+
def visit(node: Node) -> bool:
|
|
381
|
+
descendant = any([visit(child) for child in node.children])
|
|
382
|
+
active = (
|
|
383
|
+
node.kind is not NodeKind.SESSION
|
|
384
|
+
and node.start.value is not None
|
|
385
|
+
and node.end.provenance is not Provenance.EXACT
|
|
386
|
+
and (frontier is None or node.end.value is None or node.end.number() >= frontier)
|
|
387
|
+
)
|
|
388
|
+
flags[id(node)] = (active, descendant)
|
|
389
|
+
return (node.kind in _ACTIVE_KINDS and active) or descendant
|
|
390
|
+
|
|
391
|
+
visit(root)
|
|
392
|
+
return flags
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def node_out(
|
|
396
|
+
node: Node,
|
|
397
|
+
agent_ids: dict[str, int] | None = None,
|
|
398
|
+
owning_agent_id: int = 1,
|
|
399
|
+
frontier: float | None = None,
|
|
400
|
+
max_depth: int | None = None,
|
|
401
|
+
activity_flags: dict[int, tuple[bool, bool]] | None = None,
|
|
402
|
+
) -> NodeOut:
|
|
403
|
+
"""The node and its subtree, without prompt, result and tool-argument texts.
|
|
404
|
+
|
|
405
|
+
`frontier` is the session's latest known timestamp (the root's rolled-up end); a node whose own end falls
|
|
406
|
+
short of it has already been overtaken by later activity elsewhere and is therefore not active, even when
|
|
407
|
+
that end is not `EXACT` (e.g. an adapter that never recorded one for a completed node).
|
|
408
|
+
"""
|
|
409
|
+
agent_ids = agent_ids or {}
|
|
410
|
+
agent_id = (
|
|
411
|
+
agent_ids.get(node.agent_uuid or node.node_id, owning_agent_id)
|
|
412
|
+
if node.kind is NodeKind.AGENT
|
|
413
|
+
else owning_agent_id
|
|
414
|
+
)
|
|
415
|
+
children = (
|
|
416
|
+
[
|
|
417
|
+
node_out(
|
|
418
|
+
child,
|
|
419
|
+
agent_ids,
|
|
420
|
+
agent_id,
|
|
421
|
+
frontier,
|
|
422
|
+
None if max_depth is None else max_depth - 1,
|
|
423
|
+
activity_flags,
|
|
424
|
+
)
|
|
425
|
+
for child in node.children
|
|
426
|
+
]
|
|
427
|
+
if max_depth != 0
|
|
428
|
+
else []
|
|
429
|
+
)
|
|
430
|
+
active = (
|
|
431
|
+
node.kind is not NodeKind.SESSION
|
|
432
|
+
and node.start.value is not None
|
|
433
|
+
and node.end.provenance is not Provenance.EXACT
|
|
434
|
+
and (frontier is None or node.end.value is None or node.end.number() >= frontier)
|
|
435
|
+
)
|
|
436
|
+
# A tool node is never itself an activity indicator, but a sub-agent nested under one still counts.
|
|
437
|
+
active_descendant = any(
|
|
438
|
+
(child.kind in _ACTIVE_KINDS and child.active) or child.active_descendant for child in children
|
|
439
|
+
)
|
|
440
|
+
if activity_flags is not None:
|
|
441
|
+
active, active_descendant = activity_flags[id(node)]
|
|
442
|
+
activity = (
|
|
443
|
+
"waiting"
|
|
444
|
+
if node.kind in _ACTIVE_KINDS and active_descendant
|
|
445
|
+
else "running"
|
|
446
|
+
if node.kind in _ACTIVE_KINDS and active
|
|
447
|
+
else None
|
|
448
|
+
)
|
|
449
|
+
return NodeOut(
|
|
450
|
+
node_id=node.node_id,
|
|
451
|
+
agent_id=agent_id,
|
|
452
|
+
harness_agent_id=node.agent_uuid if node.kind is NodeKind.AGENT else None,
|
|
453
|
+
active=active,
|
|
454
|
+
active_descendant=active_descendant,
|
|
455
|
+
activity=activity,
|
|
456
|
+
kind=node.kind.value,
|
|
457
|
+
topic=node.topic,
|
|
458
|
+
model=node.model,
|
|
459
|
+
success=node.success,
|
|
460
|
+
tool=_tool_out(node.tool) if node.tool is not None else None,
|
|
461
|
+
start=metric_out(node.start),
|
|
462
|
+
end=metric_out(node.end),
|
|
463
|
+
duration=metric_out(node.duration),
|
|
464
|
+
user_wait=metric_out(node.user_wait),
|
|
465
|
+
llm_call_count=metric_out(node.llm_call_count),
|
|
466
|
+
tool_call_count=metric_out(node.tool_call_count),
|
|
467
|
+
tokens=tokens_out(node.tokens),
|
|
468
|
+
tokens_total=tokens_out(node.tokens_total),
|
|
469
|
+
cost_total=cost_out(node.cost_total),
|
|
470
|
+
cost_own=cost_out(node.cost_own),
|
|
471
|
+
context_peak=metric_out(node.context_peak),
|
|
472
|
+
compactions=[metric_out(compaction) for compaction in node.compactions],
|
|
473
|
+
resume_times=[metric_out(point) for point in node.resume_times],
|
|
474
|
+
llm_calls=[_llm_call_out(call) for call in node.llm_calls],
|
|
475
|
+
execution_events=[_execution_event_out(event) for event in node.execution_events],
|
|
476
|
+
findings=[finding_out(finding) for finding in node.findings],
|
|
477
|
+
children=children,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def diagnostics_out(diagnostics: Diagnostics) -> DiagnosticsOut:
|
|
482
|
+
return DiagnosticsOut(
|
|
483
|
+
malformed_lines=diagnostics.malformed_lines,
|
|
484
|
+
unknown_tool_ids=diagnostics.unknown_tool_ids,
|
|
485
|
+
warnings=diagnostics.warnings,
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def diagnostics_detail_out(diagnostics: Diagnostics) -> DiagnosticsDetailOut:
|
|
490
|
+
"""Map diagnostic counts and bounded source-line details for the dedicated endpoint."""
|
|
491
|
+
return DiagnosticsDetailOut(
|
|
492
|
+
malformed_lines=diagnostics.malformed_lines,
|
|
493
|
+
unknown_tool_ids=diagnostics.unknown_tool_ids,
|
|
494
|
+
warnings=diagnostics.warnings,
|
|
495
|
+
malformed_line_details=[
|
|
496
|
+
MalformedLineDetailOut(
|
|
497
|
+
source_path=detail.source_path,
|
|
498
|
+
line_number=detail.line_number,
|
|
499
|
+
excerpt=detail.excerpt,
|
|
500
|
+
error_category=detail.error_category,
|
|
501
|
+
)
|
|
502
|
+
for detail in diagnostics.malformed_line_details
|
|
503
|
+
],
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def session_out(session: Session, mtime: float) -> SessionOut:
|
|
508
|
+
return SessionOut(
|
|
509
|
+
id=session.id,
|
|
510
|
+
agent=session.agent,
|
|
511
|
+
title=session.title,
|
|
512
|
+
workspace=session.workspace,
|
|
513
|
+
mtime=mtime,
|
|
514
|
+
sources=session.sources,
|
|
515
|
+
diagnostics=diagnostics_out(session.diagnostics),
|
|
516
|
+
root=node_out(session.root, agent_ids(session.root), frontier=session.root.end.value),
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def agent_summary_row_out(group: AgentGroup) -> AgentSummaryRowOut:
|
|
521
|
+
return AgentSummaryRowOut(
|
|
522
|
+
agent_id=group.agent_id,
|
|
523
|
+
harness_agent_id=group.harness_agent_id,
|
|
524
|
+
parent_agent_id=group.parent_agent_id,
|
|
525
|
+
topic=group.topic,
|
|
526
|
+
models=group.models,
|
|
527
|
+
llm_calls=metric_out(group.llm_calls),
|
|
528
|
+
context_peak=metric_out(group.context_peak),
|
|
529
|
+
tokens=tokens_out(group.tokens),
|
|
530
|
+
own_cost=cost_out(group.own_cost),
|
|
531
|
+
subtree_cost=cost_out(group.subtree_cost),
|
|
532
|
+
start=metric_out(group.start),
|
|
533
|
+
end=metric_out(group.end),
|
|
534
|
+
cost_parts={kind: cost_out(part) for kind, part in group.cost_parts.items()},
|
|
535
|
+
cache_ttl=group.cache_ttl,
|
|
536
|
+
cold_rewrites=metric_out(group.cold_rewrites),
|
|
537
|
+
cold_rewrite_cost=cost_out(group.cold_rewrite_cost),
|
|
538
|
+
longest_gap=metric_out(group.longest_gap),
|
|
539
|
+
findings_count=group.findings_count,
|
|
540
|
+
resume_count=group.resume_count,
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def agent_summary_out(session: Session, mtime: float) -> AgentSummaryOut:
|
|
545
|
+
return AgentSummaryOut(
|
|
546
|
+
schema_version=1,
|
|
547
|
+
id=session.id,
|
|
548
|
+
mtime=mtime,
|
|
549
|
+
agents=[agent_summary_row_out(group) for group in agent_groups(session.root)],
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def node_detail_out(node: Node) -> NodeDetailOut:
|
|
554
|
+
return NodeDetailOut(
|
|
555
|
+
node_id=node.node_id,
|
|
556
|
+
prompt=node.prompt,
|
|
557
|
+
result=node.result,
|
|
558
|
+
arguments=node.tool.arguments if node.tool is not None else {},
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def summary_out(row: SessionRow) -> SummaryOut:
|
|
563
|
+
"""A list row: the native id as title until summarised, the summary (possibly outdated) afterwards."""
|
|
564
|
+
summary = row.summary
|
|
565
|
+
state = row.state
|
|
566
|
+
return SummaryOut(
|
|
567
|
+
id=row.ref.id,
|
|
568
|
+
agent=row.ref.agent,
|
|
569
|
+
title=summary.title if summary is not None else row.ref.native_id,
|
|
570
|
+
workspace=summary.workspace if summary is not None else None,
|
|
571
|
+
state=state.value,
|
|
572
|
+
mtime=row.ref.mtime,
|
|
573
|
+
start_ms=summary.start_ms if summary is not None else None,
|
|
574
|
+
last_activity_ms=summary.last_activity_ms if summary is not None else None,
|
|
575
|
+
end_ms=summary.end_ms if summary is not None else None,
|
|
576
|
+
file_size=summary.file_size if summary is not None else None,
|
|
577
|
+
cost_total=cost_out(summary.cost_total) if summary is not None else None,
|
|
578
|
+
error=row.error if state is RowState.ERROR else None,
|
|
579
|
+
)
|