@wentorai/research-plugins 1.4.3 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wentorai/research-plugins",
3
- "version": "1.4.3",
3
+ "version": "1.4.4",
4
4
  "type": "module",
5
5
  "description": "438 academic research skills, 34 agent tools across 18 API modules, and 6 curated lists for Research-Claw and AI agents",
6
6
  "keywords": [
@@ -24,12 +24,21 @@ The API is free to use with no authentication required. It supports complex bool
24
24
 
25
25
  No authentication required. The arXiv API is fully open. However, users must respect the rate limit of 3 requests per second. Excessive usage may result in temporary IP-based blocking. Including a descriptive User-Agent header is considered good practice.
26
26
 
27
- ## Core Endpoints
27
+ ## Using the `search_arxiv` Tool
28
+
29
+ **IMPORTANT:** When calling the `search_arxiv` tool, use parameter name `query` (NOT
30
+ `search_query`). The raw API uses `search_query`, but the tool wrapper accepts `query`.
31
+
32
+ ```
33
+ search_arxiv({ query: "ti:transformer AND cat:cs.CL", sort_by: "submittedDate" })
34
+ ```
35
+
36
+ ## Core Endpoints (Raw API Reference)
28
37
 
29
38
  ### Query: Search for Articles
30
39
 
31
40
  - **URL**: `GET http://export.arxiv.org/api/query`
32
- - **Parameters**:
41
+ - **Parameters** (raw API — the `search_arxiv` tool wraps these automatically):
33
42
  | Param | Type | Required | Description |
34
43
  |-------|------|----------|-------------|
35
44
  | search_query | string | Yes | Query string using arXiv field prefixes (ti, au, abs, cat, id) |
@@ -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, validEnum } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError, validEnum, validParam } from "./util.js";
4
4
 
5
5
  const BASE = "https://export.arxiv.org/api/query";
6
6
 
@@ -103,21 +103,27 @@ export function createArxivTools(
103
103
  Type.String({ description: "Sort order: 'ascending' or 'descending'" }),
104
104
  ),
105
105
  }),
106
- execute: async (input: {
106
+ execute: async (_toolCallId: string, input: {
107
107
  query: string;
108
108
  max_results?: number;
109
109
  sort_by?: string;
110
110
  sort_order?: string;
111
111
  }) => {
112
- if (!input?.query || input.query.trim() === "" || input.query === "undefined") {
113
- return toolResult({ error: "query parameter is required and must not be empty" });
112
+ const query = validParam(input?.query);
113
+ if (!query) {
114
+ return toolResult({
115
+ error:
116
+ "query parameter is required and must not be empty. " +
117
+ "Use the tool parameter 'query' (not 'search_query'). " +
118
+ "Example: search_arxiv({ query: \"ti:transformer AND cat:cs.CL\" })",
119
+ });
114
120
  }
115
121
 
116
122
  const SORT_BY = ["relevance", "lastUpdatedDate", "submittedDate"] as const;
117
123
  const SORT_ORDER = ["ascending", "descending"] as const;
118
124
 
119
125
  const params = new URLSearchParams({
120
- search_query: input.query,
126
+ search_query: query,
121
127
  max_results: String(Math.min(input.max_results ?? 10, 50)),
122
128
  sortBy: validEnum(input.sort_by, SORT_BY, "relevance"),
123
129
  sortOrder: validEnum(input.sort_order, SORT_ORDER, "descending"),
@@ -157,7 +163,7 @@ export function createArxivTools(
157
163
  description: "arXiv paper ID, e.g. '2301.00001' or '2301.00001v2'",
158
164
  }),
159
165
  }),
160
- execute: async (input: { arxiv_id: string }) => {
166
+ execute: async (_toolCallId: string, input: { arxiv_id: string }) => {
161
167
  if (!input?.arxiv_id) {
162
168
  return toolResult({ error: 'arxiv_id parameter is required (e.g., "2301.00001" or "2301.00001v2")' });
163
169
  }
@@ -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, validEnum } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError, validEnum, validParam } from "./util.js";
4
4
 
5
5
  const BASE = "https://api.biorxiv.org";
6
6
 
@@ -23,16 +23,17 @@ export function createBiorxivTools(
23
23
  Type.Number({ description: "Pagination offset (default 0, each page returns up to 100)" }),
24
24
  ),
25
25
  }),
26
- execute: async (input: { interval: string; cursor?: number }) => {
27
- if (!input?.interval) {
26
+ execute: async (_toolCallId: string, input: { interval: string; cursor?: number }) => {
27
+ const interval = validParam(input?.interval);
28
+ if (!interval) {
28
29
  return toolResult({ error: 'interval parameter is required (date range in YYYY-MM-DD/YYYY-MM-DD format, e.g. "2026-03-01/2026-03-18")' });
29
30
  }
30
31
  // Validate date range format — bioRxiv API only accepts YYYY-MM-DD/YYYY-MM-DD
31
- if (!/^\d{4}-\d{2}-\d{2}\/\d{4}-\d{2}-\d{2}$/.test(input.interval)) {
32
- return toolResult({ error: `Invalid interval format "${input.interval}". Must be YYYY-MM-DD/YYYY-MM-DD (e.g. "2026-03-01/2026-03-18")` });
32
+ if (!/^\d{4}-\d{2}-\d{2}\/\d{4}-\d{2}-\d{2}$/.test(interval)) {
33
+ return toolResult({ error: `Invalid interval format "${interval}". Must be YYYY-MM-DD/YYYY-MM-DD (e.g. "2026-03-01/2026-03-18")` });
33
34
  }
34
- const cursor = input.cursor ?? 0;
35
- const tracked = await trackedFetch("biorxiv", `${BASE}/details/biorxiv/${input.interval}/${cursor}/json`, undefined, 30_000);
35
+ const cursor = input?.cursor ?? 0;
36
+ const tracked = await trackedFetch("biorxiv", `${BASE}/details/biorxiv/${interval}/${cursor}/json`, undefined, 30_000);
36
37
  if (isTrackedError(tracked)) return tracked;
37
38
  const data = await tracked.res.json();
38
39
 
@@ -76,15 +77,16 @@ export function createBiorxivTools(
76
77
  Type.Number({ description: "Pagination offset (default 0)" }),
77
78
  ),
78
79
  }),
79
- execute: async (input: { interval: string; cursor?: number }) => {
80
- if (!input?.interval) {
80
+ execute: async (_toolCallId: string, input: { interval: string; cursor?: number }) => {
81
+ const interval = validParam(input?.interval);
82
+ if (!interval) {
81
83
  return toolResult({ error: 'interval parameter is required (date range in YYYY-MM-DD/YYYY-MM-DD format, e.g. "2026-03-01/2026-03-18")' });
82
84
  }
83
- if (!/^\d{4}-\d{2}-\d{2}\/\d{4}-\d{2}-\d{2}$/.test(input.interval)) {
84
- return toolResult({ error: `Invalid interval format "${input.interval}". Must be YYYY-MM-DD/YYYY-MM-DD (e.g. "2026-03-01/2026-03-18")` });
85
+ if (!/^\d{4}-\d{2}-\d{2}\/\d{4}-\d{2}-\d{2}$/.test(interval)) {
86
+ return toolResult({ error: `Invalid interval format "${interval}". Must be YYYY-MM-DD/YYYY-MM-DD (e.g. "2026-03-01/2026-03-18")` });
85
87
  }
86
- const cursor = input.cursor ?? 0;
87
- const tracked = await trackedFetch("medrxiv", `${BASE}/details/medrxiv/${input.interval}/${cursor}/json`, undefined, 30_000);
88
+ const cursor = input?.cursor ?? 0;
89
+ const tracked = await trackedFetch("medrxiv", `${BASE}/details/medrxiv/${interval}/${cursor}/json`, undefined, 30_000);
88
90
  if (isTrackedError(tracked)) return tracked;
89
91
  const data = await tracked.res.json();
90
92
 
@@ -124,7 +126,7 @@ export function createBiorxivTools(
124
126
  Type.String({ description: "Server: 'biorxiv' or 'medrxiv' (default: biorxiv)" }),
125
127
  ),
126
128
  }),
127
- execute: async (input: { doi: string; server?: string }) => {
129
+ execute: async (_toolCallId: string, input: { doi: string; server?: string }) => {
128
130
  if (!input?.doi) {
129
131
  return toolResult({ error: 'doi parameter is required (e.g., "10.1101/2024.01.15.575123")' });
130
132
  }
@@ -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
 
@@ -31,23 +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
53
  const resourceType = validParam(input.resource_type);
46
54
  if (resourceType) {
47
55
  params.set("resource-type-id", resourceType.toLowerCase());
48
56
  }
49
- if (input.from_year) {
50
- 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 *]`);
51
59
  }
52
60
 
53
61
  const tracked = await trackedFetch("datacite", `${BASE}/dois?${params}`, undefined, 15_000);
@@ -118,7 +126,7 @@ export function createDataCiteTools(
118
126
  description: "DOI to resolve, e.g. '10.5281/zenodo.1234567'",
119
127
  }),
120
128
  }),
121
- execute: async (input: { doi: string }) => {
129
+ execute: async (_toolCallId: string, input: { doi: string }) => {
122
130
  if (!input?.doi) {
123
131
  return toolResult({ error: 'doi parameter is required (e.g., "10.5281/zenodo.1234567")' });
124
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
@@ -29,16 +29,24 @@ 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}`;
49
+ let url = `${BASE}/search/articles/${encodeURIComponent(query)}?page=${page}&pageSize=${pageSize}`;
42
50
  const sort = validParam(input.sort);
43
51
  if (sort) url += `&sort=${encodeURIComponent(sort)}`;
44
52
 
@@ -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, validParam, validEnum } 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,14 +31,22 @@ 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",
@@ -92,11 +100,14 @@ export function createEuropePmcTools(
92
100
  ),
93
101
  page: Type.Optional(Type.Number({ description: "Page number (default 1)" })),
94
102
  }),
95
- 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
+ }
96
107
  const params = new URLSearchParams({
97
108
  format: "json",
98
- pageSize: String(input.max_results ?? 25),
99
- page: String(input.page ?? 1),
109
+ pageSize: String(input?.max_results ?? 25),
110
+ page: String(input?.page ?? 1),
100
111
  });
101
112
  const tracked = await trackedFetch("europe_pmc", `${BASE}/MED/${input.pmid}/citations?${params}`);
102
113
  if (isTrackedError(tracked)) return tracked;
@@ -130,10 +141,13 @@ export function createEuropePmcTools(
130
141
  Type.Number({ description: "Max references to return (default 25)" }),
131
142
  ),
132
143
  }),
133
- 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
+ }
134
148
  const params = new URLSearchParams({
135
149
  format: "json",
136
- pageSize: String(input.max_results ?? 25),
150
+ pageSize: String(input?.max_results ?? 25),
137
151
  });
138
152
  const tracked = await trackedFetch("europe_pmc", `${BASE}/MED/${input.pmid}/references?${params}`);
139
153
  if (isTrackedError(tracked)) return tracked;
package/src/tools/hal.ts CHANGED
@@ -49,13 +49,21 @@ 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;
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;
59
67
  const docType = validParam(input.doc_type);
60
68
  if (docType) {
61
69
  q = `(${q}) AND docType_s:${docType}`;
@@ -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, validEnum } 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,10 +98,17 @@ 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
+
102
109
  const sort = validEnum(input.sort, ["mostrecent", "mostcited", "bestmatch"] as const, "bestmatch");
103
110
  const params = new URLSearchParams({
104
- q: input.query,
111
+ q: query,
105
112
  size: String(Math.min(input.size ?? 10, 100)),
106
113
  sort,
107
114
  });
@@ -137,7 +144,10 @@ export function createInspireHepTools(
137
144
  "Paper identifier: arXiv ID (e.g. '1207.7214') or DOI (e.g. '10.1016/j.physletb.2012.08.020')",
138
145
  }),
139
146
  }),
140
- 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
+ }
141
151
  // Determine if it's a DOI or arXiv ID
142
152
  const id = input.identifier.trim();
143
153
  let url: string;
@@ -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,8 +122,16 @@ 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
  });
@@ -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,7 +46,8 @@ export function createOpenAlexTools(
46
46
  open_access?: boolean;
47
47
  sort_by?: string;
48
48
  }) => {
49
- if (!input?.query || input.query.trim() === "" || input.query === "undefined") {
49
+ const query = validParam(input?.query);
50
+ if (!query) {
50
51
  return toolResult({ error: "query parameter is required and must not be empty" });
51
52
  }
52
53
 
@@ -58,7 +59,7 @@ export function createOpenAlexTools(
58
59
  if (input.open_access) filters.push("is_oa:true");
59
60
 
60
61
  const params = new URLSearchParams({
61
- search: input.query,
62
+ search: query,
62
63
  per_page: String(Math.min(input.limit ?? 10, 200)),
63
64
  });
64
65
  if (filters.length > 0) params.set("filter", filters.join(","));
@@ -102,7 +103,7 @@ export function createOpenAlexTools(
102
103
  "Work identifier: OpenAlex ID (e.g. 'W2741809807'), DOI URL, or PMID",
103
104
  }),
104
105
  }),
105
- execute: async (input: { work_id: string }) => {
106
+ execute: async (_toolCallId: string, input: { work_id: string }) => {
106
107
  if (!input?.work_id) {
107
108
  return toolResult({ error: 'work_id parameter is required (e.g., "W2741809807" or a DOI like "10.1234/example")' });
108
109
  }
@@ -146,7 +147,7 @@ export function createOpenAlexTools(
146
147
  "Author identifier: OpenAlex ID (e.g. 'A5023888391'), ORCID, or name search",
147
148
  }),
148
149
  }),
149
- execute: async (input: { author_id: string }) => {
150
+ execute: async (_toolCallId: string, input: { author_id: string }) => {
150
151
  if (!input?.author_id) {
151
152
  return toolResult({ error: 'author_id parameter is required (OpenAlex ID e.g. "A5023888391", ORCID, or author name)' });
152
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
  }
@@ -28,17 +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
- const provider = validParam(input.provider);
39
+ const provider = validParam(input?.provider);
40
40
  if (provider) params.set("filter[provider]", provider);
41
- if (input.page) params.set("page", String(input.page));
41
+ if (input?.page) params.set("page", String(input.page));
42
42
 
43
43
  const result = await trackedFetch(
44
44
  "osf_preprints",
@@ -58,7 +58,7 @@ export function createOsfPreprintsTools(
58
58
 
59
59
  return toolResult({
60
60
  total_results: linksMeta?.total,
61
- page: input.page ?? 1,
61
+ page: input?.page ?? 1,
62
62
  source: "osf_preprints",
63
63
  preprints: (items ?? []).map((item) => {
64
64
  const attrs = item.attributes as Record<string, unknown> | undefined;
@@ -34,20 +34,29 @@ 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
+
44
53
  const sort = validEnum(input.sort, ["relevance", "pub_date"] as const, "relevance");
45
54
  const minDate = validParam(input.min_date);
46
55
  const maxDate = validParam(input.max_date);
47
56
 
48
57
  const searchParams = new URLSearchParams({
49
58
  db: "pubmed",
50
- term: input.query,
59
+ term: query,
51
60
  retmax: String(Math.min(input.max_results ?? 10, 100)),
52
61
  retmode: "json",
53
62
  sort,
@@ -112,7 +121,7 @@ export function createPubMedTools(
112
121
  parameters: Type.Object({
113
122
  pmid: Type.String({ description: "PubMed ID (numeric string)" }),
114
123
  }),
115
- execute: async (input: { pmid: string }) => {
124
+ execute: async (_toolCallId: string, input: { pmid: string }) => {
116
125
  if (!input?.pmid) {
117
126
  return toolResult({ error: 'pmid parameter is required (numeric PubMed ID, e.g., "33116299")' });
118
127
  }
package/src/tools/ror.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://api.ror.org/v2";
6
6
 
@@ -22,9 +22,16 @@ export function createRorTools(
22
22
  Type.Number({ description: "Max results (default 10, max 50)" }),
23
23
  ),
24
24
  }),
25
- execute: async (input: { query: string; max_results?: number }) => {
25
+ execute: async (_toolCallId: string, input: { query: string; max_results?: number }) => {
26
+ const query = validParam(input?.query);
27
+ if (!query) {
28
+ return toolResult({
29
+ error: "query parameter is required and must not be empty.",
30
+ });
31
+ }
32
+
26
33
  const params = new URLSearchParams({
27
- query: input.query,
34
+ query,
28
35
  });
29
36
 
30
37
  const tracked = await trackedFetch("ror", `${BASE}/organizations?${params}`, undefined, 10_000);
@@ -21,7 +21,7 @@ export function createUnpaywallTools(
21
21
  description: "DOI of the paper, e.g. '10.1038/nature12373'",
22
22
  }),
23
23
  }),
24
- execute: async (input: { doi: string }) => {
24
+ execute: async (_toolCallId: string, input: { doi: string }) => {
25
25
  if (!input?.doi) {
26
26
  return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
27
27
  }
@@ -37,15 +37,23 @@ export function createZenodoTools(
37
37
  }),
38
38
  ),
39
39
  }),
40
- execute: async (input: {
40
+ execute: async (_toolCallId: string, input: {
41
41
  query: string;
42
42
  type?: string;
43
43
  size?: number;
44
44
  sort?: string;
45
45
  access_right?: string;
46
46
  }) => {
47
+ const query = validParam(input?.query);
48
+ if (!query) {
49
+ return toolResult({
50
+ error:
51
+ "query parameter is required and must not be empty. " +
52
+ "Example: search_zenodo({ query: \"climate dataset\" })",
53
+ });
54
+ }
47
55
  const params = new URLSearchParams({
48
- q: input.query,
56
+ q: query,
49
57
  size: String(Math.min(input.size ?? 10, 100)),
50
58
  });
51
59
  const type = validParam(input.type);
@@ -105,7 +113,7 @@ export function createZenodoTools(
105
113
  description: "Zenodo record ID (numeric), e.g. '7042164'",
106
114
  }),
107
115
  }),
108
- execute: async (input: { record_id: string }) => {
116
+ execute: async (_toolCallId: string, input: { record_id: string }) => {
109
117
  if (!input?.record_id) {
110
118
  return toolResult({ error: 'record_id parameter is required (e.g., "1234567")' });
111
119
  }