feat: report tool calls on $ai_generation - #3
Merged
marandaneto merged 4 commits intoAug 11, 2026
Merged
Conversation
marandaneto
reviewed
Aug 7, 2026
Member
|
you can also check how https://github.com/PostHog/posthog-js/tree/main/packages/ai is done |
Member
make sure you verify your commits, the previous one as well |
marandaneto
marked this pull request as draft
August 7, 2026 09:29
Member
|
thanks for the PR @soukiassianb |
marandaneto
marked this pull request as ready for review
August 11, 2026 08:05
Tool calls were only emitted as $ai_span events. PostHog's ingestion extracts tool usage exclusively from $ai_generation, so the AI observability Tools view was empty for every OpenCode user despite the tool data being captured. Each generation now carries $ai_tools_called with the names of the tools that step called, in call order. Names are accumulated between step-start and step-finish, the same window the existing span parenting relies on. Spans are unchanged.
marandaneto
force-pushed
the
feat/report-tools-called-on-generation
branch
from
August 11, 2026 08:09
f7ce68e to
3464ef0
Compare
Member
|
Addressed the signing requirement as well: the original contributor commit was rewritten with a verified signature, and the merge plus review-fix commits are also verified. All commits currently on the PR branch now show verified signatures. |
marandaneto
approved these changes
Aug 11, 2026
marandaneto
enabled auto-merge (squash)
August 11, 2026 08:19
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
PostHog's AI observability Tools tab is empty for every OpenCode user of this plugin, even when the agent is calling tools constantly.
The plugin captures tool calls as
$ai_spanevents with$ai_span_nameset to the tool name. That's a documented use of spans and it makes the trace timeline render correctly. But PostHog extracts tool usage exclusively from$ai_generation— the ingestion pipeline's first branch is:So
$ai_tools_calledand$ai_tool_call_countare never set, and the Tools tab, Tool trends, and Tool co-occurrence have nothing to aggregate. The data is captured — it's just in a shape those views can't see.Measured on a project with heavy tool use: 0 of 169
$ai_generationevents carried tool data, while$ai_spanheld the complete tool breakdown.Changes
buildAiGenerationnow sets$ai_tools_calledto the names of the tools that step called, in call order:TraceStategainsstepToolCalls: string[], reset inhandleStepStartalongside the existing per-step state.handleToolPartrecords the tool name after emitting its span.buildAiGenerationemits a copy of the array, ornullwhen the step called no tools.Spans are untouched, so the trace tree keeps its current shape.
Why set
$ai_tools_calleddirectly rather than addingtool_callsto$ai_output_choices: ingestion respects a user-provided value and normalizes arrays, JSON-stringified arrays, and comma strings, deriving$ai_tool_call_countwhen absent. The plugin already knows the exact names, so reporting them is more robust than emitting a provider-shaped payload and relying on format detection.Privacy: names only, no arguments.
$ai_span_namealready carries tool names unredacted in privacy mode, so this exposes nothing new. Covered by a test.Compatibility: additive. A generation that called no tools sends
null, which ingestion treats exactly as the property being absent today.How did you test this code?
Automated tests, run locally:
pnpm test— 47 passed (42 pre-existing + 5 new).src/events.tsto its original content and re-ran. Exactly the 5 new tests failed, withexpected undefined to deeply equal [ 'read', 'edit' ]and similar; all 42 pre-existing tests still passed. The tests fail for the intended reason rather than passing vacuously.pnpm typecheck— clean.pnpm lint— clean (one pre-existingno-await-in-loopwarning onsrc/index.test.ts:23, untouched here).The new integration test drives the plugin through a real two-step OpenCode event sequence and asserts step 1 reports
['read', 'edit']while step 2 reportsnull, and that the tool spans are parented to the same generation the names were attributed to — so both views of a step agree. The unit tests cover call order, the empty case, privacy mode, and that the array is copied so a later step can't mutate an already-captured event.That the names then reach the Tools tab follows from the ingestion path quoted above; I verified the plugin side, not a live round-trip.
One assumption is load-bearing and worth naming: tool parts arrive between
step-startandstep-finish. It isn't new —buildAiSpanalready parents every tool span totrace.currentGenerationSpanId, which holds only under the same ordering, and the existinghandleStepStartcomments state it outright. The integration test now encodes it, so a future change to that ordering fails loudly instead of silently misattributing.Checklist
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Found while investigating why a production OpenCode agent's tool usage wasn't reaching PostHog's Tools tab: confirmed events were flowing, found no tool fields on
$ai_generation, traced that tobuildAiGeneration, then read PostHog's ingestion extractor to establish what it looks for. Addingtool_callsto$ai_output_choiceswould also work —extract-tool-calls.tshandles that shape — but it leans on format detection, so setting$ai_tools_calleddirectly won.Written by Claude Opus 5 in Claude Code, directed by @soukiassianb.