@oh-my-pi/pi-mnemopi 17.0.3 → 17.0.5
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/CHANGELOG.md +6 -0
- package/dist/types/core/embeddings.d.ts +21 -0
- package/dist/types/core/index.d.ts +1 -1
- package/package.json +4 -4
- package/src/core/embeddings.ts +58 -10
- package/src/core/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.0.4] - 2026-07-18
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed a corrupt cached embedding model (truncated `model_optimized.onnx`, `Protobuf parsing failed` on load) permanently disabling local embeddings: init now quarantines the broken cache file (rename to `*.corrupt-<ts>`, only when the path resolves inside the fastembed cache directory) and retries once so the model re-downloads.
|
|
10
|
+
|
|
5
11
|
## [17.0.1] - 2026-07-16
|
|
6
12
|
|
|
7
13
|
### Fixed
|
|
@@ -19,6 +19,27 @@ export type LocalModelInitOptions = {
|
|
|
19
19
|
showDownloadProgress?: boolean;
|
|
20
20
|
};
|
|
21
21
|
export type LocalModelInitializer = (options: LocalModelInitOptions) => Promise<LocalEmbeddingModel>;
|
|
22
|
+
/**
|
|
23
|
+
* Quarantine the exact ONNX file named by a "Protobuf parsing failed" init
|
|
24
|
+
* error. A truncated cached model blocks local embeddings forever: the
|
|
25
|
+
* downloader treats the existing file as complete, so every init re-parses
|
|
26
|
+
* the same broken bytes. The extracted path is error-message CONTENT, so it
|
|
27
|
+
* is only honored when it resolves inside the fastembed cache directory —
|
|
28
|
+
* never rename an arbitrary file a dependency happens to mention. Atomic
|
|
29
|
+
* rename; losing a concurrent-heal race (file already renamed/removed)
|
|
30
|
+
* still returns true because a retry is safe either way.
|
|
31
|
+
* @internal exported for tests
|
|
32
|
+
*/
|
|
33
|
+
export declare function quarantineCorruptModelFile(message: string, cacheDir?: string): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Shared local-model initializer: FlagEmbedding.init with BOTH cache heals.
|
|
36
|
+
* Missing sidecars (config/tokenizer/tokens map) re-fetch and retry; a
|
|
37
|
+
* corrupt model blob (Protobuf parse failure) quarantines the file and
|
|
38
|
+
* retries THROUGH the sidecar heal, so a cache that is broken in both ways
|
|
39
|
+
* still recovers in one pass. Also the initializer the embed worker uses in
|
|
40
|
+
* its subprocess; the in-process seam stays {@link setLocalModelInitializer}.
|
|
41
|
+
*/
|
|
42
|
+
export declare function defaultLocalModelInitializer(options: LocalModelInitOptions): Promise<LocalEmbeddingModel>;
|
|
22
43
|
export declare function embeddingsDisabled(): boolean;
|
|
23
44
|
/**
|
|
24
45
|
* Resolve the embedding model name for the currently active runtime scope.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { configureRecallFeatures, type RecallFeatureFlags } from "../config.js";
|
|
2
2
|
export * from "./banks.js";
|
|
3
3
|
export * from "./beam/index.js";
|
|
4
|
-
export { type LocalEmbeddingModel, type LocalModelInitializer, type LocalModelInitOptions, type StandardEmbeddingModel, setLocalModelInitializer, } from "./embeddings.js";
|
|
4
|
+
export { defaultLocalModelInitializer, type LocalEmbeddingModel, type LocalModelInitializer, type LocalModelInitOptions, type StandardEmbeddingModel, setLocalModelInitializer, } from "./embeddings.js";
|
|
5
5
|
export * from "./memory.js";
|
|
6
6
|
export { addMemory, forget, get, getBank, getContext, getDefaultInstance, getStats, Mnemopi, query, recall, recallEnhanced, remember, resetDefaultInstanceForTests, resetMemoryForTests, resetModuleStateForTests, saveMemory, scratchpadClear, scratchpadRead, scratchpadWrite, search, setBank, sleep, sleepAllSessions, storeMemory, update, } from "./memory.js";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-mnemopi",
|
|
4
|
-
"version": "17.0.
|
|
4
|
+
"version": "17.0.5",
|
|
5
5
|
"description": "Local SQLite memory engine for Oh My Pi agents",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"fmt": "biome format --write ."
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@oh-my-pi/pi-ai": "17.0.
|
|
43
|
-
"@oh-my-pi/pi-catalog": "17.0.
|
|
44
|
-
"@oh-my-pi/pi-utils": "17.0.
|
|
42
|
+
"@oh-my-pi/pi-ai": "17.0.5",
|
|
43
|
+
"@oh-my-pi/pi-catalog": "17.0.5",
|
|
44
|
+
"@oh-my-pi/pi-utils": "17.0.5",
|
|
45
45
|
"lru-cache": "11.5.2"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
package/src/core/embeddings.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { mkdirSync } from "node:fs";
|
|
2
|
+
import * as fsp from "node:fs/promises";
|
|
3
|
+
import * as nodePath from "node:path";
|
|
2
4
|
import { type ApiKey, getOpenRouterHeaders, withAuth } from "@oh-my-pi/pi-ai";
|
|
3
5
|
import { ProviderHttpError } from "@oh-my-pi/pi-ai/error";
|
|
4
6
|
import { hostMatchesUrl } from "@oh-my-pi/pi-catalog/hosts";
|
|
@@ -62,21 +64,67 @@ const queryCache = new LRUCache<string, Vector>({ max: QUERY_CACHE_MAX });
|
|
|
62
64
|
const providerIds = new WeakMap<object, number>();
|
|
63
65
|
let nextProviderId = 1;
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Quarantine the exact ONNX file named by a "Protobuf parsing failed" init
|
|
69
|
+
* error. A truncated cached model blocks local embeddings forever: the
|
|
70
|
+
* downloader treats the existing file as complete, so every init re-parses
|
|
71
|
+
* the same broken bytes. The extracted path is error-message CONTENT, so it
|
|
72
|
+
* is only honored when it resolves inside the fastembed cache directory —
|
|
73
|
+
* never rename an arbitrary file a dependency happens to mention. Atomic
|
|
74
|
+
* rename; losing a concurrent-heal race (file already renamed/removed)
|
|
75
|
+
* still returns true because a retry is safe either way.
|
|
76
|
+
* @internal exported for tests
|
|
77
|
+
*/
|
|
78
|
+
export async function quarantineCorruptModelFile(message: string, cacheDir?: string): Promise<boolean> {
|
|
79
|
+
const match = /Load model from (.+?\.onnx) failed:.*Protobuf parsing failed/i.exec(message);
|
|
80
|
+
if (!match) return false;
|
|
81
|
+
const modelFile = nodePath.resolve(match[1]);
|
|
82
|
+
const cacheRoot = nodePath.resolve(cacheDir ?? getFastembedCacheDir());
|
|
83
|
+
if (!modelFile.startsWith(cacheRoot + nodePath.sep)) return false;
|
|
84
|
+
try {
|
|
85
|
+
await fsp.rename(modelFile, `${modelFile}.corrupt-${Date.now()}`);
|
|
86
|
+
logger.warn("mnemopi: quarantined corrupt local embedding model; retrying init", { modelFile });
|
|
87
|
+
} catch {
|
|
88
|
+
// Concurrent heal or vanished file: the single retry stays safe. A
|
|
89
|
+
// rename that failed with the file still in place just makes the
|
|
90
|
+
// retry surface the original error again.
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const SIDECAR_ERROR_RE =
|
|
96
|
+
/(?:Config file not found at .*config|Tokenizer file not found at .*tokenizer|Tokens map file not found at .*special_tokens_map)/u;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Shared local-model initializer: FlagEmbedding.init with BOTH cache heals.
|
|
100
|
+
* Missing sidecars (config/tokenizer/tokens map) re-fetch and retry; a
|
|
101
|
+
* corrupt model blob (Protobuf parse failure) quarantines the file and
|
|
102
|
+
* retries THROUGH the sidecar heal, so a cache that is broken in both ways
|
|
103
|
+
* still recovers in one pass. Also the initializer the embed worker uses in
|
|
104
|
+
* its subprocess; the in-process seam stays {@link setLocalModelInitializer}.
|
|
105
|
+
*/
|
|
106
|
+
export async function defaultLocalModelInitializer(options: LocalModelInitOptions): Promise<LocalEmbeddingModel> {
|
|
107
|
+
const cacheDir = options.cacheDir ?? getFastembedCacheDir();
|
|
108
|
+
const initOptions = options.cacheDir === undefined ? { ...options, cacheDir } : options;
|
|
66
109
|
const { FlagEmbedding } = await loadFastembed();
|
|
110
|
+
const initWithSidecarHeal = async (): Promise<LocalEmbeddingModel> => {
|
|
111
|
+
try {
|
|
112
|
+
return await FlagEmbedding.init(initOptions);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
const message = error instanceof Error ? error.message : "";
|
|
115
|
+
if (!SIDECAR_ERROR_RE.test(message)) throw error;
|
|
116
|
+
if (!(await ensureFastembedModelSidecars(options.model, cacheDir))) throw error;
|
|
117
|
+
return FlagEmbedding.init(initOptions);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
67
120
|
try {
|
|
68
|
-
return await
|
|
121
|
+
return await initWithSidecarHeal();
|
|
69
122
|
} catch (error) {
|
|
70
123
|
const message = error instanceof Error ? error.message : "";
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
message,
|
|
74
|
-
)
|
|
75
|
-
) {
|
|
76
|
-
throw error;
|
|
124
|
+
if (/Protobuf parsing failed/i.test(message) && (await quarantineCorruptModelFile(message, cacheDir))) {
|
|
125
|
+
return initWithSidecarHeal();
|
|
77
126
|
}
|
|
78
|
-
|
|
79
|
-
return FlagEmbedding.init(options);
|
|
127
|
+
throw error;
|
|
80
128
|
}
|
|
81
129
|
}
|
|
82
130
|
|
package/src/core/index.ts
CHANGED