@glassly/engine 0.3.8-dev.1012 → 0.3.8-dev.1015

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/build/engine.d.ts CHANGED
@@ -163,6 +163,9 @@ export declare const engine: {
163
163
  models: () => import(".").LocalLlmModelConfig[];
164
164
  selectedModelId: () => import(".").LocalLlmModelId;
165
165
  select: (id: import(".").LocalLlmModelId) => Promise<void>;
166
+ fitsDevice: (id: import(".").LocalLlmModelId) => boolean;
167
+ isBlocked: (id: import(".").LocalLlmModelId) => boolean;
168
+ unusableReason: (id: import(".").LocalLlmModelId) => string | null;
166
169
  isDownloaded: (id?: import(".").LocalLlmModelId) => Promise<boolean>;
167
170
  downloadedModelIds: () => Promise<import(".").LocalLlmModelId[]>;
168
171
  download: (id?: import(".").LocalLlmModelId) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAC,SAAS,EAAkD,aAAa,EAAC,MAAM,qBAAqB,CAAA;AAgD5G,eAAO,MAAM,MAAM;IACjB,2EAA2E;;IAE3E,8EAA8E;;IAE9E;iDAC6C;;IAyG7C;;yCAEqC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoCrC,kEAAkE;;;;;;;;;;;;;;;;;;;;IAElE,wEAAwE;;;;;;;;IAExE,sEAAsE;;;;;;;;;;;;IAEtE,qFAAqF;;;;gBA3IpE,CAAC;mBAAiB,CAAC;;;gBAMlC,CAFF;mBAAiB,CAAC;;;;;;;;;;;;;;;;;IAyIlB,qEAAqE;;;;IAErE,sFAAsF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAEtF,6EAA6E;;;;;;;;;;;;IAE7E,iFAAiF;;;;;;;;;;;;;;;;;;;;;;;;IAEjF,8CAA8C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAE9C,uDAAuD;;;;;;;;;;;;;;;;IAEvD,8EAA8E;;;;;;;;IAE9E,qFAAqF;;;;IAErF,4EAA4E;;;;;;;;;;;IAE5E,+EAA+E;;;;;;;;;;;;;;;IAE/E,qGAAqG;;;;;;IAErG,kFAAkF;;;;;+BArKjE,CAAC;;;+BAUiC,CAAC;;;;;;;;;;;;IA6JpD,mFAAmF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAGpF,CAAA"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAC,SAAS,EAAkD,aAAa,EAAC,MAAM,qBAAqB,CAAA;AAgD5G,eAAO,MAAM,MAAM;IACjB,2EAA2E;;IAE3E,8EAA8E;;IAE9E;iDAC6C;;IAyG7C;;yCAEqC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoCrC,kEAAkE;;;;;;;;;;;;;;;;;;;;;;;IAElE,wEAAwE;;;;;;;;IAExE,sEAAsE;;;;;;;;;;;;IAEtE,qFAAqF;;;;gBA3IpE,CAAC;mBAAiB,CAAC;;;gBAMlC,CAFF;mBAAiB,CAAC;;;;;;;;;;;;;;;;;IAyIlB,qEAAqE;;;;IAErE,sFAAsF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAEtF,6EAA6E;;;;;;;;;;;;IAE7E,iFAAiF;;;;;;;;;;;;;;;;;;;;;;;;IAEjF,8CAA8C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAE9C,uDAAuD;;;;;;;;;;;;;;;;IAEvD,8EAA8E;;;;;;;;IAE9E,qFAAqF;;;;IAErF,4EAA4E;;;;;;;;;;;IAE5E,+EAA+E;;;;;;;;;;;;;;;IAE/E,qGAAqG;;;;;;IAErG,kFAAkF;;;;;+BArKjE,CAAC;;;+BAUiC,CAAC;;;;;;;;;;;;IA6JpD,mFAAmF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAGpF,CAAA"}
@@ -14,6 +14,12 @@ export declare const ai: {
14
14
  /** The Settings > Offline Models pick. */
15
15
  selectedModelId: () => LocalLlmModelId;
16
16
  select: (id: LocalLlmModelId) => Promise<void>;
17
+ /** Whether the model fits this phone's memory at all. */
18
+ fitsDevice: (id: LocalLlmModelId) => boolean;
19
+ /** The OS killed the app while this model loaded; picking it again retries it. */
20
+ isBlocked: (id: LocalLlmModelId) => boolean;
21
+ /** Why the model will not load on this phone, or null when it can. */
22
+ unusableReason: (id: LocalLlmModelId) => string | null;
17
23
  isDownloaded: (id?: LocalLlmModelId) => Promise<boolean>;
18
24
  downloadedModelIds: () => Promise<LocalLlmModelId[]>;
19
25
  download: (id?: LocalLlmModelId) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/facades/ai.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAA6B,EAE3B,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACrB,MAAM,kCAAkC,CAAA;AAEzC,OAAwB,EAAC,KAAK,eAAe,EAAC,MAAM,6BAA6B,CAAA;AAEjF,eAAO,MAAM,EAAE;;QAEX,sCAAsC;sBAC1B,mBAAmB,EAAE;QACjC,0CAA0C;+BACrB,eAAe;qBACvB,eAAe,KAAG,OAAO,CAAC,IAAI,CAAC;4BACxB,eAAe,KAAG,OAAO,CAAC,OAAO,CAAC;kCAC9B,OAAO,CAAC,eAAe,EAAE,CAAC;wBAClC,eAAe,KAAG,OAAO,CAAC,IAAI,CAAC;8BAC3B,OAAO,CAAC,IAAI,CAAC;QACjC,4EAA4E;2BACnD,eAAe,KAAG,OAAO,CAAC,IAAI,CAAC;sBAK5C,sBAAsB,GAAG,IAAI;QACzC,uFAAuF;oCAC3D,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI,KAAK,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC;QAE1F,uDAAuD;wBACzC,OAAO;QACrB,4DAA4D;sBAChD,OAAO,CAAC,IAAI,CAAC;6BACJ,MAAM,KAAG,MAAM;;2BAEjB,eAAe;kCACN,CAAC,QAAQ,EAAE,eAAe,KAAK,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC;CAEjF,CAAA"}
1
+ {"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/facades/ai.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAA6B,EAE3B,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACrB,MAAM,kCAAkC,CAAA;AAEzC,OAAwB,EAAC,KAAK,eAAe,EAAC,MAAM,6BAA6B,CAAA;AAEjF,eAAO,MAAM,EAAE;;QAEX,sCAAsC;sBAC1B,mBAAmB,EAAE;QACjC,0CAA0C;+BACrB,eAAe;qBACvB,eAAe,KAAG,OAAO,CAAC,IAAI,CAAC;QAC5C,yDAAyD;yBACxC,eAAe,KAAG,OAAO;QAC1C,kFAAkF;wBAClE,eAAe,KAAG,OAAO;QACzC,sEAAsE;6BACjD,eAAe,KAAG,MAAM,GAAG,IAAI;4BAChC,eAAe,KAAG,OAAO,CAAC,OAAO,CAAC;kCAC9B,OAAO,CAAC,eAAe,EAAE,CAAC;wBAClC,eAAe,KAAG,OAAO,CAAC,IAAI,CAAC;8BAC3B,OAAO,CAAC,IAAI,CAAC;QACjC,4EAA4E;2BACnD,eAAe,KAAG,OAAO,CAAC,IAAI,CAAC;sBAK5C,sBAAsB,GAAG,IAAI;QACzC,uFAAuF;oCAC3D,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI,KAAK,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC;QAE1F,uDAAuD;wBACzC,OAAO;QACrB,4DAA4D;sBAChD,OAAO,CAAC,IAAI,CAAC;6BACJ,MAAM,KAAG,MAAM;;2BAEjB,eAAe;kCACN,CAAC,QAAQ,EAAE,eAAe,KAAK,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC;CAEjF,CAAA"}
@@ -15,6 +15,12 @@ export const ai = {
15
15
  /** The Settings > Offline Models pick. */
16
16
  selectedModelId: () => localLlmModelManager.selectedModelId(),
17
17
  select: (id) => localLlmModelManager.select(id),
18
+ /** Whether the model fits this phone's memory at all. */
19
+ fitsDevice: (id) => localLlmModelManager.fitsDevice(id),
20
+ /** The OS killed the app while this model loaded; picking it again retries it. */
21
+ isBlocked: (id) => localLlmModelManager.isBlocked(id),
22
+ /** Why the model will not load on this phone, or null when it can. */
23
+ unusableReason: (id) => localLlmModelManager.unusableReason(id),
18
24
  isDownloaded: (id) => localLlmModelManager.isModelAvailable(id),
19
25
  downloadedModelIds: () => localLlmModelManager.downloadedModelIds(),
20
26
  download: (id) => localLlmModelManager.downloadModel(id),
@@ -1 +1 @@
1
- {"version":3,"file":"ai.js","sourceRoot":"","sources":["../../src/facades/ai.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,oBAAoB,EAAE,EAC3B,oBAAoB,GAIrB,MAAM,kCAAkC,CAAA;AACzC,OAAO,gBAAgB,MAAM,8BAA8B,CAAA;AAC3D,OAAO,eAAuC,MAAM,6BAA6B,CAAA;AAEjF,MAAM,CAAC,MAAM,EAAE,GAAG;IAChB,KAAK,EAAE;QACL,sCAAsC;QACtC,MAAM,EAAE,GAA0B,EAAE,CAAC,oBAAoB,CAAC,MAAM,EAAE;QAClE,0CAA0C;QAC1C,eAAe,EAAE,GAAoB,EAAE,CAAC,oBAAoB,CAAC,eAAe,EAAE;QAC9E,MAAM,EAAE,CAAC,EAAmB,EAAiB,EAAE,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/E,YAAY,EAAE,CAAC,EAAoB,EAAoB,EAAE,CAAC,oBAAoB,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACnG,kBAAkB,EAAE,GAA+B,EAAE,CAAC,oBAAoB,CAAC,kBAAkB,EAAE;QAC/F,QAAQ,EAAE,CAAC,EAAoB,EAAiB,EAAE,CAAC,oBAAoB,CAAC,aAAa,CAAC,EAAE,CAAC;QACzF,cAAc,EAAE,GAAkB,EAAE,CAAC,oBAAoB,CAAC,cAAc,EAAE;QAC1E,4EAA4E;QAC5E,WAAW,EAAE,KAAK,EAAE,EAAoB,EAAiB,EAAE;YACzD,MAAM,MAAM,GAAG,EAAE,IAAI,oBAAoB,CAAC,eAAe,EAAE,CAAA;YAC3D,IAAI,gBAAgB,CAAC,aAAa,EAAE,KAAK,MAAM;gBAAE,MAAM,gBAAgB,CAAC,MAAM,EAAE,CAAA;YAChF,MAAM,oBAAoB,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;QAChD,CAAC;QACD,MAAM,EAAE,GAAkC,EAAE,CAAC,oBAAoB,CAAC,SAAS,EAAE;QAC7E,uFAAuF;QACvF,eAAe,EAAE,CAAC,QAAyD,EAAgB,EAAE,CAC3F,oBAAoB,CAAC,SAAS,CAAC,QAAQ,CAAC;QAC1C,uDAAuD;QACvD,QAAQ,EAAE,GAAY,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE;QACpD,4DAA4D;QAC5D,MAAM,EAAE,GAAkB,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE;QACtD,WAAW,EAAE,CAAC,KAAa,EAAU,EAAE,CAAC,oBAAoB,CAAC,WAAW,CAAC,KAAK,CAAC;KAChF;IACD,eAAe,EAAE,GAAoB,EAAE,CAAC,eAAe,CAAC,OAAO,EAAE;IACjE,iBAAiB,EAAE,CAAC,QAA6C,EAAgB,EAAE,CACjF,eAAe,CAAC,SAAS,CAAC,QAAQ,CAAC;CACtC,CAAA","sourcesContent":["/**\n * ai facade — `engine.ai.local`: the on-device LLM model that serves\n * session.ai calls routed to the phone (forceLocal / localFallback). Thin\n * passthrough over LocalLlmModelManager and LocalLlmProvider so Settings >\n * Offline Models can list, pick, download, and delete models without\n * reaching into engine internals.\n */\nimport localLlmModelManager, {\n LocalLlmModelManager,\n type LocalLlmDownloadStatus,\n type LocalLlmModelConfig,\n type LocalLlmModelId,\n} from \"../services/LocalLlmModelManager\"\nimport localLlmProvider from \"../services/LocalLlmProvider\"\nimport offlineActivity, {type OfflineActivity} from \"../services/OfflineActivity\"\n\nexport const ai = {\n local: {\n /** The catalog, in dropdown order. */\n models: (): LocalLlmModelConfig[] => localLlmModelManager.models(),\n /** The Settings > Offline Models pick. */\n selectedModelId: (): LocalLlmModelId => localLlmModelManager.selectedModelId(),\n select: (id: LocalLlmModelId): Promise<void> => localLlmModelManager.select(id),\n isDownloaded: (id?: LocalLlmModelId): Promise<boolean> => localLlmModelManager.isModelAvailable(id),\n downloadedModelIds: (): Promise<LocalLlmModelId[]> => localLlmModelManager.downloadedModelIds(),\n download: (id?: LocalLlmModelId): Promise<void> => localLlmModelManager.downloadModel(id),\n cancelDownload: (): Promise<void> => localLlmModelManager.cancelDownload(),\n /** Removes the files and drops the resident runner if it was this model. */\n deleteModel: async (id?: LocalLlmModelId): Promise<void> => {\n const target = id ?? localLlmModelManager.selectedModelId()\n if (localLlmProvider.loadedModelId() === target) await localLlmProvider.unload()\n await localLlmModelManager.deleteModel(target)\n },\n status: (): LocalLlmDownloadStatus | null => localLlmModelManager.getStatus(),\n /** Subscribe to download status changes; fires immediately, returns an unsubscribe. */\n onStatusChanged: (listener: (status: LocalLlmDownloadStatus | null) => void): (() => void) =>\n localLlmModelManager.subscribe(listener),\n /** Whether a model is resident in memory right now. */\n isLoaded: (): boolean => localLlmProvider.isLoaded(),\n /** Drop the resident model (memory pressure, debugging). */\n unload: (): Promise<void> => localLlmProvider.unload(),\n formatBytes: (bytes: number): string => LocalLlmModelManager.formatBytes(bytes),\n },\n offlineActivity: (): OfflineActivity => offlineActivity.current(),\n onOfflineActivity: (listener: (activity: OfflineActivity) => void): (() => void) =>\n offlineActivity.subscribe(listener),\n}\n"]}
1
+ {"version":3,"file":"ai.js","sourceRoot":"","sources":["../../src/facades/ai.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,oBAAoB,EAAE,EAC3B,oBAAoB,GAIrB,MAAM,kCAAkC,CAAA;AACzC,OAAO,gBAAgB,MAAM,8BAA8B,CAAA;AAC3D,OAAO,eAAuC,MAAM,6BAA6B,CAAA;AAEjF,MAAM,CAAC,MAAM,EAAE,GAAG;IAChB,KAAK,EAAE;QACL,sCAAsC;QACtC,MAAM,EAAE,GAA0B,EAAE,CAAC,oBAAoB,CAAC,MAAM,EAAE;QAClE,0CAA0C;QAC1C,eAAe,EAAE,GAAoB,EAAE,CAAC,oBAAoB,CAAC,eAAe,EAAE;QAC9E,MAAM,EAAE,CAAC,EAAmB,EAAiB,EAAE,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/E,yDAAyD;QACzD,UAAU,EAAE,CAAC,EAAmB,EAAW,EAAE,CAAC,oBAAoB,CAAC,UAAU,CAAC,EAAE,CAAC;QACjF,kFAAkF;QAClF,SAAS,EAAE,CAAC,EAAmB,EAAW,EAAE,CAAC,oBAAoB,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/E,sEAAsE;QACtE,cAAc,EAAE,CAAC,EAAmB,EAAiB,EAAE,CAAC,oBAAoB,CAAC,cAAc,CAAC,EAAE,CAAC;QAC/F,YAAY,EAAE,CAAC,EAAoB,EAAoB,EAAE,CAAC,oBAAoB,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACnG,kBAAkB,EAAE,GAA+B,EAAE,CAAC,oBAAoB,CAAC,kBAAkB,EAAE;QAC/F,QAAQ,EAAE,CAAC,EAAoB,EAAiB,EAAE,CAAC,oBAAoB,CAAC,aAAa,CAAC,EAAE,CAAC;QACzF,cAAc,EAAE,GAAkB,EAAE,CAAC,oBAAoB,CAAC,cAAc,EAAE;QAC1E,4EAA4E;QAC5E,WAAW,EAAE,KAAK,EAAE,EAAoB,EAAiB,EAAE;YACzD,MAAM,MAAM,GAAG,EAAE,IAAI,oBAAoB,CAAC,eAAe,EAAE,CAAA;YAC3D,IAAI,gBAAgB,CAAC,aAAa,EAAE,KAAK,MAAM;gBAAE,MAAM,gBAAgB,CAAC,MAAM,EAAE,CAAA;YAChF,MAAM,oBAAoB,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;QAChD,CAAC;QACD,MAAM,EAAE,GAAkC,EAAE,CAAC,oBAAoB,CAAC,SAAS,EAAE;QAC7E,uFAAuF;QACvF,eAAe,EAAE,CAAC,QAAyD,EAAgB,EAAE,CAC3F,oBAAoB,CAAC,SAAS,CAAC,QAAQ,CAAC;QAC1C,uDAAuD;QACvD,QAAQ,EAAE,GAAY,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE;QACpD,4DAA4D;QAC5D,MAAM,EAAE,GAAkB,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE;QACtD,WAAW,EAAE,CAAC,KAAa,EAAU,EAAE,CAAC,oBAAoB,CAAC,WAAW,CAAC,KAAK,CAAC;KAChF;IACD,eAAe,EAAE,GAAoB,EAAE,CAAC,eAAe,CAAC,OAAO,EAAE;IACjE,iBAAiB,EAAE,CAAC,QAA6C,EAAgB,EAAE,CACjF,eAAe,CAAC,SAAS,CAAC,QAAQ,CAAC;CACtC,CAAA","sourcesContent":["/**\n * ai facade — `engine.ai.local`: the on-device LLM model that serves\n * session.ai calls routed to the phone (forceLocal / localFallback). Thin\n * passthrough over LocalLlmModelManager and LocalLlmProvider so Settings >\n * Offline Models can list, pick, download, and delete models without\n * reaching into engine internals.\n */\nimport localLlmModelManager, {\n LocalLlmModelManager,\n type LocalLlmDownloadStatus,\n type LocalLlmModelConfig,\n type LocalLlmModelId,\n} from \"../services/LocalLlmModelManager\"\nimport localLlmProvider from \"../services/LocalLlmProvider\"\nimport offlineActivity, {type OfflineActivity} from \"../services/OfflineActivity\"\n\nexport const ai = {\n local: {\n /** The catalog, in dropdown order. */\n models: (): LocalLlmModelConfig[] => localLlmModelManager.models(),\n /** The Settings > Offline Models pick. */\n selectedModelId: (): LocalLlmModelId => localLlmModelManager.selectedModelId(),\n select: (id: LocalLlmModelId): Promise<void> => localLlmModelManager.select(id),\n /** Whether the model fits this phone's memory at all. */\n fitsDevice: (id: LocalLlmModelId): boolean => localLlmModelManager.fitsDevice(id),\n /** The OS killed the app while this model loaded; picking it again retries it. */\n isBlocked: (id: LocalLlmModelId): boolean => localLlmModelManager.isBlocked(id),\n /** Why the model will not load on this phone, or null when it can. */\n unusableReason: (id: LocalLlmModelId): string | null => localLlmModelManager.unusableReason(id),\n isDownloaded: (id?: LocalLlmModelId): Promise<boolean> => localLlmModelManager.isModelAvailable(id),\n downloadedModelIds: (): Promise<LocalLlmModelId[]> => localLlmModelManager.downloadedModelIds(),\n download: (id?: LocalLlmModelId): Promise<void> => localLlmModelManager.downloadModel(id),\n cancelDownload: (): Promise<void> => localLlmModelManager.cancelDownload(),\n /** Removes the files and drops the resident runner if it was this model. */\n deleteModel: async (id?: LocalLlmModelId): Promise<void> => {\n const target = id ?? localLlmModelManager.selectedModelId()\n if (localLlmProvider.loadedModelId() === target) await localLlmProvider.unload()\n await localLlmModelManager.deleteModel(target)\n },\n status: (): LocalLlmDownloadStatus | null => localLlmModelManager.getStatus(),\n /** Subscribe to download status changes; fires immediately, returns an unsubscribe. */\n onStatusChanged: (listener: (status: LocalLlmDownloadStatus | null) => void): (() => void) =>\n localLlmModelManager.subscribe(listener),\n /** Whether a model is resident in memory right now. */\n isLoaded: (): boolean => localLlmProvider.isLoaded(),\n /** Drop the resident model (memory pressure, debugging). */\n unload: (): Promise<void> => localLlmProvider.unload(),\n formatBytes: (bytes: number): string => LocalLlmModelManager.formatBytes(bytes),\n },\n offlineActivity: (): OfflineActivity => offlineActivity.current(),\n onOfflineActivity: (listener: (activity: OfflineActivity) => void): (() => void) =>\n offlineActivity.subscribe(listener),\n}\n"]}
@@ -1,9 +1,9 @@
1
1
  export const ENGINE_RELEASE_METADATA = Object.freeze({
2
2
  "schemaVersion": 1,
3
3
  "familyBaseVersion": "0.3.8",
4
- "releaseIdentity": "0.3.8-dev.1012",
5
- "releaseSetId": "glassly-0.3.8-dev.1012",
6
- "sourceCommit": "35a6df924b5f3a9f3b15e5f25d4a263c6d0b82ee",
4
+ "releaseIdentity": "0.3.8-dev.1015",
5
+ "releaseSetId": "glassly-0.3.8-dev.1015",
6
+ "sourceCommit": "5c28c73387e99d17f36887d985459628009a130b",
7
7
  "otaManifestUrl": null,
8
8
  "otaManifestSha256": null
9
9
  });
@@ -1 +1 @@
1
- {"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,gBAAgB;IACnC,cAAc,EAAE,wBAAwB;IACxC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface EngineReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"0.3.8\",\n \"releaseIdentity\": \"0.3.8-dev.1012\",\n \"releaseSetId\": \"glassly-0.3.8-dev.1012\",\n \"sourceCommit\": \"35a6df924b5f3a9f3b15e5f25d4a263c6d0b82ee\",\n \"otaManifestUrl\": null,\n \"otaManifestSha256\": null\n})\n"]}
1
+ {"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,gBAAgB;IACnC,cAAc,EAAE,wBAAwB;IACxC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface EngineReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"0.3.8\",\n \"releaseIdentity\": \"0.3.8-dev.1015\",\n \"releaseSetId\": \"glassly-0.3.8-dev.1015\",\n \"sourceCommit\": \"5c28c73387e99d17f36887d985459628009a130b\",\n \"otaManifestUrl\": null,\n \"otaManifestSha256\": null\n})\n"]}
@@ -109,7 +109,7 @@ export class GlassesSensorService {
109
109
  });
110
110
  }
111
111
  commandFailed() {
112
- fail(MiniappErrorCode.INTERNAL, "Sensor command failed: connect G2 custom firmware and keep its miniapp display active");
112
+ fail(MiniappErrorCode.INTERNAL, "Sensor command failed: the glasses need Glassly custom firmware with this sensor, and on the G2 an active miniapp display");
113
113
  }
114
114
  /** Miniapps currently subscribed to the glasses compass stream. Replaces the previous set. */
115
115
  setCompassSubscribers(apps) {
@@ -1 +1 @@
1
- {"version":3,"file":"GlassesSensorService.js","sourceRoot":"","sources":["../../src/services/GlassesSensorService.ts"],"names":[],"mappings":"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","sourcesContent":["import BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport {MiniappErrorCode} from \"@glassly/miniapp/protocol\"\nimport {BgTimer} from \"../utils/timers\"\n\ntype Sensor = \"light\" | \"compass\"\ntype Operation = \"query\" | \"start\" | \"stop\"\ntype Options = Record<string, number>\ntype Control = (sensor: Sensor, operation: Operation, options: Options) => Promise<boolean>\ntype Owner = {app: string; options: Options}\n\nconst RANGES: Record<Sensor, Record<string, [number, number]>> = {\n light: {intervalMs: [100, 5000], minDelta: [0, 65535], heartbeatMs: [0, 65535]},\n compass: {intervalMs: [50, 2000], minChangeDegrees: [0, 359]},\n}\nconst COMPASS_DEFAULTS: Options = {intervalMs: 1000, minChangeDegrees: 5}\nconst COMPASS_RETRY_MS = [2000, 4000, 8000]\n\nfunction fail(code: MiniappErrorCode, message: string): never {\n throw Object.assign(new Error(message), {code})\n}\n\nfunction sameOptions(a: Options | null, b: Options | null): boolean {\n if (!a || !b) return a === b\n const keys = new Set([...Object.keys(a), ...Object.keys(b)])\n for (const key of keys) if (a[key] !== b[key]) return false\n return true\n}\n\n/**\n * Light: explicit, miniapp-scoped ownership. A subscriber alone never changes auto brightness.\n * Compass: demand-driven. It runs while any miniapp is subscribed; `start(options)` only\n * records that miniapp's override and everyone is served the smallest interval and\n * threshold requested. Native navigation temporarily borrows both; its stock configuration wins.\n */\nexport class GlassesSensorService {\n private lightOwner: Owner | null = null\n private compassSubscribers = new Set<string>()\n private compassOverrides = new Map<string, Options>()\n /** Options the firmware currently runs; null while stopped. */\n private compassApplied: Options | null = null\n private compassRetry: number | null = null\n private compassRetryAttempt = 0\n private pending: Promise<unknown> = Promise.resolve()\n private navigationActive = false\n private generation = 0\n\n constructor(private readonly control: Control) {}\n\n private serial<T>(work: () => Promise<T>): Promise<T> {\n const next = this.pending.then(work)\n this.pending = next.catch(() => {})\n return next\n }\n\n private validate(payload: Record<string, unknown>): {sensor: Sensor; operation: Operation; options: Options} {\n const sensor = payload.sensor\n const operation = payload.operation\n if (sensor !== \"light\" && sensor !== \"compass\") fail(MiniappErrorCode.INVALID_ARGUMENT, \"Unknown sensor\")\n if (operation !== \"start\" && operation !== \"stop\" && !(sensor === \"light\" && operation === \"query\")) {\n fail(MiniappErrorCode.INVALID_ARGUMENT, \"Unknown sensor operation\")\n }\n const raw = payload.options ?? {}\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw))\n fail(MiniappErrorCode.INVALID_ARGUMENT, \"Invalid sensor options\")\n const ranges = RANGES[sensor]\n const options: Options = {}\n for (const [key, value] of Object.entries(raw)) {\n const range = Object.prototype.hasOwnProperty.call(ranges, key) ? ranges[key] : undefined\n if (!range || typeof value !== \"number\" || !Number.isInteger(value) || value < range[0] || value > range[1]) {\n fail(MiniappErrorCode.INVALID_ARGUMENT, `Invalid ${key}`)\n }\n options[key] = value\n }\n return {sensor, operation, options}\n }\n\n request(app: string, payload: Record<string, unknown>): Promise<void> {\n const generation = this.generation\n return this.serial(async () => {\n if (generation !== this.generation) fail(MiniappErrorCode.NOT_CONNECTED, \"Glasses disconnected\")\n const {sensor, operation, options} = this.validate(payload)\n if (sensor === \"compass\") {\n if (operation === \"start\") this.compassOverrides.set(app, options)\n else this.compassOverrides.delete(app)\n if (this.navigationActive) return\n await this.applyCompass(generation, true)\n return\n }\n const owner = this.lightOwner\n if (operation !== \"query\" && owner && owner.app !== app) {\n fail(MiniappErrorCode.INVALID_ARGUMENT, \"This sensor is controlled by another miniapp\")\n }\n if (operation === \"stop\" && !owner) return\n if (this.navigationActive) {\n if (operation === \"stop\") {\n this.lightOwner = null\n return\n }\n fail(MiniappErrorCode.INVALID_ARGUMENT, \"Native navigation currently owns the glasses sensors\")\n }\n // Keep cleanup ownership even after an ambiguous transport failure: the firmware\n // may have applied the command before its ACK was lost.\n if (operation === \"start\") this.lightOwner = {app, options}\n if (!(await this.control(\"light\", operation, options))) this.commandFailed()\n if (generation !== this.generation) fail(MiniappErrorCode.NOT_CONNECTED, \"Glasses disconnected\")\n if (operation === \"stop\") this.lightOwner = null\n })\n }\n\n private commandFailed(): never {\n fail(\n MiniappErrorCode.INTERNAL,\n \"Sensor command failed: connect G2 custom firmware and keep its miniapp display active\",\n )\n }\n\n /** Miniapps currently subscribed to the glasses compass stream. Replaces the previous set. */\n setCompassSubscribers(apps: Iterable<string>): Promise<void> {\n const generation = this.generation\n return this.serial(async () => {\n this.compassSubscribers = new Set(apps)\n if (generation !== this.generation || this.navigationActive) return\n await this.applyCompass(generation, false)\n })\n }\n\n private compassDemand(): Options | null {\n if (this.compassSubscribers.size === 0 && this.compassOverrides.size === 0) return null\n const requests = [...this.compassOverrides.values()].map((o) => ({...COMPASS_DEFAULTS, ...o}))\n for (const app of this.compassSubscribers) if (!this.compassOverrides.has(app)) requests.push(COMPASS_DEFAULTS)\n const demand: Options = {}\n for (const key of Object.keys(COMPASS_DEFAULTS)) demand[key] = Math.min(...requests.map((r) => r[key]))\n return demand\n }\n\n /**\n * Bring the firmware in line with current demand. Explicit requests surface failures;\n * subscription-driven applies stay silent (stock firmware, no CFW carrier yet) and retry\n * a few times so a compass subscribed before firmware classification still starts.\n */\n private async applyCompass(generation: number, explicit: boolean): Promise<void> {\n this.clearCompassRetry()\n const demand = this.compassDemand()\n if (sameOptions(demand, this.compassApplied)) return\n if (!demand) {\n const ok = await this.control(\"compass\", \"stop\", {})\n if (generation === this.generation) this.compassApplied = null\n if (!ok && explicit) this.commandFailed()\n return\n }\n const ok = await this.control(\"compass\", \"start\", demand)\n if (generation !== this.generation) {\n if (explicit) fail(MiniappErrorCode.NOT_CONNECTED, \"Glasses disconnected\")\n return\n }\n if (ok) {\n this.compassApplied = demand\n this.compassRetryAttempt = 0\n return\n }\n if (explicit) this.commandFailed()\n const delay = COMPASS_RETRY_MS[this.compassRetryAttempt]\n if (delay === undefined) return\n this.compassRetryAttempt++\n this.compassRetry = BgTimer.setTimeout(() => {\n this.compassRetry = null\n void this.resync()\n }, delay)\n }\n\n private clearCompassRetry(): void {\n if (this.compassRetry != null) BgTimer.clearTimeout(this.compassRetry)\n this.compassRetry = null\n }\n\n /** Re-apply compass demand after the glasses connect or their firmware is classified. */\n resync(): Promise<void> {\n const generation = this.generation\n return this.serial(async () => {\n if (generation !== this.generation || this.navigationActive) return\n await this.applyCompass(generation, false).catch(() => {})\n })\n }\n\n release(app: string): Promise<void> {\n const generation = this.generation\n return this.serial(async () => {\n if (generation !== this.generation) return\n const hadSubscription = this.compassSubscribers.delete(app)\n const hadOverride = this.compassOverrides.delete(app)\n const hadCompass = hadSubscription || hadOverride\n if (this.lightOwner?.app === app) {\n if (!this.navigationActive) await this.control(\"light\", \"stop\", {}).catch(() => false)\n this.lightOwner = null\n }\n if (hadCompass && !this.navigationActive) await this.applyCompass(generation, false).catch(() => {})\n })\n }\n\n setNavigationActive(active: boolean): Promise<void> {\n const generation = this.generation\n return this.serial(async () => {\n if (generation !== this.generation) return\n if (this.navigationActive === active) return\n this.navigationActive = active\n this.clearCompassRetry()\n if (active) {\n if (this.lightOwner) await this.control(\"light\", \"stop\", {}).catch(() => false)\n if (this.compassApplied) await this.control(\"compass\", \"stop\", {}).catch(() => false)\n this.compassApplied = null\n return\n }\n if (this.lightOwner) await this.control(\"light\", \"start\", this.lightOwner.options).catch(() => false)\n this.compassRetryAttempt = 0\n await this.applyCompass(generation, false).catch(() => {})\n })\n }\n\n /** Never replay requests from an old miniapp connection onto a new pair of glasses. */\n disconnected(): void {\n this.generation++\n this.clearCompassRetry()\n this.compassRetryAttempt = 0\n this.lightOwner = null\n this.compassApplied = null\n this.navigationActive = false\n }\n}\n\nconst glassesSensorService = new GlassesSensorService((sensor, operation, options) =>\n BluetoothSdk.glassesSensorControl(sensor, operation, options),\n)\nexport default glassesSensorService\n"]}
1
+ {"version":3,"file":"GlassesSensorService.js","sourceRoot":"","sources":["../../src/services/GlassesSensorService.ts"],"names":[],"mappings":"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","sourcesContent":["import BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport {MiniappErrorCode} from \"@glassly/miniapp/protocol\"\nimport {BgTimer} from \"../utils/timers\"\n\ntype Sensor = \"light\" | \"compass\"\ntype Operation = \"query\" | \"start\" | \"stop\"\ntype Options = Record<string, number>\ntype Control = (sensor: Sensor, operation: Operation, options: Options) => Promise<boolean>\ntype Owner = {app: string; options: Options}\n\nconst RANGES: Record<Sensor, Record<string, [number, number]>> = {\n light: {intervalMs: [100, 5000], minDelta: [0, 65535], heartbeatMs: [0, 65535]},\n compass: {intervalMs: [50, 2000], minChangeDegrees: [0, 359]},\n}\nconst COMPASS_DEFAULTS: Options = {intervalMs: 1000, minChangeDegrees: 5}\nconst COMPASS_RETRY_MS = [2000, 4000, 8000]\n\nfunction fail(code: MiniappErrorCode, message: string): never {\n throw Object.assign(new Error(message), {code})\n}\n\nfunction sameOptions(a: Options | null, b: Options | null): boolean {\n if (!a || !b) return a === b\n const keys = new Set([...Object.keys(a), ...Object.keys(b)])\n for (const key of keys) if (a[key] !== b[key]) return false\n return true\n}\n\n/**\n * Light: explicit, miniapp-scoped ownership. A subscriber alone never changes auto brightness.\n * Compass: demand-driven. It runs while any miniapp is subscribed; `start(options)` only\n * records that miniapp's override and everyone is served the smallest interval and\n * threshold requested. Native navigation temporarily borrows both; its stock configuration wins.\n */\nexport class GlassesSensorService {\n private lightOwner: Owner | null = null\n private compassSubscribers = new Set<string>()\n private compassOverrides = new Map<string, Options>()\n /** Options the firmware currently runs; null while stopped. */\n private compassApplied: Options | null = null\n private compassRetry: number | null = null\n private compassRetryAttempt = 0\n private pending: Promise<unknown> = Promise.resolve()\n private navigationActive = false\n private generation = 0\n\n constructor(private readonly control: Control) {}\n\n private serial<T>(work: () => Promise<T>): Promise<T> {\n const next = this.pending.then(work)\n this.pending = next.catch(() => {})\n return next\n }\n\n private validate(payload: Record<string, unknown>): {sensor: Sensor; operation: Operation; options: Options} {\n const sensor = payload.sensor\n const operation = payload.operation\n if (sensor !== \"light\" && sensor !== \"compass\") fail(MiniappErrorCode.INVALID_ARGUMENT, \"Unknown sensor\")\n if (operation !== \"start\" && operation !== \"stop\" && !(sensor === \"light\" && operation === \"query\")) {\n fail(MiniappErrorCode.INVALID_ARGUMENT, \"Unknown sensor operation\")\n }\n const raw = payload.options ?? {}\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw))\n fail(MiniappErrorCode.INVALID_ARGUMENT, \"Invalid sensor options\")\n const ranges = RANGES[sensor]\n const options: Options = {}\n for (const [key, value] of Object.entries(raw)) {\n const range = Object.prototype.hasOwnProperty.call(ranges, key) ? ranges[key] : undefined\n if (!range || typeof value !== \"number\" || !Number.isInteger(value) || value < range[0] || value > range[1]) {\n fail(MiniappErrorCode.INVALID_ARGUMENT, `Invalid ${key}`)\n }\n options[key] = value\n }\n return {sensor, operation, options}\n }\n\n request(app: string, payload: Record<string, unknown>): Promise<void> {\n const generation = this.generation\n return this.serial(async () => {\n if (generation !== this.generation) fail(MiniappErrorCode.NOT_CONNECTED, \"Glasses disconnected\")\n const {sensor, operation, options} = this.validate(payload)\n if (sensor === \"compass\") {\n if (operation === \"start\") this.compassOverrides.set(app, options)\n else this.compassOverrides.delete(app)\n if (this.navigationActive) return\n await this.applyCompass(generation, true)\n return\n }\n const owner = this.lightOwner\n if (operation !== \"query\" && owner && owner.app !== app) {\n fail(MiniappErrorCode.INVALID_ARGUMENT, \"This sensor is controlled by another miniapp\")\n }\n if (operation === \"stop\" && !owner) return\n if (this.navigationActive) {\n if (operation === \"stop\") {\n this.lightOwner = null\n return\n }\n fail(MiniappErrorCode.INVALID_ARGUMENT, \"Native navigation currently owns the glasses sensors\")\n }\n // Keep cleanup ownership even after an ambiguous transport failure: the firmware\n // may have applied the command before its ACK was lost.\n if (operation === \"start\") this.lightOwner = {app, options}\n if (!(await this.control(\"light\", operation, options))) this.commandFailed()\n if (generation !== this.generation) fail(MiniappErrorCode.NOT_CONNECTED, \"Glasses disconnected\")\n if (operation === \"stop\") this.lightOwner = null\n })\n }\n\n private commandFailed(): never {\n fail(\n MiniappErrorCode.INTERNAL,\n \"Sensor command failed: the glasses need Glassly custom firmware with this sensor, and on the G2 an active miniapp display\",\n )\n }\n\n /** Miniapps currently subscribed to the glasses compass stream. Replaces the previous set. */\n setCompassSubscribers(apps: Iterable<string>): Promise<void> {\n const generation = this.generation\n return this.serial(async () => {\n this.compassSubscribers = new Set(apps)\n if (generation !== this.generation || this.navigationActive) return\n await this.applyCompass(generation, false)\n })\n }\n\n private compassDemand(): Options | null {\n if (this.compassSubscribers.size === 0 && this.compassOverrides.size === 0) return null\n const requests = [...this.compassOverrides.values()].map((o) => ({...COMPASS_DEFAULTS, ...o}))\n for (const app of this.compassSubscribers) if (!this.compassOverrides.has(app)) requests.push(COMPASS_DEFAULTS)\n const demand: Options = {}\n for (const key of Object.keys(COMPASS_DEFAULTS)) demand[key] = Math.min(...requests.map((r) => r[key]))\n return demand\n }\n\n /**\n * Bring the firmware in line with current demand. Explicit requests surface failures;\n * subscription-driven applies stay silent (stock firmware, no CFW carrier yet) and retry\n * a few times so a compass subscribed before firmware classification still starts.\n */\n private async applyCompass(generation: number, explicit: boolean): Promise<void> {\n this.clearCompassRetry()\n const demand = this.compassDemand()\n if (sameOptions(demand, this.compassApplied)) return\n if (!demand) {\n const ok = await this.control(\"compass\", \"stop\", {})\n if (generation === this.generation) this.compassApplied = null\n if (!ok && explicit) this.commandFailed()\n return\n }\n const ok = await this.control(\"compass\", \"start\", demand)\n if (generation !== this.generation) {\n if (explicit) fail(MiniappErrorCode.NOT_CONNECTED, \"Glasses disconnected\")\n return\n }\n if (ok) {\n this.compassApplied = demand\n this.compassRetryAttempt = 0\n return\n }\n if (explicit) this.commandFailed()\n const delay = COMPASS_RETRY_MS[this.compassRetryAttempt]\n if (delay === undefined) return\n this.compassRetryAttempt++\n this.compassRetry = BgTimer.setTimeout(() => {\n this.compassRetry = null\n void this.resync()\n }, delay)\n }\n\n private clearCompassRetry(): void {\n if (this.compassRetry != null) BgTimer.clearTimeout(this.compassRetry)\n this.compassRetry = null\n }\n\n /** Re-apply compass demand after the glasses connect or their firmware is classified. */\n resync(): Promise<void> {\n const generation = this.generation\n return this.serial(async () => {\n if (generation !== this.generation || this.navigationActive) return\n await this.applyCompass(generation, false).catch(() => {})\n })\n }\n\n release(app: string): Promise<void> {\n const generation = this.generation\n return this.serial(async () => {\n if (generation !== this.generation) return\n const hadSubscription = this.compassSubscribers.delete(app)\n const hadOverride = this.compassOverrides.delete(app)\n const hadCompass = hadSubscription || hadOverride\n if (this.lightOwner?.app === app) {\n if (!this.navigationActive) await this.control(\"light\", \"stop\", {}).catch(() => false)\n this.lightOwner = null\n }\n if (hadCompass && !this.navigationActive) await this.applyCompass(generation, false).catch(() => {})\n })\n }\n\n setNavigationActive(active: boolean): Promise<void> {\n const generation = this.generation\n return this.serial(async () => {\n if (generation !== this.generation) return\n if (this.navigationActive === active) return\n this.navigationActive = active\n this.clearCompassRetry()\n if (active) {\n if (this.lightOwner) await this.control(\"light\", \"stop\", {}).catch(() => false)\n if (this.compassApplied) await this.control(\"compass\", \"stop\", {}).catch(() => false)\n this.compassApplied = null\n return\n }\n if (this.lightOwner) await this.control(\"light\", \"start\", this.lightOwner.options).catch(() => false)\n this.compassRetryAttempt = 0\n await this.applyCompass(generation, false).catch(() => {})\n })\n }\n\n /** Never replay requests from an old miniapp connection onto a new pair of glasses. */\n disconnected(): void {\n this.generation++\n this.clearCompassRetry()\n this.compassRetryAttempt = 0\n this.lightOwner = null\n this.compassApplied = null\n this.navigationActive = false\n }\n}\n\nconst glassesSensorService = new GlassesSensorService((sensor, operation, options) =>\n BluetoothSdk.glassesSensorControl(sensor, operation, options),\n)\nexport default glassesSensorService\n"]}
@@ -8,7 +8,7 @@ export interface LocalLlmModelConfig {
8
8
  sizeBytes: number;
9
9
  /** Exact size of the published .pte; the availability check compares against it. */
10
10
  modelBytes: number;
11
- /** Rough resident memory while loaded; the Settings copy warns above ~half of device RAM. */
11
+ /** Rough resident memory while loaded; `fitsDevice` holds it against the phone's RAM. */
12
12
  ramBytes: number;
13
13
  urls: {
14
14
  model: string;
@@ -31,12 +31,14 @@ export interface LocalLlmDownloadStatus {
31
31
  error?: string;
32
32
  }
33
33
  export declare function isLocalLlmModelId(value: unknown): value is LocalLlmModelId;
34
+ export declare function modelFitsMemory(model: LocalLlmModelConfig, deviceMemoryBytes: number | null): boolean;
34
35
  type StatusListener = (status: LocalLlmDownloadStatus | null) => void;
35
36
  declare class LocalLlmModelManager {
36
37
  private static instance;
37
38
  private downloadJobId?;
38
39
  private inFlight;
39
40
  private cancelled;
41
+ private coldLoadRecovered;
40
42
  private currentStatus;
41
43
  private listeners;
42
44
  private modelReadyListeners;
@@ -44,9 +46,27 @@ declare class LocalLlmModelManager {
44
46
  static getInstance(): LocalLlmModelManager;
45
47
  models(): LocalLlmModelConfig[];
46
48
  model(id: LocalLlmModelId): LocalLlmModelConfig;
47
- /** The Settings > AI Models pick; garbage in the store resolves to the default. */
49
+ /**
50
+ * The model on-device requests use: the Settings > Offline Models pick
51
+ * (garbage resolves to the default) while it is usable, else the largest
52
+ * model that is. With nothing usable it stays the pick, and loading it
53
+ * fails with `unusableReason`.
54
+ */
48
55
  selectedModelId(): LocalLlmModelId;
56
+ /** Picking a model in Settings is also the way to retry one that crashed the app. */
49
57
  select(id: LocalLlmModelId): Promise<void>;
58
+ fitsDevice(id: LocalLlmModelId): boolean;
59
+ /** The OS killed the app while this model loaded; it stays off until picked again. */
60
+ isBlocked(id: LocalLlmModelId): boolean;
61
+ isUsable(id: LocalLlmModelId): boolean;
62
+ /** Why `id` will not load on this phone, or null when it can. */
63
+ unusableReason(id: LocalLlmModelId): string | null;
64
+ /** Mark the start of a model's first load and generation in this process. */
65
+ beginColdLoad(id: LocalLlmModelId): void;
66
+ endColdLoad(): void;
67
+ private blockedModelIds;
68
+ private unblock;
69
+ private recoverColdLoad;
50
70
  getModelDirectory(): string;
51
71
  paths(id?: LocalLlmModelId): LocalLlmModelPaths;
52
72
  isModelAvailable(id?: LocalLlmModelId): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"LocalLlmModelManager.d.ts","sourceRoot":"","sources":["../../src/services/LocalLlmModelManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LocalLlmModelManager.d.ts","sourceRoot":"","sources":["../../src/services/LocalLlmModelManager.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,7 @@
14
14
  import { Platform } from "react-native";
15
15
  import * as RNFS from "@dr.pogodin/react-native-fs";
16
16
  import { SETTINGS, useSettingsStore } from "../stores/settings";
17
+ import { storage } from "../utils/storage";
17
18
  export const DEFAULT_LOCAL_LLM_MODEL = "qwen3-1.7b";
18
19
  // XNNPACK 8da4w exports from software-mansion/react-native-executorch-qwen-3,
19
20
  // pinned to the v0.10.0 release tag that react-native-executorch 0.10.x
@@ -64,11 +65,40 @@ const MODELS = {
64
65
  export function isLocalLlmModelId(value) {
65
66
  return typeof value === "string" && value in MODELS;
66
67
  }
68
+ // Without the increased-memory-limit entitlement iOS kills a foreground app
69
+ // at roughly 60% of physical RAM (Android's low-memory killer is no kinder),
70
+ // and the host holds about 1 GB besides the model: JS runtimes, WebViews,
71
+ // the speech model. A 4 GB phone fits the 0.6B model and nothing larger.
72
+ const APP_MEMORY_SHARE = 0.6;
73
+ const HOST_BASELINE_BYTES = 1 * GB;
74
+ export function modelFitsMemory(model, deviceMemoryBytes) {
75
+ if (!deviceMemoryBytes)
76
+ return true;
77
+ return model.ramBytes + HOST_BASELINE_BYTES <= deviceMemoryBytes * APP_MEMORY_SHARE;
78
+ }
79
+ let deviceMemory;
80
+ function deviceMemoryBytes() {
81
+ if (deviceMemory === undefined) {
82
+ try {
83
+ deviceMemory = require("expo-device").totalMemory ?? null;
84
+ }
85
+ catch {
86
+ deviceMemory = null;
87
+ }
88
+ }
89
+ return deviceMemory;
90
+ }
91
+ // Written before a model's first load and generation, cleared after either
92
+ // ends. A marker still there at the next launch means the OS killed the app
93
+ // mid-load (out of memory), so that model stays off until picked again.
94
+ const COLD_LOAD_KEY = "local_llm_cold_load";
95
+ const BLOCKED_MODELS_KEY = "local_llm_blocked_models";
67
96
  class LocalLlmModelManager {
68
97
  static instance;
69
98
  downloadJobId;
70
99
  inFlight = null;
71
100
  cancelled = false;
101
+ coldLoadRecovered = false;
72
102
  currentStatus = null;
73
103
  listeners = new Set();
74
104
  modelReadyListeners = new Set();
@@ -85,14 +115,82 @@ class LocalLlmModelManager {
85
115
  model(id) {
86
116
  return MODELS[id];
87
117
  }
88
- /** The Settings > AI Models pick; garbage in the store resolves to the default. */
118
+ /**
119
+ * The model on-device requests use: the Settings > Offline Models pick
120
+ * (garbage resolves to the default) while it is usable, else the largest
121
+ * model that is. With nothing usable it stays the pick, and loading it
122
+ * fails with `unusableReason`.
123
+ */
89
124
  selectedModelId() {
90
125
  const raw = useSettingsStore.getState().getSetting(SETTINGS.local_llm_model.key);
91
- return isLocalLlmModelId(raw) ? raw : DEFAULT_LOCAL_LLM_MODEL;
126
+ const picked = isLocalLlmModelId(raw) ? raw : DEFAULT_LOCAL_LLM_MODEL;
127
+ if (this.isUsable(picked))
128
+ return picked;
129
+ const fallback = this.models()
130
+ .filter((m) => this.isUsable(m.id))
131
+ .sort((a, b) => b.ramBytes - a.ramBytes)[0];
132
+ return fallback?.id ?? picked;
92
133
  }
134
+ /** Picking a model in Settings is also the way to retry one that crashed the app. */
93
135
  async select(id) {
136
+ this.unblock(id);
94
137
  await useSettingsStore.getState().setSetting(SETTINGS.local_llm_model.key, id);
95
138
  }
139
+ fitsDevice(id) {
140
+ return modelFitsMemory(MODELS[id], deviceMemoryBytes());
141
+ }
142
+ /** The OS killed the app while this model loaded; it stays off until picked again. */
143
+ isBlocked(id) {
144
+ return this.blockedModelIds().includes(id);
145
+ }
146
+ isUsable(id) {
147
+ return this.fitsDevice(id) && !this.isBlocked(id);
148
+ }
149
+ /** Why `id` will not load on this phone, or null when it can. */
150
+ unusableReason(id) {
151
+ const label = MODELS[id].label;
152
+ if (!this.fitsDevice(id)) {
153
+ return `${label} needs more memory than this phone has. Pick a smaller model in Settings > Offline Models.`;
154
+ }
155
+ if (this.isBlocked(id)) {
156
+ return `${label} closed the app the last time it loaded. Pick it again in Settings > Offline Models to retry, or pick a smaller model.`;
157
+ }
158
+ return null;
159
+ }
160
+ /** Mark the start of a model's first load and generation in this process. */
161
+ beginColdLoad(id) {
162
+ this.recoverColdLoad();
163
+ storage.save(COLD_LOAD_KEY, id);
164
+ }
165
+ endColdLoad() {
166
+ storage.removeMultiple([COLD_LOAD_KEY]);
167
+ }
168
+ blockedModelIds() {
169
+ this.recoverColdLoad();
170
+ const stored = storage.load(BLOCKED_MODELS_KEY);
171
+ const ids = stored.is_ok() ? stored.value : [];
172
+ return Array.isArray(ids) ? ids.filter(isLocalLlmModelId) : [];
173
+ }
174
+ unblock(id) {
175
+ const remaining = this.blockedModelIds().filter((blocked) => blocked !== id);
176
+ storage.save(BLOCKED_MODELS_KEY, remaining);
177
+ }
178
+ // A cold-load marker left by an earlier process: that load never finished.
179
+ recoverColdLoad() {
180
+ if (this.coldLoadRecovered)
181
+ return;
182
+ this.coldLoadRecovered = true;
183
+ const marker = storage.load(COLD_LOAD_KEY);
184
+ if (!marker.is_ok() || !isLocalLlmModelId(marker.value))
185
+ return;
186
+ const id = marker.value;
187
+ console.warn(`LOCAL_LLM: the app was killed while ${id} loaded; keeping it off until it is picked again`);
188
+ const stored = storage.load(BLOCKED_MODELS_KEY);
189
+ const blocked = stored.is_ok() && Array.isArray(stored.value) ? stored.value.filter(isLocalLlmModelId) : [];
190
+ if (!blocked.includes(id))
191
+ storage.save(BLOCKED_MODELS_KEY, [...blocked, id]);
192
+ storage.removeMultiple([COLD_LOAD_KEY]);
193
+ }
96
194
  getModelDirectory() {
97
195
  return `${RNFS.DocumentDirectoryPath}/llm_models`;
98
196
  }
@@ -155,6 +253,8 @@ class LocalLlmModelManager {
155
253
  const id = this.selectedModelId();
156
254
  if (await this.isModelAvailable(id))
157
255
  return true;
256
+ if (!this.fitsDevice(id))
257
+ return false;
158
258
  void this.downloadModel(id).catch((error) => {
159
259
  console.warn(`LOCAL_LLM: background download of ${id} failed:`, error);
160
260
  });
@@ -166,6 +266,8 @@ class LocalLlmModelManager {
166
266
  * the running one — the RNFS job slot is singular.
167
267
  */
168
268
  async downloadModel(id = this.selectedModelId()) {
269
+ if (!this.fitsDevice(id))
270
+ throw new Error(this.unusableReason(id) ?? `${MODELS[id].label} does not fit this phone.`);
169
271
  if (this.inFlight) {
170
272
  if (this.inFlight.modelId === id)
171
273
  return this.inFlight.promise;
@@ -1 +1 @@
1
- {"version":3,"file":"LocalLlmModelManager.js","sourceRoot":"","sources":["../../src/services/LocalLlmModelManager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * LocalLlmModelManager — the on-device LLM model files: a catalog of\n * ExecuTorch exports (Software Mansion's Qwen3 .pte builds on Hugging Face),\n * their download into Documents/llm_models/<id>/, availability checks, and a\n * status stream the Settings > AI Models screen renders. Modelled on\n * STTModelManager; the download itself is RNFS so the Settings screen can\n * list, cancel, and delete models with the same calls the STT rows use.\n *\n * Which model is \"the\" on-device model is the `local_llm_model` setting\n * (Settings > AI Models). Downloads are on demand: the first request that\n * routes on-device without a model present starts one (ensureSelectedModel),\n * and the Settings screen can start one explicitly.\n */\nimport {Platform} from \"react-native\"\nimport * as RNFS from \"@dr.pogodin/react-native-fs\"\n\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\n\nexport type LocalLlmModelId = \"qwen3-0.6b\" | \"qwen3-1.7b\" | \"qwen3-4b\"\n\nexport const DEFAULT_LOCAL_LLM_MODEL: LocalLlmModelId = \"qwen3-1.7b\"\n\nexport interface LocalLlmModelConfig {\n id: LocalLlmModelId\n /** Human label for the Settings dropdown. */\n label: string\n /** Total download size in bytes (the .pte dominates). */\n sizeBytes: number\n /** Exact size of the published .pte; the availability check compares against it. */\n modelBytes: number\n /** Rough resident memory while loaded; the Settings copy warns above ~half of device RAM. */\n ramBytes: number\n urls: {\n model: string\n tokenizer: string\n tokenizerConfig: string\n }\n}\n\n/** The three files every model directory holds; names are fixed so paths never depend on the URL. */\nexport interface LocalLlmModelPaths {\n model: string\n tokenizer: string\n tokenizerConfig: string\n}\n\nexport type LocalLlmDownloadStage = \"idle\" | \"downloading\" | \"complete\" | \"failed\"\n\nexport interface LocalLlmDownloadStatus {\n modelId: LocalLlmModelId\n stage: LocalLlmDownloadStage\n /** 0–100, meaningful while stage is \"downloading\". */\n percent: number\n error?: string\n}\n\n// XNNPACK 8da4w exports from software-mansion/react-native-executorch-qwen-3,\n// pinned to the v0.10.0 release tag that react-native-executorch 0.10.x\n// expects. modelBytes are the .pte sizes on the Hub; sizeBytes adds the\n// tokenizer files (~11 MB).\nconst QWEN3_BASE = \"https://huggingface.co/software-mansion/react-native-executorch-qwen-3/resolve/v0.10.0\"\nconst TOKENIZER_BYTES = 11_431_080\nconst MB = 1024 * 1024\nconst GB = 1024 * MB\n\nconst MODELS: Record<LocalLlmModelId, LocalLlmModelConfig> = {\n \"qwen3-0.6b\": {\n id: \"qwen3-0.6b\",\n label: \"Qwen3 0.6B\",\n sizeBytes: 505_686_400 + TOKENIZER_BYTES,\n modelBytes: 505_686_400,\n ramBytes: 0.9 * GB,\n urls: {\n model: `${QWEN3_BASE}/0_6b/xnnpack/qwen_3_0_6b_xnnpack_8da4w.pte`,\n tokenizer: `${QWEN3_BASE}/tokenizer.json`,\n tokenizerConfig: `${QWEN3_BASE}/tokenizer_config.json`,\n },\n },\n \"qwen3-1.7b\": {\n id: \"qwen3-1.7b\",\n label: \"Qwen3 1.7B\",\n sizeBytes: 1_303_738_496 + TOKENIZER_BYTES,\n modelBytes: 1_303_738_496,\n ramBytes: 1.8 * GB,\n urls: {\n model: `${QWEN3_BASE}/1_7b/xnnpack/qwen_3_1_7b_xnnpack_8da4w.pte`,\n tokenizer: `${QWEN3_BASE}/tokenizer.json`,\n tokenizerConfig: `${QWEN3_BASE}/tokenizer_config.json`,\n },\n },\n \"qwen3-4b\": {\n id: \"qwen3-4b\",\n label: \"Qwen3 4B\",\n sizeBytes: 2_681_646_976 + TOKENIZER_BYTES,\n modelBytes: 2_681_646_976,\n ramBytes: 3.4 * GB,\n urls: {\n model: `${QWEN3_BASE}/4b/xnnpack/qwen_3_4b_xnnpack_8da4w.pte`,\n tokenizer: `${QWEN3_BASE}/tokenizer.json`,\n tokenizerConfig: `${QWEN3_BASE}/tokenizer_config.json`,\n },\n },\n}\n\nexport function isLocalLlmModelId(value: unknown): value is LocalLlmModelId {\n return typeof value === \"string\" && value in MODELS\n}\n\ntype StatusListener = (status: LocalLlmDownloadStatus | null) => void\n\nclass LocalLlmModelManager {\n private static instance: LocalLlmModelManager\n\n private downloadJobId?: number\n private inFlight: {modelId: LocalLlmModelId; promise: Promise<void>} | null = null\n private cancelled = false\n private currentStatus: LocalLlmDownloadStatus | null = null\n private listeners = new Set<StatusListener>()\n private modelReadyListeners = new Set<(modelId: LocalLlmModelId) => void>()\n\n private constructor() {}\n\n static getInstance(): LocalLlmModelManager {\n if (!LocalLlmModelManager.instance) {\n LocalLlmModelManager.instance = new LocalLlmModelManager()\n }\n return LocalLlmModelManager.instance\n }\n\n models(): LocalLlmModelConfig[] {\n return Object.values(MODELS)\n }\n\n model(id: LocalLlmModelId): LocalLlmModelConfig {\n return MODELS[id]\n }\n\n /** The Settings > AI Models pick; garbage in the store resolves to the default. */\n selectedModelId(): LocalLlmModelId {\n const raw = useSettingsStore.getState().getSetting(SETTINGS.local_llm_model.key)\n return isLocalLlmModelId(raw) ? raw : DEFAULT_LOCAL_LLM_MODEL\n }\n\n async select(id: LocalLlmModelId): Promise<void> {\n await useSettingsStore.getState().setSetting(SETTINGS.local_llm_model.key, id)\n }\n\n getModelDirectory(): string {\n return `${RNFS.DocumentDirectoryPath}/llm_models`\n }\n\n paths(id: LocalLlmModelId = this.selectedModelId()): LocalLlmModelPaths {\n const dir = `${this.getModelDirectory()}/${id}`\n return {\n model: `${dir}/model.pte`,\n tokenizer: `${dir}/tokenizer.json`,\n tokenizerConfig: `${dir}/tokenizer_config.json`,\n }\n }\n\n async isModelAvailable(id: LocalLlmModelId = this.selectedModelId()): Promise<boolean> {\n const paths = this.paths(id)\n try {\n for (const path of [paths.tokenizerConfig, paths.tokenizer, paths.model]) {\n if (!(await RNFS.exists(path))) return false\n }\n // A truncated .pte loads as garbage or crashes the runtime: insist on\n // most of the published size. Downloads land in a .part file and are\n // renamed only once whole, so this only catches external tampering.\n const stat = await RNFS.stat(paths.model)\n return Number(stat.size) >= MODELS[id].modelBytes * 0.95\n } catch (error) {\n console.warn(\"LOCAL_LLM: availability check failed:\", error)\n return false\n }\n }\n\n async downloadedModelIds(): Promise<LocalLlmModelId[]> {\n const ids: LocalLlmModelId[] = []\n for (const id of Object.keys(MODELS) as LocalLlmModelId[]) {\n if (await this.isModelAvailable(id)) ids.push(id)\n }\n return ids\n }\n\n /** Subscribe to download status changes. Fires immediately with the current status. */\n subscribe(listener: StatusListener): () => void {\n this.listeners.add(listener)\n listener(this.currentStatus)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n getStatus(): LocalLlmDownloadStatus | null {\n return this.currentStatus\n }\n\n /** Fired once a model's files are complete on disk. */\n onModelReady(listener: (modelId: LocalLlmModelId) => void): () => void {\n this.modelReadyListeners.add(listener)\n return () => this.modelReadyListeners.delete(listener)\n }\n\n /**\n * True when the selected model is on disk. Otherwise starts (or joins) its\n * download in the background and returns false — the caller reports\n * \"downloading\" rather than waiting out a multi-GB transfer.\n */\n async ensureSelectedModel(): Promise<boolean> {\n const id = this.selectedModelId()\n if (await this.isModelAvailable(id)) return true\n void this.downloadModel(id).catch((error) => {\n console.warn(`LOCAL_LLM: background download of ${id} failed:`, error)\n })\n return false\n }\n\n /**\n * Download one model (three files, model last). Concurrent calls for the\n * same model share the transfer; a call for a different model waits for\n * the running one — the RNFS job slot is singular.\n */\n async downloadModel(id: LocalLlmModelId = this.selectedModelId()): Promise<void> {\n if (this.inFlight) {\n if (this.inFlight.modelId === id) return this.inFlight.promise\n await this.inFlight.promise.catch(() => undefined)\n }\n const promise = this.downloadInner(id).finally(() => {\n this.inFlight = null\n })\n this.inFlight = {modelId: id, promise}\n return promise\n }\n\n private async downloadInner(id: LocalLlmModelId): Promise<void> {\n const config = MODELS[id]\n const paths = this.paths(id)\n const dir = `${this.getModelDirectory()}/${id}`\n this.cancelled = false\n this.setStatus({modelId: id, stage: \"downloading\", percent: 0})\n\n try {\n await RNFS.mkdir(dir, {NSURLIsExcludedFromBackupKey: true})\n // Tokenizer files first (small), the weights last: a model directory\n // is only \"available\" once model.pte exists, so an interrupted run\n // never leaves a complete-looking directory behind.\n await this.fetchFile(config.urls.tokenizerConfig, paths.tokenizerConfig)\n await this.fetchFile(config.urls.tokenizer, paths.tokenizer)\n await this.fetchFile(config.urls.model, paths.model, (percent) =>\n this.setStatus({modelId: id, stage: \"downloading\", percent}),\n )\n if (!(await this.isModelAvailable(id))) {\n throw new Error(`downloaded model is smaller than the expected ${config.modelBytes} bytes`)\n }\n this.setStatus({modelId: id, stage: \"complete\", percent: 100})\n for (const listener of this.modelReadyListeners) {\n try {\n listener(id)\n } catch (error) {\n console.warn(\"LOCAL_LLM: model-ready listener failed:\", error)\n }\n }\n } catch (error) {\n const message = this.cancelled ? \"download cancelled\" : error instanceof Error ? error.message : String(error)\n this.setStatus({modelId: id, stage: this.cancelled ? \"idle\" : \"failed\", percent: 0, error: message})\n try {\n if (await RNFS.exists(`${paths.model}.part`)) await RNFS.unlink(`${paths.model}.part`)\n } catch (cleanupError) {\n console.warn(\"LOCAL_LLM: partial cleanup failed:\", cleanupError)\n }\n throw error\n } finally {\n this.downloadJobId = undefined\n }\n }\n\n private async fetchFile(url: string, dest: string, onPercent?: (percent: number) => void): Promise<void> {\n if (this.cancelled) throw new Error(\"download cancelled\")\n const partial = `${dest}.part`\n const download = RNFS.downloadFile({\n fromUrl: url,\n toFile: partial,\n // iOS keeps a background URLSession transfer going while the app is\n // suspended; the files here are large enough to need it.\n background: Platform.OS === \"ios\",\n discretionary: false,\n progressDivider: 1,\n connectionTimeout: 30_000,\n readTimeout: 60_000,\n progress: (res) => {\n if (!onPercent || res.contentLength <= 0) return\n onPercent(Math.min(99, Math.floor((res.bytesWritten / res.contentLength) * 100)))\n },\n })\n this.downloadJobId = download.jobId\n const result = await download.promise\n if (result.statusCode !== 200) {\n throw new Error(`download failed with status ${result.statusCode}`)\n }\n if (await RNFS.exists(dest)) await RNFS.unlink(dest)\n await RNFS.moveFile(partial, dest)\n }\n\n async cancelDownload(): Promise<void> {\n if (this.downloadJobId === undefined) return\n this.cancelled = true\n await RNFS.stopDownload(this.downloadJobId)\n this.downloadJobId = undefined\n }\n\n async deleteModel(id: LocalLlmModelId = this.selectedModelId()): Promise<void> {\n const dir = `${this.getModelDirectory()}/${id}`\n if (await RNFS.exists(dir)) await RNFS.unlink(dir)\n if (this.currentStatus?.modelId === id) this.setStatus(null)\n }\n\n private setStatus(status: LocalLlmDownloadStatus | null): void {\n this.currentStatus = status\n for (const listener of this.listeners) {\n try {\n listener(status)\n } catch (error) {\n console.warn(\"LOCAL_LLM: status listener threw:\", error)\n }\n }\n }\n\n static formatBytes(bytes: number): string {\n if (bytes >= GB) return `${(bytes / GB).toFixed(1)} GB`\n return `${Math.round(bytes / MB)} MB`\n }\n}\n\nconst instance = LocalLlmModelManager.getInstance()\nexport {LocalLlmModelManager}\nexport default instance\n"]}
1
+ {"version":3,"file":"LocalLlmModelManager.js","sourceRoot":"","sources":["../../src/services/LocalLlmModelManager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * LocalLlmModelManager — the on-device LLM model files: a catalog of\n * ExecuTorch exports (Software Mansion's Qwen3 .pte builds on Hugging Face),\n * their download into Documents/llm_models/<id>/, availability checks, and a\n * status stream the Settings > AI Models screen renders. Modelled on\n * STTModelManager; the download itself is RNFS so the Settings screen can\n * list, cancel, and delete models with the same calls the STT rows use.\n *\n * Which model is \"the\" on-device model is the `local_llm_model` setting\n * (Settings > AI Models). Downloads are on demand: the first request that\n * routes on-device without a model present starts one (ensureSelectedModel),\n * and the Settings screen can start one explicitly.\n */\nimport {Platform} from \"react-native\"\nimport * as RNFS from \"@dr.pogodin/react-native-fs\"\n\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport {storage} from \"../utils/storage\"\n\nexport type LocalLlmModelId = \"qwen3-0.6b\" | \"qwen3-1.7b\" | \"qwen3-4b\"\n\nexport const DEFAULT_LOCAL_LLM_MODEL: LocalLlmModelId = \"qwen3-1.7b\"\n\nexport interface LocalLlmModelConfig {\n id: LocalLlmModelId\n /** Human label for the Settings dropdown. */\n label: string\n /** Total download size in bytes (the .pte dominates). */\n sizeBytes: number\n /** Exact size of the published .pte; the availability check compares against it. */\n modelBytes: number\n /** Rough resident memory while loaded; `fitsDevice` holds it against the phone's RAM. */\n ramBytes: number\n urls: {\n model: string\n tokenizer: string\n tokenizerConfig: string\n }\n}\n\n/** The three files every model directory holds; names are fixed so paths never depend on the URL. */\nexport interface LocalLlmModelPaths {\n model: string\n tokenizer: string\n tokenizerConfig: string\n}\n\nexport type LocalLlmDownloadStage = \"idle\" | \"downloading\" | \"complete\" | \"failed\"\n\nexport interface LocalLlmDownloadStatus {\n modelId: LocalLlmModelId\n stage: LocalLlmDownloadStage\n /** 0–100, meaningful while stage is \"downloading\". */\n percent: number\n error?: string\n}\n\n// XNNPACK 8da4w exports from software-mansion/react-native-executorch-qwen-3,\n// pinned to the v0.10.0 release tag that react-native-executorch 0.10.x\n// expects. modelBytes are the .pte sizes on the Hub; sizeBytes adds the\n// tokenizer files (~11 MB).\nconst QWEN3_BASE = \"https://huggingface.co/software-mansion/react-native-executorch-qwen-3/resolve/v0.10.0\"\nconst TOKENIZER_BYTES = 11_431_080\nconst MB = 1024 * 1024\nconst GB = 1024 * MB\n\nconst MODELS: Record<LocalLlmModelId, LocalLlmModelConfig> = {\n \"qwen3-0.6b\": {\n id: \"qwen3-0.6b\",\n label: \"Qwen3 0.6B\",\n sizeBytes: 505_686_400 + TOKENIZER_BYTES,\n modelBytes: 505_686_400,\n ramBytes: 0.9 * GB,\n urls: {\n model: `${QWEN3_BASE}/0_6b/xnnpack/qwen_3_0_6b_xnnpack_8da4w.pte`,\n tokenizer: `${QWEN3_BASE}/tokenizer.json`,\n tokenizerConfig: `${QWEN3_BASE}/tokenizer_config.json`,\n },\n },\n \"qwen3-1.7b\": {\n id: \"qwen3-1.7b\",\n label: \"Qwen3 1.7B\",\n sizeBytes: 1_303_738_496 + TOKENIZER_BYTES,\n modelBytes: 1_303_738_496,\n ramBytes: 1.8 * GB,\n urls: {\n model: `${QWEN3_BASE}/1_7b/xnnpack/qwen_3_1_7b_xnnpack_8da4w.pte`,\n tokenizer: `${QWEN3_BASE}/tokenizer.json`,\n tokenizerConfig: `${QWEN3_BASE}/tokenizer_config.json`,\n },\n },\n \"qwen3-4b\": {\n id: \"qwen3-4b\",\n label: \"Qwen3 4B\",\n sizeBytes: 2_681_646_976 + TOKENIZER_BYTES,\n modelBytes: 2_681_646_976,\n ramBytes: 3.4 * GB,\n urls: {\n model: `${QWEN3_BASE}/4b/xnnpack/qwen_3_4b_xnnpack_8da4w.pte`,\n tokenizer: `${QWEN3_BASE}/tokenizer.json`,\n tokenizerConfig: `${QWEN3_BASE}/tokenizer_config.json`,\n },\n },\n}\n\nexport function isLocalLlmModelId(value: unknown): value is LocalLlmModelId {\n return typeof value === \"string\" && value in MODELS\n}\n\n// Without the increased-memory-limit entitlement iOS kills a foreground app\n// at roughly 60% of physical RAM (Android's low-memory killer is no kinder),\n// and the host holds about 1 GB besides the model: JS runtimes, WebViews,\n// the speech model. A 4 GB phone fits the 0.6B model and nothing larger.\nconst APP_MEMORY_SHARE = 0.6\nconst HOST_BASELINE_BYTES = 1 * GB\n\nexport function modelFitsMemory(model: LocalLlmModelConfig, deviceMemoryBytes: number | null): boolean {\n if (!deviceMemoryBytes) return true\n return model.ramBytes + HOST_BASELINE_BYTES <= deviceMemoryBytes * APP_MEMORY_SHARE\n}\n\nlet deviceMemory: number | null | undefined\nfunction deviceMemoryBytes(): number | null {\n if (deviceMemory === undefined) {\n try {\n deviceMemory = (require(\"expo-device\") as {totalMemory?: number | null}).totalMemory ?? null\n } catch {\n deviceMemory = null\n }\n }\n return deviceMemory\n}\n\n// Written before a model's first load and generation, cleared after either\n// ends. A marker still there at the next launch means the OS killed the app\n// mid-load (out of memory), so that model stays off until picked again.\nconst COLD_LOAD_KEY = \"local_llm_cold_load\"\nconst BLOCKED_MODELS_KEY = \"local_llm_blocked_models\"\n\ntype StatusListener = (status: LocalLlmDownloadStatus | null) => void\n\nclass LocalLlmModelManager {\n private static instance: LocalLlmModelManager\n\n private downloadJobId?: number\n private inFlight: {modelId: LocalLlmModelId; promise: Promise<void>} | null = null\n private cancelled = false\n private coldLoadRecovered = false\n private currentStatus: LocalLlmDownloadStatus | null = null\n private listeners = new Set<StatusListener>()\n private modelReadyListeners = new Set<(modelId: LocalLlmModelId) => void>()\n\n private constructor() {}\n\n static getInstance(): LocalLlmModelManager {\n if (!LocalLlmModelManager.instance) {\n LocalLlmModelManager.instance = new LocalLlmModelManager()\n }\n return LocalLlmModelManager.instance\n }\n\n models(): LocalLlmModelConfig[] {\n return Object.values(MODELS)\n }\n\n model(id: LocalLlmModelId): LocalLlmModelConfig {\n return MODELS[id]\n }\n\n /**\n * The model on-device requests use: the Settings > Offline Models pick\n * (garbage resolves to the default) while it is usable, else the largest\n * model that is. With nothing usable it stays the pick, and loading it\n * fails with `unusableReason`.\n */\n selectedModelId(): LocalLlmModelId {\n const raw = useSettingsStore.getState().getSetting(SETTINGS.local_llm_model.key)\n const picked = isLocalLlmModelId(raw) ? raw : DEFAULT_LOCAL_LLM_MODEL\n if (this.isUsable(picked)) return picked\n const fallback = this.models()\n .filter((m) => this.isUsable(m.id))\n .sort((a, b) => b.ramBytes - a.ramBytes)[0]\n return fallback?.id ?? picked\n }\n\n /** Picking a model in Settings is also the way to retry one that crashed the app. */\n async select(id: LocalLlmModelId): Promise<void> {\n this.unblock(id)\n await useSettingsStore.getState().setSetting(SETTINGS.local_llm_model.key, id)\n }\n\n fitsDevice(id: LocalLlmModelId): boolean {\n return modelFitsMemory(MODELS[id], deviceMemoryBytes())\n }\n\n /** The OS killed the app while this model loaded; it stays off until picked again. */\n isBlocked(id: LocalLlmModelId): boolean {\n return this.blockedModelIds().includes(id)\n }\n\n isUsable(id: LocalLlmModelId): boolean {\n return this.fitsDevice(id) && !this.isBlocked(id)\n }\n\n /** Why `id` will not load on this phone, or null when it can. */\n unusableReason(id: LocalLlmModelId): string | null {\n const label = MODELS[id].label\n if (!this.fitsDevice(id)) {\n return `${label} needs more memory than this phone has. Pick a smaller model in Settings > Offline Models.`\n }\n if (this.isBlocked(id)) {\n return `${label} closed the app the last time it loaded. Pick it again in Settings > Offline Models to retry, or pick a smaller model.`\n }\n return null\n }\n\n /** Mark the start of a model's first load and generation in this process. */\n beginColdLoad(id: LocalLlmModelId): void {\n this.recoverColdLoad()\n storage.save(COLD_LOAD_KEY, id)\n }\n\n endColdLoad(): void {\n storage.removeMultiple([COLD_LOAD_KEY])\n }\n\n private blockedModelIds(): LocalLlmModelId[] {\n this.recoverColdLoad()\n const stored = storage.load<unknown>(BLOCKED_MODELS_KEY)\n const ids = stored.is_ok() ? stored.value : []\n return Array.isArray(ids) ? ids.filter(isLocalLlmModelId) : []\n }\n\n private unblock(id: LocalLlmModelId): void {\n const remaining = this.blockedModelIds().filter((blocked) => blocked !== id)\n storage.save(BLOCKED_MODELS_KEY, remaining)\n }\n\n // A cold-load marker left by an earlier process: that load never finished.\n private recoverColdLoad(): void {\n if (this.coldLoadRecovered) return\n this.coldLoadRecovered = true\n const marker = storage.load<unknown>(COLD_LOAD_KEY)\n if (!marker.is_ok() || !isLocalLlmModelId(marker.value)) return\n const id = marker.value\n console.warn(`LOCAL_LLM: the app was killed while ${id} loaded; keeping it off until it is picked again`)\n const stored = storage.load<unknown>(BLOCKED_MODELS_KEY)\n const blocked = stored.is_ok() && Array.isArray(stored.value) ? stored.value.filter(isLocalLlmModelId) : []\n if (!blocked.includes(id)) storage.save(BLOCKED_MODELS_KEY, [...blocked, id])\n storage.removeMultiple([COLD_LOAD_KEY])\n }\n\n getModelDirectory(): string {\n return `${RNFS.DocumentDirectoryPath}/llm_models`\n }\n\n paths(id: LocalLlmModelId = this.selectedModelId()): LocalLlmModelPaths {\n const dir = `${this.getModelDirectory()}/${id}`\n return {\n model: `${dir}/model.pte`,\n tokenizer: `${dir}/tokenizer.json`,\n tokenizerConfig: `${dir}/tokenizer_config.json`,\n }\n }\n\n async isModelAvailable(id: LocalLlmModelId = this.selectedModelId()): Promise<boolean> {\n const paths = this.paths(id)\n try {\n for (const path of [paths.tokenizerConfig, paths.tokenizer, paths.model]) {\n if (!(await RNFS.exists(path))) return false\n }\n // A truncated .pte loads as garbage or crashes the runtime: insist on\n // most of the published size. Downloads land in a .part file and are\n // renamed only once whole, so this only catches external tampering.\n const stat = await RNFS.stat(paths.model)\n return Number(stat.size) >= MODELS[id].modelBytes * 0.95\n } catch (error) {\n console.warn(\"LOCAL_LLM: availability check failed:\", error)\n return false\n }\n }\n\n async downloadedModelIds(): Promise<LocalLlmModelId[]> {\n const ids: LocalLlmModelId[] = []\n for (const id of Object.keys(MODELS) as LocalLlmModelId[]) {\n if (await this.isModelAvailable(id)) ids.push(id)\n }\n return ids\n }\n\n /** Subscribe to download status changes. Fires immediately with the current status. */\n subscribe(listener: StatusListener): () => void {\n this.listeners.add(listener)\n listener(this.currentStatus)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n getStatus(): LocalLlmDownloadStatus | null {\n return this.currentStatus\n }\n\n /** Fired once a model's files are complete on disk. */\n onModelReady(listener: (modelId: LocalLlmModelId) => void): () => void {\n this.modelReadyListeners.add(listener)\n return () => this.modelReadyListeners.delete(listener)\n }\n\n /**\n * True when the selected model is on disk. Otherwise starts (or joins) its\n * download in the background and returns false — the caller reports\n * \"downloading\" rather than waiting out a multi-GB transfer.\n */\n async ensureSelectedModel(): Promise<boolean> {\n const id = this.selectedModelId()\n if (await this.isModelAvailable(id)) return true\n if (!this.fitsDevice(id)) return false\n void this.downloadModel(id).catch((error) => {\n console.warn(`LOCAL_LLM: background download of ${id} failed:`, error)\n })\n return false\n }\n\n /**\n * Download one model (three files, model last). Concurrent calls for the\n * same model share the transfer; a call for a different model waits for\n * the running one — the RNFS job slot is singular.\n */\n async downloadModel(id: LocalLlmModelId = this.selectedModelId()): Promise<void> {\n if (!this.fitsDevice(id)) throw new Error(this.unusableReason(id) ?? `${MODELS[id].label} does not fit this phone.`)\n if (this.inFlight) {\n if (this.inFlight.modelId === id) return this.inFlight.promise\n await this.inFlight.promise.catch(() => undefined)\n }\n const promise = this.downloadInner(id).finally(() => {\n this.inFlight = null\n })\n this.inFlight = {modelId: id, promise}\n return promise\n }\n\n private async downloadInner(id: LocalLlmModelId): Promise<void> {\n const config = MODELS[id]\n const paths = this.paths(id)\n const dir = `${this.getModelDirectory()}/${id}`\n this.cancelled = false\n this.setStatus({modelId: id, stage: \"downloading\", percent: 0})\n\n try {\n await RNFS.mkdir(dir, {NSURLIsExcludedFromBackupKey: true})\n // Tokenizer files first (small), the weights last: a model directory\n // is only \"available\" once model.pte exists, so an interrupted run\n // never leaves a complete-looking directory behind.\n await this.fetchFile(config.urls.tokenizerConfig, paths.tokenizerConfig)\n await this.fetchFile(config.urls.tokenizer, paths.tokenizer)\n await this.fetchFile(config.urls.model, paths.model, (percent) =>\n this.setStatus({modelId: id, stage: \"downloading\", percent}),\n )\n if (!(await this.isModelAvailable(id))) {\n throw new Error(`downloaded model is smaller than the expected ${config.modelBytes} bytes`)\n }\n this.setStatus({modelId: id, stage: \"complete\", percent: 100})\n for (const listener of this.modelReadyListeners) {\n try {\n listener(id)\n } catch (error) {\n console.warn(\"LOCAL_LLM: model-ready listener failed:\", error)\n }\n }\n } catch (error) {\n const message = this.cancelled ? \"download cancelled\" : error instanceof Error ? error.message : String(error)\n this.setStatus({modelId: id, stage: this.cancelled ? \"idle\" : \"failed\", percent: 0, error: message})\n try {\n if (await RNFS.exists(`${paths.model}.part`)) await RNFS.unlink(`${paths.model}.part`)\n } catch (cleanupError) {\n console.warn(\"LOCAL_LLM: partial cleanup failed:\", cleanupError)\n }\n throw error\n } finally {\n this.downloadJobId = undefined\n }\n }\n\n private async fetchFile(url: string, dest: string, onPercent?: (percent: number) => void): Promise<void> {\n if (this.cancelled) throw new Error(\"download cancelled\")\n const partial = `${dest}.part`\n const download = RNFS.downloadFile({\n fromUrl: url,\n toFile: partial,\n // iOS keeps a background URLSession transfer going while the app is\n // suspended; the files here are large enough to need it.\n background: Platform.OS === \"ios\",\n discretionary: false,\n progressDivider: 1,\n connectionTimeout: 30_000,\n readTimeout: 60_000,\n progress: (res) => {\n if (!onPercent || res.contentLength <= 0) return\n onPercent(Math.min(99, Math.floor((res.bytesWritten / res.contentLength) * 100)))\n },\n })\n this.downloadJobId = download.jobId\n const result = await download.promise\n if (result.statusCode !== 200) {\n throw new Error(`download failed with status ${result.statusCode}`)\n }\n if (await RNFS.exists(dest)) await RNFS.unlink(dest)\n await RNFS.moveFile(partial, dest)\n }\n\n async cancelDownload(): Promise<void> {\n if (this.downloadJobId === undefined) return\n this.cancelled = true\n await RNFS.stopDownload(this.downloadJobId)\n this.downloadJobId = undefined\n }\n\n async deleteModel(id: LocalLlmModelId = this.selectedModelId()): Promise<void> {\n const dir = `${this.getModelDirectory()}/${id}`\n if (await RNFS.exists(dir)) await RNFS.unlink(dir)\n if (this.currentStatus?.modelId === id) this.setStatus(null)\n }\n\n private setStatus(status: LocalLlmDownloadStatus | null): void {\n this.currentStatus = status\n for (const listener of this.listeners) {\n try {\n listener(status)\n } catch (error) {\n console.warn(\"LOCAL_LLM: status listener threw:\", error)\n }\n }\n }\n\n static formatBytes(bytes: number): string {\n if (bytes >= GB) return `${(bytes / GB).toFixed(1)} GB`\n return `${Math.round(bytes / MB)} MB`\n }\n}\n\nconst instance = LocalLlmModelManager.getInstance()\nexport {LocalLlmModelManager}\nexport default instance\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"LocalLlmProvider.d.ts","sourceRoot":"","sources":["../../src/services/LocalLlmProvider.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,6BAA6B,CAAA;AAEnE,OAA6B,EAAC,KAAK,eAAe,EAAC,MAAM,wBAAwB,CAAA;AACjF,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAA;AA6CzB,MAAM,WAAW,sBAAuB,SAAQ,eAAe;IAC7D,OAAO,EAAE,eAAe,CAAA;CACzB;AA0BD,cAAM,gBAAgB;IACpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAkB;IAEzC,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,GAAG,CAAmB;IAE9B,OAAO;IAUP,MAAM,CAAC,WAAW,IAAI,gBAAgB;IAKtC,mFAAmF;IACnF,QAAQ,IAAI,OAAO;IAInB,aAAa,IAAI,eAAe,GAAG,IAAI;IAIvC,oGAAoG;IACpG,QAAQ,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAqBtE,kGAAkG;IAC5F,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAe7B,yEAAyE;IACzE,OAAO,CAAC,aAAa;YAaP,aAAa;YAmEb,YAAY;YAgBZ,IAAI;YAyCJ,OAAO;IASrB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,cAAc;CAMvB;AAED,QAAA,MAAM,gBAAgB,kBAAiC,CAAA;AACvD,OAAO,EAAC,gBAAgB,EAAC,CAAA;AACzB,eAAe,gBAAgB,CAAA"}
1
+ {"version":3,"file":"LocalLlmProvider.d.ts","sourceRoot":"","sources":["../../src/services/LocalLlmProvider.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,6BAA6B,CAAA;AAEnE,OAA6B,EAAC,KAAK,eAAe,EAAC,MAAM,wBAAwB,CAAA;AACjF,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAA;AA6CzB,MAAM,WAAW,sBAAuB,SAAQ,eAAe;IAC7D,OAAO,EAAE,eAAe,CAAA;CACzB;AA0BD,cAAM,gBAAgB;IACpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAkB;IAEzC,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,GAAG,CAAmB;IAE9B,OAAO;IAUP,MAAM,CAAC,WAAW,IAAI,gBAAgB;IAKtC,mFAAmF;IACnF,QAAQ,IAAI,OAAO;IAInB,aAAa,IAAI,eAAe,GAAG,IAAI;IAIvC,oGAAoG;IACpG,QAAQ,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAqBtE,kGAAkG;IAC5F,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAe7B,yEAAyE;IACzE,OAAO,CAAC,aAAa;YAaP,aAAa;YAyEb,YAAY;YAgBZ,IAAI;YA2CJ,OAAO;IASrB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,cAAc;CAMvB;AAED,QAAA,MAAM,gBAAgB,kBAAiC,CAAA;AACvD,OAAO,EAAC,gBAAgB,EAAC,CAAA;AACzB,eAAe,gBAAgB,CAAA"}
@@ -133,6 +133,12 @@ class LocalLlmProvider {
133
133
  }
134
134
  async completeInner(request) {
135
135
  this.clearIdleTimer();
136
+ // Loading and the first generation are where memory peaks; if the OS kills
137
+ // the app in between, the marker keeps the next launch from looping on it.
138
+ const selectedId = localLlmModelManager.selectedModelId();
139
+ const coldModelId = this.loaded?.modelId === selectedId ? null : selectedId;
140
+ if (coldModelId)
141
+ localLlmModelManager.beginColdLoad(coldModelId);
136
142
  try {
137
143
  const model = await this.ensureLoaded();
138
144
  const jsonMode = request.jsonMode === true;
@@ -170,6 +176,8 @@ class LocalLlmProvider {
170
176
  return { ...completion, modelId: model.modelId };
171
177
  }
172
178
  finally {
179
+ if (coldModelId)
180
+ localLlmModelManager.endColdLoad();
173
181
  this.armIdleTimer();
174
182
  }
175
183
  }
@@ -193,6 +201,9 @@ class LocalLlmProvider {
193
201
  // Runs inside the request queue, so nothing is generating; unload() would
194
202
  // wait on that same queue.
195
203
  this.releaseRunner();
204
+ const unusable = localLlmModelManager.unusableReason(modelId);
205
+ if (unusable)
206
+ throw new Error(unusable);
196
207
  if (!(await localLlmModelManager.isModelAvailable(modelId))) {
197
208
  throw new Error(`on-device model ${modelId} is not downloaded`);
198
209
  }