Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/event-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,26 @@ function generateUUID(): string {
return crypto.randomUUID()
}

function skillLoadMetadata(tool: string, args: unknown): Record<string, unknown> | undefined {
if (tool !== "skill") return undefined
const name =
args && typeof args === "object" && "name" in args && typeof args.name === "string"
? args.name
: undefined
return {
skill_name: name,
}
}

function skillToolSpanName(tool: string, title: string, args: unknown, fallback: string): string {
if (tool !== "skill") return fallback
const name =
args && typeof args === "object" && "name" in args && typeof args.name === "string"
? args.name
: undefined
return name ? `skill: ${name}` : title || "skill"
}

// State management for tracing
interface SessionState {
rootSpanId: string
Expand Down Expand Up @@ -311,6 +331,7 @@ export class EventProcessor {
// MCP tools return { content: [{ type: "text", text: "..." }] } instead of a plain
// output value, so extract the text from content[] as a last resort.
const rawOutput = capturedOutput !== undefined ? capturedOutput : extractToolOutput(output)
const formattedName = this.formatToolName(tool, title)
const toolSpan: SpanData = {
id: generateUUID(),
span_id: toolSpanId,
Expand All @@ -323,13 +344,14 @@ export class EventProcessor {
call_id: callID,
title,
reasoning: reasoning || undefined,
...skillLoadMetadata(tool, toolArgs),
},
metrics: {
start: startTime,
end: endTime,
},
span_attributes: {
name: this.formatToolName(tool, title),
name: skillToolSpanName(tool, title, toolArgs, formattedName),
type: "tool",
},
}
Expand Down
39 changes: 39 additions & 0 deletions src/tracing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,45 @@ describe("Event to Span Transformation", () => {
)
})

it("normalizes native skill tool calls as skill load spans", async () => {
const sessionId = "ses_skill_tool"
const messageId = "msg_1"

await assertEventsProduceTree(
session(
sessionId,
sessionCreated(sessionId),
chatMessage("Use the review skill"),
toolCallPart(sessionId, messageId, "call_skill", "skill", { name: "review" }),
toolExecute("call_skill", "skill", "review", { name: "review" }, "Loaded skill review"),
textPart(sessionId, messageId, "Loaded the review skill."),
messageCompleted(sessionId, messageId, { tokens: { input: 20, output: 15 } }),
sessionIdle(sessionId),
),
{
span_attributes: { name: "OpenCode: test-project", type: "task" },
children: [
{
span_attributes: { name: "Turn 1", type: "task" },
children: [
{
span_attributes: { name: "skill: review", type: "tool" },
metadata: {
tool_name: "skill",
call_id: "call_skill",
skill_name: "review",
},
},
{
span_attributes: { name: "anthropic/claude-3-haiku", type: "llm" },
},
],
},
],
},
)
})

it("session -> turn -> multiple tool calls", async () => {
const sessionId = "ses_multi_tool"
const messageId = "msg_1"
Expand Down
24 changes: 23 additions & 1 deletion src/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,26 @@ function generateUUID(): string {
return crypto.randomUUID()
}

function skillLoadMetadata(tool: string, args: unknown): Record<string, unknown> | undefined {
if (tool !== "skill") return undefined
const name =
args && typeof args === "object" && "name" in args && typeof args.name === "string"
? args.name
: undefined
return {
skill_name: name,
}
}

function skillToolSpanName(tool: string, title: string, args: unknown, fallback: string): string {
if (tool !== "skill") return fallback
const name =
args && typeof args === "object" && "name" in args && typeof args.name === "string"
? args.name
: undefined
return name ? `skill: ${name}` : title || "skill"
}

// State management for tracing
interface SessionState {
rootSpanId: string
Expand Down Expand Up @@ -1018,6 +1038,7 @@ export function createTracingHooks(
const rawOutput =
capturedOutput !== undefined ? capturedOutput : extractToolOutput(result.output ?? result)
const toolOutput = typeof rawOutput === "string" ? rawOutput.substring(0, 10000) : rawOutput
const formattedName = formatToolName(tool, result.title)
const toolSpan: SpanData = {
id: generateUUID(),
span_id: toolSpanId,
Expand All @@ -1030,13 +1051,14 @@ export function createTracingHooks(
call_id: callID,
title: result.title,
reasoning: reasoning || undefined,
...skillLoadMetadata(tool, toolArgs),
},
metrics: {
start: startTime,
end: endTime,
},
span_attributes: {
name: formatToolName(tool, result.title),
name: skillToolSpanName(tool, result.title, toolArgs, formattedName),
type: "tool",
},
}
Expand Down
Loading