feat(FN-3713): add web builtin tool opt-in for AI agents
Added agent permission policy model with persistence in core, and wired web-builtin tool opt-in enabling planning and synthesis web tools in the dashboard with corresponding docs and a changeset. Also added an inline fast-mode toggle wired into peer executor state, retry logic for cluster task-ID ov Fusion-Task-Id: FN-3713
This commit is contained in:
@@ -694,6 +694,40 @@ describe("createFnAgent", () => {
|
||||
expect(createSessionArgs.customTools.map((tool) => tool.name)).toContain("fn_list_agents");
|
||||
});
|
||||
|
||||
it("does not allow extra builtin tools in readonly sessions by default", async () => {
|
||||
const { createFnAgent } = await import("../pi.js");
|
||||
|
||||
await createFnAgent({
|
||||
cwd: "/tmp",
|
||||
systemPrompt: "test",
|
||||
tools: "readonly",
|
||||
});
|
||||
|
||||
const createSessionArgs = createAgentSessionMock.mock.calls[0]?.[0] as { tools?: string[] };
|
||||
expect(createSessionArgs.tools).toBeUndefined();
|
||||
});
|
||||
|
||||
it("passes opt-in builtin web tool allowlist to createAgentSession", async () => {
|
||||
const { createFnAgent } = await import("../pi.js");
|
||||
|
||||
await createFnAgent({
|
||||
cwd: "/tmp",
|
||||
systemPrompt: "test",
|
||||
tools: "readonly",
|
||||
builtinToolsAllowlist: ["WebSearch", "WebFetch"],
|
||||
});
|
||||
|
||||
const createSessionArgs = createAgentSessionMock.mock.calls[0]?.[0] as { tools?: string[] };
|
||||
expect(createSessionArgs.tools).toEqual(expect.arrayContaining([
|
||||
"read",
|
||||
"grep",
|
||||
"find",
|
||||
"ls",
|
||||
"WebSearch",
|
||||
"WebFetch",
|
||||
]));
|
||||
});
|
||||
|
||||
it("keeps caller customTools in coding sessions", async () => {
|
||||
createCodingToolsMock.mockReturnValueOnce([{ name: "read" }, { name: "write" }] as any);
|
||||
const customTool = {
|
||||
|
||||
@@ -429,11 +429,15 @@ export interface FallbackModelUsedPayload {
|
||||
timestamp?: string;
|
||||
}
|
||||
|
||||
export type BuiltinWebToolName = "WebSearch" | "WebFetch";
|
||||
|
||||
export interface AgentOptions {
|
||||
cwd: string;
|
||||
systemPrompt: string;
|
||||
tools?: "coding" | "readonly";
|
||||
customTools?: ToolDefinition[];
|
||||
/** Optional allowlist of builtin runtime web tools to keep enabled. */
|
||||
builtinToolsAllowlist?: BuiltinWebToolName[];
|
||||
onText?: (delta: string) => void;
|
||||
onThinking?: (delta: string) => void;
|
||||
onToolStart?: (name: string, args?: Record<string, unknown>) => void;
|
||||
@@ -1243,7 +1247,7 @@ export async function createFnAgent(options: AgentOptions): Promise<AgentResult>
|
||||
if (options.beforeSpawnSession) {
|
||||
await options.beforeSpawnSession();
|
||||
}
|
||||
return createAgentSession({
|
||||
const createSessionOptions: Parameters<typeof createAgentSession>[0] = {
|
||||
cwd: options.cwd,
|
||||
authStorage,
|
||||
modelRegistry,
|
||||
@@ -1253,7 +1257,18 @@ export async function createFnAgent(options: AgentOptions): Promise<AgentResult>
|
||||
sessionManager,
|
||||
settingsManager,
|
||||
...(modelOverride ? { model: modelOverride } : {}),
|
||||
});
|
||||
};
|
||||
|
||||
if (options.builtinToolsAllowlist && options.builtinToolsAllowlist.length > 0) {
|
||||
createSessionOptions.tools = [
|
||||
...new Set([
|
||||
...customToolList.map((tool) => tool.name),
|
||||
...options.builtinToolsAllowlist,
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
return createAgentSession(createSessionOptions);
|
||||
};
|
||||
|
||||
const emitFallbackUsed = async (triggerPoint: "session-creation" | "prompt-time"): Promise<void> => {
|
||||
|
||||
@@ -35,6 +35,9 @@ describe("LLMSynthesisProvider", () => {
|
||||
);
|
||||
|
||||
expect(promptWithFallbackMock).toHaveBeenCalled();
|
||||
expect(createFnAgentMock).toHaveBeenCalledWith(expect.objectContaining({
|
||||
builtinToolsAllowlist: ["WebSearch", "WebFetch"],
|
||||
}));
|
||||
expect(result.citations).toEqual(["https://a"]);
|
||||
expect(result.confidence).toBe(0.8);
|
||||
expect(disposeMock).toHaveBeenCalled();
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ResearchProviderError } from "../types.js";
|
||||
|
||||
const log = createLogger("research:llm-synthesis");
|
||||
const DEFAULT_TIMEOUT_MS = 120_000;
|
||||
const SYNTHESIS_BUILTIN_WEB_TOOLS = ["WebSearch", "WebFetch"] as const;
|
||||
const LARGE_MODEL_CONTEXT_CHARS = 100_000;
|
||||
const SMALL_MODEL_CONTEXT_CHARS = 30_000;
|
||||
|
||||
@@ -54,6 +55,7 @@ export class LLMSynthesisProvider implements ResearchProvider {
|
||||
const { session } = await createFnAgent({
|
||||
cwd: this.options.projectRoot,
|
||||
tools: "readonly",
|
||||
builtinToolsAllowlist: [...SYNTHESIS_BUILTIN_WEB_TOOLS],
|
||||
systemPrompt: "You synthesize research findings into concise, cited outputs.",
|
||||
defaultProvider: modelSelection.provider,
|
||||
defaultModelId: modelSelection.modelId,
|
||||
|
||||
Reference in New Issue
Block a user