@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 +0 -0
- package/dist/.setup/{exa-web-search-provider-BlNqLa7N.mjs → exa-web-search-provider-DgKhV-9B.mjs} +1 -1
- package/dist/.setup/{exa-web-search-provider.runtime-s8vRjwmE.mjs → exa-web-search-provider.runtime-DE40-fMu.mjs} +18 -38
- package/dist/index.js +1 -1
- package/dist/web-search-contract-api.js +1 -1
- package/dist/web-search-provider.js +1 -1
- package/package.json +4 -4
package/assets/icon.png
CHANGED
|
Binary file
|
package/dist/.setup/{exa-web-search-provider-BlNqLa7N.mjs → exa-web-search-provider-DgKhV-9B.mjs}
RENAMED
|
@@ -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-
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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
|
-
|
|
148
|
-
|
|
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
|
|
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
|
|
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(
|
|
236
|
-
const rawType = readStringParam(
|
|
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(
|
|
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(
|
|
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(
|
|
250
|
-
const rawDateBefore = readStringParam(
|
|
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(
|
|
266
|
-
if (
|
|
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 =
|
|
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-
|
|
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,2 +1,2 @@
|
|
|
1
|
-
import { t as createExaWebSearchProvider } from "./.setup/exa-web-search-provider-
|
|
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.
|
|
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.
|
|
21
|
+
"pluginApi": ">=2026.9.7"
|
|
22
22
|
},
|
|
23
23
|
"build": {
|
|
24
|
-
"openclawVersion": "2026.9.
|
|
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.
|
|
43
|
+
"openclaw": ">=2026.9.7"
|
|
44
44
|
},
|
|
45
45
|
"peerDependenciesMeta": {
|
|
46
46
|
"openclaw": {
|