36 lines
1.9 KiB
TypeScript
36 lines
1.9 KiB
TypeScript
import type { Plugin } from "@opencode/plugin";
|
|
import { getInternalBridgeConfig } from "./internalBridge.js";
|
|
|
|
export async function hostQuestionBridge(operation: string, body: Record<string, unknown>, signal?: AbortSignal): Promise<unknown> {
|
|
const { baseUrl, internalToken } = getInternalBridgeConfig();
|
|
const response = await fetch(`${baseUrl}/internal/chat-interactions/${operation}`, {
|
|
method: "POST", headers: { "content-type": "application/json", "x-agent-internal-token": internalToken },
|
|
body: JSON.stringify(body), signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(10000)]) : AbortSignal.timeout(10000),
|
|
});
|
|
if (!response.ok) throw new Error("Interaction gate rejected the operation. Finish existing tools before asking, then wait for the user.");
|
|
return response.json();
|
|
}
|
|
|
|
export async function registerHostQuestionPolicy(ctx: Parameters<Plugin.Plugin["setup"]>[0]) {
|
|
await ctx.session.hook("context", async (event) => {
|
|
const policy = await hostQuestionBridge("policy", { session_id: event.sessionID }) as { enabled: boolean; waiting: boolean };
|
|
if (!policy.enabled) delete event.tools.ask_user;
|
|
else {
|
|
delete event.tools.question;
|
|
if (policy.waiting) for (const name of Object.keys(event.tools)) delete event.tools[name];
|
|
}
|
|
});
|
|
// Wrap native, MCP and custom executors, including calls via Code Mode.
|
|
await ctx.tool.transform((editor) => {
|
|
for (const definition of editor.list()) editor.update(definition.id, (draft) => {
|
|
const execute = draft.execute;
|
|
draft.execute = async (input, context) => {
|
|
const identity = { session_id: context.sessionID, call_id: context.id, tool: definition.id };
|
|
await hostQuestionBridge("enter", identity, context.signal);
|
|
try { return await execute(input, context); }
|
|
finally { await hostQuestionBridge("leave", identity).catch(() => undefined); }
|
|
};
|
|
});
|
|
});
|
|
}
|