From 85e0c4d8c4cfb955c50b91bcb4e179c5ec745467 Mon Sep 17 00:00:00 2001 From: Advait Paliwal Date: Wed, 25 Mar 2026 11:07:42 -0700 Subject: [PATCH] Register alphaXiv research tools as native Pi tools Replace the alpha-research CLI skill with direct programmatic Pi tool registrations via @companion-ai/alpha-hub/lib. Tools connect to alphaXiv's MCP server through the library and reuse the connection across calls instead of spawning a new CLI process each time. Registers: alpha_search, alpha_get_paper, alpha_ask_paper, alpha_annotate_paper, alpha_list_annotations, alpha_read_code. Co-Authored-By: Claude Opus 4.6 (1M context) --- extensions/research-tools.ts | 2 + extensions/research-tools/alpha.ts | 107 +++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 extensions/research-tools/alpha.ts diff --git a/extensions/research-tools.ts b/extensions/research-tools.ts index 8b911cc..45d47af 100644 --- a/extensions/research-tools.ts +++ b/extensions/research-tools.ts @@ -1,5 +1,6 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; +import { registerAlphaTools } from "./research-tools/alpha.js"; import { installFeynmanHeader } from "./research-tools/header.js"; import { registerHelpCommand } from "./research-tools/help.js"; import { registerInitCommand, registerOutputsCommand } from "./research-tools/project.js"; @@ -15,6 +16,7 @@ export default function researchTools(pi: ExtensionAPI): void { await installFeynmanHeader(pi, ctx, cache); }); + registerAlphaTools(pi); registerHelpCommand(pi); registerInitCommand(pi); registerOutputsCommand(pi); diff --git a/extensions/research-tools/alpha.ts b/extensions/research-tools/alpha.ts new file mode 100644 index 0000000..29b31c5 --- /dev/null +++ b/extensions/research-tools/alpha.ts @@ -0,0 +1,107 @@ +import { + askPaper, + annotatePaper, + clearPaperAnnotation, + getPaper, + listPaperAnnotations, + readPaperCode, + searchPapers, +} from "@companion-ai/alpha-hub/lib"; +import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; +import { Type } from "@sinclair/typebox"; + +function formatText(value: unknown): string { + if (typeof value === "string") return value; + return JSON.stringify(value, null, 2); +} + +export function registerAlphaTools(pi: ExtensionAPI): void { + pi.registerTool({ + name: "alpha_search", + label: "Alpha Search", + description: + "Search research papers through alphaXiv. Modes: semantic (default, use 2-3 sentence queries), keyword (exact terms), agentic (broad multi-turn retrieval), both, or all.", + parameters: Type.Object({ + query: Type.String({ description: "Search query." }), + mode: Type.Optional( + Type.String({ description: "Search mode: semantic, keyword, both, agentic, or all." }), + ), + }), + async execute(_toolCallId, params) { + const result = await searchPapers(params.query, params.mode?.trim() || "semantic"); + return { content: [{ type: "text", text: formatText(result) }], details: result }; + }, + }); + + pi.registerTool({ + name: "alpha_get_paper", + label: "Alpha Get Paper", + description: "Fetch a paper's AI-generated report (or raw full text) plus any local annotation.", + parameters: Type.Object({ + paper: Type.String({ description: "arXiv ID, arXiv URL, or alphaXiv URL." }), + fullText: Type.Optional(Type.Boolean({ description: "Return raw full text instead of AI report." })), + }), + async execute(_toolCallId, params) { + const result = await getPaper(params.paper, { fullText: params.fullText }); + return { content: [{ type: "text", text: formatText(result) }], details: result }; + }, + }); + + pi.registerTool({ + name: "alpha_ask_paper", + label: "Alpha Ask Paper", + description: "Ask a targeted question about a paper. Uses AI to analyze the PDF and answer.", + parameters: Type.Object({ + paper: Type.String({ description: "arXiv ID, arXiv URL, or alphaXiv URL." }), + question: Type.String({ description: "Question about the paper." }), + }), + async execute(_toolCallId, params) { + const result = await askPaper(params.paper, params.question); + return { content: [{ type: "text", text: formatText(result) }], details: result }; + }, + }); + + pi.registerTool({ + name: "alpha_annotate_paper", + label: "Alpha Annotate Paper", + description: "Write or clear a persistent local annotation for a paper.", + parameters: Type.Object({ + paper: Type.String({ description: "Paper ID (arXiv ID or URL)." }), + note: Type.Optional(Type.String({ description: "Annotation text. Omit when clear=true." })), + clear: Type.Optional(Type.Boolean({ description: "Clear the existing annotation." })), + }), + async execute(_toolCallId, params) { + const result = params.clear + ? await clearPaperAnnotation(params.paper) + : params.note + ? await annotatePaper(params.paper, params.note) + : (() => { throw new Error("Provide either note or clear=true."); })(); + return { content: [{ type: "text", text: formatText(result) }], details: result }; + }, + }); + + pi.registerTool({ + name: "alpha_list_annotations", + label: "Alpha List Annotations", + description: "List all persistent local paper annotations.", + parameters: Type.Object({}), + async execute() { + const result = await listPaperAnnotations(); + return { content: [{ type: "text", text: formatText(result) }], details: result }; + }, + }); + + pi.registerTool({ + name: "alpha_read_code", + label: "Alpha Read Code", + description: "Read files from a paper's GitHub repository. Use '/' for repo overview.", + parameters: Type.Object({ + githubUrl: Type.String({ description: "GitHub repository URL." }), + path: Type.Optional(Type.String({ description: "File or directory path. Default: '/'" })), + }), + async execute(_toolCallId, params) { + const result = await readPaperCode(params.githubUrl, params.path?.trim() || "/"); + return { content: [{ type: "text", text: formatText(result) }], details: result }; + }, + }); +}