@openclaw/exa-plugin 2026.9.6 → 2026.9.7

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/assets/icon.png CHANGED
Binary file
@@ -44,7 +44,7 @@ const EXA_FRESHNESS_VALUES = [
44
44
  "year"
45
45
  ];
46
46
  const EXA_MAX_SEARCH_COUNT = 100;
47
- const loadExaWebSearchRuntime = createLazyRuntimeModule(() => import("./exa-web-search-provider.runtime-s8vRjwmE.mjs"));
47
+ const loadExaWebSearchRuntime = createLazyRuntimeModule(() => import("./exa-web-search-provider.runtime-DE40-fMu.mjs"));
48
48
  const ExaSearchSchema = {
49
49
  type: "object",
50
50
  properties: {
@@ -2,7 +2,7 @@ import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
2
2
  import { ProviderHttpError, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
3
3
  import { DEFAULT_SEARCH_COUNT, buildSearchCacheKey, mergeScopedSearchConfig, parseIsoDateRange, readCachedSearchPayload, readConfiguredSecretString, readPositiveIntegerParam, readProviderEnvValue, readStringParam, resolveProviderWebSearchPluginConfig, resolveSearchCacheTtlMs, resolveSearchTimeoutSeconds, resolveSiteName, withTrustedWebSearchEndpoint, wrapWebContent, writeCachedSearchPayload } from "openclaw/plugin-sdk/provider-web-search";
4
4
  import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
5
- import { normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
5
+ import { asOptionalObjectRecord, asOptionalRecord, isRecord, normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
6
6
  //#region extensions/exa/src/exa-web-search-provider.runtime.ts
7
7
  const EXA_SEARCH_ENDPOINT = "https://api.exa.ai/search";
8
8
  const EXA_SEARCH_TYPES = [
@@ -30,18 +30,11 @@ async function readExaSearchResults(response) {
30
30
  throw new Error("Exa API returned malformed JSON", { cause });
31
31
  }
32
32
  }
33
- async function readExaErrorDetail(response) {
34
- return await readResponseTextLimited(response, EXA_ERROR_BODY_LIMIT_BYTES);
35
- }
36
33
  function normalizeExaFreshness(value) {
37
34
  const trimmed = normalizeOptionalLowercaseString(value);
38
35
  if (!trimmed) return;
39
36
  return EXA_FRESHNESS_VALUES.includes(trimmed) ? trimmed : void 0;
40
37
  }
41
- function resolveExaConfig(searchConfig) {
42
- const exa = searchConfig?.exa;
43
- return exa && typeof exa === "object" && !Array.isArray(exa) ? exa : {};
44
- }
45
38
  function resolveExaApiKey(exa) {
46
39
  return readConfiguredSecretString(exa?.apiKey, "plugins.entries.exa.config.webSearch.apiKey") ?? readProviderEnvValue(["EXA_API_KEY"]);
47
40
  }
@@ -89,17 +82,9 @@ function invalidContentsPayload(message) {
89
82
  docs: "https://docs.openclaw.ai/tools/web"
90
83
  };
91
84
  }
92
- function isErrorPayload(value) {
93
- return Boolean(value && typeof value === "object" && "error" in value && "message" in value && "docs" in value);
94
- }
95
- function resolveExaSearchCount(value, fallback) {
96
- const parsed = parseStrictPositiveInteger(value);
97
- if (parsed === void 0) return fallback;
98
- return Math.min(EXA_MAX_SEARCH_COUNT, parsed);
99
- }
100
85
  function parseExaContents(rawContents) {
101
86
  if (rawContents === void 0) return { value: void 0 };
102
- if (!rawContents || typeof rawContents !== "object" || Array.isArray(rawContents)) return invalidContentsPayload("contents must be an object with optional text, highlights, and summary fields.");
87
+ if (!isRecord(rawContents)) return invalidContentsPayload("contents must be an object with optional text, highlights, and summary fields.");
103
88
  const raw = rawContents;
104
89
  const allowedKeys = /* @__PURE__ */ new Set([
105
90
  "text",
@@ -129,25 +114,21 @@ function parseExaContents(rawContents) {
129
114
  parsed[section] = value;
130
115
  continue;
131
116
  }
132
- if (!value || typeof value !== "object" || Array.isArray(value)) return invalidContentsPayload(`contents.${section} must be a boolean or an object.`);
117
+ if (!isRecord(value)) return invalidContentsPayload(`contents.${section} must be a boolean or an object.`);
133
118
  const option = value;
134
- const fields = fieldsBySection[section] ?? [];
119
+ const fields = fieldsBySection[section];
135
120
  for (const key of Object.keys(option)) if (!fields.includes(key)) return invalidContentsPayload(`contents.${section} has unknown field "${key}". ${section === "highlights" ? "Allowed fields are \"maxCharacters\", \"query\", \"numSentences\", and \"highlightsPerUrl\"." : `Only "${fields[0]}" is allowed.`}`);
136
121
  for (const field of fields) if (field !== "query" && field in option && parsePositiveInteger(option[field]) === void 0) return invalidContentsPayload(`contents.${section}.${field} must be a positive integer.`);
137
122
  if (section !== "text" && "query" in option && typeof option.query !== "string") return invalidContentsPayload(`contents.${section}.query must be a string.`);
138
123
  const normalized = {};
139
- for (const field of fields) if (field === "query") {
140
- if (typeof option.query === "string") normalized.query = option.query;
141
- } else if (parsePositiveInteger(option[field])) normalized[field] = parsePositiveInteger(option[field]);
124
+ for (const field of fields) if (field in option) normalized[field] = option[field];
142
125
  Object.assign(parsed, { [section]: normalized });
143
126
  }
144
127
  return { value: parsed };
145
128
  }
146
129
  function normalizeExaResults(payload) {
147
- if (!payload || typeof payload !== "object") return [];
148
- const results = payload.results;
149
- if (!Array.isArray(results)) return [];
150
- return results.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
130
+ const results = asOptionalObjectRecord(payload)?.results;
131
+ return Array.isArray(results) ? results.filter(isRecord) : [];
151
132
  }
152
133
  function resolveFreshnessStartDate(freshness) {
153
134
  const now = /* @__PURE__ */ new Date();
@@ -196,7 +177,7 @@ async function runExaSearch(params) {
196
177
  }
197
178
  }, async (res) => {
198
179
  if (!res.ok) {
199
- const detail = await readExaErrorDetail(res);
180
+ const detail = await readResponseTextLimited(res, EXA_ERROR_BODY_LIMIT_BYTES);
200
181
  throw new ProviderHttpError(`Exa API error (${res.status}): ${detail || res.statusText}`, { status: res.status });
201
182
  }
202
183
  return (await readExaSearchResults(res)).slice(0, params.count);
@@ -225,29 +206,28 @@ function buildExaCacheKey(params) {
225
206
  }
226
207
  async function executeExaWebSearchProviderTool(ctx, args, signal) {
227
208
  const searchConfig = mergeScopedSearchConfig(ctx.searchConfig, "exa", resolveProviderWebSearchPluginConfig(ctx.config, "exa"));
228
- const params = args;
229
- const exaConfig = resolveExaConfig(searchConfig);
209
+ const exaConfig = asOptionalRecord(searchConfig?.exa);
230
210
  const apiKey = resolveExaApiKey(exaConfig);
231
211
  if (!apiKey) return missingExaKeyPayload();
232
212
  const endpointResult = resolveExaSearchEndpoint(exaConfig);
233
213
  if ("error" in endpointResult) return endpointResult;
234
214
  const endpoint = endpointResult.endpoint;
235
- const query = readStringParam(params, "query", { required: true });
236
- const rawType = readStringParam(params, "type");
215
+ const query = readStringParam(args, "query", { required: true });
216
+ const rawType = readStringParam(args, "type");
237
217
  const type = EXA_SEARCH_TYPES.includes(rawType) ? rawType : "auto";
238
- const count = readPositiveIntegerParam(params, "count", {
218
+ const count = readPositiveIntegerParam(args, "count", {
239
219
  max: EXA_MAX_SEARCH_COUNT,
240
220
  message: `count must be an integer from 1 to ${EXA_MAX_SEARCH_COUNT}.`
241
221
  }) ?? searchConfig?.maxResults ?? void 0;
242
- const rawFreshness = readStringParam(params, "freshness");
222
+ const rawFreshness = readStringParam(args, "freshness");
243
223
  const freshness = normalizeExaFreshness(rawFreshness);
244
224
  if (rawFreshness && !freshness) return {
245
225
  error: "invalid_freshness",
246
226
  message: "freshness must be one of \"day\", \"week\", \"month\", or \"year\".",
247
227
  docs: "https://docs.openclaw.ai/tools/web"
248
228
  };
249
- const rawDateAfter = readStringParam(params, "date_after");
250
- const rawDateBefore = readStringParam(params, "date_before");
229
+ const rawDateAfter = readStringParam(args, "date_after");
230
+ const rawDateBefore = readStringParam(args, "date_before");
251
231
  if (freshness && (rawDateAfter || rawDateBefore)) return {
252
232
  error: "conflicting_time_filters",
253
233
  message: "freshness cannot be combined with date_after or date_before. Use one time-filter mode.",
@@ -262,10 +242,10 @@ async function executeExaWebSearchProviderTool(ctx, args, signal) {
262
242
  });
263
243
  if ("error" in parsedDateRange) return parsedDateRange;
264
244
  const { dateAfter, dateBefore } = parsedDateRange;
265
- const parsedContents = parseExaContents(params.contents);
266
- if (isErrorPayload(parsedContents)) return parsedContents;
245
+ const parsedContents = parseExaContents(args.contents);
246
+ if ("error" in parsedContents) return parsedContents;
267
247
  const contents = parsedContents.value && Object.keys(parsedContents.value).length > 0 ? parsedContents.value : void 0;
268
- const resolvedCount = resolveExaSearchCount(count, DEFAULT_SEARCH_COUNT);
248
+ const resolvedCount = Math.min(EXA_MAX_SEARCH_COUNT, parseStrictPositiveInteger(count) ?? DEFAULT_SEARCH_COUNT);
269
249
  const cacheKey = buildExaCacheKey({
270
250
  endpoint,
271
251
  type,
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as createExaWebSearchProvider } from "./.setup/exa-web-search-provider-BlNqLa7N.mjs";
1
+ import { t as createExaWebSearchProvider } from "./.setup/exa-web-search-provider-DgKhV-9B.mjs";
2
2
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
3
3
  //#region extensions/exa/index.ts
4
4
  var exa_default = definePluginEntry({
@@ -1,3 +1,3 @@
1
- import { t as createExaWebSearchProvider } from "./.setup/exa-web-search-provider-BlNqLa7N.mjs";
1
+ import { t as createExaWebSearchProvider } from "./.setup/exa-web-search-provider-DgKhV-9B.mjs";
2
2
  import "./web-search-provider.js";
3
3
  export { createExaWebSearchProvider };
@@ -1,2 +1,2 @@
1
- import { t as createExaWebSearchProvider } from "./.setup/exa-web-search-provider-BlNqLa7N.mjs";
1
+ import { t as createExaWebSearchProvider } from "./.setup/exa-web-search-provider-DgKhV-9B.mjs";
2
2
  export { createExaWebSearchProvider };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/exa-plugin",
3
- "version": "2026.9.6",
3
+ "version": "2026.9.7",
4
4
  "description": "OpenClaw Exa plugin.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,10 +18,10 @@
18
18
  "minHostVersion": ">=2026.6.8"
19
19
  },
20
20
  "compat": {
21
- "pluginApi": ">=2026.9.6"
21
+ "pluginApi": ">=2026.9.7"
22
22
  },
23
23
  "build": {
24
- "openclawVersion": "2026.9.6",
24
+ "openclawVersion": "2026.9.7",
25
25
  "bundledDist": false
26
26
  },
27
27
  "release": {
@@ -40,7 +40,7 @@
40
40
  "assets/activity.svg"
41
41
  ],
42
42
  "peerDependencies": {
43
- "openclaw": ">=2026.9.6"
43
+ "openclaw": ">=2026.9.7"
44
44
  },
45
45
  "peerDependenciesMeta": {
46
46
  "openclaw": {