@wentorai/research-plugins 1.2.0 → 1.2.1
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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/tools/arxiv.ts +17 -10
- package/src/tools/crossref.ts +17 -10
- package/src/tools/openalex.ts +25 -17
- package/src/tools/pubmed.ts +19 -12
- package/src/tools/semantic-scholar.ts +20 -12
- package/src/tools/unpaywall.ts +12 -6
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/tools/arxiv.ts
CHANGED
|
@@ -3,6 +3,11 @@ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plug
|
|
|
3
3
|
|
|
4
4
|
const BASE = "https://export.arxiv.org/api/query";
|
|
5
5
|
|
|
6
|
+
function toolResult(data: unknown) {
|
|
7
|
+
const text = JSON.stringify(data, null, 2);
|
|
8
|
+
return { content: [{ type: "text" as const, text }] };
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
function parseArxivXml(xml: string) {
|
|
7
12
|
const entries: Record<string, unknown>[] = [];
|
|
8
13
|
const entryBlocks = xml.split("<entry>").slice(1);
|
|
@@ -59,9 +64,10 @@ export function createArxivTools(
|
|
|
59
64
|
return [
|
|
60
65
|
{
|
|
61
66
|
name: "search_arxiv",
|
|
67
|
+
label: "Search Papers (arXiv)",
|
|
62
68
|
description:
|
|
63
69
|
"Search arXiv preprint repository. Covers physics, math, CS, biology, quantitative finance, statistics, and more.",
|
|
64
|
-
|
|
70
|
+
parameters: Type.Object({
|
|
65
71
|
query: Type.String({
|
|
66
72
|
description:
|
|
67
73
|
"Search query. Supports field prefixes: ti: (title), au: (author), abs: (abstract), cat: (category). E.g. 'ti:transformer AND cat:cs.CL'",
|
|
@@ -79,7 +85,7 @@ export function createArxivTools(
|
|
|
79
85
|
Type.String({ description: "Sort order: 'ascending' or 'descending'" }),
|
|
80
86
|
),
|
|
81
87
|
}),
|
|
82
|
-
|
|
88
|
+
execute: async (input: {
|
|
83
89
|
query: string;
|
|
84
90
|
max_results?: number;
|
|
85
91
|
sort_by?: string;
|
|
@@ -93,7 +99,7 @@ export function createArxivTools(
|
|
|
93
99
|
});
|
|
94
100
|
|
|
95
101
|
const res = await fetch(`${BASE}?${params}`);
|
|
96
|
-
if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
|
|
102
|
+
if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
|
|
97
103
|
const xml = await res.text();
|
|
98
104
|
|
|
99
105
|
const totalMatch = xml.match(
|
|
@@ -101,30 +107,31 @@ export function createArxivTools(
|
|
|
101
107
|
);
|
|
102
108
|
const total = totalMatch ? parseInt(totalMatch[1], 10) : 0;
|
|
103
109
|
|
|
104
|
-
return {
|
|
110
|
+
return toolResult({
|
|
105
111
|
total_results: total,
|
|
106
112
|
papers: parseArxivXml(xml),
|
|
107
|
-
};
|
|
113
|
+
});
|
|
108
114
|
},
|
|
109
115
|
},
|
|
110
116
|
{
|
|
111
117
|
name: "get_arxiv_paper",
|
|
118
|
+
label: "Get Paper Details (arXiv)",
|
|
112
119
|
description:
|
|
113
120
|
"Get detailed information about a specific arXiv paper by its ID.",
|
|
114
|
-
|
|
121
|
+
parameters: Type.Object({
|
|
115
122
|
arxiv_id: Type.String({
|
|
116
123
|
description: "arXiv paper ID, e.g. '2301.00001' or '2301.00001v2'",
|
|
117
124
|
}),
|
|
118
125
|
}),
|
|
119
|
-
|
|
126
|
+
execute: async (input: { arxiv_id: string }) => {
|
|
120
127
|
const id = input.arxiv_id.replace("arXiv:", "");
|
|
121
128
|
const params = new URLSearchParams({ id_list: id });
|
|
122
129
|
const res = await fetch(`${BASE}?${params}`);
|
|
123
|
-
if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
|
|
130
|
+
if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
|
|
124
131
|
const xml = await res.text();
|
|
125
132
|
const papers = parseArxivXml(xml);
|
|
126
|
-
if (papers.length === 0) return { error: "Paper not found" };
|
|
127
|
-
return papers[0];
|
|
133
|
+
if (papers.length === 0) return toolResult({ error: "Paper not found" });
|
|
134
|
+
return toolResult(papers[0]);
|
|
128
135
|
},
|
|
129
136
|
},
|
|
130
137
|
];
|
package/src/tools/crossref.ts
CHANGED
|
@@ -3,6 +3,11 @@ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plug
|
|
|
3
3
|
|
|
4
4
|
const BASE = "https://api.crossref.org";
|
|
5
5
|
|
|
6
|
+
function toolResult(data: unknown) {
|
|
7
|
+
const text = JSON.stringify(data, null, 2);
|
|
8
|
+
return { content: [{ type: "text" as const, text }] };
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
export function createCrossRefTools(
|
|
7
12
|
_ctx: OpenClawPluginToolContext,
|
|
8
13
|
_api: OpenClawPluginApi,
|
|
@@ -14,22 +19,23 @@ export function createCrossRefTools(
|
|
|
14
19
|
return [
|
|
15
20
|
{
|
|
16
21
|
name: "resolve_doi",
|
|
22
|
+
label: "Resolve DOI (CrossRef)",
|
|
17
23
|
description:
|
|
18
24
|
"Resolve a DOI to get full bibliographic metadata from CrossRef (title, authors, journal, dates, references).",
|
|
19
|
-
|
|
25
|
+
parameters: Type.Object({
|
|
20
26
|
doi: Type.String({
|
|
21
27
|
description: "DOI to resolve, e.g. '10.1038/nature12373'",
|
|
22
28
|
}),
|
|
23
29
|
}),
|
|
24
|
-
|
|
30
|
+
execute: async (input: { doi: string }) => {
|
|
25
31
|
const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
|
|
26
32
|
const res = await fetch(`${BASE}/works/${encodeURIComponent(doi)}`, {
|
|
27
33
|
headers,
|
|
28
34
|
});
|
|
29
|
-
if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
|
|
35
|
+
if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
|
|
30
36
|
const data = await res.json();
|
|
31
37
|
const w = data.message;
|
|
32
|
-
return {
|
|
38
|
+
return toolResult({
|
|
33
39
|
doi: w.DOI,
|
|
34
40
|
title: w.title?.[0],
|
|
35
41
|
authors: w.author?.map(
|
|
@@ -44,14 +50,15 @@ export function createCrossRefTools(
|
|
|
44
50
|
url: w.URL,
|
|
45
51
|
abstract: w.abstract,
|
|
46
52
|
license: w.license?.[0]?.URL,
|
|
47
|
-
};
|
|
53
|
+
});
|
|
48
54
|
},
|
|
49
55
|
},
|
|
50
56
|
{
|
|
51
57
|
name: "search_crossref",
|
|
58
|
+
label: "Search Works (CrossRef)",
|
|
52
59
|
description:
|
|
53
60
|
"Search CrossRef for scholarly works by query. Covers 150M+ DOIs across all publishers.",
|
|
54
|
-
|
|
61
|
+
parameters: Type.Object({
|
|
55
62
|
query: Type.String({ description: "Search query" }),
|
|
56
63
|
limit: Type.Optional(
|
|
57
64
|
Type.Number({ description: "Max results (default 10, max 100)" }),
|
|
@@ -71,7 +78,7 @@ export function createCrossRefTools(
|
|
|
71
78
|
}),
|
|
72
79
|
),
|
|
73
80
|
}),
|
|
74
|
-
|
|
81
|
+
execute: async (input: {
|
|
75
82
|
query: string;
|
|
76
83
|
limit?: number;
|
|
77
84
|
from_year?: number;
|
|
@@ -89,9 +96,9 @@ export function createCrossRefTools(
|
|
|
89
96
|
if (input.sort) params.set("sort", input.sort);
|
|
90
97
|
|
|
91
98
|
const res = await fetch(`${BASE}/works?${params}`, { headers });
|
|
92
|
-
if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
|
|
99
|
+
if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
|
|
93
100
|
const data = await res.json();
|
|
94
|
-
return {
|
|
101
|
+
return toolResult({
|
|
95
102
|
total_results: data.message?.["total-results"],
|
|
96
103
|
items: data.message?.items?.map((w: Record<string, unknown>) => ({
|
|
97
104
|
doi: w.DOI,
|
|
@@ -105,7 +112,7 @@ export function createCrossRefTools(
|
|
|
105
112
|
type: w.type,
|
|
106
113
|
cited_by: w["is-referenced-by-count"],
|
|
107
114
|
})),
|
|
108
|
-
};
|
|
115
|
+
});
|
|
109
116
|
},
|
|
110
117
|
},
|
|
111
118
|
];
|
package/src/tools/openalex.ts
CHANGED
|
@@ -3,6 +3,11 @@ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plug
|
|
|
3
3
|
|
|
4
4
|
const BASE = "https://api.openalex.org";
|
|
5
5
|
|
|
6
|
+
function toolResult(data: unknown) {
|
|
7
|
+
const text = JSON.stringify(data, null, 2);
|
|
8
|
+
return { content: [{ type: "text" as const, text }] };
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
export function createOpenAlexTools(
|
|
7
12
|
_ctx: OpenClawPluginToolContext,
|
|
8
13
|
_api: OpenClawPluginApi,
|
|
@@ -14,9 +19,10 @@ export function createOpenAlexTools(
|
|
|
14
19
|
return [
|
|
15
20
|
{
|
|
16
21
|
name: "search_openalex",
|
|
22
|
+
label: "Search Works (OpenAlex)",
|
|
17
23
|
description:
|
|
18
24
|
"Search academic works via OpenAlex (free, no key required). Covers 250M+ works across all disciplines.",
|
|
19
|
-
|
|
25
|
+
parameters: Type.Object({
|
|
20
26
|
query: Type.String({ description: "Search query for works" }),
|
|
21
27
|
limit: Type.Optional(
|
|
22
28
|
Type.Number({ description: "Max results (default 10, max 200)" }),
|
|
@@ -36,7 +42,7 @@ export function createOpenAlexTools(
|
|
|
36
42
|
}),
|
|
37
43
|
),
|
|
38
44
|
}),
|
|
39
|
-
|
|
45
|
+
execute: async (input: {
|
|
40
46
|
query: string;
|
|
41
47
|
limit?: number;
|
|
42
48
|
from_year?: number;
|
|
@@ -57,9 +63,9 @@ export function createOpenAlexTools(
|
|
|
57
63
|
if (input.sort_by) params.set("sort", input.sort_by);
|
|
58
64
|
|
|
59
65
|
const res = await fetch(`${BASE}/works?${params}`, { headers });
|
|
60
|
-
if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
|
|
66
|
+
if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
|
|
61
67
|
const data = await res.json();
|
|
62
|
-
return {
|
|
68
|
+
return toolResult({
|
|
63
69
|
total_count: data.meta?.count,
|
|
64
70
|
results: data.results?.map((w: Record<string, unknown>) => ({
|
|
65
71
|
id: w.id,
|
|
@@ -76,29 +82,30 @@ export function createOpenAlexTools(
|
|
|
76
82
|
)
|
|
77
83
|
: [],
|
|
78
84
|
})),
|
|
79
|
-
};
|
|
85
|
+
});
|
|
80
86
|
},
|
|
81
87
|
},
|
|
82
88
|
{
|
|
83
89
|
name: "get_work",
|
|
90
|
+
label: "Get Work Details (OpenAlex)",
|
|
84
91
|
description:
|
|
85
92
|
"Get full details of a work by its OpenAlex ID, DOI, or other identifier.",
|
|
86
|
-
|
|
93
|
+
parameters: Type.Object({
|
|
87
94
|
work_id: Type.String({
|
|
88
95
|
description:
|
|
89
96
|
"Work identifier: OpenAlex ID (e.g. 'W2741809807'), DOI URL, or PMID",
|
|
90
97
|
}),
|
|
91
98
|
}),
|
|
92
|
-
|
|
99
|
+
execute: async (input: { work_id: string }) => {
|
|
93
100
|
const id = input.work_id.startsWith("10.")
|
|
94
101
|
? `https://doi.org/${input.work_id}`
|
|
95
102
|
: input.work_id;
|
|
96
103
|
const res = await fetch(`${BASE}/works/${encodeURIComponent(id)}`, {
|
|
97
104
|
headers,
|
|
98
105
|
});
|
|
99
|
-
if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
|
|
106
|
+
if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
|
|
100
107
|
const w = await res.json();
|
|
101
|
-
return {
|
|
108
|
+
return toolResult({
|
|
102
109
|
id: w.id,
|
|
103
110
|
doi: w.doi,
|
|
104
111
|
title: w.title,
|
|
@@ -117,20 +124,21 @@ export function createOpenAlexTools(
|
|
|
117
124
|
?.slice(0, 10)
|
|
118
125
|
.map((c: Record<string, unknown>) => c.display_name),
|
|
119
126
|
referenced_works_count: w.referenced_works?.length,
|
|
120
|
-
};
|
|
127
|
+
});
|
|
121
128
|
},
|
|
122
129
|
},
|
|
123
130
|
{
|
|
124
131
|
name: "get_author_openalex",
|
|
132
|
+
label: "Get Author (OpenAlex)",
|
|
125
133
|
description:
|
|
126
134
|
"Get author information from OpenAlex including publications, h-index, and affiliations.",
|
|
127
|
-
|
|
135
|
+
parameters: Type.Object({
|
|
128
136
|
author_id: Type.String({
|
|
129
137
|
description:
|
|
130
138
|
"Author identifier: OpenAlex ID (e.g. 'A5023888391'), ORCID, or name search",
|
|
131
139
|
}),
|
|
132
140
|
}),
|
|
133
|
-
|
|
141
|
+
execute: async (input: { author_id: string }) => {
|
|
134
142
|
let url: string;
|
|
135
143
|
if (
|
|
136
144
|
input.author_id.startsWith("A") ||
|
|
@@ -145,14 +153,14 @@ export function createOpenAlexTools(
|
|
|
145
153
|
per_page: "5",
|
|
146
154
|
});
|
|
147
155
|
const res = await fetch(`${BASE}/authors?${params}`, { headers });
|
|
148
|
-
if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
|
|
149
|
-
return res.json();
|
|
156
|
+
if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
|
|
157
|
+
return toolResult(await res.json());
|
|
150
158
|
}
|
|
151
159
|
|
|
152
160
|
const res = await fetch(url, { headers });
|
|
153
|
-
if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
|
|
161
|
+
if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
|
|
154
162
|
const a = await res.json();
|
|
155
|
-
return {
|
|
163
|
+
return toolResult({
|
|
156
164
|
id: a.id,
|
|
157
165
|
display_name: a.display_name,
|
|
158
166
|
orcid: a.orcid,
|
|
@@ -167,7 +175,7 @@ export function createOpenAlexTools(
|
|
|
167
175
|
top_concepts: a.x_concepts
|
|
168
176
|
?.slice(0, 5)
|
|
169
177
|
.map((c: Record<string, unknown>) => c.display_name),
|
|
170
|
-
};
|
|
178
|
+
});
|
|
171
179
|
},
|
|
172
180
|
},
|
|
173
181
|
];
|
package/src/tools/pubmed.ts
CHANGED
|
@@ -3,6 +3,11 @@ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plug
|
|
|
3
3
|
|
|
4
4
|
const EUTILS = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils";
|
|
5
5
|
|
|
6
|
+
function toolResult(data: unknown) {
|
|
7
|
+
const text = JSON.stringify(data, null, 2);
|
|
8
|
+
return { content: [{ type: "text" as const, text }] };
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
export function createPubMedTools(
|
|
7
12
|
_ctx: OpenClawPluginToolContext,
|
|
8
13
|
_api: OpenClawPluginApi,
|
|
@@ -10,9 +15,10 @@ export function createPubMedTools(
|
|
|
10
15
|
return [
|
|
11
16
|
{
|
|
12
17
|
name: "search_pubmed",
|
|
18
|
+
label: "Search Articles (PubMed)",
|
|
13
19
|
description:
|
|
14
20
|
"Search PubMed biomedical literature database. Covers 36M+ citations from MEDLINE, life science journals, and online books.",
|
|
15
|
-
|
|
21
|
+
parameters: Type.Object({
|
|
16
22
|
query: Type.String({
|
|
17
23
|
description:
|
|
18
24
|
"PubMed search query. Supports MeSH terms and field tags, e.g. 'CRISPR[Title] AND 2024[PDAT]'",
|
|
@@ -32,7 +38,7 @@ export function createPubMedTools(
|
|
|
32
38
|
Type.String({ description: "Max publication date (YYYY/MM/DD)" }),
|
|
33
39
|
),
|
|
34
40
|
}),
|
|
35
|
-
|
|
41
|
+
execute: async (input: {
|
|
36
42
|
query: string;
|
|
37
43
|
max_results?: number;
|
|
38
44
|
sort?: string;
|
|
@@ -53,11 +59,11 @@ export function createPubMedTools(
|
|
|
53
59
|
|
|
54
60
|
const searchRes = await fetch(`${EUTILS}/esearch.fcgi?${searchParams}`);
|
|
55
61
|
if (!searchRes.ok)
|
|
56
|
-
return { error: `Search error: ${searchRes.status} ${searchRes.statusText}` };
|
|
62
|
+
return toolResult({ error: `Search error: ${searchRes.status} ${searchRes.statusText}` });
|
|
57
63
|
const searchData = await searchRes.json();
|
|
58
64
|
const ids: string[] = searchData.esearchresult?.idlist ?? [];
|
|
59
65
|
|
|
60
|
-
if (ids.length === 0) return { total_count: searchData.esearchresult?.count ?? 0, articles: [] };
|
|
66
|
+
if (ids.length === 0) return toolResult({ total_count: searchData.esearchresult?.count ?? 0, articles: [] });
|
|
61
67
|
|
|
62
68
|
const summaryParams = new URLSearchParams({
|
|
63
69
|
db: "pubmed",
|
|
@@ -66,7 +72,7 @@ export function createPubMedTools(
|
|
|
66
72
|
});
|
|
67
73
|
const summaryRes = await fetch(`${EUTILS}/esummary.fcgi?${summaryParams}`);
|
|
68
74
|
if (!summaryRes.ok)
|
|
69
|
-
return { error: `Summary error: ${summaryRes.status} ${summaryRes.statusText}` };
|
|
75
|
+
return toolResult({ error: `Summary error: ${summaryRes.status} ${summaryRes.statusText}` });
|
|
70
76
|
const summaryData = await summaryRes.json();
|
|
71
77
|
|
|
72
78
|
const articles = ids.map((id) => {
|
|
@@ -87,27 +93,28 @@ export function createPubMedTools(
|
|
|
87
93
|
};
|
|
88
94
|
});
|
|
89
95
|
|
|
90
|
-
return {
|
|
96
|
+
return toolResult({
|
|
91
97
|
total_count: parseInt(searchData.esearchresult?.count ?? "0", 10),
|
|
92
98
|
articles,
|
|
93
|
-
};
|
|
99
|
+
});
|
|
94
100
|
},
|
|
95
101
|
},
|
|
96
102
|
{
|
|
97
103
|
name: "get_article",
|
|
104
|
+
label: "Get Article Details (PubMed)",
|
|
98
105
|
description:
|
|
99
106
|
"Get detailed article metadata from PubMed by PMID, including abstract.",
|
|
100
|
-
|
|
107
|
+
parameters: Type.Object({
|
|
101
108
|
pmid: Type.String({ description: "PubMed ID (numeric string)" }),
|
|
102
109
|
}),
|
|
103
|
-
|
|
110
|
+
execute: async (input: { pmid: string }) => {
|
|
104
111
|
const params = new URLSearchParams({
|
|
105
112
|
db: "pubmed",
|
|
106
113
|
id: input.pmid,
|
|
107
114
|
retmode: "xml",
|
|
108
115
|
});
|
|
109
116
|
const res = await fetch(`${EUTILS}/efetch.fcgi?${params}`);
|
|
110
|
-
if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
|
|
117
|
+
if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
|
|
111
118
|
const xml = await res.text();
|
|
112
119
|
|
|
113
120
|
const getText = (tag: string) => {
|
|
@@ -148,7 +155,7 @@ export function createPubMedTools(
|
|
|
148
155
|
xml.match(/<ArticleId IdType="pmc">([\s\S]*?)<\/ArticleId>/)?.[1]?.trim() ??
|
|
149
156
|
"";
|
|
150
157
|
|
|
151
|
-
return {
|
|
158
|
+
return toolResult({
|
|
152
159
|
pmid: input.pmid,
|
|
153
160
|
title: getText("ArticleTitle"),
|
|
154
161
|
abstract: getAbstract(),
|
|
@@ -159,7 +166,7 @@ export function createPubMedTools(
|
|
|
159
166
|
pmc,
|
|
160
167
|
mesh_terms: getMesh(),
|
|
161
168
|
url: `https://pubmed.ncbi.nlm.nih.gov/${input.pmid}/`,
|
|
162
|
-
};
|
|
169
|
+
});
|
|
163
170
|
},
|
|
164
171
|
},
|
|
165
172
|
];
|
|
@@ -3,6 +3,11 @@ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plug
|
|
|
3
3
|
|
|
4
4
|
const BASE = "https://api.semanticscholar.org/graph/v1";
|
|
5
5
|
|
|
6
|
+
function toolResult(data: unknown) {
|
|
7
|
+
const text = JSON.stringify(data, null, 2);
|
|
8
|
+
return { content: [{ type: "text" as const, text }] };
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
export function createSemanticScholarTools(
|
|
7
12
|
ctx: OpenClawPluginToolContext,
|
|
8
13
|
api: OpenClawPluginApi,
|
|
@@ -15,9 +20,10 @@ export function createSemanticScholarTools(
|
|
|
15
20
|
return [
|
|
16
21
|
{
|
|
17
22
|
name: "search_papers",
|
|
23
|
+
label: "Search Papers (Semantic Scholar)",
|
|
18
24
|
description:
|
|
19
25
|
"Search academic papers on Semantic Scholar. Returns titles, abstracts, authors, citation counts.",
|
|
20
|
-
|
|
26
|
+
parameters: Type.Object({
|
|
21
27
|
query: Type.String({ description: "Search query keywords" }),
|
|
22
28
|
limit: Type.Optional(
|
|
23
29
|
Type.Number({ description: "Max results to return (default 10, max 100)" }),
|
|
@@ -35,7 +41,7 @@ export function createSemanticScholarTools(
|
|
|
35
41
|
Type.Boolean({ description: "Only return open access papers" }),
|
|
36
42
|
),
|
|
37
43
|
}),
|
|
38
|
-
|
|
44
|
+
execute: async (input: {
|
|
39
45
|
query: string;
|
|
40
46
|
limit?: number;
|
|
41
47
|
year?: string;
|
|
@@ -54,36 +60,38 @@ export function createSemanticScholarTools(
|
|
|
54
60
|
if (input.open_access_only) params.set("openAccessPdf", "");
|
|
55
61
|
|
|
56
62
|
const res = await fetch(`${BASE}/paper/search?${params}`, { headers });
|
|
57
|
-
if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
|
|
58
|
-
return res.json();
|
|
63
|
+
if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
|
|
64
|
+
return toolResult(await res.json());
|
|
59
65
|
},
|
|
60
66
|
},
|
|
61
67
|
{
|
|
62
68
|
name: "get_paper",
|
|
69
|
+
label: "Get Paper Details (Semantic Scholar)",
|
|
63
70
|
description:
|
|
64
71
|
"Get detailed information about a specific paper by its Semantic Scholar ID, DOI, or ArXiv ID.",
|
|
65
|
-
|
|
72
|
+
parameters: Type.Object({
|
|
66
73
|
paper_id: Type.String({
|
|
67
74
|
description:
|
|
68
75
|
"Paper identifier: Semantic Scholar ID, DOI (e.g. '10.1234/...'), or ArXiv ID (e.g. 'arXiv:2301.00001')",
|
|
69
76
|
}),
|
|
70
77
|
}),
|
|
71
|
-
|
|
78
|
+
execute: async (input: { paper_id: string }) => {
|
|
72
79
|
const fields =
|
|
73
80
|
"title,abstract,authors,year,citationCount,referenceCount,tldr,url,venue,isOpenAccess,openAccessPdf,fieldsOfStudy,publicationDate";
|
|
74
81
|
const res = await fetch(
|
|
75
82
|
`${BASE}/paper/${encodeURIComponent(input.paper_id)}?fields=${fields}`,
|
|
76
83
|
{ headers },
|
|
77
84
|
);
|
|
78
|
-
if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
|
|
79
|
-
return res.json();
|
|
85
|
+
if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
|
|
86
|
+
return toolResult(await res.json());
|
|
80
87
|
},
|
|
81
88
|
},
|
|
82
89
|
{
|
|
83
90
|
name: "get_citations",
|
|
91
|
+
label: "Get Citations (Semantic Scholar)",
|
|
84
92
|
description:
|
|
85
93
|
"Get papers that cite a given paper. Useful for forward citation tracking.",
|
|
86
|
-
|
|
94
|
+
parameters: Type.Object({
|
|
87
95
|
paper_id: Type.String({ description: "Paper identifier (S2 ID, DOI, or ArXiv ID)" }),
|
|
88
96
|
limit: Type.Optional(
|
|
89
97
|
Type.Number({ description: "Max citations to return (default 20, max 100)" }),
|
|
@@ -92,7 +100,7 @@ export function createSemanticScholarTools(
|
|
|
92
100
|
Type.Number({ description: "Pagination offset" }),
|
|
93
101
|
),
|
|
94
102
|
}),
|
|
95
|
-
|
|
103
|
+
execute: async (input: { paper_id: string; limit?: number; offset?: number }) => {
|
|
96
104
|
const fields = "title,authors,year,citationCount,url,abstract";
|
|
97
105
|
const limit = Math.min(input.limit ?? 20, 100);
|
|
98
106
|
const offset = input.offset ?? 0;
|
|
@@ -100,8 +108,8 @@ export function createSemanticScholarTools(
|
|
|
100
108
|
`${BASE}/paper/${encodeURIComponent(input.paper_id)}/citations?fields=${fields}&limit=${limit}&offset=${offset}`,
|
|
101
109
|
{ headers },
|
|
102
110
|
);
|
|
103
|
-
if (!res.ok) return { error: `API error: ${res.status} ${res.statusText}` };
|
|
104
|
-
return res.json();
|
|
111
|
+
if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
|
|
112
|
+
return toolResult(await res.json());
|
|
105
113
|
},
|
|
106
114
|
},
|
|
107
115
|
];
|
package/src/tools/unpaywall.ts
CHANGED
|
@@ -3,6 +3,11 @@ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plug
|
|
|
3
3
|
|
|
4
4
|
const BASE = "https://api.unpaywall.org/v2";
|
|
5
5
|
|
|
6
|
+
function toolResult(data: unknown) {
|
|
7
|
+
const text = JSON.stringify(data, null, 2);
|
|
8
|
+
return { content: [{ type: "text" as const, text }] };
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
export function createUnpaywallTools(
|
|
7
12
|
_ctx: OpenClawPluginToolContext,
|
|
8
13
|
_api: OpenClawPluginApi,
|
|
@@ -12,21 +17,22 @@ export function createUnpaywallTools(
|
|
|
12
17
|
return [
|
|
13
18
|
{
|
|
14
19
|
name: "find_oa_version",
|
|
20
|
+
label: "Find Open Access Version (Unpaywall)",
|
|
15
21
|
description:
|
|
16
22
|
"Find open access versions of a paper by DOI using Unpaywall. Returns free PDF links from repositories, preprint servers, and publisher OA pages.",
|
|
17
|
-
|
|
23
|
+
parameters: Type.Object({
|
|
18
24
|
doi: Type.String({
|
|
19
25
|
description: "DOI of the paper, e.g. '10.1038/nature12373'",
|
|
20
26
|
}),
|
|
21
27
|
}),
|
|
22
|
-
|
|
28
|
+
execute: async (input: { doi: string }) => {
|
|
23
29
|
const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
|
|
24
30
|
const res = await fetch(
|
|
25
31
|
`${BASE}/${encodeURIComponent(doi)}?email=${email}`,
|
|
26
32
|
);
|
|
27
33
|
if (!res.ok) {
|
|
28
|
-
if (res.status === 404) return { error: "DOI not found in Unpaywall" };
|
|
29
|
-
return { error: `API error: ${res.status} ${res.statusText}` };
|
|
34
|
+
if (res.status === 404) return toolResult({ error: "DOI not found in Unpaywall" });
|
|
35
|
+
return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
|
|
30
36
|
}
|
|
31
37
|
const data = await res.json();
|
|
32
38
|
|
|
@@ -40,7 +46,7 @@ export function createUnpaywallTools(
|
|
|
40
46
|
version: loc.version,
|
|
41
47
|
}));
|
|
42
48
|
|
|
43
|
-
return {
|
|
49
|
+
return toolResult({
|
|
44
50
|
doi: data.doi,
|
|
45
51
|
title: data.title,
|
|
46
52
|
is_oa: data.is_oa,
|
|
@@ -51,7 +57,7 @@ export function createUnpaywallTools(
|
|
|
51
57
|
publisher: data.publisher,
|
|
52
58
|
published_date: data.published_date,
|
|
53
59
|
oa_locations: oaLocations,
|
|
54
|
-
};
|
|
60
|
+
});
|
|
55
61
|
},
|
|
56
62
|
},
|
|
57
63
|
];
|