fix(tool): record locally executed tools in OpenCode V2 - #134
pedropombeiro wants to merge 3 commits into
Conversation
In OpenCode V2 tool events, `executed` means the model provider ran the tool. Locally executed tools always report `executed: false`, so the plugin skipped their spans, duration histogram, and commit detection, and reported `duration_ms: 0` in `tool_result`. Treat a tool as executed when the plugin observes `session.tool.called`. Malformed-input rejections and calls cancelled before publication never emit that event, so they are still not recorded as executions. Record provider execution as the `tool.provider_executed` span attribute, and accept both the top-level `executed` field and the nested `provider.executed` shape.
|
Non-blocking test coverage suggestion: strengthen the disabled tool-tracing test in Add a local tool sequence (
This would protect the behavior changed here: execution metadata and command capture must happen before the tracing-disabled early return, so disabling tool spans does not also disable metrics or commit detection. |
…bled Send a complete local shell call with tool tracing disabled, and assert that the plugin creates no tool span but still records duration, the tool_result log, and the git commit counter and log.
|
Thanks @dialupdisaster 🏓 Good catch: the old test only sent I replaced it in 23a6c22 and 7ae54b2 with a full local
I confirmed the test fails if the tracing-disabled early return moves above the metadata capture in |
Description
With OpenCode V2, the plugin drops telemetry for every tool that OpenCode runs locally, such as
shell,read,edit, MCP tools, Code Mode, and subagents:opencode.tool.durationis never recorded.opencode.tool.*spans are created.opencode.commit.countand thecommitlog event never fire.tool_resultlog event reportsduration_ms: 0.Root cause: In V2 tool events,
executedmeans the model provider ran the tool, not that OpenCode ran it. OpenCode publishessession.tool.calledwithexecuted: tool.providerExecuted(publish-llm-event.ts#L458-L476). It then publishes the terminal event for a locally executed tool with the samefalsevalue (#L573-L591). Only provider-hosted tools, such as OpenAIweb_searchor Anthropiccode_execution, reportexecuted: true(#L477-L513).handleToolCalledandfinishToolgated spans, durations, and commit detection onexecuted, so the plugin measured only hosted tools.Fix: A tool counts as executed once the plugin observes
session.tool.called. OpenCode starts a local execution only after that event is published (step.ts#L100-L128). Calls that never ran don't publish it:#L315-L338).#L360-L375).Neither case is recorded as an execution.
session.tool.called, including a permission rejection or user decline (step.ts#L199-L207), is recorded as a failed execution (success=false). Its duration includes any permission wait.tool.provider_executedspan attribute. It accepts both the current top-levelexecutedand the nestedprovider.executedshape, which OpenCodedevuses (session-event.ts#L312-L372). That covers only the payload shape:devalso renames the events tosession.next.tool.*, which this PR doesn't address.session.tool.calledinstead of at the first progress event that carries a child session ID. Child-session linking is unchanged.Tests now cover local success and failure, local
git commitdetection, permission rejection, malformed input without a call, provider-hosted success and failure, the nestedprovider.executedshape, and a subagent that fails before creating a child. Two existing tests encoded the old assumption thatexecuted: falsemeant the tool didn't run, so I updated them.I also checked it end to end with OpenCode
2.0.20and a local OTLP/HTTP JSON sink. Areadcall produced a 33 ms span, histogram sample, andduration_ms. Ashellcall runningsleep 2recorded 2,834 ms. Agit committhroughshellincrementedopencode.commit.countand emitted thecommitlog event.Type of change
Checklist
bun run lintpasses with no errorsbun run check:jsdoc-coveragepasses with no errorsbun run typecheckpasses with no errorsbun testpasses with no errorsRelated issues
Independent of #112, which covers V1
message.part.updatedtimestamp restamping and orphan spans. This PR fixes V2 tool events that were never measured.Additional context
The
executedgating was introduced in #132, which treatedexecuted: falsecalls as invalid. That holds for malformed calls but also matches every successful local call.