feat(coding-agent): add provider payload hook
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
|||||||
type ImageContent,
|
type ImageContent,
|
||||||
type Message,
|
type Message,
|
||||||
type Model,
|
type Model,
|
||||||
|
type SimpleStreamOptions,
|
||||||
streamSimple,
|
streamSimple,
|
||||||
type TextContent,
|
type TextContent,
|
||||||
type ThinkingBudgets,
|
type ThinkingBudgets,
|
||||||
@@ -74,6 +75,11 @@ export interface AgentOptions {
|
|||||||
*/
|
*/
|
||||||
getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
|
getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inspect or replace provider payloads before they are sent.
|
||||||
|
*/
|
||||||
|
onPayload?: SimpleStreamOptions["onPayload"];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom token budgets for thinking levels (token-based providers only).
|
* Custom token budgets for thinking levels (token-based providers only).
|
||||||
*/
|
*/
|
||||||
@@ -117,6 +123,7 @@ export class Agent {
|
|||||||
public streamFn: StreamFn;
|
public streamFn: StreamFn;
|
||||||
private _sessionId?: string;
|
private _sessionId?: string;
|
||||||
public getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
|
public getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
|
||||||
|
private _onPayload?: SimpleStreamOptions["onPayload"];
|
||||||
private runningPrompt?: Promise<void>;
|
private runningPrompt?: Promise<void>;
|
||||||
private resolveRunningPrompt?: () => void;
|
private resolveRunningPrompt?: () => void;
|
||||||
private _thinkingBudgets?: ThinkingBudgets;
|
private _thinkingBudgets?: ThinkingBudgets;
|
||||||
@@ -132,6 +139,7 @@ export class Agent {
|
|||||||
this.streamFn = opts.streamFn || streamSimple;
|
this.streamFn = opts.streamFn || streamSimple;
|
||||||
this._sessionId = opts.sessionId;
|
this._sessionId = opts.sessionId;
|
||||||
this.getApiKey = opts.getApiKey;
|
this.getApiKey = opts.getApiKey;
|
||||||
|
this._onPayload = opts.onPayload;
|
||||||
this._thinkingBudgets = opts.thinkingBudgets;
|
this._thinkingBudgets = opts.thinkingBudgets;
|
||||||
this._transport = opts.transport ?? "sse";
|
this._transport = opts.transport ?? "sse";
|
||||||
this._maxRetryDelayMs = opts.maxRetryDelayMs;
|
this._maxRetryDelayMs = opts.maxRetryDelayMs;
|
||||||
@@ -429,6 +437,7 @@ export class Agent {
|
|||||||
model,
|
model,
|
||||||
reasoning,
|
reasoning,
|
||||||
sessionId: this._sessionId,
|
sessionId: this._sessionId,
|
||||||
|
onPayload: this._onPayload,
|
||||||
transport: this._transport,
|
transport: this._transport,
|
||||||
thinkingBudgets: this._thinkingBudgets,
|
thinkingBudgets: this._thinkingBudgets,
|
||||||
maxRetryDelayMs: this._maxRetryDelayMs,
|
maxRetryDelayMs: this._maxRetryDelayMs,
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOpt
|
|||||||
const client = new BedrockRuntimeClient(config);
|
const client = new BedrockRuntimeClient(config);
|
||||||
|
|
||||||
const cacheRetention = resolveCacheRetention(options.cacheRetention);
|
const cacheRetention = resolveCacheRetention(options.cacheRetention);
|
||||||
const commandInput = {
|
let commandInput = {
|
||||||
modelId: model.id,
|
modelId: model.id,
|
||||||
messages: convertMessages(context, model, cacheRetention),
|
messages: convertMessages(context, model, cacheRetention),
|
||||||
system: buildSystemPrompt(context.systemPrompt, model, cacheRetention),
|
system: buildSystemPrompt(context.systemPrompt, model, cacheRetention),
|
||||||
@@ -154,7 +154,10 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOpt
|
|||||||
toolConfig: convertToolConfig(context.tools, options.toolChoice),
|
toolConfig: convertToolConfig(context.tools, options.toolChoice),
|
||||||
additionalModelRequestFields: buildAdditionalModelRequestFields(model, options),
|
additionalModelRequestFields: buildAdditionalModelRequestFields(model, options),
|
||||||
};
|
};
|
||||||
options?.onPayload?.(commandInput);
|
const nextCommandInput = await options?.onPayload?.(commandInput, model);
|
||||||
|
if (nextCommandInput !== undefined) {
|
||||||
|
commandInput = nextCommandInput as typeof commandInput;
|
||||||
|
}
|
||||||
const command = new ConverseStreamCommand(commandInput);
|
const command = new ConverseStreamCommand(commandInput);
|
||||||
|
|
||||||
const response = await client.send(command, { abortSignal: options.signal });
|
const response = await client.send(command, { abortSignal: options.signal });
|
||||||
|
|||||||
@@ -235,8 +235,11 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti
|
|||||||
options?.headers,
|
options?.headers,
|
||||||
copilotDynamicHeaders,
|
copilotDynamicHeaders,
|
||||||
);
|
);
|
||||||
const params = buildParams(model, context, isOAuthToken, options);
|
let params = buildParams(model, context, isOAuthToken, options);
|
||||||
options?.onPayload?.(params);
|
const nextParams = await options?.onPayload?.(params, model);
|
||||||
|
if (nextParams !== undefined) {
|
||||||
|
params = nextParams as MessageCreateParamsStreaming;
|
||||||
|
}
|
||||||
const anthropicStream = client.messages.stream({ ...params, stream: true }, { signal: options?.signal });
|
const anthropicStream = client.messages.stream({ ...params, stream: true }, { signal: options?.signal });
|
||||||
stream.push({ type: "start", partial: output });
|
stream.push({ type: "start", partial: output });
|
||||||
|
|
||||||
|
|||||||
@@ -85,8 +85,11 @@ export const streamAzureOpenAIResponses: StreamFunction<"azure-openai-responses"
|
|||||||
// Create Azure OpenAI client
|
// Create Azure OpenAI client
|
||||||
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
||||||
const client = createClient(model, apiKey, options);
|
const client = createClient(model, apiKey, options);
|
||||||
const params = buildParams(model, context, options, deploymentName);
|
let params = buildParams(model, context, options, deploymentName);
|
||||||
options?.onPayload?.(params);
|
const nextParams = await options?.onPayload?.(params, model);
|
||||||
|
if (nextParams !== undefined) {
|
||||||
|
params = nextParams as ResponseCreateParamsStreaming;
|
||||||
|
}
|
||||||
const openaiStream = await client.responses.create(
|
const openaiStream = await client.responses.create(
|
||||||
params,
|
params,
|
||||||
options?.signal ? { signal: options.signal } : undefined,
|
options?.signal ? { signal: options.signal } : undefined,
|
||||||
|
|||||||
@@ -369,8 +369,11 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli", GoogleGe
|
|||||||
const baseUrl = model.baseUrl?.trim();
|
const baseUrl = model.baseUrl?.trim();
|
||||||
const endpoints = baseUrl ? [baseUrl] : isAntigravity ? ANTIGRAVITY_ENDPOINT_FALLBACKS : [DEFAULT_ENDPOINT];
|
const endpoints = baseUrl ? [baseUrl] : isAntigravity ? ANTIGRAVITY_ENDPOINT_FALLBACKS : [DEFAULT_ENDPOINT];
|
||||||
|
|
||||||
const requestBody = buildRequest(model, context, projectId, options, isAntigravity);
|
let requestBody = buildRequest(model, context, projectId, options, isAntigravity);
|
||||||
options?.onPayload?.(requestBody);
|
const nextRequestBody = await options?.onPayload?.(requestBody, model);
|
||||||
|
if (nextRequestBody !== undefined) {
|
||||||
|
requestBody = nextRequestBody as CloudCodeAssistRequest;
|
||||||
|
}
|
||||||
const headers = isAntigravity ? getAntigravityHeaders() : GEMINI_CLI_HEADERS;
|
const headers = isAntigravity ? getAntigravityHeaders() : GEMINI_CLI_HEADERS;
|
||||||
|
|
||||||
const requestHeaders = {
|
const requestHeaders = {
|
||||||
|
|||||||
@@ -87,8 +87,11 @@ export const streamGoogleVertex: StreamFunction<"google-vertex", GoogleVertexOpt
|
|||||||
const project = resolveProject(options);
|
const project = resolveProject(options);
|
||||||
const location = resolveLocation(options);
|
const location = resolveLocation(options);
|
||||||
const client = createClient(model, project, location, options?.headers);
|
const client = createClient(model, project, location, options?.headers);
|
||||||
const params = buildParams(model, context, options);
|
let params = buildParams(model, context, options);
|
||||||
options?.onPayload?.(params);
|
const nextParams = await options?.onPayload?.(params, model);
|
||||||
|
if (nextParams !== undefined) {
|
||||||
|
params = nextParams as GenerateContentParameters;
|
||||||
|
}
|
||||||
const googleStream = await client.models.generateContentStream(params);
|
const googleStream = await client.models.generateContentStream(params);
|
||||||
|
|
||||||
stream.push({ type: "start", partial: output });
|
stream.push({ type: "start", partial: output });
|
||||||
|
|||||||
@@ -74,8 +74,11 @@ export const streamGoogle: StreamFunction<"google-generative-ai", GoogleOptions>
|
|||||||
try {
|
try {
|
||||||
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
||||||
const client = createClient(model, apiKey, options?.headers);
|
const client = createClient(model, apiKey, options?.headers);
|
||||||
const params = buildParams(model, context, options);
|
let params = buildParams(model, context, options);
|
||||||
options?.onPayload?.(params);
|
const nextParams = await options?.onPayload?.(params, model);
|
||||||
|
if (nextParams !== undefined) {
|
||||||
|
params = nextParams as GenerateContentParameters;
|
||||||
|
}
|
||||||
const googleStream = await client.models.generateContentStream(params);
|
const googleStream = await client.models.generateContentStream(params);
|
||||||
|
|
||||||
stream.push({ type: "start", partial: output });
|
stream.push({ type: "start", partial: output });
|
||||||
|
|||||||
@@ -69,8 +69,11 @@ export const streamMistral: StreamFunction<"mistral-conversations", MistralOptio
|
|||||||
const normalizeMistralToolCallId = createMistralToolCallIdNormalizer();
|
const normalizeMistralToolCallId = createMistralToolCallIdNormalizer();
|
||||||
const transformedMessages = transformMessages(context.messages, model, (id) => normalizeMistralToolCallId(id));
|
const transformedMessages = transformMessages(context.messages, model, (id) => normalizeMistralToolCallId(id));
|
||||||
|
|
||||||
const payload = buildChatPayload(model, context, transformedMessages, options);
|
let payload = buildChatPayload(model, context, transformedMessages, options);
|
||||||
options?.onPayload?.(payload);
|
const nextPayload = await options?.onPayload?.(payload, model);
|
||||||
|
if (nextPayload !== undefined) {
|
||||||
|
payload = nextPayload as ChatCompletionStreamRequest;
|
||||||
|
}
|
||||||
const mistralStream = await mistral.chat.stream(payload, buildRequestOptions(model, options));
|
const mistralStream = await mistral.chat.stream(payload, buildRequestOptions(model, options));
|
||||||
stream.push({ type: "start", partial: output });
|
stream.push({ type: "start", partial: output });
|
||||||
await consumeChatStream(model, output, stream, mistralStream);
|
await consumeChatStream(model, output, stream, mistralStream);
|
||||||
|
|||||||
@@ -140,8 +140,11 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
|
|||||||
}
|
}
|
||||||
|
|
||||||
const accountId = extractAccountId(apiKey);
|
const accountId = extractAccountId(apiKey);
|
||||||
const body = buildRequestBody(model, context, options);
|
let body = buildRequestBody(model, context, options);
|
||||||
options?.onPayload?.(body);
|
const nextBody = await options?.onPayload?.(body, model);
|
||||||
|
if (nextBody !== undefined) {
|
||||||
|
body = nextBody as RequestBody;
|
||||||
|
}
|
||||||
const headers = buildHeaders(model.headers, options?.headers, accountId, apiKey, options?.sessionId);
|
const headers = buildHeaders(model.headers, options?.headers, accountId, apiKey, options?.sessionId);
|
||||||
const bodyJson = JSON.stringify(body);
|
const bodyJson = JSON.stringify(body);
|
||||||
const transport = options?.transport || "sse";
|
const transport = options?.transport || "sse";
|
||||||
|
|||||||
@@ -86,8 +86,11 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions", OpenA
|
|||||||
try {
|
try {
|
||||||
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
||||||
const client = createClient(model, context, apiKey, options?.headers);
|
const client = createClient(model, context, apiKey, options?.headers);
|
||||||
const params = buildParams(model, context, options);
|
let params = buildParams(model, context, options);
|
||||||
options?.onPayload?.(params);
|
const nextParams = await options?.onPayload?.(params, model);
|
||||||
|
if (nextParams !== undefined) {
|
||||||
|
params = nextParams as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming;
|
||||||
|
}
|
||||||
const openaiStream = await client.chat.completions.create(params, { signal: options?.signal });
|
const openaiStream = await client.chat.completions.create(params, { signal: options?.signal });
|
||||||
stream.push({ type: "start", partial: output });
|
stream.push({ type: "start", partial: output });
|
||||||
|
|
||||||
|
|||||||
@@ -89,8 +89,11 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses", OpenAIRes
|
|||||||
// Create OpenAI client
|
// Create OpenAI client
|
||||||
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
||||||
const client = createClient(model, context, apiKey, options?.headers);
|
const client = createClient(model, context, apiKey, options?.headers);
|
||||||
const params = buildParams(model, context, options);
|
let params = buildParams(model, context, options);
|
||||||
options?.onPayload?.(params);
|
const nextParams = await options?.onPayload?.(params, model);
|
||||||
|
if (nextParams !== undefined) {
|
||||||
|
params = nextParams as ResponseCreateParamsStreaming;
|
||||||
|
}
|
||||||
const openaiStream = await client.responses.create(
|
const openaiStream = await client.responses.create(
|
||||||
params,
|
params,
|
||||||
options?.signal ? { signal: options.signal } : undefined,
|
options?.signal ? { signal: options.signal } : undefined,
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
import { clearApiProviders, registerApiProvider } from "../api-registry.js";
|
import { clearApiProviders, registerApiProvider } from "../api-registry.js";
|
||||||
import type {
|
import type { AssistantMessage, AssistantMessageEvent, Context, Model, SimpleStreamOptions } from "../types.js";
|
||||||
AssistantMessage,
|
|
||||||
AssistantMessageEvent,
|
|
||||||
Context,
|
|
||||||
Model,
|
|
||||||
SimpleStreamOptions,
|
|
||||||
StreamOptions,
|
|
||||||
} from "../types.js";
|
|
||||||
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
||||||
|
import type { BedrockOptions } from "./amazon-bedrock.js";
|
||||||
import { streamAnthropic, streamSimpleAnthropic } from "./anthropic.js";
|
import { streamAnthropic, streamSimpleAnthropic } from "./anthropic.js";
|
||||||
import { streamAzureOpenAIResponses, streamSimpleAzureOpenAIResponses } from "./azure-openai-responses.js";
|
import { streamAzureOpenAIResponses, streamSimpleAzureOpenAIResponses } from "./azure-openai-responses.js";
|
||||||
import { streamGoogle, streamSimpleGoogle } from "./google.js";
|
import { streamGoogle, streamSimpleGoogle } from "./google.js";
|
||||||
@@ -22,7 +16,7 @@ interface BedrockProviderModule {
|
|||||||
streamBedrock: (
|
streamBedrock: (
|
||||||
model: Model<"bedrock-converse-stream">,
|
model: Model<"bedrock-converse-stream">,
|
||||||
context: Context,
|
context: Context,
|
||||||
options?: StreamOptions,
|
options?: BedrockOptions,
|
||||||
) => AsyncIterable<AssistantMessageEvent>;
|
) => AsyncIterable<AssistantMessageEvent>;
|
||||||
streamSimpleBedrock: (
|
streamSimpleBedrock: (
|
||||||
model: Model<"bedrock-converse-stream">,
|
model: Model<"bedrock-converse-stream">,
|
||||||
@@ -83,7 +77,7 @@ function createLazyLoadErrorMessage(model: Model<"bedrock-converse-stream">, err
|
|||||||
function streamBedrockLazy(
|
function streamBedrockLazy(
|
||||||
model: Model<"bedrock-converse-stream">,
|
model: Model<"bedrock-converse-stream">,
|
||||||
context: Context,
|
context: Context,
|
||||||
options?: StreamOptions,
|
options?: BedrockOptions,
|
||||||
): AssistantMessageEventStream {
|
): AssistantMessageEventStream {
|
||||||
const outer = new AssistantMessageEventStream();
|
const outer = new AssistantMessageEventStream();
|
||||||
|
|
||||||
|
|||||||
@@ -79,9 +79,10 @@ export interface StreamOptions {
|
|||||||
*/
|
*/
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
/**
|
/**
|
||||||
* Optional callback for inspecting provider payloads before sending.
|
* Optional callback for inspecting or replacing provider payloads before sending.
|
||||||
|
* Return undefined to keep the payload unchanged.
|
||||||
*/
|
*/
|
||||||
onPayload?: (payload: unknown) => void;
|
onPayload?: (payload: unknown, model: Model<Api>) => unknown | undefined | Promise<unknown | undefined>;
|
||||||
/**
|
/**
|
||||||
* Optional custom HTTP headers to include in API requests.
|
* Optional custom HTTP headers to include in API requests.
|
||||||
* Merged with provider defaults; can override default headers.
|
* Merged with provider defaults; can override default headers.
|
||||||
|
|||||||
@@ -243,6 +243,7 @@ user sends prompt ────────────────────
|
|||||||
│ │ │ │
|
│ │ │ │
|
||||||
│ ├─► turn_start │ │
|
│ ├─► turn_start │ │
|
||||||
│ ├─► context (can modify messages) │ │
|
│ ├─► context (can modify messages) │ │
|
||||||
|
│ ├─► before_provider_request (can inspect or replace payload)
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
│ │ LLM responds, may call tools: │ │
|
│ │ LLM responds, may call tools: │ │
|
||||||
│ │ ├─► tool_call (can block) │ │
|
│ │ ├─► 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 Events
|
||||||
|
|
||||||
#### model_select
|
#### 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` |
|
| `dirty-repo-guard.ts` | Warn on dirty git repo | `on("session_before_*")`, `exec` |
|
||||||
| `input-transform.ts` | Transform user input | `on("input")` |
|
| `input-transform.ts` | Transform user input | `on("input")` |
|
||||||
| `model-status.ts` | React to model changes | `on("model_select")`, `setStatus` |
|
| `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` |
|
| `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")` |
|
| `claude-rules.ts` | Load rules from files | `on("session_start")`, `on("before_agent_start")` |
|
||||||
| `file-trigger.ts` | File watcher triggers messages | `sendMessage` |
|
| `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,
|
BashToolResultEvent,
|
||||||
BeforeAgentStartEvent,
|
BeforeAgentStartEvent,
|
||||||
BeforeAgentStartEventResult,
|
BeforeAgentStartEventResult,
|
||||||
|
BeforeProviderRequestEvent,
|
||||||
|
BeforeProviderRequestEventResult,
|
||||||
// Context
|
// Context
|
||||||
CompactOptions,
|
CompactOptions,
|
||||||
// Events - Agent
|
// Events - Agent
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type { SessionManager } from "../session-manager.js";
|
|||||||
import type {
|
import type {
|
||||||
BeforeAgentStartEvent,
|
BeforeAgentStartEvent,
|
||||||
BeforeAgentStartEventResult,
|
BeforeAgentStartEventResult,
|
||||||
|
BeforeProviderRequestEvent,
|
||||||
CompactOptions,
|
CompactOptions,
|
||||||
ContextEvent,
|
ContextEvent,
|
||||||
ContextEventResult,
|
ContextEventResult,
|
||||||
@@ -105,6 +106,7 @@ type RunnerEmitEvent = Exclude<
|
|||||||
| ToolResultEvent
|
| ToolResultEvent
|
||||||
| UserBashEvent
|
| UserBashEvent
|
||||||
| ContextEvent
|
| ContextEvent
|
||||||
|
| BeforeProviderRequestEvent
|
||||||
| BeforeAgentStartEvent
|
| BeforeAgentStartEvent
|
||||||
| ResourcesDiscoverEvent
|
| ResourcesDiscoverEvent
|
||||||
| InputEvent
|
| InputEvent
|
||||||
@@ -710,6 +712,40 @@ export class ExtensionRunner {
|
|||||||
return currentMessages;
|
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(
|
async emitBeforeAgentStart(
|
||||||
prompt: string,
|
prompt: string,
|
||||||
images: ImageContent[] | undefined,
|
images: ImageContent[] | undefined,
|
||||||
|
|||||||
@@ -493,6 +493,12 @@ export interface ContextEvent {
|
|||||||
messages: AgentMessage[];
|
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. */
|
/** Fired after user submits prompt but before agent loop. */
|
||||||
export interface BeforeAgentStartEvent {
|
export interface BeforeAgentStartEvent {
|
||||||
type: "before_agent_start";
|
type: "before_agent_start";
|
||||||
@@ -807,6 +813,7 @@ export type ExtensionEvent =
|
|||||||
| ResourcesDiscoverEvent
|
| ResourcesDiscoverEvent
|
||||||
| SessionEvent
|
| SessionEvent
|
||||||
| ContextEvent
|
| ContextEvent
|
||||||
|
| BeforeProviderRequestEvent
|
||||||
| BeforeAgentStartEvent
|
| BeforeAgentStartEvent
|
||||||
| AgentStartEvent
|
| AgentStartEvent
|
||||||
| AgentEndEvent
|
| AgentEndEvent
|
||||||
@@ -832,6 +839,8 @@ export interface ContextEventResult {
|
|||||||
messages?: AgentMessage[];
|
messages?: AgentMessage[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type BeforeProviderRequestEventResult = unknown;
|
||||||
|
|
||||||
export interface ToolCallEventResult {
|
export interface ToolCallEventResult {
|
||||||
block?: boolean;
|
block?: boolean;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
@@ -944,6 +953,10 @@ export interface ExtensionAPI {
|
|||||||
on(event: "session_before_tree", handler: ExtensionHandler<SessionBeforeTreeEvent, SessionBeforeTreeResult>): void;
|
on(event: "session_before_tree", handler: ExtensionHandler<SessionBeforeTreeEvent, SessionBeforeTreeResult>): void;
|
||||||
on(event: "session_tree", handler: ExtensionHandler<SessionTreeEvent>): void;
|
on(event: "session_tree", handler: ExtensionHandler<SessionTreeEvent>): void;
|
||||||
on(event: "context", handler: ExtensionHandler<ContextEvent, ContextEventResult>): 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: "before_agent_start", handler: ExtensionHandler<BeforeAgentStartEvent, BeforeAgentStartEventResult>): void;
|
||||||
on(event: "agent_start", handler: ExtensionHandler<AgentStartEvent>): void;
|
on(event: "agent_start", handler: ExtensionHandler<AgentStartEvent>): void;
|
||||||
on(event: "agent_end", handler: ExtensionHandler<AgentEndEvent>): void;
|
on(event: "agent_end", handler: ExtensionHandler<AgentEndEvent>): void;
|
||||||
|
|||||||
@@ -292,6 +292,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||||||
tools: [],
|
tools: [],
|
||||||
},
|
},
|
||||||
convertToLlm: convertToLlmWithBlockImages,
|
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(),
|
sessionId: sessionManager.getSessionId(),
|
||||||
transformContext: async (messages) => {
|
transformContext: async (messages) => {
|
||||||
const runner = extensionRunnerRef.current;
|
const runner = extensionRunnerRef.current;
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ export type {
|
|||||||
AppAction,
|
AppAction,
|
||||||
BashToolCallEvent,
|
BashToolCallEvent,
|
||||||
BeforeAgentStartEvent,
|
BeforeAgentStartEvent,
|
||||||
|
BeforeProviderRequestEvent,
|
||||||
|
BeforeProviderRequestEventResult,
|
||||||
CompactOptions,
|
CompactOptions,
|
||||||
ContextEvent,
|
ContextEvent,
|
||||||
ContextUsage,
|
ContextUsage,
|
||||||
|
|||||||
Reference in New Issue
Block a user