@wentorai/research-plugins 1.3.2 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -56
- package/curated/analysis/README.md +1 -13
- package/curated/domains/README.md +1 -5
- package/curated/literature/README.md +3 -12
- package/curated/research/README.md +1 -18
- package/curated/tools/README.md +1 -12
- package/curated/writing/README.md +2 -6
- package/index.ts +88 -5
- package/openclaw.plugin.json +3 -12
- package/package.json +3 -5
- package/skills/analysis/statistics/SKILL.md +1 -1
- package/skills/analysis/statistics/meta-analysis-guide/SKILL.md +1 -1
- package/skills/domains/ai-ml/SKILL.md +3 -2
- package/skills/domains/ai-ml/generative-ai-guide/SKILL.md +1 -0
- package/skills/domains/ai-ml/huggingface-api/SKILL.md +251 -0
- package/skills/domains/biomedical/SKILL.md +9 -2
- package/skills/domains/biomedical/alphafold-api/SKILL.md +227 -0
- package/skills/domains/biomedical/biothings-api/SKILL.md +296 -0
- package/skills/domains/biomedical/clinicaltrials-api-v2/SKILL.md +216 -0
- package/skills/domains/biomedical/enrichr-api/SKILL.md +264 -0
- package/skills/domains/biomedical/ensembl-rest-api/SKILL.md +204 -0
- package/skills/domains/biomedical/medical-data-api/SKILL.md +197 -0
- package/skills/domains/biomedical/pdb-structure-api/SKILL.md +219 -0
- package/skills/domains/business/SKILL.md +2 -3
- package/skills/domains/chemistry/SKILL.md +3 -2
- package/skills/domains/chemistry/catalysis-hub-api/SKILL.md +171 -0
- package/skills/domains/education/SKILL.md +2 -3
- package/skills/domains/law/SKILL.md +3 -2
- package/skills/domains/law/uk-legislation-api/SKILL.md +179 -0
- package/skills/literature/discovery/SKILL.md +1 -1
- package/skills/literature/discovery/citation-alert-guide/SKILL.md +2 -2
- package/skills/literature/discovery/conference-proceedings-guide/SKILL.md +2 -2
- package/skills/literature/discovery/literature-mapping-guide/SKILL.md +1 -1
- package/skills/literature/discovery/paper-recommendation-guide/SKILL.md +8 -14
- package/skills/literature/discovery/rss-paper-feeds/SKILL.md +20 -14
- package/skills/literature/discovery/semantic-paper-radar/SKILL.md +8 -8
- package/skills/literature/discovery/semantic-scholar-recs-guide/SKILL.md +103 -86
- package/skills/literature/fulltext/SKILL.md +3 -2
- package/skills/literature/fulltext/arxiv-latex-source/SKILL.md +195 -0
- package/skills/literature/fulltext/open-access-guide/SKILL.md +1 -1
- package/skills/literature/fulltext/open-access-mining-guide/SKILL.md +5 -5
- package/skills/literature/metadata/citation-network-guide/SKILL.md +3 -3
- package/skills/literature/metadata/h-index-guide/SKILL.md +0 -27
- package/skills/literature/search/SKILL.md +3 -4
- package/skills/literature/search/citation-chaining-guide/SKILL.md +42 -32
- package/skills/literature/search/database-comparison-guide/SKILL.md +1 -1
- package/skills/literature/search/semantic-scholar-api/SKILL.md +56 -53
- package/skills/research/automation/SKILL.md +2 -3
- package/skills/research/automation/datagen-research-guide/SKILL.md +1 -0
- package/skills/research/automation/mle-agent-guide/SKILL.md +1 -0
- package/skills/research/automation/paper-to-agent-guide/SKILL.md +2 -1
- package/skills/research/deep-research/auto-deep-research-guide/SKILL.md +1 -0
- package/skills/research/deep-research/in-depth-research-guide/SKILL.md +1 -1
- package/skills/research/deep-research/kosmos-scientist-guide/SKILL.md +3 -3
- package/skills/research/deep-research/llm-scientific-discovery-guide/SKILL.md +1 -1
- package/skills/research/deep-research/local-deep-research-guide/SKILL.md +6 -6
- package/skills/research/deep-research/open-researcher-guide/SKILL.md +3 -3
- package/skills/research/deep-research/tongyi-deep-research-guide/SKILL.md +4 -4
- package/skills/research/methodology/SKILL.md +1 -1
- package/skills/research/methodology/claude-scientific-guide/SKILL.md +1 -0
- package/skills/research/methodology/grad-school-guide/SKILL.md +1 -1
- package/skills/research/methodology/qualitative-research-guide/SKILL.md +1 -1
- package/skills/research/paper-review/SKILL.md +1 -1
- package/skills/research/paper-review/automated-review-guide/SKILL.md +1 -1
- package/skills/research/paper-review/peer-review-guide/SKILL.md +1 -1
- package/skills/tools/diagram/excalidraw-diagram-guide/SKILL.md +1 -1
- package/skills/tools/diagram/mermaid-architect-guide/SKILL.md +1 -1
- package/skills/tools/diagram/plantuml-guide/SKILL.md +1 -1
- package/skills/tools/document/grobid-pdf-parsing/SKILL.md +1 -1
- package/skills/tools/document/paper-parse-guide/SKILL.md +2 -2
- package/skills/tools/knowledge-graph/SKILL.md +2 -3
- package/skills/tools/knowledge-graph/citation-network-builder/SKILL.md +5 -5
- package/skills/tools/knowledge-graph/knowledge-graph-construction/SKILL.md +1 -1
- package/skills/tools/ocr-translate/zotero-pdf2zh-guide/SKILL.md +1 -0
- package/skills/tools/scraping/academic-web-scraping/SKILL.md +1 -2
- package/skills/tools/scraping/google-scholar-scraper/SKILL.md +7 -7
- package/skills/writing/citation/SKILL.md +1 -1
- package/skills/writing/citation/academic-citation-manager/SKILL.md +20 -17
- package/skills/writing/citation/citation-assistant-skill/SKILL.md +72 -58
- package/skills/writing/citation/obsidian-citation-guide/SKILL.md +1 -0
- package/skills/writing/citation/obsidian-zotero-guide/SKILL.md +1 -0
- package/skills/writing/citation/onecite-reference-guide/SKILL.md +1 -1
- package/skills/writing/citation/papersgpt-zotero-guide/SKILL.md +1 -0
- package/skills/writing/citation/zotero-mdnotes-guide/SKILL.md +1 -0
- package/skills/writing/citation/zotero-reference-guide/SKILL.md +2 -1
- package/skills/writing/citation/zotero-scholar-guide/SKILL.md +1 -1
- package/skills/writing/composition/scientific-writing-resources/SKILL.md +1 -0
- package/skills/writing/latex/latex-drawing-collection/SKILL.md +1 -0
- package/skills/writing/latex/latex-templates-collection/SKILL.md +1 -0
- package/skills/writing/templates/novathesis-guide/SKILL.md +1 -0
- package/src/tools/arxiv.ts +81 -30
- package/src/tools/biorxiv.ts +158 -0
- package/src/tools/crossref.ts +63 -22
- package/src/tools/datacite.ts +191 -0
- package/src/tools/dblp.ts +125 -0
- package/src/tools/doaj.ts +82 -0
- package/src/tools/europe-pmc.ts +159 -0
- package/src/tools/hal.ts +118 -0
- package/src/tools/inspire-hep.ts +165 -0
- package/src/tools/openaire.ts +158 -0
- package/src/tools/openalex.ts +26 -15
- package/src/tools/opencitations.ts +112 -0
- package/src/tools/orcid.ts +139 -0
- package/src/tools/osf-preprints.ts +104 -0
- package/src/tools/pubmed.ts +22 -13
- package/src/tools/ror.ts +118 -0
- package/src/tools/unpaywall.ts +15 -6
- package/src/tools/util.ts +141 -0
- package/src/tools/zenodo.ts +157 -0
- package/mcp-configs/academic-db/ChatSpatial.json +0 -17
- package/mcp-configs/academic-db/academia-mcp.json +0 -17
- package/mcp-configs/academic-db/academic-paper-explorer.json +0 -17
- package/mcp-configs/academic-db/academic-search-mcp-server.json +0 -17
- package/mcp-configs/academic-db/agentinterviews-mcp.json +0 -17
- package/mcp-configs/academic-db/all-in-mcp.json +0 -17
- package/mcp-configs/academic-db/alphafold-mcp.json +0 -20
- package/mcp-configs/academic-db/apple-health-mcp.json +0 -17
- package/mcp-configs/academic-db/arxiv-latex-mcp.json +0 -17
- package/mcp-configs/academic-db/arxiv-mcp-server.json +0 -17
- package/mcp-configs/academic-db/bgpt-mcp.json +0 -17
- package/mcp-configs/academic-db/biomcp.json +0 -17
- package/mcp-configs/academic-db/biothings-mcp.json +0 -17
- package/mcp-configs/academic-db/brightspace-mcp.json +0 -21
- package/mcp-configs/academic-db/catalysishub-mcp-server.json +0 -17
- package/mcp-configs/academic-db/climatiq-mcp.json +0 -20
- package/mcp-configs/academic-db/clinicaltrialsgov-mcp-server.json +0 -17
- package/mcp-configs/academic-db/deep-research-mcp.json +0 -17
- package/mcp-configs/academic-db/dicom-mcp.json +0 -17
- package/mcp-configs/academic-db/enrichr-mcp-server.json +0 -17
- package/mcp-configs/academic-db/fec-mcp-server.json +0 -17
- package/mcp-configs/academic-db/fhir-mcp-server-themomentum.json +0 -17
- package/mcp-configs/academic-db/fhir-mcp.json +0 -19
- package/mcp-configs/academic-db/gget-mcp.json +0 -17
- package/mcp-configs/academic-db/gibs-mcp.json +0 -20
- package/mcp-configs/academic-db/gis-mcp-server.json +0 -22
- package/mcp-configs/academic-db/google-earth-engine-mcp.json +0 -21
- package/mcp-configs/academic-db/google-researcher-mcp.json +0 -17
- package/mcp-configs/academic-db/idea-reality-mcp.json +0 -17
- package/mcp-configs/academic-db/legiscan-mcp.json +0 -19
- package/mcp-configs/academic-db/lex.json +0 -17
- package/mcp-configs/academic-db/m4-clinical-mcp.json +0 -21
- package/mcp-configs/academic-db/medical-mcp.json +0 -21
- package/mcp-configs/academic-db/nexonco-mcp.json +0 -20
- package/mcp-configs/academic-db/omop-mcp.json +0 -20
- package/mcp-configs/academic-db/onekgpd-mcp.json +0 -20
- package/mcp-configs/academic-db/openedu-mcp.json +0 -20
- package/mcp-configs/academic-db/opengenes-mcp.json +0 -20
- package/mcp-configs/academic-db/openstax-mcp.json +0 -21
- package/mcp-configs/academic-db/openstreetmap-mcp.json +0 -21
- package/mcp-configs/academic-db/opentargets-mcp.json +0 -21
- package/mcp-configs/academic-db/pdb-mcp.json +0 -21
- package/mcp-configs/academic-db/smithsonian-mcp.json +0 -20
- package/mcp-configs/ai-platform/Adaptive-Graph-of-Thoughts-MCP-server.json +0 -17
- package/mcp-configs/ai-platform/ai-counsel.json +0 -17
- package/mcp-configs/ai-platform/atlas-mcp-server.json +0 -17
- package/mcp-configs/ai-platform/counsel-mcp.json +0 -17
- package/mcp-configs/ai-platform/cross-llm-mcp.json +0 -17
- package/mcp-configs/ai-platform/gptr-mcp.json +0 -17
- package/mcp-configs/ai-platform/magi-researchers.json +0 -21
- package/mcp-configs/ai-platform/mcp-academic-researcher.json +0 -22
- package/mcp-configs/ai-platform/open-paper-machine.json +0 -21
- package/mcp-configs/ai-platform/paper-intelligence.json +0 -21
- package/mcp-configs/ai-platform/paper-reader.json +0 -21
- package/mcp-configs/ai-platform/paperdebugger.json +0 -21
- package/mcp-configs/browser/decipher-research-agent.json +0 -17
- package/mcp-configs/browser/deep-research.json +0 -17
- package/mcp-configs/browser/everything-claude-code.json +0 -17
- package/mcp-configs/browser/exa-mcp.json +0 -20
- package/mcp-configs/browser/gpt-researcher.json +0 -17
- package/mcp-configs/browser/heurist-agent-framework.json +0 -17
- package/mcp-configs/browser/mcp-searxng.json +0 -21
- package/mcp-configs/browser/mcp-webresearch.json +0 -20
- package/mcp-configs/cloud-docs/confluence-mcp.json +0 -37
- package/mcp-configs/cloud-docs/google-drive-mcp.json +0 -35
- package/mcp-configs/cloud-docs/notion-mcp.json +0 -29
- package/mcp-configs/communication/discord-mcp.json +0 -29
- package/mcp-configs/communication/discourse-mcp.json +0 -21
- package/mcp-configs/communication/slack-mcp.json +0 -29
- package/mcp-configs/communication/telegram-mcp.json +0 -28
- package/mcp-configs/data-platform/4everland-hosting-mcp.json +0 -17
- package/mcp-configs/data-platform/automl-stat-mcp.json +0 -21
- package/mcp-configs/data-platform/context-keeper.json +0 -17
- package/mcp-configs/data-platform/context7.json +0 -19
- package/mcp-configs/data-platform/contextstream-mcp.json +0 -17
- package/mcp-configs/data-platform/email-mcp.json +0 -17
- package/mcp-configs/data-platform/jefferson-stats-mcp.json +0 -22
- package/mcp-configs/data-platform/mcp-excel-server.json +0 -21
- package/mcp-configs/data-platform/mcp-stata.json +0 -21
- package/mcp-configs/data-platform/mcpstack-jupyter.json +0 -21
- package/mcp-configs/data-platform/ml-mcp.json +0 -21
- package/mcp-configs/data-platform/nasdaq-data-link-mcp.json +0 -20
- package/mcp-configs/data-platform/numpy-mcp.json +0 -21
- package/mcp-configs/database/neo4j-mcp.json +0 -37
- package/mcp-configs/database/postgres-mcp.json +0 -28
- package/mcp-configs/database/sqlite-mcp.json +0 -29
- package/mcp-configs/dev-platform/geogebra-mcp.json +0 -21
- package/mcp-configs/dev-platform/github-mcp.json +0 -31
- package/mcp-configs/dev-platform/gitlab-mcp.json +0 -34
- package/mcp-configs/dev-platform/latex-mcp-server.json +0 -21
- package/mcp-configs/dev-platform/manim-mcp.json +0 -20
- package/mcp-configs/dev-platform/mcp-echarts.json +0 -20
- package/mcp-configs/dev-platform/panel-viz-mcp.json +0 -20
- package/mcp-configs/dev-platform/paperbanana.json +0 -20
- package/mcp-configs/dev-platform/texflow-mcp.json +0 -20
- package/mcp-configs/dev-platform/texmcp.json +0 -20
- package/mcp-configs/dev-platform/typst-mcp.json +0 -21
- package/mcp-configs/dev-platform/vizro-mcp.json +0 -20
- package/mcp-configs/email/email-mcp.json +0 -40
- package/mcp-configs/email/gmail-mcp.json +0 -37
- package/mcp-configs/note-knowledge/ApeRAG.json +0 -17
- package/mcp-configs/note-knowledge/In-Memoria.json +0 -17
- package/mcp-configs/note-knowledge/agent-memory.json +0 -17
- package/mcp-configs/note-knowledge/aimemo.json +0 -17
- package/mcp-configs/note-knowledge/biel-mcp.json +0 -19
- package/mcp-configs/note-knowledge/cognee.json +0 -17
- package/mcp-configs/note-knowledge/context-awesome.json +0 -17
- package/mcp-configs/note-knowledge/context-mcp.json +0 -17
- package/mcp-configs/note-knowledge/conversation-handoff-mcp.json +0 -17
- package/mcp-configs/note-knowledge/cortex.json +0 -17
- package/mcp-configs/note-knowledge/devrag.json +0 -17
- package/mcp-configs/note-knowledge/easy-obsidian-mcp.json +0 -17
- package/mcp-configs/note-knowledge/engram.json +0 -17
- package/mcp-configs/note-knowledge/gnosis-mcp.json +0 -17
- package/mcp-configs/note-knowledge/graphlit-mcp-server.json +0 -19
- package/mcp-configs/note-knowledge/local-faiss-mcp.json +0 -21
- package/mcp-configs/note-knowledge/mcp-memory-service.json +0 -21
- package/mcp-configs/note-knowledge/mcp-obsidian.json +0 -23
- package/mcp-configs/note-knowledge/mcp-ragdocs.json +0 -20
- package/mcp-configs/note-knowledge/mcp-summarizer.json +0 -21
- package/mcp-configs/note-knowledge/mediawiki-mcp.json +0 -21
- package/mcp-configs/note-knowledge/openzim-mcp.json +0 -20
- package/mcp-configs/note-knowledge/zettelkasten-mcp.json +0 -21
- package/mcp-configs/reference-mgr/academic-paper-mcp-http.json +0 -20
- package/mcp-configs/reference-mgr/academix.json +0 -20
- package/mcp-configs/reference-mgr/arxiv-cli.json +0 -17
- package/mcp-configs/reference-mgr/arxiv-research-mcp.json +0 -21
- package/mcp-configs/reference-mgr/arxiv-search-mcp.json +0 -17
- package/mcp-configs/reference-mgr/chiken.json +0 -17
- package/mcp-configs/reference-mgr/claude-scholar.json +0 -17
- package/mcp-configs/reference-mgr/devonthink-mcp.json +0 -17
- package/mcp-configs/reference-mgr/google-scholar-abstract-mcp.json +0 -19
- package/mcp-configs/reference-mgr/google-scholar-mcp.json +0 -20
- package/mcp-configs/reference-mgr/mcp-paperswithcode.json +0 -21
- package/mcp-configs/reference-mgr/mcp-scholarly.json +0 -20
- package/mcp-configs/reference-mgr/mcp-simple-arxiv.json +0 -20
- package/mcp-configs/reference-mgr/mcp-simple-pubmed.json +0 -20
- package/mcp-configs/reference-mgr/mcp-zotero.json +0 -21
- package/mcp-configs/reference-mgr/mendeley-mcp.json +0 -20
- package/mcp-configs/reference-mgr/ncbi-mcp-server.json +0 -22
- package/mcp-configs/reference-mgr/onecite.json +0 -21
- package/mcp-configs/reference-mgr/paper-search-mcp.json +0 -21
- package/mcp-configs/reference-mgr/pubmed-search-mcp.json +0 -21
- package/mcp-configs/reference-mgr/scholar-mcp.json +0 -21
- package/mcp-configs/reference-mgr/scholar-multi-mcp.json +0 -21
- package/mcp-configs/reference-mgr/seerai.json +0 -21
- package/mcp-configs/reference-mgr/semantic-scholar-fastmcp.json +0 -21
- package/mcp-configs/reference-mgr/sourcelibrary.json +0 -20
- package/mcp-configs/registry.json +0 -476
- package/mcp-configs/repository/dataverse-mcp.json +0 -33
- package/mcp-configs/repository/huggingface-mcp.json +0 -29
- package/skills/domains/business/xpert-bi-guide/SKILL.md +0 -84
- package/skills/domains/education/edumcp-guide/SKILL.md +0 -74
- package/skills/literature/search/paper-search-mcp-guide/SKILL.md +0 -107
- package/skills/research/automation/mcp-server-guide/SKILL.md +0 -211
- package/skills/tools/knowledge-graph/paperpile-notion-guide/SKILL.md +0 -84
- package/src/tools/semantic-scholar.ts +0 -66
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
4
|
+
|
|
5
|
+
const BASE = "https://api.openaire.eu";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Parse OpenAIRE XML response wrapped in JSON.
|
|
9
|
+
* OpenAIRE returns XML by default; with format=json it wraps XML nodes in JSON objects.
|
|
10
|
+
*/
|
|
11
|
+
function parseOpenAireResult(result: Record<string, unknown>): Record<string, unknown> {
|
|
12
|
+
const metadata = result.metadata as Record<string, unknown> | undefined;
|
|
13
|
+
const oafEntity = metadata?.["oaf:entity"] as Record<string, unknown> | undefined;
|
|
14
|
+
const oafResult = oafEntity?.["oaf:result"] as Record<string, unknown> | undefined;
|
|
15
|
+
|
|
16
|
+
if (!oafResult) {
|
|
17
|
+
// Flat JSON format (newer API versions)
|
|
18
|
+
return {
|
|
19
|
+
title: result.title,
|
|
20
|
+
authors: result.authors,
|
|
21
|
+
doi: result.doi,
|
|
22
|
+
url: result.url,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Extract title
|
|
27
|
+
const titleRaw = oafResult.title;
|
|
28
|
+
let title = "";
|
|
29
|
+
if (Array.isArray(titleRaw)) {
|
|
30
|
+
const main = titleRaw.find((t: Record<string, string>) => t["@classid"] === "main title");
|
|
31
|
+
title = (main ?? titleRaw[0])?.["$"] ?? "";
|
|
32
|
+
} else if (typeof titleRaw === "object" && titleRaw !== null) {
|
|
33
|
+
title = (titleRaw as Record<string, string>)["$"] ?? "";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Extract authors
|
|
37
|
+
const creatorsRaw = oafResult.creator;
|
|
38
|
+
const authors: string[] = [];
|
|
39
|
+
if (Array.isArray(creatorsRaw)) {
|
|
40
|
+
for (const c of creatorsRaw) {
|
|
41
|
+
const name = typeof c === "string" ? c : (c as Record<string, string>)["$"];
|
|
42
|
+
if (name) authors.push(name);
|
|
43
|
+
}
|
|
44
|
+
} else if (creatorsRaw) {
|
|
45
|
+
const name = typeof creatorsRaw === "string" ? creatorsRaw : (creatorsRaw as Record<string, string>)["$"];
|
|
46
|
+
if (name) authors.push(name);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Extract DOI and other identifiers
|
|
50
|
+
const pidRaw = oafResult.pid;
|
|
51
|
+
let doi: string | undefined;
|
|
52
|
+
if (Array.isArray(pidRaw)) {
|
|
53
|
+
const doiPid = pidRaw.find((p: Record<string, string>) => p["@classid"] === "doi");
|
|
54
|
+
doi = (doiPid as Record<string, string> | undefined)?.["$"];
|
|
55
|
+
} else if (typeof pidRaw === "object" && pidRaw !== null) {
|
|
56
|
+
if ((pidRaw as Record<string, string>)["@classid"] === "doi") {
|
|
57
|
+
doi = (pidRaw as Record<string, string>)["$"];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Extract dates
|
|
62
|
+
const dateRaw = oafResult.dateofacceptance;
|
|
63
|
+
const date = typeof dateRaw === "string" ? dateRaw : (dateRaw as Record<string, string> | undefined)?.["$"];
|
|
64
|
+
|
|
65
|
+
// Extract best access right
|
|
66
|
+
const accessRaw = oafResult.bestaccessright;
|
|
67
|
+
const isOa = typeof accessRaw === "object" && accessRaw !== null
|
|
68
|
+
? (accessRaw as Record<string, string>)["@classid"] === "OPEN"
|
|
69
|
+
: false;
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
title,
|
|
73
|
+
authors,
|
|
74
|
+
doi,
|
|
75
|
+
date,
|
|
76
|
+
is_oa: isOa,
|
|
77
|
+
url: doi ? `https://doi.org/${doi}` : undefined,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function createOpenAireTools(
|
|
82
|
+
_ctx: OpenClawPluginToolContext,
|
|
83
|
+
_api: OpenClawPluginApi,
|
|
84
|
+
) {
|
|
85
|
+
return [
|
|
86
|
+
{
|
|
87
|
+
name: "search_openaire",
|
|
88
|
+
label: "Search Publications (OpenAIRE)",
|
|
89
|
+
description:
|
|
90
|
+
"Search OpenAIRE for publications (170M+ records). Uniquely supports filtering by EU funding source (EC, NSF, etc.) and project ID.",
|
|
91
|
+
parameters: Type.Object({
|
|
92
|
+
keywords: Type.String({
|
|
93
|
+
description: "Search keywords",
|
|
94
|
+
}),
|
|
95
|
+
author: Type.Optional(Type.String({ description: "Author name" })),
|
|
96
|
+
doi: Type.Optional(Type.String({ description: "DOI" })),
|
|
97
|
+
from_date: Type.Optional(
|
|
98
|
+
Type.String({ description: "From date (YYYY-MM-DD)" }),
|
|
99
|
+
),
|
|
100
|
+
to_date: Type.Optional(
|
|
101
|
+
Type.String({ description: "To date (YYYY-MM-DD)" }),
|
|
102
|
+
),
|
|
103
|
+
oa_only: Type.Optional(
|
|
104
|
+
Type.Boolean({ description: "Only open access results" }),
|
|
105
|
+
),
|
|
106
|
+
funder: Type.Optional(
|
|
107
|
+
Type.String({
|
|
108
|
+
description: "Funder abbreviation: 'EC' (EU), 'NSF', 'NIH', 'UKRI', 'DFG', etc.",
|
|
109
|
+
}),
|
|
110
|
+
),
|
|
111
|
+
max_results: Type.Optional(
|
|
112
|
+
Type.Number({ description: "Max results (default 10, max 50)" }),
|
|
113
|
+
),
|
|
114
|
+
}),
|
|
115
|
+
execute: async (input: {
|
|
116
|
+
keywords: string;
|
|
117
|
+
author?: string;
|
|
118
|
+
doi?: string;
|
|
119
|
+
from_date?: string;
|
|
120
|
+
to_date?: string;
|
|
121
|
+
oa_only?: boolean;
|
|
122
|
+
funder?: string;
|
|
123
|
+
max_results?: number;
|
|
124
|
+
}) => {
|
|
125
|
+
const params = new URLSearchParams({
|
|
126
|
+
keywords: input.keywords,
|
|
127
|
+
format: "json",
|
|
128
|
+
size: String(Math.min(input.max_results ?? 10, 50)),
|
|
129
|
+
});
|
|
130
|
+
if (input.author) params.set("author", input.author);
|
|
131
|
+
if (input.doi) params.set("doi", input.doi);
|
|
132
|
+
if (input.from_date) params.set("fromDateAccepted", input.from_date);
|
|
133
|
+
if (input.to_date) params.set("toDateAccepted", input.to_date);
|
|
134
|
+
if (input.oa_only) params.set("OA", "true");
|
|
135
|
+
if (input.funder) params.set("funder", input.funder);
|
|
136
|
+
|
|
137
|
+
const tracked = await trackedFetch("openaire", `${BASE}/search/publications?${params}`, undefined, 15_000);
|
|
138
|
+
if (isTrackedError(tracked)) return tracked;
|
|
139
|
+
const data = await tracked.res.json();
|
|
140
|
+
|
|
141
|
+
const response = data.response as Record<string, unknown> | undefined;
|
|
142
|
+
const header = response?.header as Record<string, unknown> | undefined;
|
|
143
|
+
const total = header?.total as Record<string, string> | undefined;
|
|
144
|
+
const results = response?.results as Record<string, unknown> | undefined;
|
|
145
|
+
const resultList = results?.result;
|
|
146
|
+
|
|
147
|
+
const papers = (Array.isArray(resultList) ? resultList : []).map(
|
|
148
|
+
(r: Record<string, unknown>) => parseOpenAireResult(r),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return toolResult({
|
|
152
|
+
total_results: total?.["$"] ? parseInt(total["$"], 10) : papers.length,
|
|
153
|
+
papers,
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
}
|
package/src/tools/openalex.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
-
import { toolResult } from "./util.js";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
4
4
|
|
|
5
5
|
const BASE = "https://api.openalex.org";
|
|
6
6
|
|
|
@@ -58,9 +58,9 @@ export function createOpenAlexTools(
|
|
|
58
58
|
if (filters.length > 0) params.set("filter", filters.join(","));
|
|
59
59
|
if (input.sort_by) params.set("sort", input.sort_by);
|
|
60
60
|
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
63
|
-
const data = await res.json();
|
|
61
|
+
const tracked = await trackedFetch("openalex", `${BASE}/works?${params}`, { headers });
|
|
62
|
+
if (isTrackedError(tracked)) return tracked;
|
|
63
|
+
const data = await tracked.res.json();
|
|
64
64
|
return toolResult({
|
|
65
65
|
total_count: data.meta?.count,
|
|
66
66
|
results: data.results?.map((w: Record<string, unknown>) => ({
|
|
@@ -78,6 +78,7 @@ export function createOpenAlexTools(
|
|
|
78
78
|
)
|
|
79
79
|
: [],
|
|
80
80
|
})),
|
|
81
|
+
_source_health: { source: "openalex", latency_ms: tracked.latency_ms },
|
|
81
82
|
});
|
|
82
83
|
},
|
|
83
84
|
},
|
|
@@ -93,14 +94,15 @@ export function createOpenAlexTools(
|
|
|
93
94
|
}),
|
|
94
95
|
}),
|
|
95
96
|
execute: async (input: { work_id: string }) => {
|
|
97
|
+
if (!input?.work_id) {
|
|
98
|
+
return toolResult({ error: 'work_id parameter is required (e.g., "W2741809807" or a DOI like "10.1234/example")' });
|
|
99
|
+
}
|
|
96
100
|
const id = input.work_id.startsWith("10.")
|
|
97
101
|
? `https://doi.org/${input.work_id}`
|
|
98
102
|
: input.work_id;
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
|
|
103
|
-
const w = await res.json();
|
|
103
|
+
const tracked = await trackedFetch("openalex", `${BASE}/works/${encodeURIComponent(id)}`, { headers });
|
|
104
|
+
if (isTrackedError(tracked)) return tracked;
|
|
105
|
+
const w = await tracked.res.json();
|
|
104
106
|
return toolResult({
|
|
105
107
|
id: w.id,
|
|
106
108
|
doi: w.doi,
|
|
@@ -120,6 +122,7 @@ export function createOpenAlexTools(
|
|
|
120
122
|
?.slice(0, 10)
|
|
121
123
|
.map((c: Record<string, unknown>) => c.display_name),
|
|
122
124
|
referenced_works_count: w.referenced_works?.length,
|
|
125
|
+
_source_health: { source: "openalex", latency_ms: tracked.latency_ms },
|
|
123
126
|
});
|
|
124
127
|
},
|
|
125
128
|
},
|
|
@@ -135,6 +138,9 @@ export function createOpenAlexTools(
|
|
|
135
138
|
}),
|
|
136
139
|
}),
|
|
137
140
|
execute: async (input: { author_id: string }) => {
|
|
141
|
+
if (!input?.author_id) {
|
|
142
|
+
return toolResult({ error: 'author_id parameter is required (OpenAlex ID e.g. "A5023888391", ORCID, or author name)' });
|
|
143
|
+
}
|
|
138
144
|
let url: string;
|
|
139
145
|
if (
|
|
140
146
|
input.author_id.startsWith("A") ||
|
|
@@ -148,14 +154,18 @@ export function createOpenAlexTools(
|
|
|
148
154
|
search: input.author_id,
|
|
149
155
|
per_page: "5",
|
|
150
156
|
});
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
153
|
-
|
|
157
|
+
const tracked = await trackedFetch("openalex", `${BASE}/authors?${params}`, { headers });
|
|
158
|
+
if (isTrackedError(tracked)) return tracked;
|
|
159
|
+
const data = await tracked.res.json();
|
|
160
|
+
return toolResult({
|
|
161
|
+
...data,
|
|
162
|
+
_source_health: { source: "openalex", latency_ms: tracked.latency_ms },
|
|
163
|
+
});
|
|
154
164
|
}
|
|
155
165
|
|
|
156
|
-
const
|
|
157
|
-
if (
|
|
158
|
-
const a = await res.json();
|
|
166
|
+
const tracked = await trackedFetch("openalex", url, { headers });
|
|
167
|
+
if (isTrackedError(tracked)) return tracked;
|
|
168
|
+
const a = await tracked.res.json();
|
|
159
169
|
return toolResult({
|
|
160
170
|
id: a.id,
|
|
161
171
|
display_name: a.display_name,
|
|
@@ -171,6 +181,7 @@ export function createOpenAlexTools(
|
|
|
171
181
|
top_concepts: a.x_concepts
|
|
172
182
|
?.slice(0, 5)
|
|
173
183
|
.map((c: Record<string, unknown>) => c.display_name),
|
|
184
|
+
_source_health: { source: "openalex", latency_ms: tracked.latency_ms },
|
|
174
185
|
});
|
|
175
186
|
},
|
|
176
187
|
},
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
4
|
+
|
|
5
|
+
const BASE = "https://api.opencitations.net";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extract DOI from OpenCitations multi-identifier string.
|
|
9
|
+
* Format: "omid:br/... doi:10.1038/nature12373 openalex:W..."
|
|
10
|
+
*/
|
|
11
|
+
function extractDoi(multiId: string): string | undefined {
|
|
12
|
+
const match = multiId.match(/doi:(10\.\S+)/);
|
|
13
|
+
return match ? match[1] : undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createOpenCitationsTools(
|
|
17
|
+
_ctx: OpenClawPluginToolContext,
|
|
18
|
+
_api: OpenClawPluginApi,
|
|
19
|
+
) {
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
name: "get_citations_open",
|
|
23
|
+
label: "Get Citations (OpenCitations)",
|
|
24
|
+
description:
|
|
25
|
+
"Get all papers citing a given DOI using OpenCitations (2B+ open citation links). Works across all disciplines.",
|
|
26
|
+
parameters: Type.Object({
|
|
27
|
+
doi: Type.String({
|
|
28
|
+
description: "DOI of the paper, e.g. '10.1038/nature12373'",
|
|
29
|
+
}),
|
|
30
|
+
}),
|
|
31
|
+
execute: async (input: { doi: string }) => {
|
|
32
|
+
if (!input?.doi) {
|
|
33
|
+
return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
|
|
34
|
+
}
|
|
35
|
+
const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
|
|
36
|
+
const tracked = await trackedFetch("opencitations", `${BASE}/index/v2/citations/doi:${encodeURIComponent(doi)}`);
|
|
37
|
+
if (isTrackedError(tracked)) return tracked;
|
|
38
|
+
const data = await tracked.res.json();
|
|
39
|
+
|
|
40
|
+
if (!Array.isArray(data)) return toolResult({ error: "Unexpected response format" });
|
|
41
|
+
|
|
42
|
+
return toolResult({
|
|
43
|
+
total_citations: data.length,
|
|
44
|
+
citations: data.slice(0, 100).map((c: Record<string, string>) => ({
|
|
45
|
+
citing_doi: extractDoi(c.citing ?? ""),
|
|
46
|
+
cited_doi: extractDoi(c.cited ?? ""),
|
|
47
|
+
creation_date: c.creation,
|
|
48
|
+
})),
|
|
49
|
+
_source_health: { source: "opencitations", latency_ms: tracked.latency_ms },
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "get_references_open",
|
|
55
|
+
label: "Get References (OpenCitations)",
|
|
56
|
+
description:
|
|
57
|
+
"Get all references of a paper by DOI. Shows what a paper cites.",
|
|
58
|
+
parameters: Type.Object({
|
|
59
|
+
doi: Type.String({
|
|
60
|
+
description: "DOI of the paper, e.g. '10.1038/nature12373'",
|
|
61
|
+
}),
|
|
62
|
+
}),
|
|
63
|
+
execute: async (input: { doi: string }) => {
|
|
64
|
+
if (!input?.doi) {
|
|
65
|
+
return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
|
|
66
|
+
}
|
|
67
|
+
const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
|
|
68
|
+
const tracked = await trackedFetch("opencitations", `${BASE}/index/v2/references/doi:${encodeURIComponent(doi)}`);
|
|
69
|
+
if (isTrackedError(tracked)) return tracked;
|
|
70
|
+
const data = await tracked.res.json();
|
|
71
|
+
|
|
72
|
+
if (!Array.isArray(data)) return toolResult({ error: "Unexpected response format" });
|
|
73
|
+
|
|
74
|
+
return toolResult({
|
|
75
|
+
total_references: data.length,
|
|
76
|
+
references: data.slice(0, 100).map((r: Record<string, string>) => ({
|
|
77
|
+
cited_doi: extractDoi(r.cited ?? ""),
|
|
78
|
+
citing_doi: extractDoi(r.citing ?? ""),
|
|
79
|
+
creation_date: r.creation,
|
|
80
|
+
})),
|
|
81
|
+
_source_health: { source: "opencitations", latency_ms: tracked.latency_ms },
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "get_citation_count",
|
|
87
|
+
label: "Get Citation Count (OpenCitations)",
|
|
88
|
+
description:
|
|
89
|
+
"Get the total citation count for a DOI from OpenCitations open data.",
|
|
90
|
+
parameters: Type.Object({
|
|
91
|
+
doi: Type.String({
|
|
92
|
+
description: "DOI of the paper, e.g. '10.1038/nature12373'",
|
|
93
|
+
}),
|
|
94
|
+
}),
|
|
95
|
+
execute: async (input: { doi: string }) => {
|
|
96
|
+
if (!input?.doi) {
|
|
97
|
+
return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
|
|
98
|
+
}
|
|
99
|
+
const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
|
|
100
|
+
const tracked = await trackedFetch("opencitations", `${BASE}/index/v2/citation-count/doi:${encodeURIComponent(doi)}`);
|
|
101
|
+
if (isTrackedError(tracked)) return tracked;
|
|
102
|
+
const data = await tracked.res.json();
|
|
103
|
+
|
|
104
|
+
const count = Array.isArray(data) && data[0]?.count
|
|
105
|
+
? parseInt(data[0].count, 10)
|
|
106
|
+
: 0;
|
|
107
|
+
|
|
108
|
+
return toolResult({ doi, citation_count: count, _source_health: { source: "opencitations", latency_ms: tracked.latency_ms } });
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
4
|
+
|
|
5
|
+
const BASE = "https://pub.orcid.org/v3.0";
|
|
6
|
+
|
|
7
|
+
const HEADERS: Record<string, string> = {
|
|
8
|
+
Accept: "application/json",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function createOrcidTools(
|
|
12
|
+
_ctx: OpenClawPluginToolContext,
|
|
13
|
+
_api: OpenClawPluginApi,
|
|
14
|
+
) {
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
name: "search_orcid",
|
|
18
|
+
label: "Search Researchers (ORCID)",
|
|
19
|
+
description:
|
|
20
|
+
"Search the ORCID registry for researchers by name, affiliation, or keyword. Returns ORCID iDs that can be used with get_orcid_works.",
|
|
21
|
+
parameters: Type.Object({
|
|
22
|
+
query: Type.String({
|
|
23
|
+
description:
|
|
24
|
+
"Lucene-style query. Supports fields: family-name, given-names, affiliation-org-name, keyword, orcid, ringgold-org-id. E.g. 'family-name:Smith AND affiliation-org-name:MIT'",
|
|
25
|
+
}),
|
|
26
|
+
limit: Type.Optional(
|
|
27
|
+
Type.Number({ description: "Max results (default 10, max 100)" }),
|
|
28
|
+
),
|
|
29
|
+
}),
|
|
30
|
+
execute: async (input: { query: string; limit?: number }) => {
|
|
31
|
+
const params = new URLSearchParams({
|
|
32
|
+
q: input.query,
|
|
33
|
+
rows: String(Math.min(input.limit ?? 10, 100)),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const result = await trackedFetch(
|
|
37
|
+
"orcid",
|
|
38
|
+
`${BASE}/search/?${params}`,
|
|
39
|
+
{ headers: HEADERS },
|
|
40
|
+
10_000,
|
|
41
|
+
);
|
|
42
|
+
if (isTrackedError(result)) return result;
|
|
43
|
+
const data = await result.res.json();
|
|
44
|
+
|
|
45
|
+
const researchers = (data.result ?? []).map(
|
|
46
|
+
(r: Record<string, unknown>) => {
|
|
47
|
+
const oi = r["orcid-identifier"] as Record<string, string> | undefined;
|
|
48
|
+
return {
|
|
49
|
+
orcid: oi?.path,
|
|
50
|
+
uri: oi?.uri,
|
|
51
|
+
source: "orcid",
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return toolResult({
|
|
57
|
+
total_results: data["num-found"],
|
|
58
|
+
source: "orcid",
|
|
59
|
+
researchers,
|
|
60
|
+
_latency_ms: result.latency_ms,
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "get_orcid_works",
|
|
66
|
+
label: "Get Works by ORCID (ORCID)",
|
|
67
|
+
description:
|
|
68
|
+
"Get the publication list for a researcher by their ORCID iD. Returns titles, DOIs, publication years, and work types.",
|
|
69
|
+
parameters: Type.Object({
|
|
70
|
+
orcid: Type.String({
|
|
71
|
+
description: "ORCID iD, e.g. '0000-0002-1825-0097'",
|
|
72
|
+
}),
|
|
73
|
+
}),
|
|
74
|
+
execute: async (input: { orcid: string }) => {
|
|
75
|
+
if (!input?.orcid) {
|
|
76
|
+
return toolResult({ error: 'orcid parameter is required (e.g., "0000-0002-1825-0097")' });
|
|
77
|
+
}
|
|
78
|
+
const orcid = input.orcid.replace(/^https?:\/\/orcid\.org\//, "");
|
|
79
|
+
|
|
80
|
+
const result = await trackedFetch(
|
|
81
|
+
"orcid",
|
|
82
|
+
`${BASE}/${encodeURIComponent(orcid)}/works`,
|
|
83
|
+
{ headers: HEADERS },
|
|
84
|
+
10_000,
|
|
85
|
+
);
|
|
86
|
+
if (isTrackedError(result)) return result;
|
|
87
|
+
const data = await result.res.json();
|
|
88
|
+
|
|
89
|
+
const groups = data.group as Array<Record<string, unknown>> | undefined;
|
|
90
|
+
|
|
91
|
+
const works = (groups ?? []).map((g) => {
|
|
92
|
+
const summaries = g["work-summary"] as Array<Record<string, unknown>> | undefined;
|
|
93
|
+
const first = summaries?.[0];
|
|
94
|
+
if (!first) return null;
|
|
95
|
+
|
|
96
|
+
// Extract title
|
|
97
|
+
const titleObj = first.title as Record<string, unknown> | undefined;
|
|
98
|
+
const title = (titleObj?.title as Record<string, string> | undefined)?.value;
|
|
99
|
+
|
|
100
|
+
// Extract publication date
|
|
101
|
+
const pubDate = first["publication-date"] as Record<string, unknown> | undefined;
|
|
102
|
+
const year = (pubDate?.year as Record<string, string> | undefined)?.value;
|
|
103
|
+
const month = (pubDate?.month as Record<string, string> | undefined)?.value;
|
|
104
|
+
|
|
105
|
+
// Extract DOI from external-ids
|
|
106
|
+
const extIds = first["external-ids"] as Record<string, unknown> | undefined;
|
|
107
|
+
const extIdList = extIds?.["external-id"] as Array<Record<string, unknown>> | undefined;
|
|
108
|
+
const doiEntry = extIdList?.find(
|
|
109
|
+
(e) => e["external-id-type"] === "doi" && e["external-id-relationship"] === "self",
|
|
110
|
+
);
|
|
111
|
+
const doi = doiEntry?.["external-id-value"] as string | undefined;
|
|
112
|
+
|
|
113
|
+
// Extract journal title
|
|
114
|
+
const journalTitle = first["journal-title"] as Record<string, string> | undefined;
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
title,
|
|
118
|
+
doi,
|
|
119
|
+
year,
|
|
120
|
+
month,
|
|
121
|
+
type: first.type,
|
|
122
|
+
journal: journalTitle?.value,
|
|
123
|
+
url: doi ? `https://doi.org/${doi}` : undefined,
|
|
124
|
+
authors: undefined as string[] | undefined, // ORCID works endpoint does not include author lists
|
|
125
|
+
source: "orcid",
|
|
126
|
+
};
|
|
127
|
+
}).filter(Boolean);
|
|
128
|
+
|
|
129
|
+
return toolResult({
|
|
130
|
+
orcid,
|
|
131
|
+
total_works: works.length,
|
|
132
|
+
source: "orcid",
|
|
133
|
+
works,
|
|
134
|
+
_latency_ms: result.latency_ms,
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
4
|
+
|
|
5
|
+
const BASE = "https://api.osf.io/v2";
|
|
6
|
+
|
|
7
|
+
export function createOsfPreprintsTools(
|
|
8
|
+
_ctx: OpenClawPluginToolContext,
|
|
9
|
+
_api: OpenClawPluginApi,
|
|
10
|
+
) {
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
name: "search_osf_preprints",
|
|
14
|
+
label: "Search Preprints (OSF)",
|
|
15
|
+
description:
|
|
16
|
+
"Search OSF Preprints, an aggregator covering 180K+ preprints across multiple providers including SocArXiv, PsyArXiv, EarthArXiv, EngrXiv, and NutriXiv. Covers social sciences, psychology, earth sciences, engineering, and more.",
|
|
17
|
+
parameters: Type.Object({
|
|
18
|
+
provider: Type.Optional(
|
|
19
|
+
Type.String({
|
|
20
|
+
description:
|
|
21
|
+
"Preprint provider filter: 'osf', 'socarxiv', 'psyarxiv', 'engrxiv', 'eartharxiv', 'nutrixiv'. Omit for all providers.",
|
|
22
|
+
}),
|
|
23
|
+
),
|
|
24
|
+
size: Type.Optional(
|
|
25
|
+
Type.Number({ description: "Max results (default 10, max 100)" }),
|
|
26
|
+
),
|
|
27
|
+
page: Type.Optional(
|
|
28
|
+
Type.Number({ description: "Page number (default 1)" }),
|
|
29
|
+
),
|
|
30
|
+
}),
|
|
31
|
+
execute: async (input: {
|
|
32
|
+
provider?: string;
|
|
33
|
+
size?: number;
|
|
34
|
+
page?: number;
|
|
35
|
+
}) => {
|
|
36
|
+
const params = new URLSearchParams({
|
|
37
|
+
"page[size]": String(Math.min(input.size ?? 10, 100)),
|
|
38
|
+
});
|
|
39
|
+
if (input.provider) params.set("filter[provider]", input.provider);
|
|
40
|
+
if (input.page) params.set("page", String(input.page));
|
|
41
|
+
|
|
42
|
+
const result = await trackedFetch(
|
|
43
|
+
"osf_preprints",
|
|
44
|
+
`${BASE}/preprints/?${params}`,
|
|
45
|
+
{
|
|
46
|
+
headers: {
|
|
47
|
+
Accept: "application/vnd.api+json",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
20_000,
|
|
51
|
+
);
|
|
52
|
+
if (isTrackedError(result)) return result;
|
|
53
|
+
const data = await result.res.json();
|
|
54
|
+
|
|
55
|
+
const items = data.data as Array<Record<string, unknown>> | undefined;
|
|
56
|
+
const linksMeta = (data.links as Record<string, unknown>)?.meta as Record<string, unknown> | undefined;
|
|
57
|
+
|
|
58
|
+
return toolResult({
|
|
59
|
+
total_results: linksMeta?.total,
|
|
60
|
+
page: input.page ?? 1,
|
|
61
|
+
source: "osf_preprints",
|
|
62
|
+
preprints: (items ?? []).map((item) => {
|
|
63
|
+
const attrs = item.attributes as Record<string, unknown> | undefined;
|
|
64
|
+
const relationships = item.relationships as Record<string, unknown> | undefined;
|
|
65
|
+
const providerData = (
|
|
66
|
+
relationships?.provider as Record<string, unknown> | undefined
|
|
67
|
+
)?.data as Record<string, string> | undefined;
|
|
68
|
+
const subjects = attrs?.subjects as Array<Array<{ id: string; text: string }>> | undefined;
|
|
69
|
+
|
|
70
|
+
// Flatten nested subject arrays
|
|
71
|
+
const subjectNames = subjects
|
|
72
|
+
?.flat()
|
|
73
|
+
.map((s) => s.text)
|
|
74
|
+
.filter(Boolean);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
id: item.id,
|
|
78
|
+
title: attrs?.title,
|
|
79
|
+
description: attrs?.description
|
|
80
|
+
? String(attrs.description).slice(0, 500)
|
|
81
|
+
: undefined,
|
|
82
|
+
doi: attrs?.doi ?? undefined,
|
|
83
|
+
date_created: attrs?.date_created,
|
|
84
|
+
date_published: attrs?.date_published,
|
|
85
|
+
year: attrs?.date_published
|
|
86
|
+
? String(attrs.date_published).slice(0, 4)
|
|
87
|
+
: attrs?.date_created
|
|
88
|
+
? String(attrs.date_created).slice(0, 4)
|
|
89
|
+
: undefined,
|
|
90
|
+
provider: providerData?.id,
|
|
91
|
+
tags: attrs?.tags,
|
|
92
|
+
subjects: subjectNames,
|
|
93
|
+
is_published: attrs?.is_published,
|
|
94
|
+
authors: undefined as string[] | undefined, // OSF preprint list does not embed contributor names
|
|
95
|
+
url: `https://osf.io/preprints/${providerData?.id ?? "osf"}/${String(item.id).replace(/_v\d+$/, "")}`,
|
|
96
|
+
source: "osf_preprints",
|
|
97
|
+
};
|
|
98
|
+
}),
|
|
99
|
+
_latency_ms: result.latency_ms,
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
}
|