@wentorai/research-plugins 1.4.2 → 1.4.4

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.
@@ -1,6 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
- import { toolResult, trackedFetch, isTrackedError } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError, validParam } from "./util.js";
4
4
 
5
5
  const BASE = "https://api.crossref.org";
6
6
 
@@ -23,7 +23,7 @@ export function createCrossRefTools(
23
23
  description: "DOI to resolve, e.g. '10.1038/nature12373'",
24
24
  }),
25
25
  }),
26
- execute: async (input: { doi: string }) => {
26
+ execute: async (_toolCallId: string, input: { doi: string }) => {
27
27
  if (!input?.doi) {
28
28
  return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
29
29
  }
@@ -94,7 +94,7 @@ export function createCrossRefTools(
94
94
  Type.Number({ description: "Max results (default 10, max 100)" }),
95
95
  ),
96
96
  }),
97
- execute: async (input: {
97
+ execute: async (_toolCallId: string, input: {
98
98
  query: string;
99
99
  journal?: string;
100
100
  issn?: string;
@@ -105,8 +105,17 @@ export function createCrossRefTools(
105
105
  sort?: string;
106
106
  limit?: number;
107
107
  }) => {
108
+ const query = validParam(input?.query);
109
+ if (!query) {
110
+ return toolResult({
111
+ error:
112
+ "query parameter is required and must not be empty. " +
113
+ "Example: search_crossref({ query: \"PFAS machine learning\", from_year: 2023 })",
114
+ });
115
+ }
116
+
108
117
  const params = new URLSearchParams({
109
- query: input.query,
118
+ query,
110
119
  rows: String(Math.min(input.limit ?? 10, 100)),
111
120
  });
112
121
 
@@ -114,16 +123,20 @@ export function createCrossRefTools(
114
123
  const filters: string[] = [];
115
124
  if (input.from_year) filters.push(`from-pub-date:${input.from_year}`);
116
125
  if (input.until_year) filters.push(`until-pub-date:${input.until_year}`);
117
- if (input.type) filters.push(`type:${input.type}`);
126
+ const type = validParam(input.type);
127
+ if (type) filters.push(`type:${type}`);
118
128
  if (input.has_abstract) filters.push("has-abstract:true");
119
- if (input.issn) filters.push(`issn:${input.issn}`);
129
+ const issn = validParam(input.issn);
130
+ if (issn) filters.push(`issn:${issn}`);
120
131
  if (filters.length > 0) params.set("filter", filters.join(","));
121
132
 
122
133
  // Journal name as query.container-title (separate from filter)
123
- if (input.journal) params.set("query.container-title", input.journal);
134
+ const journal = validParam(input.journal);
135
+ if (journal) params.set("query.container-title", journal);
124
136
 
125
- if (input.sort) {
126
- params.set("sort", input.sort);
137
+ const sort = validParam(input.sort);
138
+ if (sort) {
139
+ params.set("sort", sort);
127
140
  params.set("order", "desc");
128
141
  }
129
142
 
@@ -1,6 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
- import { toolResult, trackedFetch, isTrackedError } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError, validParam } from "./util.js";
4
4
 
5
5
  const BASE = "https://api.datacite.org";
6
6
 
@@ -31,22 +31,31 @@ export function createDataCiteTools(
31
31
  Type.Number({ description: "Published from this year onward" }),
32
32
  ),
33
33
  }),
34
- execute: async (input: {
34
+ execute: async (_toolCallId: string, input: {
35
35
  query: string;
36
36
  max_results?: number;
37
37
  resource_type?: string;
38
38
  from_year?: number;
39
39
  }) => {
40
- const pageSize = Math.min(input.max_results ?? 10, 100);
40
+ const query = validParam(input?.query);
41
+ if (!query) {
42
+ return toolResult({
43
+ error:
44
+ "query parameter is required and must not be empty. " +
45
+ "Example: search_datacite({ query: \"climate dataset\" })",
46
+ });
47
+ }
48
+ const pageSize = Math.min(input?.max_results ?? 10, 100);
41
49
  const params = new URLSearchParams({
42
- query: input.query,
50
+ query,
43
51
  "page[size]": String(pageSize),
44
52
  });
45
- if (input.resource_type) {
46
- params.set("resource-type-id", input.resource_type.toLowerCase());
53
+ const resourceType = validParam(input.resource_type);
54
+ if (resourceType) {
55
+ params.set("resource-type-id", resourceType.toLowerCase());
47
56
  }
48
- if (input.from_year) {
49
- params.set("query", `${input.query} AND publicationYear:[${input.from_year} TO *]`);
57
+ if (input?.from_year) {
58
+ params.set("query", `${query} AND publicationYear:[${input.from_year} TO *]`);
50
59
  }
51
60
 
52
61
  const tracked = await trackedFetch("datacite", `${BASE}/dois?${params}`, undefined, 15_000);
@@ -117,7 +126,7 @@ export function createDataCiteTools(
117
126
  description: "DOI to resolve, e.g. '10.5281/zenodo.1234567'",
118
127
  }),
119
128
  }),
120
- execute: async (input: { doi: string }) => {
129
+ execute: async (_toolCallId: string, input: { doi: string }) => {
121
130
  if (!input?.doi) {
122
131
  return toolResult({ error: 'doi parameter is required (e.g., "10.5281/zenodo.1234567")' });
123
132
  }
package/src/tools/dblp.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, trackedFetch, isTrackedError } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError, validParam } from "./util.js";
4
4
 
5
5
  export function createDblpTools(
6
6
  _ctx: OpenClawPluginToolContext,
@@ -23,13 +23,20 @@ export function createDblpTools(
23
23
  Type.Number({ description: "Result offset for pagination" }),
24
24
  ),
25
25
  }),
26
- execute: async (input: {
26
+ execute: async (_toolCallId: string, input: {
27
27
  query: string;
28
28
  max_results?: number;
29
29
  offset?: number;
30
30
  }) => {
31
+ const query = validParam(input?.query);
32
+ if (!query) {
33
+ return toolResult({
34
+ error: "query parameter is required and must not be empty.",
35
+ });
36
+ }
37
+
31
38
  const params = new URLSearchParams({
32
- q: input.query,
39
+ q: query,
33
40
  format: "json",
34
41
  h: String(Math.min(input.max_results ?? 10, 1000)),
35
42
  });
@@ -90,9 +97,16 @@ export function createDblpTools(
90
97
  Type.Number({ description: "Max results (default 10)" }),
91
98
  ),
92
99
  }),
93
- execute: async (input: { query: string; max_results?: number }) => {
100
+ execute: async (_toolCallId: string, input: { query: string; max_results?: number }) => {
101
+ const query = validParam(input?.query);
102
+ if (!query) {
103
+ return toolResult({
104
+ error: "query parameter is required and must not be empty.",
105
+ });
106
+ }
107
+
94
108
  const params = new URLSearchParams({
95
- q: input.query,
109
+ q: query,
96
110
  format: "json",
97
111
  h: String(Math.min(input.max_results ?? 10, 100)),
98
112
  });
package/src/tools/doaj.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, trackedFetch, isTrackedError } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError, validParam } from "./util.js";
4
4
 
5
5
  const BASE = "https://doaj.org/api";
6
6
 
@@ -29,17 +29,26 @@ export function createDoajTools(
29
29
  Type.String({ description: "Sort field, e.g. 'created_date:desc'" }),
30
30
  ),
31
31
  }),
32
- execute: async (input: {
32
+ execute: async (_toolCallId: string, input: {
33
33
  query: string;
34
34
  max_results?: number;
35
35
  page?: number;
36
36
  sort?: string;
37
37
  }) => {
38
- const pageSize = Math.min(input.max_results ?? 10, 100);
39
- const page = input.page ?? 1;
38
+ const query = validParam(input?.query);
39
+ if (!query) {
40
+ return toolResult({
41
+ error:
42
+ "query parameter is required and must not be empty. " +
43
+ "Example: search_doaj({ query: \"open access genomics\" })",
44
+ });
45
+ }
46
+ const pageSize = Math.min(input?.max_results ?? 10, 100);
47
+ const page = input?.page ?? 1;
40
48
 
41
- let url = `${BASE}/search/articles/${encodeURIComponent(input.query)}?page=${page}&pageSize=${pageSize}`;
42
- if (input.sort) url += `&sort=${encodeURIComponent(input.sort)}`;
49
+ let url = `${BASE}/search/articles/${encodeURIComponent(query)}?page=${page}&pageSize=${pageSize}`;
50
+ const sort = validParam(input.sort);
51
+ if (sort) url += `&sort=${encodeURIComponent(sort)}`;
43
52
 
44
53
  const tracked = await trackedFetch("doaj", url);
45
54
  if (isTrackedError(tracked)) return tracked;
@@ -1,6 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
- import { toolResult, trackedFetch, isTrackedError } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError, validParam } from "./util.js";
4
4
 
5
5
  const BASE = "https://www.ebi.ac.uk/europepmc/webservices/rest";
6
6
 
@@ -31,20 +31,29 @@ export function createEuropePmcTools(
31
31
  Type.String({ description: "Pagination cursor (use value from previous response)" }),
32
32
  ),
33
33
  }),
34
- execute: async (input: {
34
+ execute: async (_toolCallId: string, input: {
35
35
  query: string;
36
36
  max_results?: number;
37
37
  sort?: string;
38
38
  cursor?: string;
39
39
  }) => {
40
+ const query = validParam(input?.query);
41
+ if (!query) {
42
+ return toolResult({
43
+ error:
44
+ "query parameter is required and must not be empty. " +
45
+ "Example: search_europe_pmc({ query: \"CRISPR gene editing\" })",
46
+ });
47
+ }
40
48
  const params = new URLSearchParams({
41
- query: input.query,
49
+ query,
42
50
  format: "json",
43
51
  pageSize: String(Math.min(input.max_results ?? 10, 1000)),
44
52
  resultType: "core",
45
53
  });
46
- if (input.sort) params.set("sort", input.sort);
47
- params.set("cursorMark", input.cursor ?? "*");
54
+ const sort = validParam(input.sort);
55
+ if (sort) params.set("sort", sort);
56
+ params.set("cursorMark", validParam(input.cursor) ?? "*");
48
57
 
49
58
  const tracked = await trackedFetch("europe_pmc", `${BASE}/search?${params}`);
50
59
  if (isTrackedError(tracked)) return tracked;
@@ -91,11 +100,14 @@ export function createEuropePmcTools(
91
100
  ),
92
101
  page: Type.Optional(Type.Number({ description: "Page number (default 1)" })),
93
102
  }),
94
- execute: async (input: { pmid: string; max_results?: number; page?: number }) => {
103
+ execute: async (_toolCallId: string, input: { pmid: string; max_results?: number; page?: number }) => {
104
+ if (!input?.pmid) {
105
+ return toolResult({ error: 'pmid parameter is required (PubMed ID, e.g., "33116299")' });
106
+ }
95
107
  const params = new URLSearchParams({
96
108
  format: "json",
97
- pageSize: String(input.max_results ?? 25),
98
- page: String(input.page ?? 1),
109
+ pageSize: String(input?.max_results ?? 25),
110
+ page: String(input?.page ?? 1),
99
111
  });
100
112
  const tracked = await trackedFetch("europe_pmc", `${BASE}/MED/${input.pmid}/citations?${params}`);
101
113
  if (isTrackedError(tracked)) return tracked;
@@ -129,10 +141,13 @@ export function createEuropePmcTools(
129
141
  Type.Number({ description: "Max references to return (default 25)" }),
130
142
  ),
131
143
  }),
132
- execute: async (input: { pmid: string; max_results?: number }) => {
144
+ execute: async (_toolCallId: string, input: { pmid: string; max_results?: number }) => {
145
+ if (!input?.pmid) {
146
+ return toolResult({ error: 'pmid parameter is required (PubMed ID, e.g., "33116299")' });
147
+ }
133
148
  const params = new URLSearchParams({
134
149
  format: "json",
135
- pageSize: String(input.max_results ?? 25),
150
+ pageSize: String(input?.max_results ?? 25),
136
151
  });
137
152
  const tracked = await trackedFetch("europe_pmc", `${BASE}/MED/${input.pmid}/references?${params}`);
138
153
  if (isTrackedError(tracked)) return tracked;
package/src/tools/hal.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, trackedFetch, isTrackedError } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError, validParam, validEnum } from "./util.js";
4
4
 
5
5
  const BASE = "https://api.archives-ouvertes.fr";
6
6
 
@@ -49,15 +49,24 @@ export function createHalTools(
49
49
  }),
50
50
  ),
51
51
  }),
52
- execute: async (input: {
52
+ execute: async (_toolCallId: string, input: {
53
53
  query: string;
54
54
  rows?: number;
55
55
  sort?: string;
56
56
  doc_type?: string;
57
57
  }) => {
58
- let q = input.query;
59
- if (input.doc_type) {
60
- q = `(${q}) AND docType_s:${input.doc_type}`;
58
+ const query = validParam(input?.query);
59
+ if (!query) {
60
+ return toolResult({
61
+ error:
62
+ "query parameter is required and must not be empty. " +
63
+ "Example: search_hal({ query: \"machine learning\" })",
64
+ });
65
+ }
66
+ let q = query;
67
+ const docType = validParam(input.doc_type);
68
+ if (docType) {
69
+ q = `(${q}) AND docType_s:${docType}`;
61
70
  }
62
71
 
63
72
  const params = new URLSearchParams({
@@ -66,7 +75,8 @@ export function createHalTools(
66
75
  rows: String(Math.min(input.rows ?? 10, 100)),
67
76
  wt: "json",
68
77
  });
69
- if (input.sort) params.set("sort", input.sort);
78
+ const sort = validParam(input.sort);
79
+ if (sort) params.set("sort", sort);
70
80
 
71
81
  const result = await trackedFetch(
72
82
  "hal",
@@ -1,6 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
- import { toolResult, trackedFetch, isTrackedError } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError, validEnum, validParam } from "./util.js";
4
4
 
5
5
  const BASE = "https://inspirehep.net/api";
6
6
 
@@ -98,11 +98,19 @@ export function createInspireHepTools(
98
98
  }),
99
99
  ),
100
100
  }),
101
- execute: async (input: { query: string; size?: number; sort?: string }) => {
101
+ execute: async (_toolCallId: string, input: { query: string; size?: number; sort?: string }) => {
102
+ const query = validParam(input?.query);
103
+ if (!query) {
104
+ return toolResult({
105
+ error: "query parameter is required and must not be empty.",
106
+ });
107
+ }
108
+
109
+ const sort = validEnum(input.sort, ["mostrecent", "mostcited", "bestmatch"] as const, "bestmatch");
102
110
  const params = new URLSearchParams({
103
- q: input.query,
111
+ q: query,
104
112
  size: String(Math.min(input.size ?? 10, 100)),
105
- sort: input.sort ?? "bestmatch",
113
+ sort,
106
114
  });
107
115
 
108
116
  const result = await trackedFetch(
@@ -136,7 +144,10 @@ export function createInspireHepTools(
136
144
  "Paper identifier: arXiv ID (e.g. '1207.7214') or DOI (e.g. '10.1016/j.physletb.2012.08.020')",
137
145
  }),
138
146
  }),
139
- execute: async (input: { identifier: string }) => {
147
+ execute: async (_toolCallId: string, input: { identifier: string }) => {
148
+ if (!input?.identifier) {
149
+ return toolResult({ error: 'identifier parameter is required (arXiv ID e.g. "1207.7214" or DOI e.g. "10.1016/j.physletb.2012.08.020")' });
150
+ }
140
151
  // Determine if it's a DOI or arXiv ID
141
152
  const id = input.identifier.trim();
142
153
  let url: string;
@@ -1,6 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
- import { toolResult, trackedFetch, isTrackedError } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError, validParam } from "./util.js";
4
4
 
5
5
  const BASE = "https://api.openaire.eu";
6
6
 
@@ -112,7 +112,7 @@ export function createOpenAireTools(
112
112
  Type.Number({ description: "Max results (default 10, max 50)" }),
113
113
  ),
114
114
  }),
115
- execute: async (input: {
115
+ execute: async (_toolCallId: string, input: {
116
116
  keywords: string;
117
117
  author?: string;
118
118
  doi?: string;
@@ -122,17 +122,30 @@ export function createOpenAireTools(
122
122
  funder?: string;
123
123
  max_results?: number;
124
124
  }) => {
125
+ const keywords = validParam(input?.keywords);
126
+ if (!keywords) {
127
+ return toolResult({
128
+ error:
129
+ "keywords parameter is required and must not be empty. " +
130
+ "Example: search_openaire({ keywords: \"machine learning\" })",
131
+ });
132
+ }
125
133
  const params = new URLSearchParams({
126
- keywords: input.keywords,
134
+ keywords,
127
135
  format: "json",
128
136
  size: String(Math.min(input.max_results ?? 10, 50)),
129
137
  });
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);
138
+ const author = validParam(input.author);
139
+ if (author) params.set("author", author);
140
+ const doi = validParam(input.doi);
141
+ if (doi) params.set("doi", doi);
142
+ const fromDate = validParam(input.from_date);
143
+ if (fromDate) params.set("fromDateAccepted", fromDate);
144
+ const toDate = validParam(input.to_date);
145
+ if (toDate) params.set("toDateAccepted", toDate);
134
146
  if (input.oa_only) params.set("OA", "true");
135
- if (input.funder) params.set("funder", input.funder);
147
+ const funder = validParam(input.funder);
148
+ if (funder) params.set("funder", funder);
136
149
 
137
150
  const tracked = await trackedFetch("openaire", `${BASE}/search/publications?${params}`, undefined, 15_000);
138
151
  if (isTrackedError(tracked)) return tracked;
@@ -1,6 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
- import { toolResult, trackedFetch, isTrackedError } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError, validParam, validEnum } from "./util.js";
4
4
 
5
5
  const BASE = "https://api.openalex.org";
6
6
 
@@ -38,7 +38,7 @@ export function createOpenAlexTools(
38
38
  }),
39
39
  ),
40
40
  }),
41
- execute: async (input: {
41
+ execute: async (_toolCallId: string, input: {
42
42
  query: string;
43
43
  limit?: number;
44
44
  from_year?: number;
@@ -46,17 +46,27 @@ export function createOpenAlexTools(
46
46
  open_access?: boolean;
47
47
  sort_by?: string;
48
48
  }) => {
49
+ const query = validParam(input?.query);
50
+ if (!query) {
51
+ return toolResult({ error: "query parameter is required and must not be empty" });
52
+ }
53
+
54
+ const SORT_BY = ["cited_by_count", "publication_date", "relevance_score"] as const;
55
+
49
56
  const filters: string[] = [];
50
57
  if (input.from_year) filters.push(`from_publication_date:${input.from_year}-01-01`);
51
58
  if (input.to_year) filters.push(`to_publication_date:${input.to_year}-12-31`);
52
59
  if (input.open_access) filters.push("is_oa:true");
53
60
 
54
61
  const params = new URLSearchParams({
55
- search: input.query,
62
+ search: query,
56
63
  per_page: String(Math.min(input.limit ?? 10, 200)),
57
64
  });
58
65
  if (filters.length > 0) params.set("filter", filters.join(","));
59
- if (input.sort_by) params.set("sort", input.sort_by);
66
+ const sortBy = validParam(input.sort_by);
67
+ if (sortBy && (SORT_BY as readonly string[]).includes(sortBy)) {
68
+ params.set("sort", sortBy);
69
+ }
60
70
 
61
71
  const tracked = await trackedFetch("openalex", `${BASE}/works?${params}`, { headers });
62
72
  if (isTrackedError(tracked)) return tracked;
@@ -93,7 +103,7 @@ export function createOpenAlexTools(
93
103
  "Work identifier: OpenAlex ID (e.g. 'W2741809807'), DOI URL, or PMID",
94
104
  }),
95
105
  }),
96
- execute: async (input: { work_id: string }) => {
106
+ execute: async (_toolCallId: string, input: { work_id: string }) => {
97
107
  if (!input?.work_id) {
98
108
  return toolResult({ error: 'work_id parameter is required (e.g., "W2741809807" or a DOI like "10.1234/example")' });
99
109
  }
@@ -137,7 +147,7 @@ export function createOpenAlexTools(
137
147
  "Author identifier: OpenAlex ID (e.g. 'A5023888391'), ORCID, or name search",
138
148
  }),
139
149
  }),
140
- execute: async (input: { author_id: string }) => {
150
+ execute: async (_toolCallId: string, input: { author_id: string }) => {
141
151
  if (!input?.author_id) {
142
152
  return toolResult({ error: 'author_id parameter is required (OpenAlex ID e.g. "A5023888391", ORCID, or author name)' });
143
153
  }
@@ -28,7 +28,7 @@ export function createOpenCitationsTools(
28
28
  description: "DOI of the paper, e.g. '10.1038/nature12373'",
29
29
  }),
30
30
  }),
31
- execute: async (input: { doi: string }) => {
31
+ execute: async (_toolCallId: string, input: { doi: string }) => {
32
32
  if (!input?.doi) {
33
33
  return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
34
34
  }
@@ -60,7 +60,7 @@ export function createOpenCitationsTools(
60
60
  description: "DOI of the paper, e.g. '10.1038/nature12373'",
61
61
  }),
62
62
  }),
63
- execute: async (input: { doi: string }) => {
63
+ execute: async (_toolCallId: string, input: { doi: string }) => {
64
64
  if (!input?.doi) {
65
65
  return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
66
66
  }
@@ -92,7 +92,7 @@ export function createOpenCitationsTools(
92
92
  description: "DOI of the paper, e.g. '10.1038/nature12373'",
93
93
  }),
94
94
  }),
95
- execute: async (input: { doi: string }) => {
95
+ execute: async (_toolCallId: string, input: { doi: string }) => {
96
96
  if (!input?.doi) {
97
97
  return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
98
98
  }
@@ -1,6 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
- import { toolResult, trackedFetch, isTrackedError } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError, validParam } from "./util.js";
4
4
 
5
5
  const BASE = "https://pub.orcid.org/v3.0";
6
6
 
@@ -27,9 +27,16 @@ export function createOrcidTools(
27
27
  Type.Number({ description: "Max results (default 10, max 100)" }),
28
28
  ),
29
29
  }),
30
- execute: async (input: { query: string; limit?: number }) => {
30
+ execute: async (_toolCallId: string, input: { query: string; limit?: number }) => {
31
+ const query = validParam(input?.query);
32
+ if (!query) {
33
+ return toolResult({
34
+ error: "query parameter is required and must not be empty.",
35
+ });
36
+ }
37
+
31
38
  const params = new URLSearchParams({
32
- q: input.query,
39
+ q: query,
33
40
  rows: String(Math.min(input.limit ?? 10, 100)),
34
41
  });
35
42
 
@@ -71,7 +78,7 @@ export function createOrcidTools(
71
78
  description: "ORCID iD, e.g. '0000-0002-1825-0097'",
72
79
  }),
73
80
  }),
74
- execute: async (input: { orcid: string }) => {
81
+ execute: async (_toolCallId: string, input: { orcid: string }) => {
75
82
  if (!input?.orcid) {
76
83
  return toolResult({ error: 'orcid parameter is required (e.g., "0000-0002-1825-0097")' });
77
84
  }
@@ -1,6 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
- import { toolResult, trackedFetch, isTrackedError } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError, validParam } from "./util.js";
4
4
 
5
5
  const BASE = "https://api.osf.io/v2";
6
6
 
@@ -28,16 +28,17 @@ export function createOsfPreprintsTools(
28
28
  Type.Number({ description: "Page number (default 1)" }),
29
29
  ),
30
30
  }),
31
- execute: async (input: {
31
+ execute: async (_toolCallId: string, input: {
32
32
  provider?: string;
33
33
  size?: number;
34
34
  page?: number;
35
35
  }) => {
36
36
  const params = new URLSearchParams({
37
- "page[size]": String(Math.min(input.size ?? 10, 100)),
37
+ "page[size]": String(Math.min(input?.size ?? 10, 100)),
38
38
  });
39
- if (input.provider) params.set("filter[provider]", input.provider);
40
- if (input.page) params.set("page", String(input.page));
39
+ const provider = validParam(input?.provider);
40
+ if (provider) params.set("filter[provider]", provider);
41
+ if (input?.page) params.set("page", String(input.page));
41
42
 
42
43
  const result = await trackedFetch(
43
44
  "osf_preprints",
@@ -57,7 +58,7 @@ export function createOsfPreprintsTools(
57
58
 
58
59
  return toolResult({
59
60
  total_results: linksMeta?.total,
60
- page: input.page ?? 1,
61
+ page: input?.page ?? 1,
61
62
  source: "osf_preprints",
62
63
  preprints: (items ?? []).map((item) => {
63
64
  const attrs = item.attributes as Record<string, unknown> | undefined;
@@ -1,6 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
- import { toolResult, trackedFetch, isTrackedError } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError, validParam, validEnum } from "./util.js";
4
4
 
5
5
  const EUTILS = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils";
6
6
 
@@ -34,24 +34,37 @@ export function createPubMedTools(
34
34
  Type.String({ description: "Max publication date (YYYY/MM/DD)" }),
35
35
  ),
36
36
  }),
37
- execute: async (input: {
37
+ execute: async (_toolCallId: string, input: {
38
38
  query: string;
39
39
  max_results?: number;
40
40
  sort?: string;
41
41
  min_date?: string;
42
42
  max_date?: string;
43
43
  }) => {
44
+ const query = validParam(input?.query);
45
+ if (!query) {
46
+ return toolResult({
47
+ error:
48
+ "query parameter is required and must not be empty. " +
49
+ "Example: search_pubmed({ query: \"CRISPR[Title] AND 2024[PDAT]\" })",
50
+ });
51
+ }
52
+
53
+ const sort = validEnum(input.sort, ["relevance", "pub_date"] as const, "relevance");
54
+ const minDate = validParam(input.min_date);
55
+ const maxDate = validParam(input.max_date);
56
+
44
57
  const searchParams = new URLSearchParams({
45
58
  db: "pubmed",
46
- term: input.query,
59
+ term: query,
47
60
  retmax: String(Math.min(input.max_results ?? 10, 100)),
48
61
  retmode: "json",
49
- sort: input.sort ?? "relevance",
62
+ sort,
50
63
  usehistory: "y",
51
64
  });
52
- if (input.min_date) searchParams.set("mindate", input.min_date);
53
- if (input.max_date) searchParams.set("maxdate", input.max_date);
54
- if (input.min_date || input.max_date) searchParams.set("datetype", "pdat");
65
+ if (minDate) searchParams.set("mindate", minDate);
66
+ if (maxDate) searchParams.set("maxdate", maxDate);
67
+ if (minDate || maxDate) searchParams.set("datetype", "pdat");
55
68
 
56
69
  const searchTracked = await trackedFetch("pubmed", `${EUTILS}/esearch.fcgi?${searchParams}`);
57
70
  if (isTrackedError(searchTracked)) return searchTracked;
@@ -108,7 +121,7 @@ export function createPubMedTools(
108
121
  parameters: Type.Object({
109
122
  pmid: Type.String({ description: "PubMed ID (numeric string)" }),
110
123
  }),
111
- execute: async (input: { pmid: string }) => {
124
+ execute: async (_toolCallId: string, input: { pmid: string }) => {
112
125
  if (!input?.pmid) {
113
126
  return toolResult({ error: 'pmid parameter is required (numeric PubMed ID, e.g., "33116299")' });
114
127
  }