feat(coding-agent): add provider payload hook
This commit is contained in:
@@ -243,6 +243,7 @@ user sends prompt ────────────────────
|
||||
│ │ │ │
|
||||
│ ├─► turn_start │ │
|
||||
│ ├─► context (can modify messages) │ │
|
||||
│ ├─► before_provider_request (can inspect or replace payload)
|
||||
│ │ │ │
|
||||
│ │ LLM responds, may call tools: │ │
|
||||
│ │ ├─► tool_call (can block) │ │
|
||||
@@ -489,6 +490,21 @@ pi.on("context", async (event, ctx) => {
|
||||
});
|
||||
```
|
||||
|
||||
#### before_provider_request
|
||||
|
||||
Fired after the provider-specific payload is built, right before the request is sent. Handlers run in extension load order. Returning `undefined` keeps the payload unchanged. Returning any other value replaces the payload for later handlers and for the actual request.
|
||||
|
||||
```typescript
|
||||
pi.on("before_provider_request", (event, ctx) => {
|
||||
console.log(JSON.stringify(event.payload, null, 2));
|
||||
|
||||
// Optional: replace payload
|
||||
// return { ...event.payload, temperature: 0 };
|
||||
});
|
||||
```
|
||||
|
||||
This is mainly useful for debugging provider serialization and cache behavior.
|
||||
|
||||
### Model Events
|
||||
|
||||
#### model_select
|
||||
@@ -1934,6 +1950,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")` |
|
||||
| `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` |
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { appendFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
const logFile = join(process.cwd(), ".pi", "provider-payload.log");
|
||||
|
||||
pi.on("before_provider_request", (event) => {
|
||||
appendFileSync(logFile, `${JSON.stringify(event.payload, null, 2)}\n\n`, "utf8");
|
||||
|
||||
// Optional: replace the payload instead of only logging it.
|
||||
// return { ...event.payload, temperature: 0 };
|
||||
});
|
||||
}
|
||||
@@ -32,6 +32,8 @@ export type {
|
||||
BashToolResultEvent,
|
||||
BeforeAgentStartEvent,
|
||||
BeforeAgentStartEventResult,
|
||||
BeforeProviderRequestEvent,
|
||||
BeforeProviderRequestEventResult,
|
||||
// Context
|
||||
CompactOptions,
|
||||
// Events - Agent
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { SessionManager } from "../session-manager.js";
|
||||
import type {
|
||||
BeforeAgentStartEvent,
|
||||
BeforeAgentStartEventResult,
|
||||
BeforeProviderRequestEvent,
|
||||
CompactOptions,
|
||||
ContextEvent,
|
||||
ContextEventResult,
|
||||
@@ -105,6 +106,7 @@ type RunnerEmitEvent = Exclude<
|
||||
| ToolResultEvent
|
||||
| UserBashEvent
|
||||
| ContextEvent
|
||||
| BeforeProviderRequestEvent
|
||||
| BeforeAgentStartEvent
|
||||
| ResourcesDiscoverEvent
|
||||
| InputEvent
|
||||
@@ -710,6 +712,40 @@ export class ExtensionRunner {
|
||||
return currentMessages;
|
||||
}
|
||||
|
||||
async emitBeforeProviderRequest(payload: unknown): Promise<unknown> {
|
||||
const ctx = this.createContext();
|
||||
let currentPayload = payload;
|
||||
|
||||
for (const ext of this.extensions) {
|
||||
const handlers = ext.handlers.get("before_provider_request");
|
||||
if (!handlers || handlers.length === 0) continue;
|
||||
|
||||
for (const handler of handlers) {
|
||||
try {
|
||||
const event: BeforeProviderRequestEvent = {
|
||||
type: "before_provider_request",
|
||||
payload: currentPayload,
|
||||
};
|
||||
const handlerResult = await handler(event, ctx);
|
||||
if (handlerResult !== undefined) {
|
||||
currentPayload = handlerResult;
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
const stack = err instanceof Error ? err.stack : undefined;
|
||||
this.emitError({
|
||||
extensionPath: ext.path,
|
||||
event: "before_provider_request",
|
||||
error: message,
|
||||
stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return currentPayload;
|
||||
}
|
||||
|
||||
async emitBeforeAgentStart(
|
||||
prompt: string,
|
||||
images: ImageContent[] | undefined,
|
||||
|
||||
@@ -493,6 +493,12 @@ export interface ContextEvent {
|
||||
messages: AgentMessage[];
|
||||
}
|
||||
|
||||
/** Fired before a provider request is sent. Can replace the payload. */
|
||||
export interface BeforeProviderRequestEvent {
|
||||
type: "before_provider_request";
|
||||
payload: unknown;
|
||||
}
|
||||
|
||||
/** Fired after user submits prompt but before agent loop. */
|
||||
export interface BeforeAgentStartEvent {
|
||||
type: "before_agent_start";
|
||||
@@ -807,6 +813,7 @@ export type ExtensionEvent =
|
||||
| ResourcesDiscoverEvent
|
||||
| SessionEvent
|
||||
| ContextEvent
|
||||
| BeforeProviderRequestEvent
|
||||
| BeforeAgentStartEvent
|
||||
| AgentStartEvent
|
||||
| AgentEndEvent
|
||||
@@ -832,6 +839,8 @@ export interface ContextEventResult {
|
||||
messages?: AgentMessage[];
|
||||
}
|
||||
|
||||
export type BeforeProviderRequestEventResult = unknown;
|
||||
|
||||
export interface ToolCallEventResult {
|
||||
block?: boolean;
|
||||
reason?: string;
|
||||
@@ -944,6 +953,10 @@ export interface ExtensionAPI {
|
||||
on(event: "session_before_tree", handler: ExtensionHandler<SessionBeforeTreeEvent, SessionBeforeTreeResult>): void;
|
||||
on(event: "session_tree", handler: ExtensionHandler<SessionTreeEvent>): void;
|
||||
on(event: "context", handler: ExtensionHandler<ContextEvent, ContextEventResult>): void;
|
||||
on(
|
||||
event: "before_provider_request",
|
||||
handler: ExtensionHandler<BeforeProviderRequestEvent, BeforeProviderRequestEventResult>,
|
||||
): 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;
|
||||
|
||||
@@ -292,6 +292,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
||||
tools: [],
|
||||
},
|
||||
convertToLlm: convertToLlmWithBlockImages,
|
||||
onPayload: async (payload, _model) => {
|
||||
const runner = extensionRunnerRef.current;
|
||||
if (!runner?.hasHandlers("before_provider_request")) {
|
||||
return payload;
|
||||
}
|
||||
return runner.emitBeforeProviderRequest(payload);
|
||||
},
|
||||
sessionId: sessionManager.getSessionId(),
|
||||
transformContext: async (messages) => {
|
||||
const runner = extensionRunnerRef.current;
|
||||
|
||||
@@ -56,6 +56,8 @@ export type {
|
||||
AppAction,
|
||||
BashToolCallEvent,
|
||||
BeforeAgentStartEvent,
|
||||
BeforeProviderRequestEvent,
|
||||
BeforeProviderRequestEventResult,
|
||||
CompactOptions,
|
||||
ContextEvent,
|
||||
ContextUsage,
|
||||
|
||||
Reference in New Issue
Block a user