opencode-mem 2.13.0 → 2.14.1
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/README.md +13 -3
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +43 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +78 -20
- package/dist/plugin.d.ts +2 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +3 -1
- package/dist/services/ai/opencode-provider.d.ts +31 -25
- package/dist/services/ai/opencode-provider.d.ts.map +1 -1
- package/dist/services/ai/opencode-provider.js +76 -222
- package/dist/services/ai/providers/openai-chat-completion.d.ts +6 -5
- package/dist/services/ai/providers/openai-chat-completion.d.ts.map +1 -1
- package/dist/services/ai/providers/openai-chat-completion.js +56 -15
- package/dist/services/api-handlers.d.ts.map +1 -1
- package/dist/services/api-handlers.js +8 -3
- package/dist/services/auto-capture.js +8 -5
- package/dist/services/client.d.ts +3 -2
- package/dist/services/client.d.ts.map +1 -1
- package/dist/services/client.js +14 -8
- package/dist/services/embedding.d.ts.map +1 -1
- package/dist/services/embedding.js +34 -6
- package/dist/services/language-detector.d.ts.map +1 -1
- package/dist/services/language-detector.js +20 -3
- package/dist/services/sqlite/vector-search.d.ts.map +1 -1
- package/dist/services/sqlite/vector-search.js +15 -4
- package/dist/services/user-memory-learning.js +8 -5
- package/package.json +2 -5
package/dist/services/client.js
CHANGED
|
@@ -39,6 +39,12 @@ function extractScopeFromContainerTag(containerTag) {
|
|
|
39
39
|
}
|
|
40
40
|
return { scope: "user", hash: containerTag };
|
|
41
41
|
}
|
|
42
|
+
function resolveScopeValue(scope, containerTag) {
|
|
43
|
+
if (scope === "all-projects") {
|
|
44
|
+
return { scope: "project", hash: "" };
|
|
45
|
+
}
|
|
46
|
+
return extractScopeFromContainerTag(containerTag);
|
|
47
|
+
}
|
|
42
48
|
export class LocalMemoryClient {
|
|
43
49
|
initPromise = null;
|
|
44
50
|
isInitialized = false;
|
|
@@ -77,16 +83,16 @@ export class LocalMemoryClient {
|
|
|
77
83
|
close() {
|
|
78
84
|
connectionManager.closeAll();
|
|
79
85
|
}
|
|
80
|
-
async searchMemories(query, containerTag) {
|
|
86
|
+
async searchMemories(query, containerTag, scope = "project") {
|
|
81
87
|
try {
|
|
82
88
|
await this.initialize();
|
|
83
89
|
const queryVector = await embeddingService.embedWithTimeout(query);
|
|
84
|
-
const
|
|
85
|
-
const shards = shardManager.getAllShards(scope, hash);
|
|
90
|
+
const resolved = resolveScopeValue(scope, containerTag);
|
|
91
|
+
const shards = shardManager.getAllShards(resolved.scope, resolved.hash);
|
|
86
92
|
if (shards.length === 0) {
|
|
87
93
|
return { success: true, results: [], total: 0, timing: 0 };
|
|
88
94
|
}
|
|
89
|
-
const results = await vectorSearch.searchAcrossShards(shards, queryVector, containerTag, CONFIG.maxMemories, CONFIG.similarityThreshold, query);
|
|
95
|
+
const results = await vectorSearch.searchAcrossShards(shards, queryVector, scope === "all-projects" ? "" : containerTag, CONFIG.maxMemories, CONFIG.similarityThreshold, query);
|
|
90
96
|
return { success: true, results, total: results.length, timing: 0 };
|
|
91
97
|
}
|
|
92
98
|
catch (error) {
|
|
@@ -161,11 +167,11 @@ export class LocalMemoryClient {
|
|
|
161
167
|
return { success: false, error: errorMessage };
|
|
162
168
|
}
|
|
163
169
|
}
|
|
164
|
-
async listMemories(containerTag, limit = 20) {
|
|
170
|
+
async listMemories(containerTag, limit = 20, scope = "project") {
|
|
165
171
|
try {
|
|
166
172
|
await this.initialize();
|
|
167
|
-
const
|
|
168
|
-
const shards = shardManager.getAllShards(scope, hash);
|
|
173
|
+
const resolved = resolveScopeValue(scope, containerTag);
|
|
174
|
+
const shards = shardManager.getAllShards(resolved.scope, resolved.hash);
|
|
169
175
|
if (shards.length === 0) {
|
|
170
176
|
return {
|
|
171
177
|
success: true,
|
|
@@ -176,7 +182,7 @@ export class LocalMemoryClient {
|
|
|
176
182
|
const allMemories = [];
|
|
177
183
|
for (const shard of shards) {
|
|
178
184
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
179
|
-
const memories = vectorSearch.listMemories(db, containerTag, limit);
|
|
185
|
+
const memories = vectorSearch.listMemories(db, scope === "all-projects" ? "" : containerTag, limit);
|
|
180
186
|
allMemories.push(...memories);
|
|
181
187
|
}
|
|
182
188
|
allMemories.sort((a, b) => Number(b.created_at) - Number(a.created_at));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embedding.d.ts","sourceRoot":"","sources":["../../src/services/embedding.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"embedding.d.ts","sourceRoot":"","sources":["../../src/services/embedding.ts"],"names":[],"mappings":"AAgDA,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,WAAW,CAA8B;IAC1C,UAAU,EAAE,OAAO,CAAS;IACnC,OAAO,CAAC,KAAK,CAAwC;IACrD,OAAO,CAAC,eAAe,CAAuB;IAE9C,MAAM,CAAC,WAAW,IAAI,gBAAgB;IAOhC,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;YAOzD,eAAe;IAuBvB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAmD1C,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAI3D,UAAU,IAAI,IAAI;CAGnB;AAED,eAAO,MAAM,gBAAgB,kBAAiC,CAAC"}
|
|
@@ -1,13 +1,35 @@
|
|
|
1
|
-
import { pipeline, env } from "@xenova/transformers";
|
|
2
1
|
import { CONFIG } from "../config.js";
|
|
3
2
|
import { log } from "./logger.js";
|
|
4
3
|
import { join } from "node:path";
|
|
5
|
-
env.allowLocalModels = true;
|
|
6
|
-
env.allowRemoteModels = true;
|
|
7
|
-
env.cacheDir = join(CONFIG.storagePath, ".cache");
|
|
8
4
|
const TIMEOUT_MS = 30000;
|
|
9
5
|
const GLOBAL_EMBEDDING_KEY = Symbol.for("opencode-mem.embedding.instance");
|
|
10
6
|
const MAX_CACHE_SIZE = 100;
|
|
7
|
+
let _transformers = null;
|
|
8
|
+
function getTransformersPackageSpecifier() {
|
|
9
|
+
// Keep this non-literal so OpenCode/Bun plugin-loader bundling does not eagerly
|
|
10
|
+
// traverse @huggingface/transformers internals during plugin startup. The package
|
|
11
|
+
// is only needed for the local embedding backend, and should stay lazy.
|
|
12
|
+
return ["@huggingface", "transformers"].join("/");
|
|
13
|
+
}
|
|
14
|
+
async function ensureTransformersLoaded() {
|
|
15
|
+
if (_transformers !== null)
|
|
16
|
+
return _transformers;
|
|
17
|
+
const mod = (await import(getTransformersPackageSpecifier()));
|
|
18
|
+
mod.env.allowLocalModels = true;
|
|
19
|
+
mod.env.allowRemoteModels = true;
|
|
20
|
+
mod.env.cacheDir = join(CONFIG.storagePath, ".cache");
|
|
21
|
+
// CRITICAL: Disable WASM multi-threading. In Node.js/Bun (no SharedArrayBuffer),
|
|
22
|
+
// ONNX runtime hangs indefinitely during pipeline() init when threads > 1.
|
|
23
|
+
// See https://github.com/xenova/transformers.js/pull/488
|
|
24
|
+
try {
|
|
25
|
+
mod.env.backends.onnx.wasm.numThreads = 1;
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
log("Failed to set wasm.numThreads", { error: String(e) });
|
|
29
|
+
}
|
|
30
|
+
_transformers = mod;
|
|
31
|
+
return _transformers;
|
|
32
|
+
}
|
|
11
33
|
function withTimeout(promise, ms) {
|
|
12
34
|
return Promise.race([
|
|
13
35
|
promise,
|
|
@@ -40,9 +62,15 @@ export class EmbeddingService {
|
|
|
40
62
|
this.isWarmedUp = true;
|
|
41
63
|
return;
|
|
42
64
|
}
|
|
43
|
-
|
|
65
|
+
const { pipeline } = await ensureTransformersLoaded();
|
|
66
|
+
const pipelineOptions = {
|
|
44
67
|
progress_callback: progressCallback,
|
|
45
|
-
|
|
68
|
+
// Force quantized ONNX. Default is fp32 model.onnx which transformers v4
|
|
69
|
+
// tries to download from huggingface.co; cache only ships model_quantized.onnx
|
|
70
|
+
// and HF is unreachable behind GFW, causing init to fail.
|
|
71
|
+
dtype: "q8",
|
|
72
|
+
};
|
|
73
|
+
this.pipe = await pipeline("feature-extraction", CONFIG.embeddingModel, pipelineOptions);
|
|
46
74
|
this.isWarmedUp = true;
|
|
47
75
|
}
|
|
48
76
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"language-detector.d.ts","sourceRoot":"","sources":["../../src/services/language-detector.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"language-detector.d.ts","sourceRoot":"","sources":["../../src/services/language-detector.ts"],"names":[],"mappings":"AAWA,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAiBnD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQpD"}
|
|
@@ -1,16 +1,33 @@
|
|
|
1
1
|
import { franc } from "franc-min";
|
|
2
2
|
import { iso6393, iso6393To1 } from "iso-639-3";
|
|
3
|
+
// 3-letter codes without ISO 639-1 equivalents
|
|
4
|
+
const FALLBACK_MAP = {
|
|
5
|
+
cmn: "zh", // Mandarin Chinese
|
|
6
|
+
yue: "zh", // Cantonese
|
|
7
|
+
arz: "ar", // Egyptian Arabic
|
|
8
|
+
hbs: "sr", // Serbo-Croatian
|
|
9
|
+
};
|
|
3
10
|
export function detectLanguage(text) {
|
|
4
11
|
if (!text || text.trim().length === 0) {
|
|
5
12
|
return "en";
|
|
6
13
|
}
|
|
7
|
-
const detected = franc(text, { minLength:
|
|
14
|
+
const detected = franc(text, { minLength: 5 });
|
|
8
15
|
if (detected === "und") {
|
|
9
16
|
return "en";
|
|
10
17
|
}
|
|
11
|
-
|
|
18
|
+
// Try 2-letter mapping first
|
|
19
|
+
const twoLetter = iso6393To1[detected];
|
|
20
|
+
if (twoLetter)
|
|
21
|
+
return twoLetter;
|
|
22
|
+
// Fallback for 3-letter codes without 2-letter equivalent
|
|
23
|
+
return FALLBACK_MAP[detected] || "en";
|
|
12
24
|
}
|
|
13
25
|
export function getLanguageName(code) {
|
|
14
|
-
|
|
26
|
+
// Try 2-letter lookup first
|
|
27
|
+
let lang = iso6393.find((l) => l.iso6391 === code);
|
|
28
|
+
// Fallback to 3-letter lookup
|
|
29
|
+
if (!lang) {
|
|
30
|
+
lang = iso6393.find((l) => l.iso6393 === code);
|
|
31
|
+
}
|
|
15
32
|
return lang?.name || "English";
|
|
16
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/vector-search.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE,QAAA,MAAM,QAAQ,sCAAgB,CAAC;AAC/B,KAAK,YAAY,GAAG,OAAO,QAAQ,CAAC,SAAS,CAAC;AAM9C,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;IACxD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAgB;gBAEpC,OAAO,CAAC,EAAE,aAAa,EAAE,eAAe,GAAE,aAAsC;YAO9E,UAAU;IAIlB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAyCtF,aAAa,CACjB,KAAK,EAAE,SAAS,EAChB,WAAW,EAAE,YAAY,EACzB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/vector-search.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE,QAAA,MAAM,QAAQ,sCAAgB,CAAC;AAC/B,KAAK,YAAY,GAAG,OAAO,QAAQ,CAAC,SAAS,CAAC;AAM9C,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;IACxD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAgB;gBAEpC,OAAO,CAAC,EAAE,aAAa,EAAE,eAAe,GAAE,aAAsC;YAO9E,UAAU;IAIlB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAyCtF,aAAa,CACjB,KAAK,EAAE,SAAS,EAChB,WAAW,EAAE,YAAY,EACzB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC;IA8HpB,kBAAkB,CACtB,MAAM,EAAE,SAAS,EAAE,EACnB,WAAW,EAAE,YAAY,EACzB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,mBAAmB,EAAE,MAAM,EAC3B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC;IAiBpB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlF,YAAY,CAChB,EAAE,EAAE,YAAY,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,EACpB,KAAK,CAAC,EAAE,SAAS,EACjB,UAAU,CAAC,EAAE,YAAY,GACxB,OAAO,CAAC,IAAI,CAAC;IAkBhB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;IAmB1E,cAAc,CAAC,EAAE,EAAE,YAAY,GAAG,GAAG,EAAE;IAKvC,aAAa,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAK7D,sBAAsB,CAAC,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE;IAgBlE,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;IAM5D,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,MAAM;IAMzC,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,GAAG,EAAE;IAexC,SAAS,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKnD,WAAW,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK/C,oBAAoB,CACxB,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAgBV,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;CAI1D;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
|
|
@@ -106,11 +106,16 @@ export class VectorSearch {
|
|
|
106
106
|
return [];
|
|
107
107
|
const placeholders = ids.map(() => "?").join(",");
|
|
108
108
|
const rows = db
|
|
109
|
-
.prepare(
|
|
109
|
+
.prepare(containerTag === ""
|
|
110
|
+
? `
|
|
111
|
+
SELECT * FROM memories
|
|
112
|
+
WHERE id IN (${placeholders})
|
|
113
|
+
`
|
|
114
|
+
: `
|
|
110
115
|
SELECT * FROM memories
|
|
111
116
|
WHERE id IN (${placeholders}) AND container_tag = ?
|
|
112
117
|
`)
|
|
113
|
-
.all(...ids, containerTag);
|
|
118
|
+
.all(...ids, ...(containerTag === "" ? [] : [containerTag]));
|
|
114
119
|
const queryWords = queryText
|
|
115
120
|
? queryText
|
|
116
121
|
.toLowerCase()
|
|
@@ -184,13 +189,19 @@ export class VectorSearch {
|
|
|
184
189
|
}
|
|
185
190
|
}
|
|
186
191
|
listMemories(db, containerTag, limit) {
|
|
187
|
-
const stmt = db.prepare(
|
|
192
|
+
const stmt = db.prepare(containerTag === ""
|
|
193
|
+
? `
|
|
194
|
+
SELECT * FROM memories
|
|
195
|
+
ORDER BY created_at DESC
|
|
196
|
+
LIMIT ?
|
|
197
|
+
`
|
|
198
|
+
: `
|
|
188
199
|
SELECT * FROM memories
|
|
189
200
|
WHERE container_tag = ?
|
|
190
201
|
ORDER BY created_at DESC
|
|
191
202
|
LIMIT ?
|
|
192
203
|
`);
|
|
193
|
-
return stmt.all(containerTag, limit);
|
|
204
|
+
return (containerTag === "" ? stmt.all(limit) : stmt.all(containerTag, limit));
|
|
194
205
|
}
|
|
195
206
|
getAllMemories(db) {
|
|
196
207
|
const stmt = db.prepare(`SELECT * FROM memories ORDER BY created_at DESC`);
|
|
@@ -106,10 +106,14 @@ ${existingProfile ? "Merge with existing profile, incrementing frequencies and u
|
|
|
106
106
|
}
|
|
107
107
|
async function analyzeUserProfile(context, existingProfile) {
|
|
108
108
|
if (CONFIG.opencodeProvider && CONFIG.opencodeModel) {
|
|
109
|
-
const { isProviderConnected,
|
|
109
|
+
const { isProviderConnected, getV2Client, generateStructuredOutput } = await import("./ai/opencode-provider.js");
|
|
110
110
|
if (!isProviderConnected(CONFIG.opencodeProvider)) {
|
|
111
111
|
throw new Error(`opencode provider '${CONFIG.opencodeProvider}' is not connected. Check your opencode provider configuration.`);
|
|
112
112
|
}
|
|
113
|
+
const v2Client = getV2Client();
|
|
114
|
+
if (!v2Client) {
|
|
115
|
+
throw new Error("opencode-mem: v2 client not initialized; cannot perform user-profile learning");
|
|
116
|
+
}
|
|
113
117
|
const systemPrompt = `You are a user behavior analyst for a coding assistant.
|
|
114
118
|
|
|
115
119
|
Your task is to analyze user prompts and ${existingProfile ? "update" : "create"} a comprehensive user profile.
|
|
@@ -135,13 +139,12 @@ Use the update_user_profile tool to save the ${existingProfile ? "updated" : "ne
|
|
|
135
139
|
})),
|
|
136
140
|
});
|
|
137
141
|
const result = await generateStructuredOutput({
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
142
|
+
client: v2Client,
|
|
143
|
+
providerID: CONFIG.opencodeProvider,
|
|
144
|
+
modelID: CONFIG.opencodeModel,
|
|
141
145
|
systemPrompt,
|
|
142
146
|
userPrompt: context,
|
|
143
147
|
schema,
|
|
144
|
-
temperature: CONFIG.memoryTemperature === false ? undefined : (CONFIG.memoryTemperature ?? 0.3),
|
|
145
148
|
});
|
|
146
149
|
if (existingProfile) {
|
|
147
150
|
const existingData = JSON.parse(existingProfile.profileData);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-mem",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.1",
|
|
4
4
|
"description": "OpenCode plugin that gives coding agents persistent memory using local vector database",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/plugin.js",
|
|
@@ -43,12 +43,9 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@
|
|
47
|
-
"@ai-sdk/openai": "^3.0.41",
|
|
46
|
+
"@huggingface/transformers": "4.0.1",
|
|
48
47
|
"@opencode-ai/plugin": "^1.3.0",
|
|
49
48
|
"@opencode-ai/sdk": "^1.3.0",
|
|
50
|
-
"@xenova/transformers": "^2.17.2",
|
|
51
|
-
"ai": "^6.0.116",
|
|
52
49
|
"franc-min": "^6.2.0",
|
|
53
50
|
"iso-639-3": "^3.0.1",
|
|
54
51
|
"usearch": "^2.21.4",
|