@wentorai/research-plugins 1.3.2 → 1.4.0
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 +1 -10
- package/curated/research/README.md +1 -18
- package/curated/tools/README.md +1 -12
- package/curated/writing/README.md +1 -5
- 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/fulltext/SKILL.md +3 -2
- package/skills/literature/fulltext/arxiv-latex-source/SKILL.md +195 -0
- package/skills/literature/search/SKILL.md +2 -3
- 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 +1 -0
- package/skills/research/deep-research/auto-deep-research-guide/SKILL.md +1 -0
- package/skills/research/methodology/SKILL.md +1 -1
- package/skills/research/methodology/claude-scientific-guide/SKILL.md +1 -0
- 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/peer-review-guide/SKILL.md +1 -1
- package/skills/tools/knowledge-graph/SKILL.md +2 -3
- package/skills/tools/ocr-translate/zotero-pdf2zh-guide/SKILL.md +1 -0
- 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/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 +1 -0
- 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 +78 -30
- package/src/tools/biorxiv.ts +142 -0
- package/src/tools/crossref.ts +60 -22
- package/src/tools/datacite.ts +188 -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 +20 -15
- package/src/tools/opencitations.ts +103 -0
- package/src/tools/orcid.ts +136 -0
- package/src/tools/osf-preprints.ts +104 -0
- package/src/tools/pubmed.ts +19 -13
- package/src/tools/ror.ts +118 -0
- package/src/tools/unpaywall.ts +12 -6
- package/src/tools/util.ts +141 -0
- package/src/tools/zenodo.ts +154 -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,31 @@ export function createArxivTools(
|
|
|
120
151
|
}),
|
|
121
152
|
}),
|
|
122
153
|
execute: async (input: { arxiv_id: string }) => {
|
|
123
|
-
const id = input.arxiv_id.replace("arXiv:", "");
|
|
154
|
+
const id = input.arxiv_id.replace("arXiv:", "").replace(/https?:\/\/arxiv\.org\/abs\//, "");
|
|
124
155
|
const params = new URLSearchParams({ id_list: id });
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
156
|
+
|
|
157
|
+
const tracked = await trackedFetch("arxiv", `${BASE}?${params}`, undefined, 15_000);
|
|
158
|
+
if (isTrackedError(tracked)) return tracked;
|
|
159
|
+
const xml = await tracked.res.text();
|
|
160
|
+
|
|
161
|
+
if (!xml.includes("<feed")) {
|
|
162
|
+
return toolResult({
|
|
163
|
+
error: "arXiv returned non-XML response",
|
|
164
|
+
_source_health: { source: "arxiv", latency_ms: tracked.latency_ms },
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
128
168
|
const papers = parseArxivXml(xml);
|
|
129
|
-
if (papers.length === 0)
|
|
130
|
-
|
|
169
|
+
if (papers.length === 0) {
|
|
170
|
+
return toolResult({
|
|
171
|
+
error: `Paper not found: ${id}`,
|
|
172
|
+
_source_health: { source: "arxiv", latency_ms: tracked.latency_ms },
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return toolResult({
|
|
176
|
+
...papers[0],
|
|
177
|
+
_source_health: { source: "arxiv", latency_ms: tracked.latency_ms },
|
|
178
|
+
});
|
|
131
179
|
},
|
|
132
180
|
},
|
|
133
181
|
];
|
|
@@ -0,0 +1,142 @@
|
|
|
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 date range (e.g. '2026-03-01/2026-03-18'), recent count (e.g. '50'), or recent days (e.g. '7d').",
|
|
17
|
+
parameters: Type.Object({
|
|
18
|
+
interval: Type.String({
|
|
19
|
+
description:
|
|
20
|
+
"Date range 'YYYY-MM-DD/YYYY-MM-DD', recent count '50', or recent days '7d'",
|
|
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
|
+
const cursor = input.cursor ?? 0;
|
|
28
|
+
const tracked = await trackedFetch("biorxiv", `${BASE}/details/biorxiv/${input.interval}/${cursor}/json`, undefined, 30_000);
|
|
29
|
+
if (isTrackedError(tracked)) return tracked;
|
|
30
|
+
const data = await tracked.res.json();
|
|
31
|
+
|
|
32
|
+
const meta = data.messages?.[0];
|
|
33
|
+
const papers = (data.collection ?? []).map(
|
|
34
|
+
(p: Record<string, unknown>) => ({
|
|
35
|
+
doi: p.doi,
|
|
36
|
+
title: p.title,
|
|
37
|
+
authors: typeof p.authors === "string"
|
|
38
|
+
? (p.authors as string).split("; ").filter(Boolean)
|
|
39
|
+
: [],
|
|
40
|
+
abstract: p.abstract,
|
|
41
|
+
date: p.date,
|
|
42
|
+
category: p.category,
|
|
43
|
+
version: p.version,
|
|
44
|
+
license: p.license,
|
|
45
|
+
url: p.doi ? `https://www.biorxiv.org/content/${p.doi}` : undefined,
|
|
46
|
+
pdf_url: p.doi ? `https://www.biorxiv.org/content/${p.doi}v${p.version ?? 1}.full.pdf` : undefined,
|
|
47
|
+
source: "biorxiv",
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return toolResult({
|
|
52
|
+
total_results: meta?.total ?? papers.length,
|
|
53
|
+
cursor: meta?.cursor,
|
|
54
|
+
papers,
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "search_medrxiv",
|
|
60
|
+
label: "Search Preprints (medRxiv)",
|
|
61
|
+
description:
|
|
62
|
+
"Get recent medRxiv preprints by date range. Covers medical/health science preprints (100K+).",
|
|
63
|
+
parameters: Type.Object({
|
|
64
|
+
interval: Type.String({
|
|
65
|
+
description:
|
|
66
|
+
"Date range 'YYYY-MM-DD/YYYY-MM-DD', recent count '50', or recent days '7d'",
|
|
67
|
+
}),
|
|
68
|
+
cursor: Type.Optional(
|
|
69
|
+
Type.Number({ description: "Pagination offset (default 0)" }),
|
|
70
|
+
),
|
|
71
|
+
}),
|
|
72
|
+
execute: async (input: { interval: string; cursor?: number }) => {
|
|
73
|
+
const cursor = input.cursor ?? 0;
|
|
74
|
+
const tracked = await trackedFetch("medrxiv", `${BASE}/details/medrxiv/${input.interval}/${cursor}/json`, undefined, 30_000);
|
|
75
|
+
if (isTrackedError(tracked)) return tracked;
|
|
76
|
+
const data = await tracked.res.json();
|
|
77
|
+
|
|
78
|
+
const meta = data.messages?.[0];
|
|
79
|
+
const papers = (data.collection ?? []).map(
|
|
80
|
+
(p: Record<string, unknown>) => ({
|
|
81
|
+
doi: p.doi,
|
|
82
|
+
title: p.title,
|
|
83
|
+
authors: typeof p.authors === "string"
|
|
84
|
+
? (p.authors as string).split("; ").filter(Boolean)
|
|
85
|
+
: [],
|
|
86
|
+
abstract: p.abstract,
|
|
87
|
+
date: p.date,
|
|
88
|
+
category: p.category,
|
|
89
|
+
version: p.version,
|
|
90
|
+
url: p.doi ? `https://www.medrxiv.org/content/${p.doi}` : undefined,
|
|
91
|
+
pdf_url: p.doi ? `https://www.medrxiv.org/content/${p.doi}v${p.version ?? 1}.full.pdf` : undefined,
|
|
92
|
+
source: "medrxiv",
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return toolResult({
|
|
97
|
+
total_results: meta?.total ?? papers.length,
|
|
98
|
+
cursor: meta?.cursor,
|
|
99
|
+
papers,
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "get_preprint_by_doi",
|
|
105
|
+
label: "Get Preprint by DOI (bioRxiv/medRxiv)",
|
|
106
|
+
description:
|
|
107
|
+
"Get a specific bioRxiv or medRxiv preprint by its DOI.",
|
|
108
|
+
parameters: Type.Object({
|
|
109
|
+
doi: Type.String({ description: "DOI of the preprint (e.g. '10.1101/2024.01.15.575123')" }),
|
|
110
|
+
server: Type.Optional(
|
|
111
|
+
Type.String({ description: "Server: 'biorxiv' or 'medrxiv' (default: biorxiv)" }),
|
|
112
|
+
),
|
|
113
|
+
}),
|
|
114
|
+
execute: async (input: { doi: string; server?: string }) => {
|
|
115
|
+
const server = input.server ?? "biorxiv";
|
|
116
|
+
const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
|
|
117
|
+
const tracked = await trackedFetch(server, `${BASE}/details/${server}/${doi}/na/json`, undefined, 15_000);
|
|
118
|
+
if (isTrackedError(tracked)) return tracked;
|
|
119
|
+
const data = await tracked.res.json();
|
|
120
|
+
const papers = data.collection ?? [];
|
|
121
|
+
if (papers.length === 0) return toolResult({ error: "Preprint not found" });
|
|
122
|
+
|
|
123
|
+
const p = papers[0] as Record<string, unknown>;
|
|
124
|
+
return toolResult({
|
|
125
|
+
doi: p.doi,
|
|
126
|
+
title: p.title,
|
|
127
|
+
authors: typeof p.authors === "string"
|
|
128
|
+
? (p.authors as string).split("; ").filter(Boolean)
|
|
129
|
+
: [],
|
|
130
|
+
abstract: p.abstract,
|
|
131
|
+
date: p.date,
|
|
132
|
+
category: p.category,
|
|
133
|
+
version: p.version,
|
|
134
|
+
license: p.license,
|
|
135
|
+
url: `https://www.${server}.org/content/${p.doi}`,
|
|
136
|
+
pdf_url: `https://www.${server}.org/content/${p.doi}v${p.version ?? 1}.full.pdf`,
|
|
137
|
+
source: server,
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
}
|
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
|
|
|
@@ -25,11 +25,9 @@ export function createCrossRefTools(
|
|
|
25
25
|
}),
|
|
26
26
|
execute: async (input: { doi: string }) => {
|
|
27
27
|
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();
|
|
28
|
+
const tracked = await trackedFetch("crossref", `${BASE}/works/${encodeURIComponent(doi)}`, { headers });
|
|
29
|
+
if (isTrackedError(tracked)) return tracked;
|
|
30
|
+
const data = await tracked.res.json();
|
|
33
31
|
const w = data.message;
|
|
34
32
|
return toolResult({
|
|
35
33
|
doi: w.DOI,
|
|
@@ -46,6 +44,7 @@ export function createCrossRefTools(
|
|
|
46
44
|
url: w.URL,
|
|
47
45
|
abstract: w.abstract,
|
|
48
46
|
license: w.license?.[0]?.URL,
|
|
47
|
+
_source_health: { source: "crossref", latency_ms: tracked.latency_ms },
|
|
49
48
|
});
|
|
50
49
|
},
|
|
51
50
|
},
|
|
@@ -53,47 +52,82 @@ export function createCrossRefTools(
|
|
|
53
52
|
name: "search_crossref",
|
|
54
53
|
label: "Search Works (CrossRef)",
|
|
55
54
|
description:
|
|
56
|
-
"Search CrossRef for scholarly works
|
|
55
|
+
"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
56
|
parameters: Type.Object({
|
|
58
|
-
query: Type.String({ description: "Search query" }),
|
|
59
|
-
|
|
60
|
-
Type.
|
|
57
|
+
query: Type.String({ description: "Search query (keywords)" }),
|
|
58
|
+
journal: Type.Optional(
|
|
59
|
+
Type.String({
|
|
60
|
+
description:
|
|
61
|
+
"Journal name filter (container-title). E.g. 'Nature', 'American Economic Review', 'The Lancet'",
|
|
62
|
+
}),
|
|
63
|
+
),
|
|
64
|
+
issn: Type.Optional(
|
|
65
|
+
Type.String({
|
|
66
|
+
description: "Journal ISSN filter. E.g. '0028-0836' for Nature, '0002-8282' for AER",
|
|
67
|
+
}),
|
|
61
68
|
),
|
|
62
69
|
from_year: Type.Optional(
|
|
63
|
-
Type.Number({ description: "Published from this year onward" }),
|
|
70
|
+
Type.Number({ description: "Published from this year onward (inclusive)" }),
|
|
71
|
+
),
|
|
72
|
+
until_year: Type.Optional(
|
|
73
|
+
Type.Number({ description: "Published until this year (inclusive)" }),
|
|
64
74
|
),
|
|
65
75
|
type: Type.Optional(
|
|
66
76
|
Type.String({
|
|
67
77
|
description:
|
|
68
|
-
"Work type
|
|
78
|
+
"Work type: 'journal-article', 'book-chapter', 'proceedings-article', 'posted-content' (preprint), 'dissertation'",
|
|
69
79
|
}),
|
|
70
80
|
),
|
|
81
|
+
has_abstract: Type.Optional(
|
|
82
|
+
Type.Boolean({ description: "Only results with abstracts" }),
|
|
83
|
+
),
|
|
71
84
|
sort: Type.Optional(
|
|
72
85
|
Type.String({
|
|
73
|
-
description:
|
|
86
|
+
description:
|
|
87
|
+
"Sort by: 'relevance' (default), 'published' (newest first), 'is-referenced-by-count' (most cited)",
|
|
74
88
|
}),
|
|
75
89
|
),
|
|
90
|
+
limit: Type.Optional(
|
|
91
|
+
Type.Number({ description: "Max results (default 10, max 100)" }),
|
|
92
|
+
),
|
|
76
93
|
}),
|
|
77
94
|
execute: async (input: {
|
|
78
95
|
query: string;
|
|
79
|
-
|
|
96
|
+
journal?: string;
|
|
97
|
+
issn?: string;
|
|
80
98
|
from_year?: number;
|
|
99
|
+
until_year?: number;
|
|
81
100
|
type?: string;
|
|
101
|
+
has_abstract?: boolean;
|
|
82
102
|
sort?: string;
|
|
103
|
+
limit?: number;
|
|
83
104
|
}) => {
|
|
84
105
|
const params = new URLSearchParams({
|
|
85
106
|
query: input.query,
|
|
86
107
|
rows: String(Math.min(input.limit ?? 10, 100)),
|
|
87
108
|
});
|
|
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
109
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
110
|
+
// Build filter chain
|
|
111
|
+
const filters: string[] = [];
|
|
112
|
+
if (input.from_year) filters.push(`from-pub-date:${input.from_year}`);
|
|
113
|
+
if (input.until_year) filters.push(`until-pub-date:${input.until_year}`);
|
|
114
|
+
if (input.type) filters.push(`type:${input.type}`);
|
|
115
|
+
if (input.has_abstract) filters.push("has-abstract:true");
|
|
116
|
+
if (input.issn) filters.push(`issn:${input.issn}`);
|
|
117
|
+
if (filters.length > 0) params.set("filter", filters.join(","));
|
|
118
|
+
|
|
119
|
+
// Journal name as query.container-title (separate from filter)
|
|
120
|
+
if (input.journal) params.set("query.container-title", input.journal);
|
|
121
|
+
|
|
122
|
+
if (input.sort) {
|
|
123
|
+
params.set("sort", input.sort);
|
|
124
|
+
params.set("order", "desc");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const tracked = await trackedFetch("crossref", `${BASE}/works?${params}`, { headers });
|
|
128
|
+
if (isTrackedError(tracked)) return tracked;
|
|
129
|
+
const data = await tracked.res.json();
|
|
130
|
+
|
|
97
131
|
return toolResult({
|
|
98
132
|
total_results: data.message?.["total-results"],
|
|
99
133
|
items: data.message?.items?.map((w: Record<string, unknown>) => ({
|
|
@@ -107,7 +141,11 @@ export function createCrossRefTools(
|
|
|
107
141
|
(w.published as Record<string, unknown>)?.["date-parts"],
|
|
108
142
|
type: w.type,
|
|
109
143
|
cited_by: w["is-referenced-by-count"],
|
|
144
|
+
abstract: typeof w.abstract === "string"
|
|
145
|
+
? (w.abstract as string).replace(/<[^>]*>/g, "").slice(0, 300)
|
|
146
|
+
: undefined,
|
|
110
147
|
})),
|
|
148
|
+
_source_health: { source: "crossref", latency_ms: tracked.latency_ms },
|
|
111
149
|
});
|
|
112
150
|
},
|
|
113
151
|
},
|
|
@@ -0,0 +1,188 @@
|
|
|
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
|
+
const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
|
|
122
|
+
|
|
123
|
+
const tracked = await trackedFetch(
|
|
124
|
+
"datacite",
|
|
125
|
+
`${BASE}/dois/${encodeURIComponent(doi)}`,
|
|
126
|
+
undefined,
|
|
127
|
+
15_000,
|
|
128
|
+
);
|
|
129
|
+
if (isTrackedError(tracked)) return tracked;
|
|
130
|
+
const data = await tracked.res.json();
|
|
131
|
+
|
|
132
|
+
const attrs = data.data?.attributes as Record<string, unknown> | undefined;
|
|
133
|
+
if (!attrs) return toolResult({ error: "DOI not found or no attributes" });
|
|
134
|
+
|
|
135
|
+
const titles = attrs.titles as Array<{ title: string }> | undefined;
|
|
136
|
+
const creators = attrs.creators as Array<{
|
|
137
|
+
name: string;
|
|
138
|
+
givenName?: string;
|
|
139
|
+
familyName?: string;
|
|
140
|
+
}> | undefined;
|
|
141
|
+
const types = attrs.types as Record<string, string> | undefined;
|
|
142
|
+
const descriptions = attrs.descriptions as Array<{
|
|
143
|
+
description: string;
|
|
144
|
+
descriptionType: string;
|
|
145
|
+
}> | undefined;
|
|
146
|
+
const dates = attrs.dates as Array<{
|
|
147
|
+
date: string;
|
|
148
|
+
dateType: string;
|
|
149
|
+
}> | undefined;
|
|
150
|
+
const rights = attrs.rightsList as Array<{
|
|
151
|
+
rights: string;
|
|
152
|
+
rightsUri?: string;
|
|
153
|
+
rightsIdentifier?: string;
|
|
154
|
+
}> | undefined;
|
|
155
|
+
const subjects = attrs.subjects as Array<{
|
|
156
|
+
subject: string;
|
|
157
|
+
}> | undefined;
|
|
158
|
+
|
|
159
|
+
return toolResult({
|
|
160
|
+
doi: attrs.doi,
|
|
161
|
+
title: titles?.[0]?.title,
|
|
162
|
+
creators: creators?.map((c) =>
|
|
163
|
+
c.givenName && c.familyName
|
|
164
|
+
? `${c.givenName} ${c.familyName}`
|
|
165
|
+
: c.name,
|
|
166
|
+
),
|
|
167
|
+
publisher: attrs.publisher,
|
|
168
|
+
publication_year: attrs.publicationYear,
|
|
169
|
+
resource_type: types?.resourceTypeGeneral,
|
|
170
|
+
resource_type_specific: types?.resourceType || undefined,
|
|
171
|
+
description: descriptions?.find(
|
|
172
|
+
(d) => d.descriptionType === "Abstract",
|
|
173
|
+
)?.description,
|
|
174
|
+
url: attrs.url,
|
|
175
|
+
dates: dates?.map((d) => ({ date: d.date, type: d.dateType })),
|
|
176
|
+
license: rights?.[0]?.rightsIdentifier ?? rights?.[0]?.rights,
|
|
177
|
+
license_url: rights?.[0]?.rightsUri,
|
|
178
|
+
subjects: subjects?.map((s) => s.subject),
|
|
179
|
+
citation_count: attrs.citationCount,
|
|
180
|
+
view_count: attrs.viewCount,
|
|
181
|
+
download_count: attrs.downloadCount,
|
|
182
|
+
version: attrs.version,
|
|
183
|
+
_source_health: { source: "datacite", latency_ms: tracked.latency_ms },
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
];
|
|
188
|
+
}
|