@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
|
@@ -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
|
},
|
|
@@ -96,11 +97,9 @@ export function createOpenAlexTools(
|
|
|
96
97
|
const id = input.work_id.startsWith("10.")
|
|
97
98
|
? `https://doi.org/${input.work_id}`
|
|
98
99
|
: 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();
|
|
100
|
+
const tracked = await trackedFetch("openalex", `${BASE}/works/${encodeURIComponent(id)}`, { headers });
|
|
101
|
+
if (isTrackedError(tracked)) return tracked;
|
|
102
|
+
const w = await tracked.res.json();
|
|
104
103
|
return toolResult({
|
|
105
104
|
id: w.id,
|
|
106
105
|
doi: w.doi,
|
|
@@ -120,6 +119,7 @@ export function createOpenAlexTools(
|
|
|
120
119
|
?.slice(0, 10)
|
|
121
120
|
.map((c: Record<string, unknown>) => c.display_name),
|
|
122
121
|
referenced_works_count: w.referenced_works?.length,
|
|
122
|
+
_source_health: { source: "openalex", latency_ms: tracked.latency_ms },
|
|
123
123
|
});
|
|
124
124
|
},
|
|
125
125
|
},
|
|
@@ -148,14 +148,18 @@ export function createOpenAlexTools(
|
|
|
148
148
|
search: input.author_id,
|
|
149
149
|
per_page: "5",
|
|
150
150
|
});
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
153
|
-
|
|
151
|
+
const tracked = await trackedFetch("openalex", `${BASE}/authors?${params}`, { headers });
|
|
152
|
+
if (isTrackedError(tracked)) return tracked;
|
|
153
|
+
const data = await tracked.res.json();
|
|
154
|
+
return toolResult({
|
|
155
|
+
...data,
|
|
156
|
+
_source_health: { source: "openalex", latency_ms: tracked.latency_ms },
|
|
157
|
+
});
|
|
154
158
|
}
|
|
155
159
|
|
|
156
|
-
const
|
|
157
|
-
if (
|
|
158
|
-
const a = await res.json();
|
|
160
|
+
const tracked = await trackedFetch("openalex", url, { headers });
|
|
161
|
+
if (isTrackedError(tracked)) return tracked;
|
|
162
|
+
const a = await tracked.res.json();
|
|
159
163
|
return toolResult({
|
|
160
164
|
id: a.id,
|
|
161
165
|
display_name: a.display_name,
|
|
@@ -171,6 +175,7 @@ export function createOpenAlexTools(
|
|
|
171
175
|
top_concepts: a.x_concepts
|
|
172
176
|
?.slice(0, 5)
|
|
173
177
|
.map((c: Record<string, unknown>) => c.display_name),
|
|
178
|
+
_source_health: { source: "openalex", latency_ms: tracked.latency_ms },
|
|
174
179
|
});
|
|
175
180
|
},
|
|
176
181
|
},
|
|
@@ -0,0 +1,103 @@
|
|
|
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
|
+
const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
|
|
33
|
+
const tracked = await trackedFetch("opencitations", `${BASE}/index/v2/citations/doi:${encodeURIComponent(doi)}`);
|
|
34
|
+
if (isTrackedError(tracked)) return tracked;
|
|
35
|
+
const data = await tracked.res.json();
|
|
36
|
+
|
|
37
|
+
if (!Array.isArray(data)) return toolResult({ error: "Unexpected response format" });
|
|
38
|
+
|
|
39
|
+
return toolResult({
|
|
40
|
+
total_citations: data.length,
|
|
41
|
+
citations: data.slice(0, 100).map((c: Record<string, string>) => ({
|
|
42
|
+
citing_doi: extractDoi(c.citing ?? ""),
|
|
43
|
+
cited_doi: extractDoi(c.cited ?? ""),
|
|
44
|
+
creation_date: c.creation,
|
|
45
|
+
})),
|
|
46
|
+
_source_health: { source: "opencitations", latency_ms: tracked.latency_ms },
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "get_references_open",
|
|
52
|
+
label: "Get References (OpenCitations)",
|
|
53
|
+
description:
|
|
54
|
+
"Get all references of a paper by DOI. Shows what a paper cites.",
|
|
55
|
+
parameters: Type.Object({
|
|
56
|
+
doi: Type.String({
|
|
57
|
+
description: "DOI of the paper, e.g. '10.1038/nature12373'",
|
|
58
|
+
}),
|
|
59
|
+
}),
|
|
60
|
+
execute: async (input: { doi: string }) => {
|
|
61
|
+
const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
|
|
62
|
+
const tracked = await trackedFetch("opencitations", `${BASE}/index/v2/references/doi:${encodeURIComponent(doi)}`);
|
|
63
|
+
if (isTrackedError(tracked)) return tracked;
|
|
64
|
+
const data = await tracked.res.json();
|
|
65
|
+
|
|
66
|
+
if (!Array.isArray(data)) return toolResult({ error: "Unexpected response format" });
|
|
67
|
+
|
|
68
|
+
return toolResult({
|
|
69
|
+
total_references: data.length,
|
|
70
|
+
references: data.slice(0, 100).map((r: Record<string, string>) => ({
|
|
71
|
+
cited_doi: extractDoi(r.cited ?? ""),
|
|
72
|
+
citing_doi: extractDoi(r.citing ?? ""),
|
|
73
|
+
creation_date: r.creation,
|
|
74
|
+
})),
|
|
75
|
+
_source_health: { source: "opencitations", latency_ms: tracked.latency_ms },
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "get_citation_count",
|
|
81
|
+
label: "Get Citation Count (OpenCitations)",
|
|
82
|
+
description:
|
|
83
|
+
"Get the total citation count for a DOI from OpenCitations open data.",
|
|
84
|
+
parameters: Type.Object({
|
|
85
|
+
doi: Type.String({
|
|
86
|
+
description: "DOI of the paper, e.g. '10.1038/nature12373'",
|
|
87
|
+
}),
|
|
88
|
+
}),
|
|
89
|
+
execute: async (input: { doi: string }) => {
|
|
90
|
+
const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
|
|
91
|
+
const tracked = await trackedFetch("opencitations", `${BASE}/index/v2/citation-count/doi:${encodeURIComponent(doi)}`);
|
|
92
|
+
if (isTrackedError(tracked)) return tracked;
|
|
93
|
+
const data = await tracked.res.json();
|
|
94
|
+
|
|
95
|
+
const count = Array.isArray(data) && data[0]?.count
|
|
96
|
+
? parseInt(data[0].count, 10)
|
|
97
|
+
: 0;
|
|
98
|
+
|
|
99
|
+
return toolResult({ doi, citation_count: count, _source_health: { source: "opencitations", latency_ms: tracked.latency_ms } });
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
const orcid = input.orcid.replace(/^https?:\/\/orcid\.org\//, "");
|
|
76
|
+
|
|
77
|
+
const result = await trackedFetch(
|
|
78
|
+
"orcid",
|
|
79
|
+
`${BASE}/${encodeURIComponent(orcid)}/works`,
|
|
80
|
+
{ headers: HEADERS },
|
|
81
|
+
10_000,
|
|
82
|
+
);
|
|
83
|
+
if (isTrackedError(result)) return result;
|
|
84
|
+
const data = await result.res.json();
|
|
85
|
+
|
|
86
|
+
const groups = data.group as Array<Record<string, unknown>> | undefined;
|
|
87
|
+
|
|
88
|
+
const works = (groups ?? []).map((g) => {
|
|
89
|
+
const summaries = g["work-summary"] as Array<Record<string, unknown>> | undefined;
|
|
90
|
+
const first = summaries?.[0];
|
|
91
|
+
if (!first) return null;
|
|
92
|
+
|
|
93
|
+
// Extract title
|
|
94
|
+
const titleObj = first.title as Record<string, unknown> | undefined;
|
|
95
|
+
const title = (titleObj?.title as Record<string, string> | undefined)?.value;
|
|
96
|
+
|
|
97
|
+
// Extract publication date
|
|
98
|
+
const pubDate = first["publication-date"] as Record<string, unknown> | undefined;
|
|
99
|
+
const year = (pubDate?.year as Record<string, string> | undefined)?.value;
|
|
100
|
+
const month = (pubDate?.month as Record<string, string> | undefined)?.value;
|
|
101
|
+
|
|
102
|
+
// Extract DOI from external-ids
|
|
103
|
+
const extIds = first["external-ids"] as Record<string, unknown> | undefined;
|
|
104
|
+
const extIdList = extIds?.["external-id"] as Array<Record<string, unknown>> | undefined;
|
|
105
|
+
const doiEntry = extIdList?.find(
|
|
106
|
+
(e) => e["external-id-type"] === "doi" && e["external-id-relationship"] === "self",
|
|
107
|
+
);
|
|
108
|
+
const doi = doiEntry?.["external-id-value"] as string | undefined;
|
|
109
|
+
|
|
110
|
+
// Extract journal title
|
|
111
|
+
const journalTitle = first["journal-title"] as Record<string, string> | undefined;
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
title,
|
|
115
|
+
doi,
|
|
116
|
+
year,
|
|
117
|
+
month,
|
|
118
|
+
type: first.type,
|
|
119
|
+
journal: journalTitle?.value,
|
|
120
|
+
url: doi ? `https://doi.org/${doi}` : undefined,
|
|
121
|
+
authors: undefined as string[] | undefined, // ORCID works endpoint does not include author lists
|
|
122
|
+
source: "orcid",
|
|
123
|
+
};
|
|
124
|
+
}).filter(Boolean);
|
|
125
|
+
|
|
126
|
+
return toolResult({
|
|
127
|
+
orcid,
|
|
128
|
+
total_works: works.length,
|
|
129
|
+
source: "orcid",
|
|
130
|
+
works,
|
|
131
|
+
_latency_ms: result.latency_ms,
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
}
|
|
@@ -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
|
+
}
|
package/src/tools/pubmed.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 EUTILS = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils";
|
|
6
6
|
|
|
@@ -53,23 +53,27 @@ export function createPubMedTools(
|
|
|
53
53
|
if (input.max_date) searchParams.set("maxdate", input.max_date);
|
|
54
54
|
if (input.min_date || input.max_date) searchParams.set("datetype", "pdat");
|
|
55
55
|
|
|
56
|
-
const
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
const searchData = await searchRes.json();
|
|
56
|
+
const searchTracked = await trackedFetch("pubmed", `${EUTILS}/esearch.fcgi?${searchParams}`);
|
|
57
|
+
if (isTrackedError(searchTracked)) return searchTracked;
|
|
58
|
+
const searchData = await searchTracked.res.json();
|
|
60
59
|
const ids: string[] = searchData.esearchresult?.idlist ?? [];
|
|
61
60
|
|
|
62
|
-
if (ids.length === 0)
|
|
61
|
+
if (ids.length === 0) {
|
|
62
|
+
return toolResult({
|
|
63
|
+
total_count: searchData.esearchresult?.count ?? 0,
|
|
64
|
+
articles: [],
|
|
65
|
+
_source_health: { source: "pubmed", latency_ms: searchTracked.latency_ms },
|
|
66
|
+
});
|
|
67
|
+
}
|
|
63
68
|
|
|
64
69
|
const summaryParams = new URLSearchParams({
|
|
65
70
|
db: "pubmed",
|
|
66
71
|
id: ids.join(","),
|
|
67
72
|
retmode: "json",
|
|
68
73
|
});
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
const summaryData = await summaryRes.json();
|
|
74
|
+
const summaryTracked = await trackedFetch("pubmed", `${EUTILS}/esummary.fcgi?${summaryParams}`);
|
|
75
|
+
if (isTrackedError(summaryTracked)) return summaryTracked;
|
|
76
|
+
const summaryData = await summaryTracked.res.json();
|
|
73
77
|
|
|
74
78
|
const articles = ids.map((id) => {
|
|
75
79
|
const doc = summaryData.result?.[id];
|
|
@@ -92,6 +96,7 @@ export function createPubMedTools(
|
|
|
92
96
|
return toolResult({
|
|
93
97
|
total_count: parseInt(searchData.esearchresult?.count ?? "0", 10),
|
|
94
98
|
articles,
|
|
99
|
+
_source_health: { source: "pubmed", latency_ms: summaryTracked.latency_ms },
|
|
95
100
|
});
|
|
96
101
|
},
|
|
97
102
|
},
|
|
@@ -109,9 +114,9 @@ export function createPubMedTools(
|
|
|
109
114
|
id: input.pmid,
|
|
110
115
|
retmode: "xml",
|
|
111
116
|
});
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
114
|
-
const xml = await res.text();
|
|
117
|
+
const tracked = await trackedFetch("pubmed", `${EUTILS}/efetch.fcgi?${params}`);
|
|
118
|
+
if (isTrackedError(tracked)) return tracked;
|
|
119
|
+
const xml = await tracked.res.text();
|
|
115
120
|
|
|
116
121
|
const getText = (tag: string) => {
|
|
117
122
|
const m = xml.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
|
|
@@ -162,6 +167,7 @@ export function createPubMedTools(
|
|
|
162
167
|
pmc,
|
|
163
168
|
mesh_terms: getMesh(),
|
|
164
169
|
url: `https://pubmed.ncbi.nlm.nih.gov/${input.pmid}/`,
|
|
170
|
+
_source_health: { source: "pubmed", latency_ms: tracked.latency_ms },
|
|
165
171
|
});
|
|
166
172
|
},
|
|
167
173
|
},
|