Skip to content

feat(extension): add tool runtime hooks#48

Merged
omarluq merged 2 commits into
mainfrom
feat/tool-runtime-hooks
May 25, 2026
Merged

feat(extension): add tool runtime hooks#48
omarluq merged 2 commits into
mainfrom
feat/tool-runtime-hooks

Conversation

@omarluq
Copy link
Copy Markdown
Owner

@omarluq omarluq commented May 25, 2026

No description provided.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7b50ed4c-fc86-4e38-a0fb-4f2b28d2c93d

📥 Commits

Reviewing files that changed from the base of the PR and between adca0b0 and 7825dd3.

📒 Files selected for processing (2)
  • internal/assistant/lifecycle.go
  • internal/assistant/tool_lifecycle_test.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Extensions can now mutate tool call arguments and tool results via lifecycle events.
  • Bug Fixes

    • Improved propagation and handling of errors during tool lifecycle processing.
    • Tool error events are dispatched consistently when a tool reports an error.
  • Refactor

    • Tool execution and lifecycle dispatching reorganized for clearer flow and consistent result handling.
  • Tests

    • Added tests validating tool-call/result mutations and error dispatching.

Walkthrough

The PR adds extension-driven tool-call and tool-result mutation support: callback signatures now accept context and return errors, the extension lifecycle can return tool mutations parsed from Lua, the runtime dispatches lifecycle events and applies mutations, and the tool loop is refactored to use these dispatch hooks; tests exercise end-to-end behavior.

Changes

Tool Lifecycle Dispatch and Extension Mutations

Layer / File(s) Summary
Tool Callback API Contract
internal/assistant/client.go, internal/assistant/export_test.go
CompletionRequest.OnToolCall and OnToolResult now accept context.Context, use pointer event parameters, and return error. Test export helpers DispatchToolCallLifecycleForTest and DispatchToolResultLifecycleForTest enable external tests to dispatch lifecycle events.
Extension System Tool Mutation Support
internal/extension/lifecycle.go, internal/extension/lifecycle_test.go
New ToolCallMutation and ToolResultMutation structs carry mutations from Lua handlers. LifecycleDispatchResult gains ToolCall and ToolResult fields. applyLifecycleLuaResult parses tool_call and tool_result keys from Lua returns and merges mutations; helpers apply argument overrides and update non-nil result fields. Tests verify merged mutations match extension handler outputs.
Assistant Runtime Tool Lifecycle Dispatch
internal/assistant/lifecycle.go
New dispatchToolCallLifecycle and dispatchToolResultLifecycle emit LifecycleToolCall/LifecycleToolResult events, dispatch to extensions, and apply returned mutations to tool call arguments and result fields via applyToolCallMutation/applyToolResultMutation.
Tool Loop Execution Refactoring
internal/assistant/tool_loop.go, internal/assistant/tool_loop_test.go
executeToolCalls delegates to executeOneToolCall for each call, orchestrating start/hook-dispatch/execute/result-hook/event-stream. Helpers centralize tool execution (runToolCall with error mapping and JSON encoding), error handling (toolLifecycleErrorEvent), and output construction (toolOutputForCall). Callbacks now accept context and pointer parameters; tests updated.
Runtime Integration and Payload Wiring
internal/assistant/runtime.go, internal/assistant/runtime_events.go, internal/assistant/runtime_events_test.go, internal/assistant/runtime_lifecycle_test.go, internal/assistant/runtime_test.go
modelCompletionRequest wires OnToolCall/OnToolResult to dispatchToolCallLifecycle/dispatchToolResultLifecycle. runtime_events.go removes emitToolCall/emitToolResult, adds toolCallPayload and toolEventPayload payload builders. Tests use shared testToolName and testToolArgsJSON constants for tool metadata consistency.
Tool Lifecycle Integration Tests
internal/assistant/tool_lifecycle_test.go
TestRuntime_ToolCallLifecycleAppliesArgumentMutation verifies tool-call argument mutation via Lua extensions; TestRuntime_ToolResultLifecycleAppliesResultMutation verifies tool-result field mutations (result, details, error); TestRuntime_ToolResultLifecycleDispatchesToolErrorHandlers verifies tool error dispatch. All dispatch through runtime helpers and assert mutated field values.

Sequence Diagram

sequenceDiagram
  participant Client as Client/executeToolCalls
  participant Hook as dispatchToolCallHook
  participant Dispatch as dispatchToolCallLifecycle
  participant Extensions as Lua Extensions
  participant Execute as runToolCall
  participant ResultHook as dispatchToolResultHook
  Client->>Hook: onToolCall(ctx, *ToolCallEvent)
  Hook->>Dispatch: delegate dispatch
  Dispatch->>Extensions: emit LifecycleToolCall, collect mutations
  Extensions->>Dispatch: return ToolCallMutation
  Dispatch->>Hook: apply mutation to event.Arguments
  Hook->>Execute: execute tool via registry
  Execute->>ResultHook: onToolResult(ctx, *ToolEvent)
  ResultHook->>Dispatch: delegate dispatch
  Dispatch->>Extensions: emit LifecycleToolResult, collect mutations
  Extensions->>Dispatch: return ToolResultMutation
  Dispatch->>ResultHook: apply mutations to event.Result/DetailsJSON/Error
  ResultHook->>Client: return final tool result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • omarluq/librecode#21: Introduces the foundational LifecycleToolCall and LifecycleToolResult event contracts in internal/extension/lifecycle.go that this PR builds upon for tool mutation dispatch.
  • omarluq/librecode#30: Modifies internal/assistant/runtime_events.go's tool event emission; this PR replaces those emit methods with dispatch-based lifecycle handling and payload helpers.
  • omarluq/librecode#32: Refactors internal/assistant/tool_loop.go's executeToolCalls; this PR reorganizes the same function to delegate to new dispatch helpers and lifecycle hooks.

Poem

🐰 A humble rabbit hops through tool-call lands,
Where extensions dance and mutations expand,
Arguments shift, results transform with grace,
Each lifecycle event finds its rightful place!
Context and pointers now guide the way,
Tool dispatch blooms in every loop today!

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to evaluate relevance to the changeset. Add a description explaining the purpose of tool runtime hooks, how they integrate with the extension system, and the rationale for the API changes to the callback signatures.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main feature: adding tool runtime hooks to the extension system with context and error handling capabilities.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tool-runtime-hooks

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 25, 2026

Codecov Report

❌ Patch coverage is 79.39394% with 34 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.65%. Comparing base (9f182f8) to head (7825dd3).

Files with missing lines Patch % Lines
internal/assistant/tool_loop.go 77.77% 13 Missing and 3 partials ⚠️
internal/assistant/lifecycle.go 73.33% 7 Missing and 5 partials ⚠️
internal/extension/lifecycle.go 86.95% 3 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #48      +/-   ##
==========================================
+ Coverage   61.55%   61.65%   +0.10%     
==========================================
  Files         173      173              
  Lines       17073    17186     +113     
==========================================
+ Hits        10509    10596      +87     
- Misses       5516     5535      +19     
- Partials     1048     1055       +7     
Flag Coverage Δ
unittests 61.65% <79.39%> (+0.10%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/assistant/tool_lifecycle_test.go (1)

13-75: ⚡ Quick win

Convert these lifecycle mutation tests to a table-driven structure.

These are core behavior cases, and table-driven form will make it easier to expand mutation scenarios and regressions without duplicating setup.

As per coding guidelines **/*_test.go: Prefer table-driven tests for core behavior and regression tests for terminal rendering bugs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/assistant/tool_lifecycle_test.go` around lines 13 - 75, Convert the
two tests into table-driven subtests: for
TestRuntime_ToolCallLifecycleAppliesArgumentMutation, create a slice of cases
each with a name, Lua extension string, initial call (assistant.ToolCallEvent)
and expected mutated fields, then loop cases with t.Run(case.name, func(t
*testing.T){ t.Parallel(); setup runtime/manager via newTestRuntimeWithManager,
loadRuntimeExtension(manager, case.lua), call
runtime.DispatchToolCallLifecycleForTest(...), require.NoError(t, err) and
assert expected Argument values and ArgumentsJSON using assert.Equal /
assert.JSONEq }); do the analogous table-driven refactor for
TestRuntime_ToolResultLifecycleAppliesResultMutation using cases that include
initial assistant.ToolEvent and expected Result, DetailsJSON and Error, invoking
runtime.DispatchToolResultLifecycleForTest and asserting expectations; reuse
existing helpers (newTestRuntimeWithManager, loadRuntimeExtension) and existing
assertion helpers (require.NoError, assert.Equal, assert.JSONEq) so adding new
cases only requires adding entries to the table.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/assistant/lifecycle.go`:
- Around line 270-272: When a tool error is detected (event.Error != ""),
dispatch the LifecycleToolError through the lifecycle dispatcher in addition to
(or instead of) the runtime event bus so extension handlers (lc.on("tool_error",
...)) are called; call the runtime lifecycle dispatcher method (e.g.,
runtime.lifecycle.Emit or runtime.dispatch) with the same ctx,
extension.LifecycleToolError and toolEventPayload(event) instead of relying only
on runtime.emit(ctx, ...). Ensure you use extension.LifecycleToolError and
toolEventPayload(event) as the type and payload so existing handlers pick it up.

---

Nitpick comments:
In `@internal/assistant/tool_lifecycle_test.go`:
- Around line 13-75: Convert the two tests into table-driven subtests: for
TestRuntime_ToolCallLifecycleAppliesArgumentMutation, create a slice of cases
each with a name, Lua extension string, initial call (assistant.ToolCallEvent)
and expected mutated fields, then loop cases with t.Run(case.name, func(t
*testing.T){ t.Parallel(); setup runtime/manager via newTestRuntimeWithManager,
loadRuntimeExtension(manager, case.lua), call
runtime.DispatchToolCallLifecycleForTest(...), require.NoError(t, err) and
assert expected Argument values and ArgumentsJSON using assert.Equal /
assert.JSONEq }); do the analogous table-driven refactor for
TestRuntime_ToolResultLifecycleAppliesResultMutation using cases that include
initial assistant.ToolEvent and expected Result, DetailsJSON and Error, invoking
runtime.DispatchToolResultLifecycleForTest and asserting expectations; reuse
existing helpers (newTestRuntimeWithManager, loadRuntimeExtension) and existing
assertion helpers (require.NoError, assert.Equal, assert.JSONEq) so adding new
cases only requires adding entries to the table.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f7d815bc-c558-45cd-a235-63ee1d3610ac

📥 Commits

Reviewing files that changed from the base of the PR and between 9f182f8 and adca0b0.

📒 Files selected for processing (13)
  • internal/assistant/client.go
  • internal/assistant/export_test.go
  • internal/assistant/lifecycle.go
  • internal/assistant/runtime.go
  • internal/assistant/runtime_events.go
  • internal/assistant/runtime_events_test.go
  • internal/assistant/runtime_lifecycle_test.go
  • internal/assistant/runtime_test.go
  • internal/assistant/tool_lifecycle_test.go
  • internal/assistant/tool_loop.go
  • internal/assistant/tool_loop_test.go
  • internal/extension/lifecycle.go
  • internal/extension/lifecycle_test.go
💤 Files with no reviewable changes (1)
  • internal/assistant/runtime_events.go

Comment thread internal/assistant/lifecycle.go
@sonarqubecloud
Copy link
Copy Markdown

@omarluq omarluq merged commit 6de0d57 into main May 25, 2026
13 checks passed
@omarluq omarluq deleted the feat/tool-runtime-hooks branch May 25, 2026 05:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants