@openclaw/exa-plugin 2026.8.1-beta.3 → 2026.8.2
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/dist/{exa-web-search-provider-BJD3zsL0.js → exa-web-search-provider-DAcMYoLQ.js} +30 -2
- package/dist/{exa-web-search-provider.runtime-Uysj-tWk.js → exa-web-search-provider.runtime-MGDc8RH0.js} +34 -52
- package/dist/index.js +1 -1
- package/dist/web-search-contract-api.js +2 -9
- package/dist/web-search-provider.js +1 -1
- package/package.json +4 -4
- package/dist/exa-web-search-provider.shared-CWlikD_3.js +0 -30
|
@@ -1,5 +1,33 @@
|
|
|
1
|
-
import { t as createExaWebSearchProviderBase } from "./exa-web-search-provider.shared-CWlikD_3.js";
|
|
2
1
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
2
|
+
import { createWebSearchProviderContractFields } from "openclaw/plugin-sdk/provider-web-search-contract";
|
|
3
|
+
//#region extensions/exa/src/exa-web-search-provider.shared.ts
|
|
4
|
+
const EXA_CREDENTIAL_PATH = "plugins.entries.exa.config.webSearch.apiKey";
|
|
5
|
+
const EXA_ONBOARDING_SCOPES = ["text-inference"];
|
|
6
|
+
function createExaWebSearchProviderBase() {
|
|
7
|
+
return {
|
|
8
|
+
id: "exa",
|
|
9
|
+
label: "Exa Search",
|
|
10
|
+
hint: "Neural + keyword search with date filters and content extraction",
|
|
11
|
+
onboardingScopes: [...EXA_ONBOARDING_SCOPES],
|
|
12
|
+
credentialLabel: "Exa API key",
|
|
13
|
+
envVars: ["EXA_API_KEY"],
|
|
14
|
+
placeholder: "exa-...",
|
|
15
|
+
signupUrl: "https://exa.ai/",
|
|
16
|
+
docsUrl: "https://docs.openclaw.ai/tools/web",
|
|
17
|
+
autoDetectOrder: 65,
|
|
18
|
+
credentialPath: EXA_CREDENTIAL_PATH,
|
|
19
|
+
...createWebSearchProviderContractFields({
|
|
20
|
+
credentialPath: EXA_CREDENTIAL_PATH,
|
|
21
|
+
searchCredential: {
|
|
22
|
+
type: "scoped",
|
|
23
|
+
scopeId: "exa"
|
|
24
|
+
},
|
|
25
|
+
configuredCredential: { pluginId: "exa" },
|
|
26
|
+
selectionPluginId: "exa"
|
|
27
|
+
})
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
3
31
|
//#region extensions/exa/src/exa-web-search-provider.ts
|
|
4
32
|
const EXA_SEARCH_TYPES = [
|
|
5
33
|
"auto",
|
|
@@ -16,7 +44,7 @@ const EXA_FRESHNESS_VALUES = [
|
|
|
16
44
|
"year"
|
|
17
45
|
];
|
|
18
46
|
const EXA_MAX_SEARCH_COUNT = 100;
|
|
19
|
-
const loadExaWebSearchRuntime = createLazyRuntimeModule(() => import("./exa-web-search-provider.runtime-
|
|
47
|
+
const loadExaWebSearchRuntime = createLazyRuntimeModule(() => import("./exa-web-search-provider.runtime-MGDc8RH0.js"));
|
|
20
48
|
const ExaSearchSchema = {
|
|
21
49
|
type: "object",
|
|
22
50
|
properties: {
|
|
@@ -20,10 +20,11 @@ const EXA_FRESHNESS_VALUES = [
|
|
|
20
20
|
"year"
|
|
21
21
|
];
|
|
22
22
|
const EXA_MAX_SEARCH_COUNT = 100;
|
|
23
|
-
const EXA_ERROR_BODY_LIMIT_BYTES =
|
|
24
|
-
const EXA_SEARCH_JSON_MAX_BYTES =
|
|
23
|
+
const EXA_ERROR_BODY_LIMIT_BYTES = 8192;
|
|
24
|
+
const EXA_SEARCH_JSON_MAX_BYTES = 16777216;
|
|
25
25
|
async function readExaSearchResults(response, opts) {
|
|
26
|
-
const
|
|
26
|
+
const maxBytes = opts?.maxBytes ?? EXA_SEARCH_JSON_MAX_BYTES;
|
|
27
|
+
const bytes = await readResponseWithLimit(response, maxBytes, { onOverflow: ({ maxBytes: maxBytesLocal }) => /* @__PURE__ */ new Error(`Exa API response exceeds ${maxBytesLocal} bytes`) });
|
|
27
28
|
try {
|
|
28
29
|
return normalizeExaResults(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)));
|
|
29
30
|
} catch (cause) {
|
|
@@ -108,58 +109,38 @@ function parseExaContents(rawContents) {
|
|
|
108
109
|
]);
|
|
109
110
|
for (const key of Object.keys(raw)) if (!allowedKeys.has(key)) return invalidContentsPayload(`contents has unknown field "${key}". Only "text", "highlights", and "summary" are allowed.`);
|
|
110
111
|
const parsed = {};
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const obj = value;
|
|
115
|
-
for (const key of Object.keys(obj)) if (key !== "maxCharacters") return invalidContentsPayload(`contents.text has unknown field "${key}". Only "maxCharacters" is allowed.`);
|
|
116
|
-
if ("maxCharacters" in obj && parsePositiveInteger(obj.maxCharacters) === void 0) return invalidContentsPayload("contents.text.maxCharacters must be a positive integer.");
|
|
117
|
-
return parsePositiveInteger(obj.maxCharacters) ? { maxCharacters: parsePositiveInteger(obj.maxCharacters) } : {};
|
|
118
|
-
};
|
|
119
|
-
const parseHighlights = (value) => {
|
|
120
|
-
if (typeof value === "boolean") return value;
|
|
121
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return invalidContentsPayload("contents.highlights must be a boolean or an object.");
|
|
122
|
-
const obj = value;
|
|
123
|
-
const allowed = /* @__PURE__ */ new Set([
|
|
112
|
+
const fieldsBySection = {
|
|
113
|
+
text: ["maxCharacters"],
|
|
114
|
+
highlights: [
|
|
124
115
|
"maxCharacters",
|
|
125
116
|
"query",
|
|
126
117
|
"numSentences",
|
|
127
118
|
"highlightsPerUrl"
|
|
128
|
-
]
|
|
129
|
-
|
|
130
|
-
if ("maxCharacters" in obj && parsePositiveInteger(obj.maxCharacters) === void 0) return invalidContentsPayload("contents.highlights.maxCharacters must be a positive integer.");
|
|
131
|
-
if ("numSentences" in obj && parsePositiveInteger(obj.numSentences) === void 0) return invalidContentsPayload("contents.highlights.numSentences must be a positive integer.");
|
|
132
|
-
if ("highlightsPerUrl" in obj && parsePositiveInteger(obj.highlightsPerUrl) === void 0) return invalidContentsPayload("contents.highlights.highlightsPerUrl must be a positive integer.");
|
|
133
|
-
if ("query" in obj && typeof obj.query !== "string") return invalidContentsPayload("contents.highlights.query must be a string.");
|
|
134
|
-
return {
|
|
135
|
-
...parsePositiveInteger(obj.maxCharacters) ? { maxCharacters: parsePositiveInteger(obj.maxCharacters) } : {},
|
|
136
|
-
...typeof obj.query === "string" ? { query: obj.query } : {},
|
|
137
|
-
...parsePositiveInteger(obj.numSentences) ? { numSentences: parsePositiveInteger(obj.numSentences) } : {},
|
|
138
|
-
...parsePositiveInteger(obj.highlightsPerUrl) ? { highlightsPerUrl: parsePositiveInteger(obj.highlightsPerUrl) } : {}
|
|
139
|
-
};
|
|
140
|
-
};
|
|
141
|
-
const parseSummary = (value) => {
|
|
142
|
-
if (typeof value === "boolean") return value;
|
|
143
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return invalidContentsPayload("contents.summary must be a boolean or an object.");
|
|
144
|
-
const obj = value;
|
|
145
|
-
for (const key of Object.keys(obj)) if (key !== "query") return invalidContentsPayload(`contents.summary has unknown field "${key}". Only "query" is allowed.`);
|
|
146
|
-
if ("query" in obj && typeof obj.query !== "string") return invalidContentsPayload("contents.summary.query must be a string.");
|
|
147
|
-
return typeof obj.query === "string" ? { query: obj.query } : {};
|
|
119
|
+
],
|
|
120
|
+
summary: ["query"]
|
|
148
121
|
};
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
122
|
+
for (const section of [
|
|
123
|
+
"text",
|
|
124
|
+
"highlights",
|
|
125
|
+
"summary"
|
|
126
|
+
]) {
|
|
127
|
+
if (!(section in raw)) continue;
|
|
128
|
+
const value = raw[section];
|
|
129
|
+
if (typeof value === "boolean") {
|
|
130
|
+
parsed[section] = value;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return invalidContentsPayload(`contents.${section} must be a boolean or an object.`);
|
|
134
|
+
const option = value;
|
|
135
|
+
const fields = fieldsBySection[section] ?? [];
|
|
136
|
+
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.`}`);
|
|
137
|
+
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.`);
|
|
138
|
+
if (section !== "text" && "query" in option && typeof option.query !== "string") return invalidContentsPayload(`contents.${section}.query must be a string.`);
|
|
139
|
+
const normalized = {};
|
|
140
|
+
for (const field of fields) if (field === "query") {
|
|
141
|
+
if (typeof option.query === "string") normalized.query = option.query;
|
|
142
|
+
} else if (parsePositiveInteger(option[field])) normalized[field] = parsePositiveInteger(option[field]);
|
|
143
|
+
Object.assign(parsed, { [section]: normalized });
|
|
163
144
|
}
|
|
164
145
|
return { value: parsed };
|
|
165
146
|
}
|
|
@@ -296,7 +277,8 @@ async function executeExaWebSearchProviderTool(ctx, args, signal) {
|
|
|
296
277
|
dateBefore,
|
|
297
278
|
contents
|
|
298
279
|
});
|
|
299
|
-
const
|
|
280
|
+
const cacheTtlMs = resolveSearchCacheTtlMs(searchConfig);
|
|
281
|
+
const cached = readCachedSearchPayload(cacheKey, cacheTtlMs);
|
|
300
282
|
if (cached) return cached;
|
|
301
283
|
const start = Date.now();
|
|
302
284
|
const results = await runExaSearch({
|
|
@@ -340,7 +322,7 @@ async function executeExaWebSearchProviderTool(ctx, args, signal) {
|
|
|
340
322
|
}, summary ? { summary: wrapWebContent(summary, `web_search`) } : {}, highlightScores.length > 0 ? { highlightScores } : {});
|
|
341
323
|
})
|
|
342
324
|
};
|
|
343
|
-
writeCachedSearchPayload(cacheKey, payload,
|
|
325
|
+
writeCachedSearchPayload(cacheKey, payload, cacheTtlMs);
|
|
344
326
|
return payload;
|
|
345
327
|
}
|
|
346
328
|
const testing = {
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as createExaWebSearchProvider } from "./exa-web-search-provider-
|
|
1
|
+
import { t as createExaWebSearchProvider } from "./exa-web-search-provider-DAcMYoLQ.js";
|
|
2
2
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
3
|
//#region extensions/exa/index.ts
|
|
4
4
|
var exa_default = definePluginEntry({
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
|
|
3
|
-
function createExaWebSearchProvider() {
|
|
4
|
-
return {
|
|
5
|
-
...createExaWebSearchProviderBase(),
|
|
6
|
-
createTool: () => null
|
|
7
|
-
};
|
|
8
|
-
}
|
|
9
|
-
//#endregion
|
|
1
|
+
import { t as createExaWebSearchProvider } from "./exa-web-search-provider-DAcMYoLQ.js";
|
|
2
|
+
import "./web-search-provider.js";
|
|
10
3
|
export { createExaWebSearchProvider };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createExaWebSearchProvider } from "./exa-web-search-provider-
|
|
1
|
+
import { t as createExaWebSearchProvider } from "./exa-web-search-provider-DAcMYoLQ.js";
|
|
2
2
|
export { createExaWebSearchProvider };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/exa-plugin",
|
|
3
|
-
"version": "2026.8.
|
|
3
|
+
"version": "2026.8.2",
|
|
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.8.
|
|
21
|
+
"pluginApi": ">=2026.8.2"
|
|
22
22
|
},
|
|
23
23
|
"build": {
|
|
24
|
-
"openclawVersion": "2026.8.
|
|
24
|
+
"openclawVersion": "2026.8.2",
|
|
25
25
|
"bundledDist": false
|
|
26
26
|
},
|
|
27
27
|
"release": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"README.md"
|
|
39
39
|
],
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"openclaw": ">=2026.8.
|
|
41
|
+
"openclaw": ">=2026.8.2"
|
|
42
42
|
},
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"openclaw": {
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { createWebSearchProviderContractFields } from "openclaw/plugin-sdk/provider-web-search-contract";
|
|
2
|
-
//#region extensions/exa/src/exa-web-search-provider.shared.ts
|
|
3
|
-
const EXA_CREDENTIAL_PATH = "plugins.entries.exa.config.webSearch.apiKey";
|
|
4
|
-
const EXA_ONBOARDING_SCOPES = ["text-inference"];
|
|
5
|
-
function createExaWebSearchProviderBase() {
|
|
6
|
-
return {
|
|
7
|
-
id: "exa",
|
|
8
|
-
label: "Exa Search",
|
|
9
|
-
hint: "Neural + keyword search with date filters and content extraction",
|
|
10
|
-
onboardingScopes: [...EXA_ONBOARDING_SCOPES],
|
|
11
|
-
credentialLabel: "Exa API key",
|
|
12
|
-
envVars: ["EXA_API_KEY"],
|
|
13
|
-
placeholder: "exa-...",
|
|
14
|
-
signupUrl: "https://exa.ai/",
|
|
15
|
-
docsUrl: "https://docs.openclaw.ai/tools/web",
|
|
16
|
-
autoDetectOrder: 65,
|
|
17
|
-
credentialPath: EXA_CREDENTIAL_PATH,
|
|
18
|
-
...createWebSearchProviderContractFields({
|
|
19
|
-
credentialPath: EXA_CREDENTIAL_PATH,
|
|
20
|
-
searchCredential: {
|
|
21
|
-
type: "scoped",
|
|
22
|
-
scopeId: "exa"
|
|
23
|
-
},
|
|
24
|
-
configuredCredential: { pluginId: "exa" },
|
|
25
|
-
selectionPluginId: "exa"
|
|
26
|
-
})
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
//#endregion
|
|
30
|
-
export { createExaWebSearchProviderBase as t };
|