#22624: feat(plugins): add before_context_send hook and model routing via before_prompt_build
agents
size: L
trusted-contributor
Cluster:
Plugin and Hook Enhancements
## Summary
- Adds `before_context_send` plugin hook — sync, per-LLM-call, modifying. Plugins can transform `AgentMessage[]` with model awareness (modelId, provider, contextWindowTokens) on every LLM call via a new pi-agent-core bridge extension.
- Enriches `before_prompt_build` with model info (`modelId`, `provider`, `contextWindowTokens`) and adds `modelOverride`/`providerOverride` to its result type, enabling dynamic model routing based on conversation context.
- New `context-hooks` pi-extension bridges pi-agent-core's sync `context` event to the plugin hook system, following the same pattern as `context-pruning`.
Standalone from PR #14544 (Windelly's `before_context_send`). If both land, they coexist via independent registries.
### Example plugin pattern enabled by this PR
```
session_start -> load config (model tiers, token budgets, decay rules)
tool_result_persist -> note large results, kick off background summarization
before_prompt_build -> classify complexity -> return modelOverride
-> store pre-computed summaries in plugin state
before_context_send -> strip thinking blocks from old messages
-> replace tool results with pre-computed summaries
-> enforce token budget based on contextWindowTokens
llm_output -> accumulate usage stats, adjust decay aggressiveness
```
### Files changed (9)
| File | Change |
|---|---|
| `src/plugins/types.ts` | New hook name, event/result types, enriched prompt build types |
| `src/plugins/hooks.ts` | `runBeforeContextSend()` sync chain-pass, `mergeBeforePromptBuild` with model override |
| `src/plugin-sdk/index.ts` | SDK exports for plugin authors |
| `src/agents/pi-extensions/context-hooks/runtime.ts` | **NEW** — WeakMap registry for bridge runtime |
| `src/agents/pi-extensions/context-hooks/extension.ts` | **NEW** — Bridge: pi-agent-core context event → plugin hooks |
| `src/agents/pi-extensions/context-hooks.ts` | **NEW** — Barrel re-export |
| `src/agents/pi-embedded-runner/extensions.ts` | `buildContextHooksExtension()`, pass `hookCtx` |
| `src/agents/pi-embedded-runner/run/attempt.ts` | Early hookCtx, model info in prompt build, model routing via `streamFn` wrap |
| `src/agents/pi-extensions/context-hooks.test.ts` | **NEW** — 6 tests |
## Test plan
- [x] `npx tsc --noEmit` — no new type errors
- [x] `npx oxlint` — 0 warnings, 0 errors on all changed files
- [x] `npx vitest run src/agents/pi-extensions/context-hooks.test.ts` — 6/6 pass
- [x] `npx vitest run src/plugins/` — 117/117 pass (existing hooks tests unaffected)
- [ ] Manual: create a minimal test plugin registering `before_context_send` — verify it fires on every LLM call
- [ ] Manual: create a test plugin returning `modelOverride` from `before_prompt_build` — verify routed model is used
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR adds two complementary plugin hook features:
1. **`before_context_send` hook**: A new synchronous plugin hook that fires on every LLM call, allowing plugins to transform the `AgentMessage[]` array before it's sent to the model. Receives model metadata (`modelId`, `provider`, `contextWindowTokens`) to enable token-aware filtering and summarization.
2. **Enhanced `before_prompt_build` hook**: Enriched with model info (`modelId`, `provider`, `contextWindowTokens`) and adds `modelOverride`/`providerOverride` to the result type, enabling dynamic model routing based on conversation context.
The implementation follows existing patterns from `context-pruning` and uses a WeakMap-based registry to bridge pi-agent-core's synchronous context event to the plugin system. Model routing is handled by wrapping the agent's `streamFn`, and the context-hooks runtime is updated when routing occurs so `before_context_send` sees the correct model.
All changes are well-tested (6 new tests pass) and backward-compatible. The PR description clearly documents the intended plugin pattern and states it's standalone from PR #14544.
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge with minor considerations
- The implementation is solid with good test coverage and follows existing patterns. Score reflects well-structured code with proper error handling and guard clauses. The synchronous hook design is appropriate and properly documented. Minor consideration: dynamic imports in hot path (model routing) may have performance implications, but this only occurs when plugins actively route models.
- Pay close attention to `src/agents/pi-embedded-runner/run/attempt.ts` for the model routing logic and runtime updates
<sub>Last reviewed commit: 97b1448</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#23559: feat(plugins): add before_context_send hook and model routing via b...
by davidrudduck · 2026-02-22
94.6%
#14544: feat: add before_context_send plugin hook
by Windelly · 2026-02-12
87.0%
#14647: feat(plugins): allow before_agent_start hook to override model (#14...
by lailoo · 2026-02-12
86.6%
#17614: feat: allow before_agent_start hook to override model selection
by plc · 2026-02-16
84.9%
#14873: [Feature]: Extend before_agent_start hook context with Model, Tools...
by akv2011 · 2026-02-12
84.6%
#8022: feat: implement before_model_select plugin hook
by dead-pool-aka-wilson · 2026-02-03
83.3%
#11124: feat(plugins): add before_llm_request hook for custom LLM headers
by johnlanni · 2026-02-07
83.0%
#20067: feat(plugins): add before_agent_reply hook for message interception
by JoshuaLelon · 2026-02-18
82.3%
#11155: feat(hooks): before_agent_start model/provider override (run-scoped...
by alanranger · 2026-02-07
81.4%
#11732: feat(plugins): add injectMessages to before_agent_start hook
by antra-tess · 2026-02-08
80.3%