import { CAPABILITY_SEMANTIC_TOOL_CATALOG } from "../../vendor/paperclip-runner/index.js"; import type { NativeRunTrace } from "./native-run-trace.js"; const SAFE_OPERATIONS = new Set(CAPABILITY_SEMANTIC_TOOL_CATALOG.map((tool) => tool.operationId)); function operation(value: unknown): string { const name = typeof value === "string" ? value.replace(/^mcp__paperclip__/, "") : ""; return SAFE_OPERATIONS.has(name) ? name : "other"; } /** Provider stream IDs and MCP request IDs are separate namespaces. Never join by name or order. */ export function createNativeToolTrace(trace: NativeRunTrace, now = Date.now) { let firstCompletion = false; const announced = new Map(); return { observe(event: { eventType: string; emittedAt: string; payload: unknown }): void | Promise { if (["turn.started", "turn.completed", "turn.failed", "turn.cancelled"].includes(event.eventType)) { announced.clear(); firstCompletion = false; return; } if (!event.eventType.startsWith("tool.execution.")) return; const payload = event.payload as { executionId?: unknown; name?: unknown; inputUpdated?: unknown } | null; const id = payload?.executionId; const time = Date.parse(event.emittedAt); if (typeof id !== "string" || !id || !Number.isFinite(time)) return; if (event.eventType === "tool.execution.started") { if (announced.has(id)) return; if (announced.size >= 512) announced.delete(announced.keys().next().value!); announced.set(id, { startedAtMs: time, operation: operation(payload?.name) }); if (!firstCompletion && typeof payload?.name === "string" && /^(?:mcp__paperclip__)?paperclip_(?:finish|block)$/.test(payload.name)) { firstCompletion = true; return trace.record({name:"completion.request.first",parentName:"agent.turn",startedAtMs:time,endedAtMs:time}); } return; } const pending = announced.get(id); if (!pending) return; if (payload?.inputUpdated === true && time >= pending.startedAtMs) { pending.lastInputAtMs = Math.max(pending.lastInputAtMs ?? pending.startedAtMs, time); } if (event.eventType !== "tool.execution.completed") return; announced.delete(id); if (pending.lastInputAtMs === undefined) return; // Measures observed argument streaming, not an inferred execution-start boundary. return trace.record({ name: "tool.request.input_stream", parentName: "agent.turn", startedAtMs: pending.startedAtMs, endedAtMs: pending.lastInputAtMs, attributes: { operation: pending.operation }, }); }, async execute(call: { callId: string; tool: string }, work: () => Promise): Promise { const scope = trace.start("tool.execute", { parentName: "agent.turn", startedAtMs: now(), attributes: { operation: operation(call.tool) }, }); let outcome: "ok" | "failed" = "ok"; try { return await trace.run(scope, work); } catch (error) { outcome = "failed"; throw error; } finally { await trace.end(scope, { endedAtMs: now(), outcome }); } }, }; } export type NativeToolTrace = ReturnType;