* Shared extension runtime wiring for print and RPC modes.
*
* Both modes initialize the extension runner with the same action handlers
* that delegate to the {@link AgentSession}. Only error reporting, shutdown
* behavior, and UI context differ between callers — those stay as
* caller-supplied hooks.
*/
import { runExtensionCompact, runExtensionSetModel } from "../extensibility/extensions/compact-handler";
import { getSessionSlashCommands } from "../extensibility/extensions/get-commands-handler";
import type { ExtensionError, ExtensionUIContext } from "../extensibility/extensions/types";
import type { AgentSession } from "../session/agent-session";
export type ExtensionSendAction = "extension_send" | "extension_send_user";
export interface InitializeExtensionsOptions {
reportSendError: (action: ExtensionSendAction, error: Error) => void;
reportRuntimeError: (error: ExtensionError) => void;
onShutdown?: () => void;
uiContext?: ExtensionUIContext;
}
* Initialize the session's extension runner with the standard action set
* shared by non-interactive modes, then emit `session_start`.
*
* No-op when the session was constructed without an extension runner.
*/
export async function initializeExtensions(session: AgentSession, options: InitializeExtensionsOptions): Promise<void> {
const runner = session.extensionRunner;
if (!runner) return;
const { reportSendError, reportRuntimeError, onShutdown, uiContext } = options;
const shutdown = onShutdown ?? (() => {});
runner.initialize(
{
sendMessage: (message, sendOptions) => {
session.sendCustomMessage(message, sendOptions).catch(e => {
reportSendError("extension_send", e instanceof Error ? e : new Error(String(e)));
});
},
sendUserMessage: (content, sendOptions) => {
session.sendUserMessage(content, sendOptions).catch(e => {
reportSendError("extension_send_user", e instanceof Error ? e : new Error(String(e)));
});
},
appendEntry: (customType, data) => {
session.sessionManager.appendCustomEntry(customType, data);
},
setLabel: (targetId, label) => {
session.sessionManager.appendLabelChange(targetId, label);
},
getActiveTools: () => session.getActiveToolNames(),
getAllTools: () => session.getAllToolNames(),
setActiveTools: (toolNames: string[]) => session.setActiveToolsByName(toolNames),
getCommands: () => getSessionSlashCommands(session),
setModel: model => runExtensionSetModel(session, model),
getThinkingLevel: () => session.thinkingLevel,
setThinkingLevel: level => session.setThinkingLevel(level),
getSessionName: () => session.sessionManager.getSessionName(),
setSessionName: async name => {
await session.sessionManager.setSessionName(name, "user");
},
},
{
getModel: () => session.model,
isIdle: () => !session.isStreaming,
abort: () => session.abort(),
hasPendingMessages: () => session.queuedMessageCount > 0,
shutdown,
getContextUsage: () => session.getContextUsage(),
getSystemPrompt: () => session.systemPrompt,
compact: instructionsOrOptions => runExtensionCompact(session, instructionsOrOptions),
},
{
getContextUsage: () => session.getContextUsage(),
waitForIdle: () => session.agent.waitForIdle(),
newSession: async newOptions => {
const success = await session.newSession({ parentSession: newOptions?.parentSession });
if (success && newOptions?.setup) {
await newOptions.setup(session.sessionManager);
}
return { cancelled: !success };
},
branch: async entryId => {
const result = await session.branch(entryId);
return { cancelled: result.cancelled };
},
navigateTree: async (targetId, navOptions) => {
const result = await session.navigateTree(targetId, { summarize: navOptions?.summarize });
return { cancelled: result.cancelled };
},
switchSession: async sessionPath => {
const success = await session.switchSession(sessionPath);
return { cancelled: !success };
},
reload: async () => {
await session.reload();
},
compact: instructionsOrOptions => runExtensionCompact(session, instructionsOrOptions),
},
uiContext,
);
runner.onError(reportRuntimeError);
await runner.emit({ type: "session_start" });
}