@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.
@@ -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-Uysj-tWk.js"));
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 = 8 * 1024;
24
- const EXA_SEARCH_JSON_MAX_BYTES = 16 * 1024 * 1024;
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 bytes = await readResponseWithLimit(response, opts?.maxBytes ?? EXA_SEARCH_JSON_MAX_BYTES, { onOverflow: ({ maxBytes: maxBytesLocal }) => /* @__PURE__ */ new Error(`Exa API response exceeds ${maxBytesLocal} bytes`) });
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 parseText = (value) => {
112
- if (typeof value === "boolean") return value;
113
- if (!value || typeof value !== "object" || Array.isArray(value)) return invalidContentsPayload("contents.text must be a boolean or an object.");
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
- for (const key of Object.keys(obj)) if (!allowed.has(key)) return invalidContentsPayload(`contents.highlights has unknown field "${key}". Allowed fields are "maxCharacters", "query", "numSentences", and "highlightsPerUrl".`);
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
- if ("text" in raw) {
150
- const parsedText = parseText(raw.text);
151
- if (isErrorPayload(parsedText)) return parsedText;
152
- parsed.text = parsedText;
153
- }
154
- if ("highlights" in raw) {
155
- const parsedHighlights = parseHighlights(raw.highlights);
156
- if (isErrorPayload(parsedHighlights)) return parsedHighlights;
157
- parsed.highlights = parsedHighlights;
158
- }
159
- if ("summary" in raw) {
160
- const parsedSummary = parseSummary(raw.summary);
161
- if (isErrorPayload(parsedSummary)) return parsedSummary;
162
- parsed.summary = parsedSummary;
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 cached = readCachedSearchPayload(cacheKey);
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, resolveSearchCacheTtlMs(searchConfig));
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-BJD3zsL0.js";
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 createExaWebSearchProviderBase } from "./exa-web-search-provider.shared-CWlikD_3.js";
2
- //#region extensions/exa/web-search-contract-api.ts
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-BJD3zsL0.js";
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.1-beta.3",
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.1-beta.3"
21
+ "pluginApi": ">=2026.8.2"
22
22
  },
23
23
  "build": {
24
- "openclawVersion": "2026.8.1-beta.3",
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.1-beta.3"
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 };