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.
@@ -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 { scope, hash } = extractScopeFromContainerTag(containerTag);
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 { scope, hash } = extractScopeFromContainerTag(containerTag);
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":"AAoBA,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;IAiBvB,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
+ {"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
- this.pipe = await pipeline("feature-extraction", CONFIG.embeddingModel, {
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":"AAGA,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYnD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGpD"}
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: 10 });
14
+ const detected = franc(text, { minLength: 5 });
8
15
  if (detected === "und") {
9
16
  return "en";
10
17
  }
11
- return iso6393To1[detected] || "en";
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
- const lang = iso6393.find((l) => l.iso6391 === code);
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;IAyHpB,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;IAW1E,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"}
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, getStatePath, generateStructuredOutput } = await import("./ai/opencode-provider.js");
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
- providerName: CONFIG.opencodeProvider,
139
- modelId: CONFIG.opencodeModel,
140
- statePath: getStatePath(),
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.13.0",
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
- "@ai-sdk/anthropic": "^3.0.58",
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",