@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
package/src/tools/arxiv.ts
CHANGED
|
@@ -1,43 +1,65 @@
|
|
|
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://export.arxiv.org/api/query";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Parse arXiv Atom XML response into structured paper objects.
|
|
9
|
+
*
|
|
10
|
+
* Uses regex-based extraction (arXiv returns Atom 1.0 XML).
|
|
11
|
+
* Handles edge cases: namespace prefixes, missing fields, HTML error pages.
|
|
12
|
+
*/
|
|
13
|
+
function parseArxivXml(xml: string): Record<string, unknown>[] {
|
|
14
|
+
// Guard: if arXiv returned an error page (HTML) or empty response
|
|
15
|
+
if (!xml || !xml.includes("<entry>")) return [];
|
|
16
|
+
|
|
8
17
|
const entries: Record<string, unknown>[] = [];
|
|
9
|
-
const entryBlocks = xml.split("<entry>").slice(1);
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
|
|
19
|
+
// Use regex to extract <entry>...</entry> blocks robustly
|
|
20
|
+
const entryRegex = /<entry>([\s\S]*?)<\/entry>/g;
|
|
21
|
+
let entryMatch: RegExpExecArray | null;
|
|
22
|
+
|
|
23
|
+
while ((entryMatch = entryRegex.exec(xml)) !== null) {
|
|
24
|
+
const block = entryMatch[1];
|
|
25
|
+
|
|
26
|
+
const getText = (tag: string): string => {
|
|
27
|
+
// Match tags with or without namespace prefixes and attributes
|
|
13
28
|
const m = block.match(new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}>`));
|
|
14
29
|
return m ? m[1].trim() : "";
|
|
15
30
|
};
|
|
16
31
|
|
|
17
|
-
const getAll = (tag: string) => {
|
|
32
|
+
const getAll = (tag: string): string[] => {
|
|
18
33
|
const results: string[] = [];
|
|
19
34
|
const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}>`, "g");
|
|
20
|
-
let m;
|
|
35
|
+
let m: RegExpExecArray | null;
|
|
21
36
|
while ((m = re.exec(block)) !== null) results.push(m[1].trim());
|
|
22
37
|
return results;
|
|
23
38
|
};
|
|
24
39
|
|
|
25
|
-
const getAttr = (tag: string, attr: string) => {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
const getAttr = (tag: string, attr: string): string[] => {
|
|
41
|
+
const results: string[] = [];
|
|
42
|
+
const re = new RegExp(`<${tag}[^>]*?${attr}="([^"]*)"[^>]*/?>`, "g");
|
|
43
|
+
let m: RegExpExecArray | null;
|
|
44
|
+
while ((m = re.exec(block)) !== null) {
|
|
45
|
+
if (m[1]) results.push(m[1]);
|
|
46
|
+
}
|
|
47
|
+
return results;
|
|
33
48
|
};
|
|
34
49
|
|
|
35
|
-
const
|
|
36
|
-
|
|
50
|
+
const rawId = getText("id");
|
|
51
|
+
if (!rawId) continue; // skip malformed entries
|
|
52
|
+
|
|
53
|
+
const arxivId = rawId
|
|
54
|
+
.replace(/https?:\/\/arxiv\.org\/abs\//, "")
|
|
55
|
+
.replace(/v\d+$/, "");
|
|
56
|
+
|
|
57
|
+
const title = getText("title").replace(/\s+/g, " ");
|
|
58
|
+
if (!title) continue; // skip entries without title
|
|
37
59
|
|
|
38
60
|
entries.push({
|
|
39
61
|
arxiv_id: arxivId,
|
|
40
|
-
title
|
|
62
|
+
title,
|
|
41
63
|
summary: getText("summary").replace(/\s+/g, " "),
|
|
42
64
|
authors: getAll("name"),
|
|
43
65
|
published: getText("published"),
|
|
@@ -45,8 +67,8 @@ function parseArxivXml(xml: string) {
|
|
|
45
67
|
categories: getAttr("category", "term"),
|
|
46
68
|
pdf_url: `https://arxiv.org/pdf/${arxivId}`,
|
|
47
69
|
abs_url: `https://arxiv.org/abs/${arxivId}`,
|
|
48
|
-
doi: getText("arxiv:doi"),
|
|
49
|
-
comment: getText("arxiv:comment"),
|
|
70
|
+
doi: getText("arxiv:doi") || undefined,
|
|
71
|
+
comment: getText("arxiv:comment") || undefined,
|
|
50
72
|
});
|
|
51
73
|
}
|
|
52
74
|
|
|
@@ -62,7 +84,7 @@ export function createArxivTools(
|
|
|
62
84
|
name: "search_arxiv",
|
|
63
85
|
label: "Search Papers (arXiv)",
|
|
64
86
|
description:
|
|
65
|
-
"Search arXiv preprint repository. Covers physics, math, CS, biology, quantitative finance, statistics, and more.",
|
|
87
|
+
"Search arXiv preprint repository. Covers physics, math, CS, biology, quantitative finance, statistics, and more. All results are open access.",
|
|
66
88
|
parameters: Type.Object({
|
|
67
89
|
query: Type.String({
|
|
68
90
|
description:
|
|
@@ -94,9 +116,17 @@ export function createArxivTools(
|
|
|
94
116
|
sortOrder: input.sort_order ?? "descending",
|
|
95
117
|
});
|
|
96
118
|
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
99
|
-
const xml = await res.text();
|
|
119
|
+
const tracked = await trackedFetch("arxiv", `${BASE}?${params}`, undefined, 15_000);
|
|
120
|
+
if (isTrackedError(tracked)) return tracked;
|
|
121
|
+
const xml = await tracked.res.text();
|
|
122
|
+
|
|
123
|
+
// Check if response is actually XML (not HTML error page)
|
|
124
|
+
if (!xml.includes("<feed")) {
|
|
125
|
+
return toolResult({
|
|
126
|
+
error: "arXiv returned non-XML response (possibly rate-limited or error page)",
|
|
127
|
+
_source_health: { source: "arxiv", latency_ms: tracked.latency_ms },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
100
130
|
|
|
101
131
|
const totalMatch = xml.match(
|
|
102
132
|
/<opensearch:totalResults[^>]*>(\d+)<\/opensearch:totalResults>/,
|
|
@@ -106,6 +136,7 @@ export function createArxivTools(
|
|
|
106
136
|
return toolResult({
|
|
107
137
|
total_results: total,
|
|
108
138
|
papers: parseArxivXml(xml),
|
|
139
|
+
_source_health: { source: "arxiv", latency_ms: tracked.latency_ms },
|
|
109
140
|
});
|
|
110
141
|
},
|
|
111
142
|
},
|
|
@@ -120,14 +151,34 @@ export function createArxivTools(
|
|
|
120
151
|
}),
|
|
121
152
|
}),
|
|
122
153
|
execute: async (input: { arxiv_id: string }) => {
|
|
123
|
-
|
|
154
|
+
if (!input?.arxiv_id) {
|
|
155
|
+
return toolResult({ error: 'arxiv_id parameter is required (e.g., "2301.00001" or "2301.00001v2")' });
|
|
156
|
+
}
|
|
157
|
+
const id = input.arxiv_id.replace("arXiv:", "").replace(/https?:\/\/arxiv\.org\/abs\//, "");
|
|
124
158
|
const params = new URLSearchParams({ id_list: id });
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
159
|
+
|
|
160
|
+
const tracked = await trackedFetch("arxiv", `${BASE}?${params}`, undefined, 15_000);
|
|
161
|
+
if (isTrackedError(tracked)) return tracked;
|
|
162
|
+
const xml = await tracked.res.text();
|
|
163
|
+
|
|
164
|
+
if (!xml.includes("<feed")) {
|
|
165
|
+
return toolResult({
|
|
166
|
+
error: "arXiv returned non-XML response",
|
|
167
|
+
_source_health: { source: "arxiv", latency_ms: tracked.latency_ms },
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
128
171
|
const papers = parseArxivXml(xml);
|
|
129
|
-
if (papers.length === 0)
|
|
130
|
-
|
|
172
|
+
if (papers.length === 0) {
|
|
173
|
+
return toolResult({
|
|
174
|
+
error: `Paper not found: ${id}`,
|
|
175
|
+
_source_health: { source: "arxiv", latency_ms: tracked.latency_ms },
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return toolResult({
|
|
179
|
+
...papers[0],
|
|
180
|
+
_source_health: { source: "arxiv", latency_ms: tracked.latency_ms },
|
|
181
|
+
});
|
|
131
182
|
},
|
|
132
183
|
},
|
|
133
184
|
];
|
|
@@ -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.biorxiv.org";
|
|
6
|
+
|
|
7
|
+
export function createBiorxivTools(
|
|
8
|
+
_ctx: OpenClawPluginToolContext,
|
|
9
|
+
_api: OpenClawPluginApi,
|
|
10
|
+
) {
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
name: "search_biorxiv",
|
|
14
|
+
label: "Search Preprints (bioRxiv)",
|
|
15
|
+
description:
|
|
16
|
+
"Get recent bioRxiv preprints by date range. Covers biology preprints (300K+). Use a date range like '2026-03-01/2026-03-18'. Returns up to 100 preprints per page.",
|
|
17
|
+
parameters: Type.Object({
|
|
18
|
+
interval: Type.String({
|
|
19
|
+
description:
|
|
20
|
+
"Date range in YYYY-MM-DD/YYYY-MM-DD format (e.g. '2026-03-01/2026-03-18'). Use today's date for most recent.",
|
|
21
|
+
}),
|
|
22
|
+
cursor: Type.Optional(
|
|
23
|
+
Type.Number({ description: "Pagination offset (default 0, each page returns up to 100)" }),
|
|
24
|
+
),
|
|
25
|
+
}),
|
|
26
|
+
execute: async (input: { interval: string; cursor?: number }) => {
|
|
27
|
+
if (!input?.interval) {
|
|
28
|
+
return toolResult({ error: 'interval parameter is required (date range in YYYY-MM-DD/YYYY-MM-DD format, e.g. "2026-03-01/2026-03-18")' });
|
|
29
|
+
}
|
|
30
|
+
// Validate date range format — bioRxiv API only accepts YYYY-MM-DD/YYYY-MM-DD
|
|
31
|
+
if (!/^\d{4}-\d{2}-\d{2}\/\d{4}-\d{2}-\d{2}$/.test(input.interval)) {
|
|
32
|
+
return toolResult({ error: `Invalid interval format "${input.interval}". Must be YYYY-MM-DD/YYYY-MM-DD (e.g. "2026-03-01/2026-03-18")` });
|
|
33
|
+
}
|
|
34
|
+
const cursor = input.cursor ?? 0;
|
|
35
|
+
const tracked = await trackedFetch("biorxiv", `${BASE}/details/biorxiv/${input.interval}/${cursor}/json`, undefined, 30_000);
|
|
36
|
+
if (isTrackedError(tracked)) return tracked;
|
|
37
|
+
const data = await tracked.res.json();
|
|
38
|
+
|
|
39
|
+
const meta = data.messages?.[0];
|
|
40
|
+
const papers = (data.collection ?? []).map(
|
|
41
|
+
(p: Record<string, unknown>) => ({
|
|
42
|
+
doi: p.doi,
|
|
43
|
+
title: p.title,
|
|
44
|
+
authors: typeof p.authors === "string"
|
|
45
|
+
? (p.authors as string).split("; ").filter(Boolean)
|
|
46
|
+
: [],
|
|
47
|
+
abstract: p.abstract,
|
|
48
|
+
date: p.date,
|
|
49
|
+
category: p.category,
|
|
50
|
+
version: p.version,
|
|
51
|
+
license: p.license,
|
|
52
|
+
url: p.doi ? `https://www.biorxiv.org/content/${p.doi}` : undefined,
|
|
53
|
+
pdf_url: p.doi ? `https://www.biorxiv.org/content/${p.doi}v${p.version ?? 1}.full.pdf` : undefined,
|
|
54
|
+
source: "biorxiv",
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return toolResult({
|
|
59
|
+
total_results: meta?.total ?? papers.length,
|
|
60
|
+
cursor: meta?.cursor,
|
|
61
|
+
papers,
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "search_medrxiv",
|
|
67
|
+
label: "Search Preprints (medRxiv)",
|
|
68
|
+
description:
|
|
69
|
+
"Get recent medRxiv preprints by date range. Covers medical/health science preprints (100K+).",
|
|
70
|
+
parameters: Type.Object({
|
|
71
|
+
interval: Type.String({
|
|
72
|
+
description:
|
|
73
|
+
"Date range in YYYY-MM-DD/YYYY-MM-DD format (e.g. '2026-03-01/2026-03-18'). Use today's date for most recent.",
|
|
74
|
+
}),
|
|
75
|
+
cursor: Type.Optional(
|
|
76
|
+
Type.Number({ description: "Pagination offset (default 0)" }),
|
|
77
|
+
),
|
|
78
|
+
}),
|
|
79
|
+
execute: async (input: { interval: string; cursor?: number }) => {
|
|
80
|
+
if (!input?.interval) {
|
|
81
|
+
return toolResult({ error: 'interval parameter is required (date range in YYYY-MM-DD/YYYY-MM-DD format, e.g. "2026-03-01/2026-03-18")' });
|
|
82
|
+
}
|
|
83
|
+
if (!/^\d{4}-\d{2}-\d{2}\/\d{4}-\d{2}-\d{2}$/.test(input.interval)) {
|
|
84
|
+
return toolResult({ error: `Invalid interval format "${input.interval}". Must be YYYY-MM-DD/YYYY-MM-DD (e.g. "2026-03-01/2026-03-18")` });
|
|
85
|
+
}
|
|
86
|
+
const cursor = input.cursor ?? 0;
|
|
87
|
+
const tracked = await trackedFetch("medrxiv", `${BASE}/details/medrxiv/${input.interval}/${cursor}/json`, undefined, 30_000);
|
|
88
|
+
if (isTrackedError(tracked)) return tracked;
|
|
89
|
+
const data = await tracked.res.json();
|
|
90
|
+
|
|
91
|
+
const meta = data.messages?.[0];
|
|
92
|
+
const papers = (data.collection ?? []).map(
|
|
93
|
+
(p: Record<string, unknown>) => ({
|
|
94
|
+
doi: p.doi,
|
|
95
|
+
title: p.title,
|
|
96
|
+
authors: typeof p.authors === "string"
|
|
97
|
+
? (p.authors as string).split("; ").filter(Boolean)
|
|
98
|
+
: [],
|
|
99
|
+
abstract: p.abstract,
|
|
100
|
+
date: p.date,
|
|
101
|
+
category: p.category,
|
|
102
|
+
version: p.version,
|
|
103
|
+
url: p.doi ? `https://www.medrxiv.org/content/${p.doi}` : undefined,
|
|
104
|
+
pdf_url: p.doi ? `https://www.medrxiv.org/content/${p.doi}v${p.version ?? 1}.full.pdf` : undefined,
|
|
105
|
+
source: "medrxiv",
|
|
106
|
+
}),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
return toolResult({
|
|
110
|
+
total_results: meta?.total ?? papers.length,
|
|
111
|
+
cursor: meta?.cursor,
|
|
112
|
+
papers,
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "get_preprint_by_doi",
|
|
118
|
+
label: "Get Preprint by DOI (bioRxiv/medRxiv)",
|
|
119
|
+
description:
|
|
120
|
+
"Get a specific bioRxiv or medRxiv preprint by its DOI.",
|
|
121
|
+
parameters: Type.Object({
|
|
122
|
+
doi: Type.String({ description: "DOI of the preprint (e.g. '10.1101/2024.01.15.575123')" }),
|
|
123
|
+
server: Type.Optional(
|
|
124
|
+
Type.String({ description: "Server: 'biorxiv' or 'medrxiv' (default: biorxiv)" }),
|
|
125
|
+
),
|
|
126
|
+
}),
|
|
127
|
+
execute: async (input: { doi: string; server?: string }) => {
|
|
128
|
+
if (!input?.doi) {
|
|
129
|
+
return toolResult({ error: 'doi parameter is required (e.g., "10.1101/2024.01.15.575123")' });
|
|
130
|
+
}
|
|
131
|
+
const server = input.server ?? "biorxiv";
|
|
132
|
+
const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
|
|
133
|
+
const tracked = await trackedFetch(server, `${BASE}/details/${server}/${doi}/na/json`, undefined, 15_000);
|
|
134
|
+
if (isTrackedError(tracked)) return tracked;
|
|
135
|
+
const data = await tracked.res.json();
|
|
136
|
+
const papers = data.collection ?? [];
|
|
137
|
+
if (papers.length === 0) return toolResult({ error: "Preprint not found" });
|
|
138
|
+
|
|
139
|
+
const p = papers[0] as Record<string, unknown>;
|
|
140
|
+
return toolResult({
|
|
141
|
+
doi: p.doi,
|
|
142
|
+
title: p.title,
|
|
143
|
+
authors: typeof p.authors === "string"
|
|
144
|
+
? (p.authors as string).split("; ").filter(Boolean)
|
|
145
|
+
: [],
|
|
146
|
+
abstract: p.abstract,
|
|
147
|
+
date: p.date,
|
|
148
|
+
category: p.category,
|
|
149
|
+
version: p.version,
|
|
150
|
+
license: p.license,
|
|
151
|
+
url: `https://www.${server}.org/content/${p.doi}`,
|
|
152
|
+
pdf_url: `https://www.${server}.org/content/${p.doi}v${p.version ?? 1}.full.pdf`,
|
|
153
|
+
source: server,
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
}
|
package/src/tools/crossref.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.crossref.org";
|
|
6
6
|
|
|
@@ -24,12 +24,13 @@ export function createCrossRefTools(
|
|
|
24
24
|
}),
|
|
25
25
|
}),
|
|
26
26
|
execute: async (input: { doi: string }) => {
|
|
27
|
+
if (!input?.doi) {
|
|
28
|
+
return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
|
|
29
|
+
}
|
|
27
30
|
const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
|
|
32
|
-
const data = await res.json();
|
|
31
|
+
const tracked = await trackedFetch("crossref", `${BASE}/works/${encodeURIComponent(doi)}`, { headers });
|
|
32
|
+
if (isTrackedError(tracked)) return tracked;
|
|
33
|
+
const data = await tracked.res.json();
|
|
33
34
|
const w = data.message;
|
|
34
35
|
return toolResult({
|
|
35
36
|
doi: w.DOI,
|
|
@@ -46,6 +47,7 @@ export function createCrossRefTools(
|
|
|
46
47
|
url: w.URL,
|
|
47
48
|
abstract: w.abstract,
|
|
48
49
|
license: w.license?.[0]?.URL,
|
|
50
|
+
_source_health: { source: "crossref", latency_ms: tracked.latency_ms },
|
|
49
51
|
});
|
|
50
52
|
},
|
|
51
53
|
},
|
|
@@ -53,47 +55,82 @@ export function createCrossRefTools(
|
|
|
53
55
|
name: "search_crossref",
|
|
54
56
|
label: "Search Works (CrossRef)",
|
|
55
57
|
description:
|
|
56
|
-
"Search CrossRef for scholarly works
|
|
58
|
+
"Search CrossRef for scholarly works. Covers 150M+ DOIs across ALL publishers and disciplines. Supports journal/ISSN filtering, year range, work type, and citation-count sorting. Best general-purpose academic search tool.",
|
|
57
59
|
parameters: Type.Object({
|
|
58
|
-
query: Type.String({ description: "Search query" }),
|
|
59
|
-
|
|
60
|
-
Type.
|
|
60
|
+
query: Type.String({ description: "Search query (keywords)" }),
|
|
61
|
+
journal: Type.Optional(
|
|
62
|
+
Type.String({
|
|
63
|
+
description:
|
|
64
|
+
"Journal name filter (container-title). E.g. 'Nature', 'American Economic Review', 'The Lancet'",
|
|
65
|
+
}),
|
|
66
|
+
),
|
|
67
|
+
issn: Type.Optional(
|
|
68
|
+
Type.String({
|
|
69
|
+
description: "Journal ISSN filter. E.g. '0028-0836' for Nature, '0002-8282' for AER",
|
|
70
|
+
}),
|
|
61
71
|
),
|
|
62
72
|
from_year: Type.Optional(
|
|
63
|
-
Type.Number({ description: "Published from this year onward" }),
|
|
73
|
+
Type.Number({ description: "Published from this year onward (inclusive)" }),
|
|
74
|
+
),
|
|
75
|
+
until_year: Type.Optional(
|
|
76
|
+
Type.Number({ description: "Published until this year (inclusive)" }),
|
|
64
77
|
),
|
|
65
78
|
type: Type.Optional(
|
|
66
79
|
Type.String({
|
|
67
80
|
description:
|
|
68
|
-
"Work type
|
|
81
|
+
"Work type: 'journal-article', 'book-chapter', 'proceedings-article', 'posted-content' (preprint), 'dissertation'",
|
|
69
82
|
}),
|
|
70
83
|
),
|
|
84
|
+
has_abstract: Type.Optional(
|
|
85
|
+
Type.Boolean({ description: "Only results with abstracts" }),
|
|
86
|
+
),
|
|
71
87
|
sort: Type.Optional(
|
|
72
88
|
Type.String({
|
|
73
|
-
description:
|
|
89
|
+
description:
|
|
90
|
+
"Sort by: 'relevance' (default), 'published' (newest first), 'is-referenced-by-count' (most cited)",
|
|
74
91
|
}),
|
|
75
92
|
),
|
|
93
|
+
limit: Type.Optional(
|
|
94
|
+
Type.Number({ description: "Max results (default 10, max 100)" }),
|
|
95
|
+
),
|
|
76
96
|
}),
|
|
77
97
|
execute: async (input: {
|
|
78
98
|
query: string;
|
|
79
|
-
|
|
99
|
+
journal?: string;
|
|
100
|
+
issn?: string;
|
|
80
101
|
from_year?: number;
|
|
102
|
+
until_year?: number;
|
|
81
103
|
type?: string;
|
|
104
|
+
has_abstract?: boolean;
|
|
82
105
|
sort?: string;
|
|
106
|
+
limit?: number;
|
|
83
107
|
}) => {
|
|
84
108
|
const params = new URLSearchParams({
|
|
85
109
|
query: input.query,
|
|
86
110
|
rows: String(Math.min(input.limit ?? 10, 100)),
|
|
87
111
|
});
|
|
88
|
-
if (input.from_year)
|
|
89
|
-
params.set("filter", `from-pub-date:${input.from_year}`);
|
|
90
|
-
if (input.type)
|
|
91
|
-
params.append("filter", `type:${input.type}`);
|
|
92
|
-
if (input.sort) params.set("sort", input.sort);
|
|
93
112
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
113
|
+
// Build filter chain
|
|
114
|
+
const filters: string[] = [];
|
|
115
|
+
if (input.from_year) filters.push(`from-pub-date:${input.from_year}`);
|
|
116
|
+
if (input.until_year) filters.push(`until-pub-date:${input.until_year}`);
|
|
117
|
+
if (input.type) filters.push(`type:${input.type}`);
|
|
118
|
+
if (input.has_abstract) filters.push("has-abstract:true");
|
|
119
|
+
if (input.issn) filters.push(`issn:${input.issn}`);
|
|
120
|
+
if (filters.length > 0) params.set("filter", filters.join(","));
|
|
121
|
+
|
|
122
|
+
// Journal name as query.container-title (separate from filter)
|
|
123
|
+
if (input.journal) params.set("query.container-title", input.journal);
|
|
124
|
+
|
|
125
|
+
if (input.sort) {
|
|
126
|
+
params.set("sort", input.sort);
|
|
127
|
+
params.set("order", "desc");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const tracked = await trackedFetch("crossref", `${BASE}/works?${params}`, { headers });
|
|
131
|
+
if (isTrackedError(tracked)) return tracked;
|
|
132
|
+
const data = await tracked.res.json();
|
|
133
|
+
|
|
97
134
|
return toolResult({
|
|
98
135
|
total_results: data.message?.["total-results"],
|
|
99
136
|
items: data.message?.items?.map((w: Record<string, unknown>) => ({
|
|
@@ -107,7 +144,11 @@ export function createCrossRefTools(
|
|
|
107
144
|
(w.published as Record<string, unknown>)?.["date-parts"],
|
|
108
145
|
type: w.type,
|
|
109
146
|
cited_by: w["is-referenced-by-count"],
|
|
147
|
+
abstract: typeof w.abstract === "string"
|
|
148
|
+
? (w.abstract as string).replace(/<[^>]*>/g, "").slice(0, 300)
|
|
149
|
+
: undefined,
|
|
110
150
|
})),
|
|
151
|
+
_source_health: { source: "crossref", latency_ms: tracked.latency_ms },
|
|
111
152
|
});
|
|
112
153
|
},
|
|
113
154
|
},
|
|
@@ -0,0 +1,191 @@
|
|
|
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.datacite.org";
|
|
6
|
+
|
|
7
|
+
export function createDataCiteTools(
|
|
8
|
+
_ctx: OpenClawPluginToolContext,
|
|
9
|
+
_api: OpenClawPluginApi,
|
|
10
|
+
) {
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
name: "search_datacite",
|
|
14
|
+
label: "Search Datasets & DOIs (DataCite)",
|
|
15
|
+
description:
|
|
16
|
+
"Search DataCite for datasets, software, and other research outputs (50M+ DOIs). Covers Zenodo, Figshare, Dryad, and 2000+ repositories. Best for finding datasets, software, and non-journal outputs.",
|
|
17
|
+
parameters: Type.Object({
|
|
18
|
+
query: Type.String({
|
|
19
|
+
description: "Search query for datasets, software, or other research outputs",
|
|
20
|
+
}),
|
|
21
|
+
max_results: Type.Optional(
|
|
22
|
+
Type.Number({ description: "Max results (default 10, max 100)" }),
|
|
23
|
+
),
|
|
24
|
+
resource_type: Type.Optional(
|
|
25
|
+
Type.String({
|
|
26
|
+
description:
|
|
27
|
+
"Filter by type: 'Dataset', 'Software', 'Text', 'Collection', 'Audiovisual', 'Image', etc.",
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
from_year: Type.Optional(
|
|
31
|
+
Type.Number({ description: "Published from this year onward" }),
|
|
32
|
+
),
|
|
33
|
+
}),
|
|
34
|
+
execute: async (input: {
|
|
35
|
+
query: string;
|
|
36
|
+
max_results?: number;
|
|
37
|
+
resource_type?: string;
|
|
38
|
+
from_year?: number;
|
|
39
|
+
}) => {
|
|
40
|
+
const pageSize = Math.min(input.max_results ?? 10, 100);
|
|
41
|
+
const params = new URLSearchParams({
|
|
42
|
+
query: input.query,
|
|
43
|
+
"page[size]": String(pageSize),
|
|
44
|
+
});
|
|
45
|
+
if (input.resource_type) {
|
|
46
|
+
params.set("resource-type-id", input.resource_type.toLowerCase());
|
|
47
|
+
}
|
|
48
|
+
if (input.from_year) {
|
|
49
|
+
params.set("query", `${input.query} AND publicationYear:[${input.from_year} TO *]`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const tracked = await trackedFetch("datacite", `${BASE}/dois?${params}`, undefined, 15_000);
|
|
53
|
+
if (isTrackedError(tracked)) return tracked;
|
|
54
|
+
const data = await tracked.res.json();
|
|
55
|
+
|
|
56
|
+
const items = (data.data ?? []).map(
|
|
57
|
+
(item: Record<string, unknown>) => {
|
|
58
|
+
const attrs = item.attributes as Record<string, unknown> | undefined;
|
|
59
|
+
if (!attrs) return { id: item.id };
|
|
60
|
+
|
|
61
|
+
const titles = attrs.titles as Array<{ title: string }> | undefined;
|
|
62
|
+
const creators = attrs.creators as Array<{
|
|
63
|
+
name: string;
|
|
64
|
+
nameType?: string;
|
|
65
|
+
givenName?: string;
|
|
66
|
+
familyName?: string;
|
|
67
|
+
}> | undefined;
|
|
68
|
+
const types = attrs.types as Record<string, string> | undefined;
|
|
69
|
+
const descriptions = attrs.descriptions as Array<{
|
|
70
|
+
description: string;
|
|
71
|
+
descriptionType: string;
|
|
72
|
+
}> | undefined;
|
|
73
|
+
const dates = attrs.dates as Array<{
|
|
74
|
+
date: string;
|
|
75
|
+
dateType: string;
|
|
76
|
+
}> | undefined;
|
|
77
|
+
|
|
78
|
+
const issuedDate = dates?.find((d) => d.dateType === "Issued")?.date;
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
doi: attrs.doi,
|
|
82
|
+
title: titles?.[0]?.title,
|
|
83
|
+
creators: creators?.slice(0, 5).map((c) =>
|
|
84
|
+
c.givenName && c.familyName
|
|
85
|
+
? `${c.givenName} ${c.familyName}`
|
|
86
|
+
: c.name,
|
|
87
|
+
),
|
|
88
|
+
publisher: attrs.publisher,
|
|
89
|
+
publication_year: attrs.publicationYear,
|
|
90
|
+
resource_type: types?.resourceTypeGeneral,
|
|
91
|
+
description: descriptions?.find(
|
|
92
|
+
(d) => d.descriptionType === "Abstract",
|
|
93
|
+
)?.description,
|
|
94
|
+
url: attrs.url,
|
|
95
|
+
issued_date: issuedDate,
|
|
96
|
+
citation_count: attrs.citationCount,
|
|
97
|
+
view_count: attrs.viewCount,
|
|
98
|
+
download_count: attrs.downloadCount,
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return toolResult({
|
|
104
|
+
total_results: data.meta?.total,
|
|
105
|
+
items,
|
|
106
|
+
_source_health: { source: "datacite", latency_ms: tracked.latency_ms },
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "resolve_datacite_doi",
|
|
112
|
+
label: "Resolve DOI (DataCite)",
|
|
113
|
+
description:
|
|
114
|
+
"Resolve a DataCite DOI to get full metadata. Best for DOIs from Zenodo, Figshare, Dryad, and other data repositories (10.5281/*, 10.6084/*, etc.).",
|
|
115
|
+
parameters: Type.Object({
|
|
116
|
+
doi: Type.String({
|
|
117
|
+
description: "DOI to resolve, e.g. '10.5281/zenodo.1234567'",
|
|
118
|
+
}),
|
|
119
|
+
}),
|
|
120
|
+
execute: async (input: { doi: string }) => {
|
|
121
|
+
if (!input?.doi) {
|
|
122
|
+
return toolResult({ error: 'doi parameter is required (e.g., "10.5281/zenodo.1234567")' });
|
|
123
|
+
}
|
|
124
|
+
const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
|
|
125
|
+
|
|
126
|
+
const tracked = await trackedFetch(
|
|
127
|
+
"datacite",
|
|
128
|
+
`${BASE}/dois/${encodeURIComponent(doi)}`,
|
|
129
|
+
undefined,
|
|
130
|
+
15_000,
|
|
131
|
+
);
|
|
132
|
+
if (isTrackedError(tracked)) return tracked;
|
|
133
|
+
const data = await tracked.res.json();
|
|
134
|
+
|
|
135
|
+
const attrs = data.data?.attributes as Record<string, unknown> | undefined;
|
|
136
|
+
if (!attrs) return toolResult({ error: "DOI not found or no attributes" });
|
|
137
|
+
|
|
138
|
+
const titles = attrs.titles as Array<{ title: string }> | undefined;
|
|
139
|
+
const creators = attrs.creators as Array<{
|
|
140
|
+
name: string;
|
|
141
|
+
givenName?: string;
|
|
142
|
+
familyName?: string;
|
|
143
|
+
}> | undefined;
|
|
144
|
+
const types = attrs.types as Record<string, string> | undefined;
|
|
145
|
+
const descriptions = attrs.descriptions as Array<{
|
|
146
|
+
description: string;
|
|
147
|
+
descriptionType: string;
|
|
148
|
+
}> | undefined;
|
|
149
|
+
const dates = attrs.dates as Array<{
|
|
150
|
+
date: string;
|
|
151
|
+
dateType: string;
|
|
152
|
+
}> | undefined;
|
|
153
|
+
const rights = attrs.rightsList as Array<{
|
|
154
|
+
rights: string;
|
|
155
|
+
rightsUri?: string;
|
|
156
|
+
rightsIdentifier?: string;
|
|
157
|
+
}> | undefined;
|
|
158
|
+
const subjects = attrs.subjects as Array<{
|
|
159
|
+
subject: string;
|
|
160
|
+
}> | undefined;
|
|
161
|
+
|
|
162
|
+
return toolResult({
|
|
163
|
+
doi: attrs.doi,
|
|
164
|
+
title: titles?.[0]?.title,
|
|
165
|
+
creators: creators?.map((c) =>
|
|
166
|
+
c.givenName && c.familyName
|
|
167
|
+
? `${c.givenName} ${c.familyName}`
|
|
168
|
+
: c.name,
|
|
169
|
+
),
|
|
170
|
+
publisher: attrs.publisher,
|
|
171
|
+
publication_year: attrs.publicationYear,
|
|
172
|
+
resource_type: types?.resourceTypeGeneral,
|
|
173
|
+
resource_type_specific: types?.resourceType || undefined,
|
|
174
|
+
description: descriptions?.find(
|
|
175
|
+
(d) => d.descriptionType === "Abstract",
|
|
176
|
+
)?.description,
|
|
177
|
+
url: attrs.url,
|
|
178
|
+
dates: dates?.map((d) => ({ date: d.date, type: d.dateType })),
|
|
179
|
+
license: rights?.[0]?.rightsIdentifier ?? rights?.[0]?.rights,
|
|
180
|
+
license_url: rights?.[0]?.rightsUri,
|
|
181
|
+
subjects: subjects?.map((s) => s.subject),
|
|
182
|
+
citation_count: attrs.citationCount,
|
|
183
|
+
view_count: attrs.viewCount,
|
|
184
|
+
download_count: attrs.downloadCount,
|
|
185
|
+
version: attrs.version,
|
|
186
|
+
_source_health: { source: "datacite", latency_ms: tracked.latency_ms },
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
}
|