mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-22 01:02:16 +00:00
Extensions: add pi.sendUserMessage() for sending user messages
Adds sendUserMessage() to the extension API, allowing extensions to send actual user messages (role: user) rather than custom messages. Unlike sendMessage(), this always triggers a turn and behaves as if the user typed the message. - Add SendUserMessageHandler type and sendUserMessage() to ExtensionAPI - Wire handler through loader, runner, and all modes - Implement via prompt() with expandPromptTemplates: false - Add send-user-message.ts example with /ask, /steer, /followup commands - Document in extensions.md fixes #483
This commit is contained in:
parent
f023af0dab
commit
7210086677
13 changed files with 222 additions and 1 deletions
|
|
@ -7,6 +7,7 @@
|
||||||
- Extensions can now replace the footer with `ctx.ui.setFooter()`, see `examples/extensions/custom-footer.ts` ([#481](https://github.com/badlogic/pi-mono/issues/481))
|
- Extensions can now replace the footer with `ctx.ui.setFooter()`, see `examples/extensions/custom-footer.ts` ([#481](https://github.com/badlogic/pi-mono/issues/481))
|
||||||
- Session ID is now forwarded to LLM providers for session-based caching (used by OpenAI Codex for prompt caching).
|
- Session ID is now forwarded to LLM providers for session-based caching (used by OpenAI Codex for prompt caching).
|
||||||
- Added `blockImages` setting to prevent images from being sent to LLM providers ([#492](https://github.com/badlogic/pi-mono/pull/492) by [@jsinge97](https://github.com/jsinge97))
|
- Added `blockImages` setting to prevent images from being sent to LLM providers ([#492](https://github.com/badlogic/pi-mono/pull/492) by [@jsinge97](https://github.com/jsinge97))
|
||||||
|
- Extensions can now send user messages via `pi.sendUserMessage()` ([#483](https://github.com/badlogic/pi-mono/issues/483))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -632,7 +632,7 @@ pi.registerTool({
|
||||||
|
|
||||||
### pi.sendMessage(message, options?)
|
### pi.sendMessage(message, options?)
|
||||||
|
|
||||||
Inject a message into the session:
|
Inject a custom message into the session:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
pi.sendMessage({
|
pi.sendMessage({
|
||||||
|
|
@ -653,6 +653,34 @@ pi.sendMessage({
|
||||||
- `"nextTurn"` - Queued for next user prompt. Does not interrupt or trigger anything.
|
- `"nextTurn"` - Queued for next user prompt. Does not interrupt or trigger anything.
|
||||||
- `triggerTurn: true` - If agent is idle, trigger an LLM response immediately. Only applies to `"steer"` and `"followUp"` modes (ignored for `"nextTurn"`).
|
- `triggerTurn: true` - If agent is idle, trigger an LLM response immediately. Only applies to `"steer"` and `"followUp"` modes (ignored for `"nextTurn"`).
|
||||||
|
|
||||||
|
### pi.sendUserMessage(content, options?)
|
||||||
|
|
||||||
|
Send a user message to the agent. Unlike `sendMessage()` which sends custom messages, this sends an actual user message that appears as if typed by the user. Always triggers a turn.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Simple text message
|
||||||
|
pi.sendUserMessage("What is 2+2?");
|
||||||
|
|
||||||
|
// With content array (text + images)
|
||||||
|
pi.sendUserMessage([
|
||||||
|
{ type: "text", text: "Describe this image:" },
|
||||||
|
{ type: "image", source: { type: "base64", mediaType: "image/png", data: "..." } },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// During streaming - must specify delivery mode
|
||||||
|
pi.sendUserMessage("Focus on error handling", { deliverAs: "steer" });
|
||||||
|
pi.sendUserMessage("And then summarize", { deliverAs: "followUp" });
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
- `deliverAs` - Required when agent is streaming:
|
||||||
|
- `"steer"` - Interrupts after current tool, remaining tools skipped
|
||||||
|
- `"followUp"` - Waits for agent to finish all tools
|
||||||
|
|
||||||
|
When not streaming, the message is sent immediately and triggers a new turn. When streaming without `deliverAs`, throws an error.
|
||||||
|
|
||||||
|
See [send-user-message.ts](../examples/extensions/send-user-message.ts) for a complete example.
|
||||||
|
|
||||||
### pi.appendEntry(customType, data?)
|
### pi.appendEntry(customType, data?)
|
||||||
|
|
||||||
Persist extension state (does NOT participate in LLM context):
|
Persist extension state (does NOT participate in LLM context):
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|
||||||
| `qna.ts` | Extracts questions from last response into editor via `ctx.ui.setEditorText()` |
|
| `qna.ts` | Extracts questions from last response into editor via `ctx.ui.setEditorText()` |
|
||||||
| `status-line.ts` | Shows turn progress in footer via `ctx.ui.setStatus()` with themed colors |
|
| `status-line.ts` | Shows turn progress in footer via `ctx.ui.setStatus()` with themed colors |
|
||||||
| `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
|
| `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
|
||||||
|
| `send-user-message.ts` | Demonstrates `pi.sendUserMessage()` for sending user messages from extensions |
|
||||||
|
|
||||||
### Git Integration
|
### Git Integration
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
/**
|
||||||
|
* Send User Message Example
|
||||||
|
*
|
||||||
|
* Demonstrates pi.sendUserMessage() for sending user messages from extensions.
|
||||||
|
* Unlike pi.sendMessage() which sends custom messages, sendUserMessage() sends
|
||||||
|
* actual user messages that appear in the conversation as if typed by the user.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* /ask What is 2+2? - Sends a user message (always triggers a turn)
|
||||||
|
* /steer Focus on X - Sends while streaming with steer delivery
|
||||||
|
* /followup And then? - Sends while streaming with followUp delivery
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
|
export default function (pi: ExtensionAPI) {
|
||||||
|
// Simple command that sends a user message
|
||||||
|
pi.registerCommand("ask", {
|
||||||
|
description: "Send a user message to the agent",
|
||||||
|
handler: async (args, ctx) => {
|
||||||
|
if (!args.trim()) {
|
||||||
|
ctx.ui.notify("Usage: /ask <message>", "warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendUserMessage always triggers a turn when not streaming
|
||||||
|
// If streaming, it will throw (no deliverAs specified)
|
||||||
|
if (!ctx.isIdle()) {
|
||||||
|
ctx.ui.notify("Agent is busy. Use /steer or /followup instead.", "warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pi.sendUserMessage(args);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Command that steers the agent mid-conversation
|
||||||
|
pi.registerCommand("steer", {
|
||||||
|
description: "Send a steering message (interrupts current processing)",
|
||||||
|
handler: async (args, ctx) => {
|
||||||
|
if (!args.trim()) {
|
||||||
|
ctx.ui.notify("Usage: /steer <message>", "warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.isIdle()) {
|
||||||
|
// Not streaming, just send normally
|
||||||
|
pi.sendUserMessage(args);
|
||||||
|
} else {
|
||||||
|
// Streaming - use steer to interrupt
|
||||||
|
pi.sendUserMessage(args, { deliverAs: "steer" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Command that queues a follow-up message
|
||||||
|
pi.registerCommand("followup", {
|
||||||
|
description: "Queue a follow-up message (waits for current processing)",
|
||||||
|
handler: async (args, ctx) => {
|
||||||
|
if (!args.trim()) {
|
||||||
|
ctx.ui.notify("Usage: /followup <message>", "warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.isIdle()) {
|
||||||
|
// Not streaming, just send normally
|
||||||
|
pi.sendUserMessage(args);
|
||||||
|
} else {
|
||||||
|
// Streaming - queue as follow-up
|
||||||
|
pi.sendUserMessage(args, { deliverAs: "followUp" });
|
||||||
|
ctx.ui.notify("Follow-up queued", "info");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Example with content array (text + images would go here)
|
||||||
|
pi.registerCommand("askwith", {
|
||||||
|
description: "Send a user message with structured content",
|
||||||
|
handler: async (args, ctx) => {
|
||||||
|
if (!args.trim()) {
|
||||||
|
ctx.ui.notify("Usage: /askwith <message>", "warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx.isIdle()) {
|
||||||
|
ctx.ui.notify("Agent is busy", "warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendUserMessage accepts string or (TextContent | ImageContent)[]
|
||||||
|
pi.sendUserMessage([
|
||||||
|
{ type: "text", text: `User request: ${args}` },
|
||||||
|
{ type: "text", text: "Please respond concisely." },
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -788,6 +788,45 @@ export class AgentSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a user message to the agent. Always triggers a turn.
|
||||||
|
* When the agent is streaming, use deliverAs to specify how to queue the message.
|
||||||
|
*
|
||||||
|
* @param content User message content (string or content array)
|
||||||
|
* @param options.deliverAs Delivery mode when streaming: "steer" or "followUp"
|
||||||
|
*/
|
||||||
|
async sendUserMessage(
|
||||||
|
content: string | (TextContent | ImageContent)[],
|
||||||
|
options?: { deliverAs?: "steer" | "followUp" },
|
||||||
|
): Promise<void> {
|
||||||
|
// Normalize content to text string + optional images
|
||||||
|
let text: string;
|
||||||
|
let images: ImageContent[] | undefined;
|
||||||
|
|
||||||
|
if (typeof content === "string") {
|
||||||
|
text = content;
|
||||||
|
} else {
|
||||||
|
const textParts: string[] = [];
|
||||||
|
images = [];
|
||||||
|
for (const part of content) {
|
||||||
|
if (part.type === "text") {
|
||||||
|
textParts.push(part.text);
|
||||||
|
} else {
|
||||||
|
images.push(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text = textParts.join("\n");
|
||||||
|
if (images.length === 0) images = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use prompt() with expandPromptTemplates: false to skip command handling and template expansion
|
||||||
|
await this.prompt(text, {
|
||||||
|
expandPromptTemplates: false,
|
||||||
|
streamingBehavior: options?.deliverAs,
|
||||||
|
images,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear all queued messages and return them.
|
* Clear all queued messages and return them.
|
||||||
* Useful for restoring to editor when user aborts.
|
* Useful for restoring to editor when user aborts.
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ export type {
|
||||||
RegisteredCommand,
|
RegisteredCommand,
|
||||||
RegisteredTool,
|
RegisteredTool,
|
||||||
SendMessageHandler,
|
SendMessageHandler,
|
||||||
|
SendUserMessageHandler,
|
||||||
SessionBeforeBranchEvent,
|
SessionBeforeBranchEvent,
|
||||||
SessionBeforeBranchResult,
|
SessionBeforeBranchResult,
|
||||||
SessionBeforeCompactEvent,
|
SessionBeforeCompactEvent,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import type {
|
||||||
RegisteredCommand,
|
RegisteredCommand,
|
||||||
RegisteredTool,
|
RegisteredTool,
|
||||||
SendMessageHandler,
|
SendMessageHandler,
|
||||||
|
SendUserMessageHandler,
|
||||||
SetActiveToolsHandler,
|
SetActiveToolsHandler,
|
||||||
ToolDefinition,
|
ToolDefinition,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
|
@ -117,6 +118,7 @@ function createExtensionAPI(
|
||||||
flagValues: Map<string, boolean | string>;
|
flagValues: Map<string, boolean | string>;
|
||||||
shortcuts: Map<KeyId, ExtensionShortcut>;
|
shortcuts: Map<KeyId, ExtensionShortcut>;
|
||||||
setSendMessageHandler: (handler: SendMessageHandler) => void;
|
setSendMessageHandler: (handler: SendMessageHandler) => void;
|
||||||
|
setSendUserMessageHandler: (handler: SendUserMessageHandler) => void;
|
||||||
setAppendEntryHandler: (handler: AppendEntryHandler) => void;
|
setAppendEntryHandler: (handler: AppendEntryHandler) => void;
|
||||||
setGetActiveToolsHandler: (handler: GetActiveToolsHandler) => void;
|
setGetActiveToolsHandler: (handler: GetActiveToolsHandler) => void;
|
||||||
setGetAllToolsHandler: (handler: GetAllToolsHandler) => void;
|
setGetAllToolsHandler: (handler: GetAllToolsHandler) => void;
|
||||||
|
|
@ -124,6 +126,7 @@ function createExtensionAPI(
|
||||||
setFlagValue: (name: string, value: boolean | string) => void;
|
setFlagValue: (name: string, value: boolean | string) => void;
|
||||||
} {
|
} {
|
||||||
let sendMessageHandler: SendMessageHandler = () => {};
|
let sendMessageHandler: SendMessageHandler = () => {};
|
||||||
|
let sendUserMessageHandler: SendUserMessageHandler = () => {};
|
||||||
let appendEntryHandler: AppendEntryHandler = () => {};
|
let appendEntryHandler: AppendEntryHandler = () => {};
|
||||||
let getActiveToolsHandler: GetActiveToolsHandler = () => [];
|
let getActiveToolsHandler: GetActiveToolsHandler = () => [];
|
||||||
let getAllToolsHandler: GetAllToolsHandler = () => [];
|
let getAllToolsHandler: GetAllToolsHandler = () => [];
|
||||||
|
|
@ -185,6 +188,10 @@ function createExtensionAPI(
|
||||||
sendMessageHandler(message, options);
|
sendMessageHandler(message, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sendUserMessage(content, options): void {
|
||||||
|
sendUserMessageHandler(content, options);
|
||||||
|
},
|
||||||
|
|
||||||
appendEntry(customType: string, data?: unknown): void {
|
appendEntry(customType: string, data?: unknown): void {
|
||||||
appendEntryHandler(customType, data);
|
appendEntryHandler(customType, data);
|
||||||
},
|
},
|
||||||
|
|
@ -218,6 +225,9 @@ function createExtensionAPI(
|
||||||
setSendMessageHandler: (handler: SendMessageHandler) => {
|
setSendMessageHandler: (handler: SendMessageHandler) => {
|
||||||
sendMessageHandler = handler;
|
sendMessageHandler = handler;
|
||||||
},
|
},
|
||||||
|
setSendUserMessageHandler: (handler: SendUserMessageHandler) => {
|
||||||
|
sendUserMessageHandler = handler;
|
||||||
|
},
|
||||||
setAppendEntryHandler: (handler: AppendEntryHandler) => {
|
setAppendEntryHandler: (handler: AppendEntryHandler) => {
|
||||||
appendEntryHandler = handler;
|
appendEntryHandler = handler;
|
||||||
},
|
},
|
||||||
|
|
@ -261,6 +271,7 @@ async function loadExtensionWithBun(
|
||||||
flagValues,
|
flagValues,
|
||||||
shortcuts,
|
shortcuts,
|
||||||
setSendMessageHandler,
|
setSendMessageHandler,
|
||||||
|
setSendUserMessageHandler,
|
||||||
setAppendEntryHandler,
|
setAppendEntryHandler,
|
||||||
setGetActiveToolsHandler,
|
setGetActiveToolsHandler,
|
||||||
setGetAllToolsHandler,
|
setGetAllToolsHandler,
|
||||||
|
|
@ -282,6 +293,7 @@ async function loadExtensionWithBun(
|
||||||
flagValues,
|
flagValues,
|
||||||
shortcuts,
|
shortcuts,
|
||||||
setSendMessageHandler,
|
setSendMessageHandler,
|
||||||
|
setSendUserMessageHandler,
|
||||||
setAppendEntryHandler,
|
setAppendEntryHandler,
|
||||||
setGetActiveToolsHandler,
|
setGetActiveToolsHandler,
|
||||||
setGetAllToolsHandler,
|
setGetAllToolsHandler,
|
||||||
|
|
@ -341,6 +353,7 @@ async function loadExtension(
|
||||||
flagValues,
|
flagValues,
|
||||||
shortcuts,
|
shortcuts,
|
||||||
setSendMessageHandler,
|
setSendMessageHandler,
|
||||||
|
setSendUserMessageHandler,
|
||||||
setAppendEntryHandler,
|
setAppendEntryHandler,
|
||||||
setGetActiveToolsHandler,
|
setGetActiveToolsHandler,
|
||||||
setGetAllToolsHandler,
|
setGetAllToolsHandler,
|
||||||
|
|
@ -362,6 +375,7 @@ async function loadExtension(
|
||||||
flagValues,
|
flagValues,
|
||||||
shortcuts,
|
shortcuts,
|
||||||
setSendMessageHandler,
|
setSendMessageHandler,
|
||||||
|
setSendUserMessageHandler,
|
||||||
setAppendEntryHandler,
|
setAppendEntryHandler,
|
||||||
setGetActiveToolsHandler,
|
setGetActiveToolsHandler,
|
||||||
setGetAllToolsHandler,
|
setGetAllToolsHandler,
|
||||||
|
|
@ -396,6 +410,7 @@ export function loadExtensionFromFactory(
|
||||||
flagValues,
|
flagValues,
|
||||||
shortcuts,
|
shortcuts,
|
||||||
setSendMessageHandler,
|
setSendMessageHandler,
|
||||||
|
setSendUserMessageHandler,
|
||||||
setAppendEntryHandler,
|
setAppendEntryHandler,
|
||||||
setGetActiveToolsHandler,
|
setGetActiveToolsHandler,
|
||||||
setGetAllToolsHandler,
|
setGetAllToolsHandler,
|
||||||
|
|
@ -416,6 +431,7 @@ export function loadExtensionFromFactory(
|
||||||
flagValues,
|
flagValues,
|
||||||
shortcuts,
|
shortcuts,
|
||||||
setSendMessageHandler,
|
setSendMessageHandler,
|
||||||
|
setSendUserMessageHandler,
|
||||||
setAppendEntryHandler,
|
setAppendEntryHandler,
|
||||||
setGetActiveToolsHandler,
|
setGetActiveToolsHandler,
|
||||||
setGetAllToolsHandler,
|
setGetAllToolsHandler,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import type {
|
||||||
RegisteredCommand,
|
RegisteredCommand,
|
||||||
RegisteredTool,
|
RegisteredTool,
|
||||||
SendMessageHandler,
|
SendMessageHandler,
|
||||||
|
SendUserMessageHandler,
|
||||||
SessionBeforeCompactResult,
|
SessionBeforeCompactResult,
|
||||||
SessionBeforeTreeResult,
|
SessionBeforeTreeResult,
|
||||||
SetActiveToolsHandler,
|
SetActiveToolsHandler,
|
||||||
|
|
@ -108,6 +109,7 @@ export class ExtensionRunner {
|
||||||
initialize(options: {
|
initialize(options: {
|
||||||
getModel: () => Model<any> | undefined;
|
getModel: () => Model<any> | undefined;
|
||||||
sendMessageHandler: SendMessageHandler;
|
sendMessageHandler: SendMessageHandler;
|
||||||
|
sendUserMessageHandler: SendUserMessageHandler;
|
||||||
appendEntryHandler: AppendEntryHandler;
|
appendEntryHandler: AppendEntryHandler;
|
||||||
getActiveToolsHandler: GetActiveToolsHandler;
|
getActiveToolsHandler: GetActiveToolsHandler;
|
||||||
getAllToolsHandler: GetAllToolsHandler;
|
getAllToolsHandler: GetAllToolsHandler;
|
||||||
|
|
@ -140,6 +142,7 @@ export class ExtensionRunner {
|
||||||
|
|
||||||
for (const ext of this.extensions) {
|
for (const ext of this.extensions) {
|
||||||
ext.setSendMessageHandler(options.sendMessageHandler);
|
ext.setSendMessageHandler(options.sendMessageHandler);
|
||||||
|
ext.setSendUserMessageHandler(options.sendUserMessageHandler);
|
||||||
ext.setAppendEntryHandler(options.appendEntryHandler);
|
ext.setAppendEntryHandler(options.appendEntryHandler);
|
||||||
ext.setGetActiveToolsHandler(options.getActiveToolsHandler);
|
ext.setGetActiveToolsHandler(options.getActiveToolsHandler);
|
||||||
ext.setGetAllToolsHandler(options.getAllToolsHandler);
|
ext.setGetAllToolsHandler(options.getAllToolsHandler);
|
||||||
|
|
|
||||||
|
|
@ -592,6 +592,15 @@ export interface ExtensionAPI {
|
||||||
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
|
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a user message to the agent. Always triggers a turn.
|
||||||
|
* When the agent is streaming, use deliverAs to specify how to queue the message.
|
||||||
|
*/
|
||||||
|
sendUserMessage(
|
||||||
|
content: string | (TextContent | ImageContent)[],
|
||||||
|
options?: { deliverAs?: "steer" | "followUp" },
|
||||||
|
): void;
|
||||||
|
|
||||||
/** Append a custom entry to the session for state persistence (not sent to LLM). */
|
/** Append a custom entry to the session for state persistence (not sent to LLM). */
|
||||||
appendEntry<T = unknown>(customType: string, data?: T): void;
|
appendEntry<T = unknown>(customType: string, data?: T): void;
|
||||||
|
|
||||||
|
|
@ -645,6 +654,11 @@ export type SendMessageHandler = <T = unknown>(
|
||||||
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
|
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
|
export type SendUserMessageHandler = (
|
||||||
|
content: string | (TextContent | ImageContent)[],
|
||||||
|
options?: { deliverAs?: "steer" | "followUp" },
|
||||||
|
) => void;
|
||||||
|
|
||||||
export type AppendEntryHandler = <T = unknown>(customType: string, data?: T) => void;
|
export type AppendEntryHandler = <T = unknown>(customType: string, data?: T) => void;
|
||||||
|
|
||||||
export type GetActiveToolsHandler = () => string[];
|
export type GetActiveToolsHandler = () => string[];
|
||||||
|
|
@ -665,6 +679,7 @@ export interface LoadedExtension {
|
||||||
flagValues: Map<string, boolean | string>;
|
flagValues: Map<string, boolean | string>;
|
||||||
shortcuts: Map<KeyId, ExtensionShortcut>;
|
shortcuts: Map<KeyId, ExtensionShortcut>;
|
||||||
setSendMessageHandler: (handler: SendMessageHandler) => void;
|
setSendMessageHandler: (handler: SendMessageHandler) => void;
|
||||||
|
setSendUserMessageHandler: (handler: SendUserMessageHandler) => void;
|
||||||
setAppendEntryHandler: (handler: AppendEntryHandler) => void;
|
setAppendEntryHandler: (handler: AppendEntryHandler) => void;
|
||||||
setGetActiveToolsHandler: (handler: GetActiveToolsHandler) => void;
|
setGetActiveToolsHandler: (handler: GetActiveToolsHandler) => void;
|
||||||
setGetAllToolsHandler: (handler: GetAllToolsHandler) => void;
|
setGetAllToolsHandler: (handler: GetAllToolsHandler) => void;
|
||||||
|
|
|
||||||
|
|
@ -442,6 +442,11 @@ export class InteractiveMode {
|
||||||
this.showError(`Extension sendMessage failed: ${err instanceof Error ? err.message : String(err)}`);
|
this.showError(`Extension sendMessage failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
sendUserMessageHandler: (content, options) => {
|
||||||
|
this.session.sendUserMessage(content, options).catch((err) => {
|
||||||
|
this.showError(`Extension sendUserMessage failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
|
});
|
||||||
|
},
|
||||||
appendEntryHandler: (customType, data) => {
|
appendEntryHandler: (customType, data) => {
|
||||||
this.sessionManager.appendCustomEntry(customType, data);
|
this.sessionManager.appendCustomEntry(customType, data);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,11 @@ export async function runPrintMode(
|
||||||
console.error(`Extension sendMessage failed: ${e instanceof Error ? e.message : String(e)}`);
|
console.error(`Extension sendMessage failed: ${e instanceof Error ? e.message : String(e)}`);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
sendUserMessageHandler: (content, options) => {
|
||||||
|
session.sendUserMessage(content, options).catch((e) => {
|
||||||
|
console.error(`Extension sendUserMessage failed: ${e instanceof Error ? e.message : String(e)}`);
|
||||||
|
});
|
||||||
|
},
|
||||||
appendEntryHandler: (customType, data) => {
|
appendEntryHandler: (customType, data) => {
|
||||||
session.sessionManager.appendCustomEntry(customType, data);
|
session.sessionManager.appendCustomEntry(customType, data);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -229,6 +229,11 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
||||||
output(error(undefined, "extension_send", e.message));
|
output(error(undefined, "extension_send", e.message));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
sendUserMessageHandler: (content, options) => {
|
||||||
|
session.sendUserMessage(content, options).catch((e) => {
|
||||||
|
output(error(undefined, "extension_send_user", e.message));
|
||||||
|
});
|
||||||
|
},
|
||||||
appendEntryHandler: (customType, data) => {
|
appendEntryHandler: (customType, data) => {
|
||||||
session.sessionManager.appendCustomEntry(customType, data);
|
session.sessionManager.appendCustomEntry(customType, data);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ describe.skipIf(!API_KEY)("Compaction extensions", () => {
|
||||||
flagValues: new Map(),
|
flagValues: new Map(),
|
||||||
shortcuts: new Map(),
|
shortcuts: new Map(),
|
||||||
setSendMessageHandler: () => {},
|
setSendMessageHandler: () => {},
|
||||||
|
setSendUserMessageHandler: () => {},
|
||||||
setAppendEntryHandler: () => {},
|
setAppendEntryHandler: () => {},
|
||||||
setGetActiveToolsHandler: () => {},
|
setGetActiveToolsHandler: () => {},
|
||||||
setGetAllToolsHandler: () => {},
|
setGetAllToolsHandler: () => {},
|
||||||
|
|
@ -111,6 +112,7 @@ describe.skipIf(!API_KEY)("Compaction extensions", () => {
|
||||||
extensionRunner.initialize({
|
extensionRunner.initialize({
|
||||||
getModel: () => session.model,
|
getModel: () => session.model,
|
||||||
sendMessageHandler: async () => {},
|
sendMessageHandler: async () => {},
|
||||||
|
sendUserMessageHandler: async () => {},
|
||||||
appendEntryHandler: async () => {},
|
appendEntryHandler: async () => {},
|
||||||
getActiveToolsHandler: () => [],
|
getActiveToolsHandler: () => [],
|
||||||
getAllToolsHandler: () => [],
|
getAllToolsHandler: () => [],
|
||||||
|
|
@ -284,6 +286,7 @@ describe.skipIf(!API_KEY)("Compaction extensions", () => {
|
||||||
flagValues: new Map(),
|
flagValues: new Map(),
|
||||||
shortcuts: new Map(),
|
shortcuts: new Map(),
|
||||||
setSendMessageHandler: () => {},
|
setSendMessageHandler: () => {},
|
||||||
|
setSendUserMessageHandler: () => {},
|
||||||
setAppendEntryHandler: () => {},
|
setAppendEntryHandler: () => {},
|
||||||
setGetActiveToolsHandler: () => {},
|
setGetActiveToolsHandler: () => {},
|
||||||
setGetAllToolsHandler: () => {},
|
setGetAllToolsHandler: () => {},
|
||||||
|
|
@ -339,6 +342,7 @@ describe.skipIf(!API_KEY)("Compaction extensions", () => {
|
||||||
flagValues: new Map(),
|
flagValues: new Map(),
|
||||||
shortcuts: new Map(),
|
shortcuts: new Map(),
|
||||||
setSendMessageHandler: () => {},
|
setSendMessageHandler: () => {},
|
||||||
|
setSendUserMessageHandler: () => {},
|
||||||
setAppendEntryHandler: () => {},
|
setAppendEntryHandler: () => {},
|
||||||
setGetActiveToolsHandler: () => {},
|
setGetActiveToolsHandler: () => {},
|
||||||
setGetAllToolsHandler: () => {},
|
setGetAllToolsHandler: () => {},
|
||||||
|
|
@ -376,6 +380,7 @@ describe.skipIf(!API_KEY)("Compaction extensions", () => {
|
||||||
flagValues: new Map(),
|
flagValues: new Map(),
|
||||||
shortcuts: new Map(),
|
shortcuts: new Map(),
|
||||||
setSendMessageHandler: () => {},
|
setSendMessageHandler: () => {},
|
||||||
|
setSendUserMessageHandler: () => {},
|
||||||
setAppendEntryHandler: () => {},
|
setAppendEntryHandler: () => {},
|
||||||
setGetActiveToolsHandler: () => {},
|
setGetActiveToolsHandler: () => {},
|
||||||
setGetAllToolsHandler: () => {},
|
setGetAllToolsHandler: () => {},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue