MCPcopy
hub / github.com/IBM/AssetOpsBench / _build_trajectory_from_events

Function _build_trajectory_from_events

src/agent/opencode_agent/runner.py:423–564  ·  view source on GitHub ↗

Convert OpenCode events into the shared SDK-style trajectory shape. Text/tool parts are merged by part id, turns are split on OpenCode step-finish boundaries, and the canonical answer is scoped to the final assistant message instead of concatenating all intermediate narration.

(
    events: list[dict[str, Any]],
    plain_lines: list[str],
    *,
    duration_ms: float | None = None,
    stderr: str = "",
)

Source from the content-addressed store, hash-verified

421
422
423def _build_trajectory_from_events(
424 events: list[dict[str, Any]],
425 plain_lines: list[str],
426 *,
427 duration_ms: float | None = None,
428 stderr: str = "",
429) -> tuple[str, OpenCodeTrajectory]:
430 """Convert OpenCode events into the shared SDK-style trajectory shape.
431
432 Text/tool parts are merged by part id, turns are split on OpenCode
433 step-finish boundaries, and the canonical answer is scoped to the final
434 assistant message instead of concatenating all intermediate narration.
435 """
436 text_by_part: OrderedDict[str, str] = OrderedDict()
437 msg_by_part: dict[str, str] = {}
438 tool_calls: OrderedDict[str, ToolCall] = OrderedDict()
439
440 steps: list[dict[str, Any]] = []
441 pending_text: list[str] = []
442 pending_tools: list[str] = []
443
444 def _close_step(input_tokens: int, output_tokens: int) -> None:
445 if not (pending_text or pending_tools or input_tokens or output_tokens):
446 return
447 steps.append(
448 {
449 "text_ids": list(pending_text),
450 "tool_ids": list(pending_tools),
451 "input_tokens": input_tokens,
452 "output_tokens": output_tokens,
453 }
454 )
455 pending_text.clear()
456 pending_tools.clear()
457
458 for index, event in enumerate(events):
459 part = _candidate_part(event)
460 if part is None:
461 part = event
462
463 part_type = str(part.get("type") or part.get("kind") or "").lower()
464 part_id = str(
465 part.get("id")
466 or part.get("partID")
467 or part.get("messageID")
468 or f"event_{index}"
469 )
470
471 if _is_step_finish(event, part, part_type):
472 step_in, step_out = _usage_from_events([event])
473 _close_step(step_in, step_out)
474 continue
475
476 text_value = part.get("text") or part.get("content")
477 if isinstance(text_value, str) and _is_answer_text(part_type, part):
478 text_by_part[part_id] = _merge_text(
479 text_by_part.get(part_id, ""), text_value
480 )

Calls 13

_candidate_partFunction · 0.85
_is_step_finishFunction · 0.85
_usage_from_eventsFunction · 0.85
_close_stepFunction · 0.85
_is_answer_textFunction · 0.85
_merge_textFunction · 0.85
ToolCallClass · 0.85
_coerce_tool_inputFunction · 0.85
_final_answerFunction · 0.85
_visible_textFunction · 0.85
OpenCodeTrajectoryClass · 0.85
TurnRecordClass · 0.85