← Back to PRs

#23559: feat(plugins): add before_context_send hook and model routing via before_prompt_build

by davidrudduck open 2026-02-22 12:42 View on GitHub →
agents size: L trusted-contributor
## Summary Add two new plugin hook surfaces for per-LLM-call context transformation and dynamic model routing, enabling plugins to inspect/modify messages and switch models based on conversation context — without touching core. **Split from previous PRs (#20850, #20851, #20859) where this feature was inadvertently bundled with unrelated bug fixes.** ## New hooks ### `before_context_send` (sync, per-LLM-call) Fires on every LLM invocation (including tool continuations) via a pi-agent-core `context` event bridge. Plugins can inspect or transform the message array before it reaches the model. - Sync execution — no async overhead on the hot path - Receives `modelId`, `provider`, `contextWindowTokens` for routing-aware decisions - Independent registry via `session-manager-runtime-registry` (no conflicts with existing extensions) ### `before_prompt_build` enrichment (per-turn) Enriched with `modelId`, `provider`, and `contextWindowTokens` parameters. Can now return `modelOverride` and `providerOverride` for dynamic per-turn model routing. ## Architecture - New pi-extension `context-hooks` bridges the sync pi-agent-core `context` event to the plugin hook system - `ContextHooksRuntimeValue` stores mutable model state per session manager (updated on routing) - `buildContextHooksFactory()` wired into `buildEmbeddedExtensionFactories()` - Model routing wrapper installed in `attempt.ts` when `before_prompt_build` returns `modelOverride` - Standalone from PR #14544; coexists via independent registries ## Changes - `src/agents/pi-extensions/context-hooks/extension.ts` — new pi-extension (31 lines) - `src/agents/pi-extensions/context-hooks/runtime.ts` — session-scoped runtime registry (17 lines) - `src/agents/pi-embedded-runner/extensions.ts` — wire context-hooks factory - `src/agents/pi-embedded-runner/run/attempt.ts` — model routing wrapper installation - `src/plugins/hooks.ts` — `runBeforeContextSend` hook runner method - `src/plugins/types.ts` — hook type definitions - `src/plugin-sdk/index.ts` — export new hook types ## Known limitations (to address in follow-ups) - Model routing only swaps the model arg in `streamFn`; provider-specific stream wrappers from `applyExtraParamsToAgent` are not rebuilt (same-provider routing only) - Compaction/pruning context windows not updated after routing (only `contextHooksRuntime` is mutated) - Per steipete's review: cross-provider routing needs explicit validation or full "effective model" state rebuild ## Test plan - [x] 6 tests in `context-hooks.test.ts`: - Extension calls hook runner on context event - Passes model metadata to hook - Skips when no hooks registered - Handles missing runtime gracefully - Returns modified messages from hook - Preserves original messages when hook returns undefined - [x] `pnpm build` passes - [x] `pnpm vitest run src/agents/pi-extensions/context-hooks.test.ts` — 6/6 pass - [x] Rebased on latest `main`, single focused commit <!-- greptile_comment --> <h3>Greptile Summary</h3> Adds two new plugin hooks for dynamic model routing and per-LLM-call message transformation. The `before_context_send` hook fires synchronously on every LLM invocation via pi-agent-core's context event bridge, allowing plugins to inspect or modify messages before they reach the model. The `before_prompt_build` hook gains model metadata (`modelId`, `provider`, `contextWindowTokens`) and can return `modelOverride`/`providerOverride` for dynamic per-turn model routing. **Key changes:** - New pi-extension `context-hooks` bridges pi-agent-core context events to plugin hooks - Session-scoped `ContextHooksRuntimeValue` stores mutable model routing state - Model routing wrapper installed in `attempt.ts` when `before_prompt_build` returns `modelOverride` - Well-tested with 6 focused unit tests covering hook execution, model metadata passing, and runtime mutations **Architecture concerns:** - Model routing only swaps the streamFn wrapper—provider-specific wrappers from `applyExtraParamsToAgent` are not rebuilt (acknowledged limitation) - Context window state not updated after routing (only `contextHooksRuntime` is mutated) - Model override merge logic in hooks.ts:151-152 prioritizes the first plugin's override, which is inconsistent with how `systemPrompt` is merged (where later plugins override earlier ones) <h3>Confidence Score: 4/5</h3> - Safe to merge with one logic issue that should be addressed - The implementation is well-structured with good test coverage and proper error handling. The model override merge logic inconsistency is a logical error that could cause unexpected behavior when multiple plugins try to route models, but it's contained to a specific edge case. The acknowledged architectural limitations (provider-specific wrappers not rebuilt, context windows not updated) are documented and acceptable for initial implementation. - Check `src/plugins/hooks.ts:151-152` for the model override merge priority issue <sub>Last reviewed commit: c3b7ef5</sub> <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs