feat(coding-agent): add after_provider_response hook closes #3128
This commit is contained in:
@@ -246,6 +246,7 @@ user sends prompt ────────────────────
|
||||
│ ├─► turn_start │ │
|
||||
│ ├─► context (can modify messages) │ │
|
||||
│ ├─► before_provider_request (can inspect or replace payload)
|
||||
│ ├─► after_provider_response (status + headers, before stream consume)
|
||||
│ │ │ │
|
||||
│ │ LLM responds, may call tools: │ │
|
||||
│ │ ├─► tool_execution_start │ │
|
||||
@@ -534,6 +535,22 @@ pi.on("before_provider_request", (event, ctx) => {
|
||||
|
||||
This is mainly useful for debugging provider serialization and cache behavior.
|
||||
|
||||
#### after_provider_response
|
||||
|
||||
Fired after an HTTP response is received and before its stream body is consumed. Handlers run in extension load order.
|
||||
|
||||
```typescript
|
||||
pi.on("after_provider_response", (event, ctx) => {
|
||||
// event.status - HTTP status code
|
||||
// event.headers - normalized response headers
|
||||
if (event.status === 429) {
|
||||
console.log("rate limited", event.headers["retry-after"]);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Header availability depends on provider and transport. Providers that abstract HTTP responses may not expose headers.
|
||||
|
||||
### Model Events
|
||||
|
||||
#### model_select
|
||||
@@ -2231,7 +2248,7 @@ All examples in [examples/extensions/](../examples/extensions/).
|
||||
| `dirty-repo-guard.ts` | Warn on dirty git repo | `on("session_before_*")`, `exec` |
|
||||
| `input-transform.ts` | Transform user input | `on("input")` |
|
||||
| `model-status.ts` | React to model changes | `on("model_select")`, `setStatus` |
|
||||
| `provider-payload.ts` | Inspect or patch provider payloads | `on("before_provider_request")` |
|
||||
| `provider-payload.ts` | Inspect payloads and provider response headers | `on("before_provider_request")`, `on("after_provider_response")` |
|
||||
| `system-prompt-header.ts` | Display system prompt info | `on("agent_start")`, `getSystemPrompt` |
|
||||
| `claude-rules.ts` | Load rules from files | `on("session_start")`, `on("before_agent_start")` |
|
||||
| `file-trigger.ts` | File watcher triggers messages | `sendMessage` |
|
||||
|
||||
@@ -11,4 +11,8 @@ export default function (pi: ExtensionAPI) {
|
||||
// Optional: replace the payload instead of only logging it.
|
||||
// return { ...event.payload, temperature: 0 };
|
||||
});
|
||||
|
||||
pi.on("after_provider_response", (event) => {
|
||||
appendFileSync(logFile, `[${event.status}] ${JSON.stringify(event.headers)}\n\n`, "utf8");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ export type {
|
||||
} from "./runner.js";
|
||||
export { ExtensionRunner } from "./runner.js";
|
||||
export type {
|
||||
AfterProviderResponseEvent,
|
||||
AgentEndEvent,
|
||||
AgentStartEvent,
|
||||
// Re-exports
|
||||
|
||||
@@ -543,6 +543,13 @@ export interface BeforeProviderRequestEvent {
|
||||
payload: unknown;
|
||||
}
|
||||
|
||||
/** Fired after a provider response is received and before the response stream is consumed. */
|
||||
export interface AfterProviderResponseEvent {
|
||||
type: "after_provider_response";
|
||||
status: number;
|
||||
headers: Record<string, string>;
|
||||
}
|
||||
|
||||
/** Fired after user submits prompt but before agent loop. */
|
||||
export interface BeforeAgentStartEvent {
|
||||
type: "before_agent_start";
|
||||
@@ -863,6 +870,7 @@ export type ExtensionEvent =
|
||||
| SessionEvent
|
||||
| ContextEvent
|
||||
| BeforeProviderRequestEvent
|
||||
| AfterProviderResponseEvent
|
||||
| BeforeAgentStartEvent
|
||||
| AgentStartEvent
|
||||
| AgentEndEvent
|
||||
@@ -1010,6 +1018,7 @@ export interface ExtensionAPI {
|
||||
event: "before_provider_request",
|
||||
handler: ExtensionHandler<BeforeProviderRequestEvent, BeforeProviderRequestEventResult>,
|
||||
): void;
|
||||
on(event: "after_provider_response", handler: ExtensionHandler<AfterProviderResponseEvent>): void;
|
||||
on(event: "before_agent_start", handler: ExtensionHandler<BeforeAgentStartEvent, BeforeAgentStartEventResult>): void;
|
||||
on(event: "agent_start", handler: ExtensionHandler<AgentStartEvent>): void;
|
||||
on(event: "agent_end", handler: ExtensionHandler<AgentEndEvent>): void;
|
||||
|
||||
@@ -314,6 +314,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
||||
}
|
||||
return runner.emitBeforeProviderRequest(payload);
|
||||
},
|
||||
onResponse: async (response, _model) => {
|
||||
const runner = extensionRunnerRef.current;
|
||||
if (!runner?.hasHandlers("after_provider_response")) {
|
||||
return;
|
||||
}
|
||||
await runner.emit({
|
||||
type: "after_provider_response",
|
||||
status: response.status,
|
||||
headers: response.headers,
|
||||
});
|
||||
},
|
||||
sessionId: sessionManager.getSessionId(),
|
||||
transformContext: async (messages) => {
|
||||
const runner = extensionRunnerRef.current;
|
||||
|
||||
@@ -142,6 +142,17 @@ export async function createHarness(options: HarnessOptions = {}): Promise<Harne
|
||||
}
|
||||
return runner.emitBeforeProviderRequest(payload);
|
||||
},
|
||||
onResponse: async (response) => {
|
||||
const runner = extensionRunnerRef.current;
|
||||
if (!runner?.hasHandlers("after_provider_response")) {
|
||||
return;
|
||||
}
|
||||
await runner.emit({
|
||||
type: "after_provider_response",
|
||||
status: response.status,
|
||||
headers: response.headers,
|
||||
});
|
||||
},
|
||||
transformContext: async (messages: AgentMessage[]) => {
|
||||
const runner = extensionRunnerRef.current;
|
||||
if (!runner) return messages;
|
||||
|
||||
Reference in New Issue
Block a user