Skip to content

feat(extension): add provider lifecycle hooks#45

Merged
omarluq merged 4 commits into
mainfrom
feat/provider-lifecycle-hooks
May 24, 2026
Merged

feat(extension): add provider lifecycle hooks#45
omarluq merged 4 commits into
mainfrom
feat/provider-lifecycle-hooks

Conversation

@omarluq
Copy link
Copy Markdown
Owner

@omarluq omarluq commented May 24, 2026

Summary

  • add provider request lifecycle hook with bounded payload/header mutation
  • expose provider request data to extension lifecycle handlers with sensitive header redaction
  • wire provider request hooks into OpenAI Chat, OpenAI Responses, and Anthropic request paths

Validation

  • mise exec -- go test ./...
  • mise exec -- task build
  • mise exec -- task ci

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 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: c419f867-7d1a-4b75-a465-b8d762cb4e26

📥 Commits

Reviewing files that changed from the base of the PR and between 63cbda0 and fe9395c.

📒 Files selected for processing (3)
  • internal/assistant/provider_hooks.go
  • internal/assistant/provider_payload_hook_internal_test.go
  • internal/assistant/runtime_events.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Extensions can conservatively modify outgoing provider requests (payload and added headers) via lifecycle hooks; sensitive headers are redacted and protected.
    • Provider-request hooks are invoked per provider call/iteration and surface per-attempt metadata.
  • Documentation

    • Extension API docs updated with provider-request mutation contracts, token-budget breakdown fields in context payload, examples (including Lua), and explicit rules for sensitive headers.

Walkthrough

Adds a provider-request hook pipeline: CompletionRequest observe/mutate hooks, lifecycle before_provider_request provider_request mutations (payload + non-sensitive headers), header redaction/validation, provider-client wiring, tests, and updated docs.

Changes

Provider Request Hook System

Layer / File(s) Summary
Hook types and request contracts
internal/assistant/client.go, internal/assistant/context_build.go, internal/assistant/lifecycle.go
Adds jsonSystemRole/jsonMessagesKey; CompletionRequest gains OnProviderObserve, OnProviderRequest, and ProviderAttempt; token-breakdown and dispatch defaults updated.
Core provider request hook pipeline
internal/assistant/provider_hooks.go
Implements applyProviderRequestHook and Runtime.dispatchProviderRequestHook: clone payload/headers, run observe/mutate hooks, dispatch before_provider_request, extract/merge payload and header mutations, validate blocked sensitive headers, and return final payload+headers.
Extension lifecycle provider_request support
internal/extension/lifecycle.go, internal/extension/lua_values.go, internal/extension/lifecycle_test.go
LifecycleDispatchResult adds ProviderRequest/ProviderRequestMutation; lifecycle handlers can return provider_request.headers parsed/merged; adds Lua conversion for map[string]string and tests for header merging.
Provider client integrations
internal/assistant/anthropic.go, internal/assistant/openai_chat.go, internal/assistant/openai_responses.go
Anthropic/OpenAI paths call applyProviderRequestHook before HTTP POST and use returned providerRequest.Payload/providerRequest.Headers; payload/message keys use new JSON constants.
Runtime completion wiring
internal/assistant/runtime.go, internal/assistant/runtime_events.go, internal/assistant/runtime_lifecycle_test.go
modelCompletionRequest wires provider hooks; completeWithRetry sets ProviderAttempt per attempt; emitProviderRequest includes redacted provider headers; test client invokes observer callback.
Comprehensive tests
internal/assistant/provider_hooks_internal_test.go, internal/assistant/provider_payload_hook_internal_test.go, internal/assistant/provider_tool_calls_test.go, internal/assistant/anthropic_internal_test.go
Adds tests for Lua before_provider_request mutations, header redaction/rejection, HTTP client hook application (including OpenAI responses iterations), hook ordering, nil-request handling, and updated test helpers.
Extension API documentation
docs/extension-api.md
Documents context_build token breakdown fields and conservative before_provider_request mutation contract with a Lua example and explicit sensitive-header redaction rules.

Sequence Diagram (high-level request flow)

sequenceDiagram
  participant Runtime
  participant ExtensionManager
  participant ProviderClient
  Runtime->>ExtensionManager: dispatch before_provider_request (payload, redacted headers)
  ExtensionManager->>Runtime: return mutated payload + provider_request.headers
  Runtime->>ProviderClient: POST with providerRequest.Payload and providerRequest.Headers
  ProviderClient->>Runtime: provider response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • omarluq/librecode#21: Related lifecycle dispatch changes extended here to carry provider_request mutations and Lua header parsing.
  • omarluq/librecode#31: Overlaps on context and lifecycle plumbing related to token breakdown keys.
  • omarluq/librecode#32: Related changes to openai chat loop behavior and iterative tool-call wiring.

Poem

🐰 I hopped through hooks with careful ears,

Lua stirred the payload in midnight code,
Secrets wrapped in "[redacted]" she wears,
Each attempt counted down the winding road,
A rabbit tended headers, light and bold.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(extension): add provider lifecycle hooks' directly and clearly summarizes the main change: adding provider lifecycle hooks to the extension system.
Description check ✅ Passed The description is directly related to the changeset, detailing the three main aspects of the change: provider request lifecycle hooks, sensitive header redaction, and integration with request paths.
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/provider-lifecycle-hooks

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

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 24, 2026

Codecov Report

❌ Patch coverage is 75.64767% with 47 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.48%. Comparing base (1819fcf) to head (fe9395c).

Files with missing lines Patch % Lines
internal/assistant/provider_hooks.go 77.64% 12 Missing and 7 partials ⚠️
internal/assistant/lifecycle.go 0.00% 8 Missing ⚠️
internal/extension/lua_values.go 0.00% 7 Missing ⚠️
internal/extension/lifecycle.go 83.78% 3 Missing and 3 partials ⚠️
internal/assistant/anthropic.go 77.77% 1 Missing and 1 partial ⚠️
internal/assistant/openai_chat.go 71.42% 1 Missing and 1 partial ⚠️
internal/assistant/openai_responses.go 81.81% 1 Missing and 1 partial ⚠️
internal/assistant/runtime.go 94.11% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #45      +/-   ##
==========================================
+ Coverage   60.72%   61.48%   +0.75%     
==========================================
  Files         172      173       +1     
  Lines       16928    17073     +145     
==========================================
+ Hits        10280    10497     +217     
+ Misses       5624     5526      -98     
- Partials     1024     1050      +26     
Flag Coverage Δ
unittests 61.48% <75.64%> (+0.75%) ⬆️

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: 3

🤖 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/openai_responses.go`:
- Around line 44-55: The headers map is reused across iterations and can be
mutated by applyProviderRequestHook, causing leakage between provider calls;
before calling applyProviderRequestHook in the loop, make a shallow copy of the
headers map (e.g., clone the original headers into a new map) and pass that
cloned map so applyProviderRequestHook and subsequent providerRequest.Headers
are isolated per iteration; ensure client.requestResponses continues to receive
providerRequest.Headers (the cloned, hook-modified map) and that no references
to the shared headers map are reused after the call.

In `@internal/assistant/provider_payload_hook_internal_test.go`:
- Around line 25-33: The handler currently calls require.NoError(t, ...) inside
the httptest http.HandlerFunc and writes to shared variables requestHeader and
requestBody without synchronization; instead capture any decode/write error and
the request data inside the handler and send them back to the test goroutine
(e.g., via a channel or sync.WaitGroup + mutex) so assertions like
require.NoError(t, err) and checks of requestHeader/requestBody happen in the
test goroutine; update the handler used with httptest.NewServer to avoid FailNow
from the handler goroutine and synchronize access to requestHeader/requestBody
before performing assertions.

In `@internal/extension/lifecycle.go`:
- Around line 162-165: The code currently overwrites result.ProviderRequest for
each handler (using providerRequestValue and providerRequestMutationFromLua),
causing earlier provider_request.headers to be lost; change the logic so when
providerRequestMutationFromLua returns a providerRequest, you merge its headers
into result.ProviderRequest.Headers (preserving existing header keys and
combining or appending values per your header semantics) and only set non-header
fields on result.ProviderRequest if they are not already present or should be
replaced; update the handling around
providerRequestValue/providerRequestMutationFromLua and result.ProviderRequest
to perform a shallow merge of structs and a deep merge of the headers map so
multiple lifecycle handlers compose correctly.
🪄 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: ea18fe65-1c48-45de-99df-9176260add35

📥 Commits

Reviewing files that changed from the base of the PR and between f64fe71 and d369a8d.

📒 Files selected for processing (17)
  • docs/extension-api.md
  • internal/assistant/anthropic.go
  • internal/assistant/anthropic_internal_test.go
  • internal/assistant/client.go
  • internal/assistant/context_build.go
  • internal/assistant/lifecycle.go
  • internal/assistant/openai_chat.go
  • internal/assistant/openai_responses.go
  • internal/assistant/provider_hooks.go
  • internal/assistant/provider_hooks_internal_test.go
  • internal/assistant/provider_payload_hook_internal_test.go
  • internal/assistant/runtime.go
  • internal/assistant/runtime_events.go
  • internal/assistant/runtime_lifecycle_test.go
  • internal/extension/lifecycle.go
  • internal/extension/lifecycle_test.go
  • internal/extension/lua_values.go

Comment thread internal/assistant/openai_responses.go Outdated
Comment thread internal/assistant/provider_payload_hook_internal_test.go Outdated
Comment thread internal/extension/lifecycle.go
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/extension/lifecycle.go (1)

168-181: 💤 Low value

Consider grouping consecutive same-type parameters.

The function signature can be simplified by grouping the parameters of the same type, which is idiomatic Go style.

♻️ Suggested refactor
-func mergeProviderRequestMutation(
-	base ProviderRequestMutation,
-	override ProviderRequestMutation,
-) ProviderRequestMutation {
+func mergeProviderRequestMutation(base, override ProviderRequestMutation) ProviderRequestMutation {
 	merged := ProviderRequestMutation{Headers: map[string]string{}}
 	for key, value := range base.Headers {
 		merged.Headers[key] = value
🤖 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/extension/lifecycle.go` around lines 168 - 181, The function
mergeProviderRequestMutation uses two consecutive parameters of the same type;
change the signature to group them as "base, override ProviderRequestMutation"
(keeping the return type and function body unchanged), update any callers if
needed, and leave the logic inside mergeProviderRequestMutation (merging Headers
into merged) as-is.
🤖 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/provider_tool_calls_test.go`:
- Around line 96-120: The test currently uses input.Attempt and asserts "1"
twice which can pass if the hook is only invoked once and reused; update the
request.OnProviderRequest hook to record each invocation (e.g., append
input.Attempt to a local slice or increment a local counter) and return
iteration specific headers/payload, then after calling
NewHTTPCompletionClient().completeOpenAIResponses(...) assert that the recorded
invocations show 1 then 2 (and that captures/headers/payloads reflect attempt 1
and attempt 2) to prove the hook runs per request; locate and modify the
request.OnProviderRequest closure, the captures channel checks, and the
assertions around first/second to expect "1" then "2" and corresponding
iteration values.

---

Nitpick comments:
In `@internal/extension/lifecycle.go`:
- Around line 168-181: The function mergeProviderRequestMutation uses two
consecutive parameters of the same type; change the signature to group them as
"base, override ProviderRequestMutation" (keeping the return type and function
body unchanged), update any callers if needed, and leave the logic inside
mergeProviderRequestMutation (merging Headers into merged) as-is.
🪄 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: 5ccd6b48-caf0-45d3-8c1c-78b0167774c5

📥 Commits

Reviewing files that changed from the base of the PR and between d369a8d and f9536bb.

📒 Files selected for processing (7)
  • internal/assistant/openai_responses.go
  • internal/assistant/provider_hooks.go
  • internal/assistant/provider_hooks_internal_test.go
  • internal/assistant/provider_payload_hook_internal_test.go
  • internal/assistant/provider_tool_calls_test.go
  • internal/extension/lifecycle.go
  • internal/extension/lifecycle_test.go

Comment thread internal/assistant/provider_tool_calls_test.go
@omarluq omarluq force-pushed the feat/provider-lifecycle-hooks branch from f9536bb to 63cbda0 Compare May 24, 2026 20:53
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: 2

🤖 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/provider_hooks.go`:
- Around line 59-60: The function currently returns raw errors from the provider
hook call (request.OnProviderRequest) — capture the returned error, and if
non-nil wrap it with samber/oops using the assistant domain and a descriptive
code (e.g. oops.In("assistant").Code("hook_failure").Wrapf(err, "provider hook
OnProviderRequest failed")) before returning; apply the same pattern to the
other raw error returns around the same file (the block at lines ~79-83) so all
hook/lifecycle errors are wrapped with
oops.In("assistant").Code(...).Wrapf(...).

In `@internal/assistant/runtime.go`:
- Around line 827-830: The code currently wires both OnProviderObserve ->
runtime.emitProviderRequest and OnProviderRequest ->
runtime.dispatchProviderRequestHook which causes before_provider_request
handlers to run twice; fix by keeping a single dispatch path: either remove the
OnProviderObserve binding to runtime.emitProviderRequest or modify
emitProviderRequest so it only observes and does not dispatch lifecycle events,
and retain runtime.dispatchProviderRequestHook on OnProviderRequest as the sole
dispatcher; update references to runtime.emitProviderRequest and
runtime.dispatchProviderRequestHook accordingly to ensure
before_provider_request is invoked exactly once per provider call.
🪄 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: eab40fcb-6da1-44ca-8e33-4ae91712e37f

📥 Commits

Reviewing files that changed from the base of the PR and between f9536bb and 63cbda0.

📒 Files selected for processing (18)
  • docs/extension-api.md
  • internal/assistant/anthropic.go
  • internal/assistant/anthropic_internal_test.go
  • internal/assistant/client.go
  • internal/assistant/context_build.go
  • internal/assistant/lifecycle.go
  • internal/assistant/openai_chat.go
  • internal/assistant/openai_responses.go
  • internal/assistant/provider_hooks.go
  • internal/assistant/provider_hooks_internal_test.go
  • internal/assistant/provider_payload_hook_internal_test.go
  • internal/assistant/provider_tool_calls_test.go
  • internal/assistant/runtime.go
  • internal/assistant/runtime_events.go
  • internal/assistant/runtime_lifecycle_test.go
  • internal/extension/lifecycle.go
  • internal/extension/lifecycle_test.go
  • internal/extension/lua_values.go
✅ Files skipped from review due to trivial changes (1)
  • docs/extension-api.md

Comment thread internal/assistant/provider_hooks.go Outdated
Comment thread internal/assistant/runtime.go
@sonarqubecloud
Copy link
Copy Markdown

@omarluq omarluq merged commit deeb867 into main May 24, 2026
13 checks passed
@omarluq omarluq deleted the feat/provider-lifecycle-hooks branch May 24, 2026 21:54
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