memory-lancedb-pro 1.0.24 → 1.0.25

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/index.ts CHANGED
@@ -743,11 +743,22 @@ function parsePluginConfig(value: unknown): PluginConfig {
743
743
  }
744
744
 
745
745
  // Accept single key (string) or array of keys for round-robin rotation
746
- const apiKey: string | string[] = typeof embedding.apiKey === "string"
747
- ? embedding.apiKey
748
- : Array.isArray(embedding.apiKey) && embedding.apiKey.length > 0 && embedding.apiKey.every((k: unknown) => typeof k === "string")
749
- ? (embedding.apiKey as string[])
750
- : process.env.OPENAI_API_KEY || "";
746
+ let apiKey: string | string[];
747
+ if (typeof embedding.apiKey === "string") {
748
+ apiKey = embedding.apiKey;
749
+ } else if (Array.isArray(embedding.apiKey) && embedding.apiKey.length > 0) {
750
+ // Validate every element is a non-empty string
751
+ const invalid = embedding.apiKey.findIndex((k: unknown) => typeof k !== "string" || (k as string).trim().length === 0);
752
+ if (invalid !== -1) {
753
+ throw new Error(`embedding.apiKey[${invalid}] is invalid: expected non-empty string`);
754
+ }
755
+ apiKey = embedding.apiKey as string[];
756
+ } else if (embedding.apiKey !== undefined) {
757
+ // apiKey is present but wrong type — throw, don't silently fall back
758
+ throw new Error("embedding.apiKey must be a string or non-empty array of strings");
759
+ } else {
760
+ apiKey = process.env.OPENAI_API_KEY || "";
761
+ }
751
762
 
752
763
  if (!apiKey || (Array.isArray(apiKey) && apiKey.length === 0)) {
753
764
  throw new Error("embedding.apiKey is required (set directly or via OPENAI_API_KEY env var)");
@@ -2,7 +2,7 @@
2
2
  "id": "memory-lancedb-pro",
3
3
  "name": "Memory (LanceDB Pro)",
4
4
  "description": "Enhanced LanceDB-backed long-term memory with hybrid retrieval, multi-scope isolation, long-context chunking, and management CLI",
5
- "version": "1.0.23",
5
+ "version": "1.0.24",
6
6
  "kind": "memory",
7
7
  "configSchema": {
8
8
  "type": "object",
@@ -18,8 +18,18 @@
18
18
  },
19
19
  "apiKey": {
20
20
  "oneOf": [
21
- { "type": "string" },
22
- { "type": "array", "items": { "type": "string" }, "minItems": 1 }
21
+ {
22
+ "type": "string",
23
+ "minLength": 1
24
+ },
25
+ {
26
+ "type": "array",
27
+ "items": {
28
+ "type": "string",
29
+ "minLength": 1
30
+ },
31
+ "minItems": 1
32
+ }
23
33
  ],
24
34
  "description": "Single API key or array of keys for round-robin rotation"
25
35
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-lancedb-pro",
3
- "version": "1.0.24",
3
+ "version": "1.0.25",
4
4
  "description": "OpenClaw enhanced LanceDB memory plugin with hybrid retrieval (Vector + BM25), cross-encoder rerank, multi-scope isolation, long-context chunking, and management CLI",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/src/embedder.ts CHANGED
@@ -208,14 +208,28 @@ export class Embedder {
208
208
  return client;
209
209
  }
210
210
 
211
- /** Check whether an error is a rate-limit / quota-exceeded error. */
211
+ /** Check whether an error is a rate-limit / quota-exceeded / overload error. */
212
212
  private isRateLimitError(error: unknown): boolean {
213
- // OpenAI SDK typed error
214
- if (error && typeof error === "object" && "status" in error && (error as any).status === 429) {
215
- return true;
213
+ if (!error || typeof error !== "object") return false;
214
+
215
+ const err = error as Record<string, any>;
216
+
217
+ // HTTP status: 429 (rate limit) or 503 (service overload)
218
+ if (err.status === 429 || err.status === 503) return true;
219
+
220
+ // OpenAI SDK structured error code
221
+ if (err.code === "rate_limit_exceeded" || err.code === "insufficient_quota") return true;
222
+
223
+ // Nested error object (some providers)
224
+ const nested = err.error;
225
+ if (nested && typeof nested === "object") {
226
+ if (nested.type === "rate_limit_exceeded" || nested.type === "insufficient_quota") return true;
227
+ if (nested.code === "rate_limit_exceeded" || nested.code === "insufficient_quota") return true;
216
228
  }
229
+
230
+ // Fallback: message text matching
217
231
  const msg = error instanceof Error ? error.message : String(error);
218
- return /rate.limit|quota|too many requests|insufficient.*credit|429/i.test(msg);
232
+ return /rate.limit|quota|too many requests|insufficient.*credit|429|503.*overload/i.test(msg);
219
233
  }
220
234
 
221
235
  /**
@@ -231,19 +245,27 @@ export class Embedder {
231
245
  try {
232
246
  return await client.embeddings.create(payload);
233
247
  } catch (error) {
248
+ lastError = error instanceof Error ? error : new Error(String(error));
249
+
234
250
  if (this.isRateLimitError(error) && attempt < maxAttempts - 1) {
235
251
  console.log(
236
- `[memory-lancedb-pro] API key ${attempt + 1}/${maxAttempts} hit rate limit, rotating to next key...`
252
+ `[memory-lancedb-pro] Attempt ${attempt + 1}/${maxAttempts} hit rate limit, rotating to next key...`
237
253
  );
238
- lastError = error instanceof Error ? error : new Error(String(error));
239
254
  continue;
240
255
  }
241
- // Non-rate-limit error or last attempt — let caller handle
242
- throw error;
256
+
257
+ // Non-rate-limit error → don't retry, let caller handle (e.g. chunking)
258
+ if (!this.isRateLimitError(error)) {
259
+ throw error;
260
+ }
243
261
  }
244
262
  }
245
263
 
246
- throw lastError || new Error("All API keys exhausted (rate limited)");
264
+ // All keys exhausted with rate-limit errors
265
+ throw new Error(
266
+ `All ${maxAttempts} API keys exhausted (rate limited). Last error: ${lastError?.message || "unknown"}`,
267
+ { cause: lastError }
268
+ );
247
269
  }
248
270
 
249
271
  /** Number of API keys in the rotation pool. */