@glassly/engine 0.3.4-dev.674 → 0.3.4-dev.676
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/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/LocalLlmProvider.d.ts.map +1 -1
- package/build/services/LocalLlmProvider.js +5 -4
- package/build/services/LocalLlmProvider.js.map +1 -1
- package/package.json +7 -7
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/services/LocalLlmProvider.ts +6 -5
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export const ENGINE_RELEASE_METADATA = Object.freeze({
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"familyBaseVersion": "0.3.4",
|
|
4
|
-
"releaseIdentity": "0.3.4-dev.
|
|
5
|
-
"releaseSetId": "glassly-0.3.4-dev.
|
|
6
|
-
"sourceCommit": "
|
|
4
|
+
"releaseIdentity": "0.3.4-dev.676",
|
|
5
|
+
"releaseSetId": "glassly-0.3.4-dev.676",
|
|
6
|
+
"sourceCommit": "9066b8df01df6448c03c1765e1dbc11965bcaf6c",
|
|
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,eAAe;IAClC,cAAc,EAAE,uBAAuB;IACvC,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.4\",\n \"releaseIdentity\": \"0.3.4-dev.
|
|
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,eAAe;IAClC,cAAc,EAAE,uBAAuB;IACvC,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.4\",\n \"releaseIdentity\": \"0.3.4-dev.676\",\n \"releaseSetId\": \"glassly-0.3.4-dev.676\",\n \"sourceCommit\": \"9066b8df01df6448c03c1765e1dbc11965bcaf6c\",\n \"otaManifestUrl\": null,\n \"otaManifestSha256\": null\n})\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;
|
|
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,EAML,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAA;AAmCzB,MAAM,WAAW,sBAAuB,SAAQ,eAAe;IAC7D,OAAO,EAAE,eAAe,CAAA;CACzB;AAqBD,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,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;IAMtE,kGAAkG;IAC5F,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAe7B,yEAAyE;IACzE,OAAO,CAAC,aAAa;YAWP,aAAa;YA6Db,YAAY;YAgBZ,IAAI;YA6BJ,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"}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import * as RNFS from "@dr.pogodin/react-native-fs";
|
|
18
18
|
import { Template } from "@huggingface/jinja";
|
|
19
|
+
import { BgTimer } from "../utils/timers";
|
|
19
20
|
import localLlmModelManager from "./LocalLlmModelManager";
|
|
20
21
|
import { buildTemplateMessages, estimateTokens, finalizeLocalResult, fitToBudget, toTemplateTools, } from "./LocalLlmPrompt";
|
|
21
22
|
import { SETTINGS, useSettingsStore } from "../stores/settings";
|
|
@@ -119,7 +120,7 @@ class LocalLlmProvider {
|
|
|
119
120
|
enable_thinking: false,
|
|
120
121
|
});
|
|
121
122
|
model.runner.reset(0);
|
|
122
|
-
const deadline = setTimeout(() => {
|
|
123
|
+
const deadline = BgTimer.setTimeout(() => {
|
|
123
124
|
console.warn(`${LOG_TAG}: generation deadline hit, stopping`);
|
|
124
125
|
model.runner.stop();
|
|
125
126
|
}, GENERATION_DEADLINE_MS);
|
|
@@ -129,7 +130,7 @@ class LocalLlmProvider {
|
|
|
129
130
|
generated = await model.generate(model.runner, prompt, { maxNewTokens, temperature: request.temperature ?? 0.7 }, model.stopTokens);
|
|
130
131
|
}
|
|
131
132
|
finally {
|
|
132
|
-
clearTimeout(deadline);
|
|
133
|
+
BgTimer.clearTimeout(deadline);
|
|
133
134
|
}
|
|
134
135
|
const elapsedMs = Date.now() - startedAt;
|
|
135
136
|
console.log(`${LOG_TAG}: ${model.modelId} prompt=${generated.stats.numPromptTokens} out=${generated.stats.numGeneratedTokens} tokens in ${elapsedMs} ms`);
|
|
@@ -198,7 +199,7 @@ class LocalLlmProvider {
|
|
|
198
199
|
}
|
|
199
200
|
armIdleTimer() {
|
|
200
201
|
this.clearIdleTimer();
|
|
201
|
-
this.idleTimer = setTimeout(() => {
|
|
202
|
+
this.idleTimer = BgTimer.setTimeout(() => {
|
|
202
203
|
this.idleTimer = null;
|
|
203
204
|
console.log(`${LOG_TAG}: idle, unloading ${this.loaded?.modelId ?? "nothing"}`);
|
|
204
205
|
void this.unload();
|
|
@@ -206,7 +207,7 @@ class LocalLlmProvider {
|
|
|
206
207
|
}
|
|
207
208
|
clearIdleTimer() {
|
|
208
209
|
if (this.idleTimer) {
|
|
209
|
-
clearTimeout(this.idleTimer);
|
|
210
|
+
BgTimer.clearTimeout(this.idleTimer);
|
|
210
211
|
this.idleTimer = null;
|
|
211
212
|
}
|
|
212
213
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalLlmProvider.js","sourceRoot":"","sources":["../../src/services/LocalLlmProvider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * LocalLlmProvider — session.ai.complete served by the phone itself: an\n * ExecuTorch runner (react-native-executorch) over the Qwen3 export that\n * LocalLlmModelManager downloaded. Stateless per request like the vendor\n * providers: every call renders the whole conversation through the model's\n * chat template, resets the KV cache, and generates on ExecuTorch's worklet\n * thread so the JS thread stays free.\n *\n * The runner is loaded on the first request and kept resident while calls\n * keep coming (loading a 1–3 GB model is seconds, not milliseconds); it is\n * dropped after an idle period and whenever the selected model changes.\n * Requests are serialized — one model, one KV cache.\n *\n * react-native-executorch is imported lazily so the engine module graph\n * loads without the native runtime (bun tests, hosts without the dependency).\n */\nimport * as RNFS from \"@dr.pogodin/react-native-fs\"\nimport {Template} from \"@huggingface/jinja\"\n\nimport type {LlmCompleteRequest} from \"@glassly/cloud-protocol/llm\"\nimport localLlmModelManager, {type LocalLlmModelId} from \"./LocalLlmModelManager\"\nimport {\n buildTemplateMessages,\n estimateTokens,\n finalizeLocalResult,\n fitToBudget,\n toTemplateTools,\n type LocalCompletion,\n} from \"./LocalLlmPrompt\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\n\ntype Rne = typeof import(\"react-native-executorch\")\n// The runner API lives on the `llm` namespace; derive its types from there so\n// the import stays a type-only reference (no native module at load time).\ntype LLMRunner = ReturnType<Rne[\"llm\"][\"createLLMRunner\"]>\ntype LLMGenerationStats = ReturnType<LLMRunner[\"generate\"]>\ntype LLMGenerationConfig = NonNullable<Parameters<LLMRunner[\"generate\"]>[1]>\n\nconst LOG_TAG = \"LOCAL_LLM\"\n\n/** Drop the resident model after this long without a request. */\nconst IDLE_UNLOAD_MS = 5 * 60_000\n/** Hard stop on one generation — the SDK request times out at 120 s anyway. */\nconst GENERATION_DEADLINE_MS = 100_000\nconst DEFAULT_MAX_NEW_TOKENS = 512\nconst MAX_NEW_TOKENS_CEILING = 1_024\n/** Headroom between the estimated prompt and the context end; estimates are rough. */\nconst CONTEXT_MARGIN_TOKENS = 96\n\ninterface LoadedModel {\n modelId: LocalLlmModelId\n runner: LLMRunner\n template: Template\n stopTokens: string[]\n maxSeqLen: number\n generate: (\n runner: LLMRunner,\n prompt: string,\n config: LLMGenerationConfig,\n stopTokens: string[],\n ) => Promise<{text: string; stats: LLMGenerationStats}>\n}\n\nexport interface LocalLlmCompleteResult extends LocalCompletion {\n modelId: LocalLlmModelId\n}\n\n/**\n * Runs on ExecuTorch's worklet runtime: `runner.generate` blocks its thread\n * until EOS, `maxNewTokens`, or `runner.stop()` from the JS thread.\n */\nfunction generateWorklet(\n runner: LLMRunner,\n prompt: string,\n config: LLMGenerationConfig,\n stopTokens: string[],\n): {text: string; stats: LLMGenerationStats} {\n \"worklet\"\n let text = \"\"\n const stats = runner.generate(prompt, config, (token: string) => {\n if (stopTokens.includes(token)) return\n text += token\n })\n return {text, stats}\n}\n\nclass LocalLlmProvider {\n private static instance: LocalLlmProvider\n\n private loaded: LoadedModel | null = null\n private loading: Promise<LoadedModel> | null = null\n private queue: Promise<unknown> = Promise.resolve()\n private idleTimer: ReturnType<typeof setTimeout> | null = null\n private rne: Rne | null = null\n\n private constructor() {\n // A different model picked in Settings invalidates the resident runner.\n useSettingsStore.subscribe(\n (state) => state.settings[SETTINGS.local_llm_model.key],\n () => {\n void this.unload()\n },\n )\n }\n\n static getInstance(): LocalLlmProvider {\n if (!LocalLlmProvider.instance) LocalLlmProvider.instance = new LocalLlmProvider()\n return LocalLlmProvider.instance\n }\n\n /** Whether a runner is resident right now (diagnostics; not a readiness check). */\n isLoaded(): boolean {\n return this.loaded !== null\n }\n\n loadedModelId(): LocalLlmModelId | null {\n return this.loaded?.modelId ?? null\n }\n\n /** Serve one neutral completion on-device. Throws when the model is missing or generation fails. */\n complete(request: LlmCompleteRequest): Promise<LocalLlmCompleteResult> {\n const run = this.queue.then(() => this.completeInner(request))\n this.queue = run.catch(() => undefined)\n return run\n }\n\n /** Release the resident runner (idle, model switch, memory pressure). Safe to call repeatedly. */\n async unload(): Promise<void> {\n this.clearIdleTimer()\n // Never dispose under a running generation: let the queue drain first.\n await this.queue\n const pending = this.loading\n if (pending) {\n try {\n await pending\n } catch {\n // A failed load left nothing to release.\n }\n }\n this.releaseRunner()\n }\n\n /** Synchronous dispose; callers guarantee no generation is in flight. */\n private releaseRunner(): void {\n const loaded = this.loaded\n this.loaded = null\n if (!loaded) return\n try {\n loaded.runner.dispose()\n } catch (error) {\n console.warn(`${LOG_TAG}: runner dispose failed:`, error)\n }\n }\n\n private async completeInner(request: LlmCompleteRequest): Promise<LocalLlmCompleteResult> {\n this.clearIdleTimer()\n try {\n const model = await this.ensureLoaded()\n const jsonMode = request.jsonMode === true\n const tools = toTemplateTools(request.tools)\n const toolTokens = tools ? estimateTokens(JSON.stringify(tools)) + 64 : 0\n\n const maxNewTokens = Math.max(\n 16,\n Math.min(request.maxTokens ?? DEFAULT_MAX_NEW_TOKENS, MAX_NEW_TOKENS_CEILING, Math.floor(model.maxSeqLen / 4)),\n )\n const promptBudget = model.maxSeqLen - maxNewTokens - CONTEXT_MARGIN_TOKENS\n const messages = fitToBudget(buildTemplateMessages({...request, jsonMode}), promptBudget, toolTokens)\n\n const prompt = model.template.render({\n messages,\n tools,\n add_generation_prompt: true,\n enable_thinking: false,\n })\n\n model.runner.reset(0)\n const deadline = setTimeout(() => {\n console.warn(`${LOG_TAG}: generation deadline hit, stopping`)\n model.runner.stop()\n }, GENERATION_DEADLINE_MS)\n let generated: {text: string; stats: LLMGenerationStats}\n const startedAt = Date.now()\n try {\n generated = await model.generate(\n model.runner,\n prompt,\n {maxNewTokens, temperature: request.temperature ?? 0.7},\n model.stopTokens,\n )\n } finally {\n clearTimeout(deadline)\n }\n\n const elapsedMs = Date.now() - startedAt\n console.log(\n `${LOG_TAG}: ${model.modelId} prompt=${generated.stats.numPromptTokens} out=${generated.stats.numGeneratedTokens} tokens in ${elapsedMs} ms`,\n )\n\n const seed = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`\n const completion = finalizeLocalResult(\n {\n text: generated.text,\n promptTokens: generated.stats.numPromptTokens,\n generatedTokens: generated.stats.numGeneratedTokens,\n maxNewTokens,\n },\n {jsonMode, toolsOffered: Boolean(tools), makeToolCallId: (index) => `local_${seed}_${index}`},\n )\n return {...completion, modelId: model.modelId}\n } finally {\n this.armIdleTimer()\n }\n }\n\n private async ensureLoaded(): Promise<LoadedModel> {\n const modelId = localLlmModelManager.selectedModelId()\n if (this.loaded?.modelId === modelId) return this.loaded\n if (this.loading) return this.loading\n\n this.loading = this.load(modelId)\n .then((model) => {\n this.loaded = model\n return model\n })\n .finally(() => {\n this.loading = null\n })\n return this.loading\n }\n\n private async load(modelId: LocalLlmModelId): Promise<LoadedModel> {\n // Runs inside the request queue, so nothing is generating; unload() would\n // wait on that same queue.\n this.releaseRunner()\n if (!(await localLlmModelManager.isModelAvailable(modelId))) {\n throw new Error(`on-device model ${modelId} is not downloaded`)\n }\n const rne = await this.runtime()\n const paths = localLlmModelManager.paths(modelId)\n\n const tokenizerConfig = rne.llm.parseTokenizerConfig(JSON.parse(await RNFS.readFile(paths.tokenizerConfig, \"utf8\")))\n const template = new Template(tokenizerConfig.chatTemplate)\n\n const startedAt = Date.now()\n const createRunner = rne.wrapAsync(rne.llm.createLLMRunner)\n const runner = await createRunner(paths.model, paths.tokenizer, [])\n const maxSeqLen = runner.getKVCacheState().maxSeqLen\n console.log(`${LOG_TAG}: loaded ${modelId} (context ${maxSeqLen}) in ${Date.now() - startedAt} ms`)\n\n return {\n modelId,\n runner,\n template,\n stopTokens: [...tokenizerConfig.stopTokens],\n maxSeqLen,\n generate: rne.wrapAsync(generateWorklet),\n }\n }\n\n private async runtime(): Promise<Rne> {\n if (this.rne) return this.rne\n const rne: Rne = await import(\"react-native-executorch\")\n // Software Mansion's anonymous download analytics; the host has its own telemetry.\n rne.setTelemetryEnabled(false)\n this.rne = rne\n return rne\n }\n\n private armIdleTimer(): void {\n this.clearIdleTimer()\n this.idleTimer = setTimeout(() => {\n this.idleTimer = null\n console.log(`${LOG_TAG}: idle, unloading ${this.loaded?.modelId ?? \"nothing\"}`)\n void this.unload()\n }, IDLE_UNLOAD_MS)\n }\n\n private clearIdleTimer(): void {\n if (this.idleTimer) {\n clearTimeout(this.idleTimer)\n this.idleTimer = null\n }\n }\n}\n\nconst localLlmProvider = LocalLlmProvider.getInstance()\nexport {LocalLlmProvider}\nexport default localLlmProvider\n"]}
|
|
1
|
+
{"version":3,"file":"LocalLlmProvider.js","sourceRoot":"","sources":["../../src/services/LocalLlmProvider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * LocalLlmProvider — session.ai.complete served by the phone itself: an\n * ExecuTorch runner (react-native-executorch) over the Qwen3 export that\n * LocalLlmModelManager downloaded. Stateless per request like the vendor\n * providers: every call renders the whole conversation through the model's\n * chat template, resets the KV cache, and generates on ExecuTorch's worklet\n * thread so the JS thread stays free.\n *\n * The runner is loaded on the first request and kept resident while calls\n * keep coming (loading a 1–3 GB model is seconds, not milliseconds); it is\n * dropped after an idle period and whenever the selected model changes.\n * Requests are serialized — one model, one KV cache.\n *\n * react-native-executorch is imported lazily so the engine module graph\n * loads without the native runtime (bun tests, hosts without the dependency).\n */\nimport * as RNFS from \"@dr.pogodin/react-native-fs\"\nimport {Template} from \"@huggingface/jinja\"\n\nimport type {LlmCompleteRequest} from \"@glassly/cloud-protocol/llm\"\nimport {BgTimer} from \"../utils/timers\"\nimport localLlmModelManager, {type LocalLlmModelId} from \"./LocalLlmModelManager\"\nimport {\n buildTemplateMessages,\n estimateTokens,\n finalizeLocalResult,\n fitToBudget,\n toTemplateTools,\n type LocalCompletion,\n} from \"./LocalLlmPrompt\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\n\ntype Rne = typeof import(\"react-native-executorch\")\n// The runner API lives on the `llm` namespace; derive its types from there so\n// the import stays a type-only reference (no native module at load time).\ntype LLMRunner = ReturnType<Rne[\"llm\"][\"createLLMRunner\"]>\ntype LLMGenerationStats = ReturnType<LLMRunner[\"generate\"]>\ntype LLMGenerationConfig = NonNullable<Parameters<LLMRunner[\"generate\"]>[1]>\n\nconst LOG_TAG = \"LOCAL_LLM\"\n\n/** Drop the resident model after this long without a request. */\nconst IDLE_UNLOAD_MS = 5 * 60_000\n/** Hard stop on one generation — the SDK request times out at 120 s anyway. */\nconst GENERATION_DEADLINE_MS = 100_000\nconst DEFAULT_MAX_NEW_TOKENS = 512\nconst MAX_NEW_TOKENS_CEILING = 1_024\n/** Headroom between the estimated prompt and the context end; estimates are rough. */\nconst CONTEXT_MARGIN_TOKENS = 96\n\ninterface LoadedModel {\n modelId: LocalLlmModelId\n runner: LLMRunner\n template: Template\n stopTokens: string[]\n maxSeqLen: number\n generate: (\n runner: LLMRunner,\n prompt: string,\n config: LLMGenerationConfig,\n stopTokens: string[],\n ) => Promise<{text: string; stats: LLMGenerationStats}>\n}\n\nexport interface LocalLlmCompleteResult extends LocalCompletion {\n modelId: LocalLlmModelId\n}\n\n/**\n * Runs on ExecuTorch's worklet runtime: `runner.generate` blocks its thread\n * until EOS, `maxNewTokens`, or `runner.stop()` from the JS thread.\n */\nfunction generateWorklet(\n runner: LLMRunner,\n prompt: string,\n config: LLMGenerationConfig,\n stopTokens: string[],\n): {text: string; stats: LLMGenerationStats} {\n \"worklet\"\n let text = \"\"\n const stats = runner.generate(prompt, config, (token: string) => {\n if (stopTokens.includes(token)) return\n text += token\n })\n return {text, stats}\n}\n\nclass LocalLlmProvider {\n private static instance: LocalLlmProvider\n\n private loaded: LoadedModel | null = null\n private loading: Promise<LoadedModel> | null = null\n private queue: Promise<unknown> = Promise.resolve()\n private idleTimer: number | null = null\n private rne: Rne | null = null\n\n private constructor() {\n // A different model picked in Settings invalidates the resident runner.\n useSettingsStore.subscribe(\n (state) => state.settings[SETTINGS.local_llm_model.key],\n () => {\n void this.unload()\n },\n )\n }\n\n static getInstance(): LocalLlmProvider {\n if (!LocalLlmProvider.instance) LocalLlmProvider.instance = new LocalLlmProvider()\n return LocalLlmProvider.instance\n }\n\n /** Whether a runner is resident right now (diagnostics; not a readiness check). */\n isLoaded(): boolean {\n return this.loaded !== null\n }\n\n loadedModelId(): LocalLlmModelId | null {\n return this.loaded?.modelId ?? null\n }\n\n /** Serve one neutral completion on-device. Throws when the model is missing or generation fails. */\n complete(request: LlmCompleteRequest): Promise<LocalLlmCompleteResult> {\n const run = this.queue.then(() => this.completeInner(request))\n this.queue = run.catch(() => undefined)\n return run\n }\n\n /** Release the resident runner (idle, model switch, memory pressure). Safe to call repeatedly. */\n async unload(): Promise<void> {\n this.clearIdleTimer()\n // Never dispose under a running generation: let the queue drain first.\n await this.queue\n const pending = this.loading\n if (pending) {\n try {\n await pending\n } catch {\n // A failed load left nothing to release.\n }\n }\n this.releaseRunner()\n }\n\n /** Synchronous dispose; callers guarantee no generation is in flight. */\n private releaseRunner(): void {\n const loaded = this.loaded\n this.loaded = null\n if (!loaded) return\n try {\n loaded.runner.dispose()\n } catch (error) {\n console.warn(`${LOG_TAG}: runner dispose failed:`, error)\n }\n }\n\n private async completeInner(request: LlmCompleteRequest): Promise<LocalLlmCompleteResult> {\n this.clearIdleTimer()\n try {\n const model = await this.ensureLoaded()\n const jsonMode = request.jsonMode === true\n const tools = toTemplateTools(request.tools)\n const toolTokens = tools ? estimateTokens(JSON.stringify(tools)) + 64 : 0\n\n const maxNewTokens = Math.max(\n 16,\n Math.min(request.maxTokens ?? DEFAULT_MAX_NEW_TOKENS, MAX_NEW_TOKENS_CEILING, Math.floor(model.maxSeqLen / 4)),\n )\n const promptBudget = model.maxSeqLen - maxNewTokens - CONTEXT_MARGIN_TOKENS\n const messages = fitToBudget(buildTemplateMessages({...request, jsonMode}), promptBudget, toolTokens)\n\n const prompt = model.template.render({\n messages,\n tools,\n add_generation_prompt: true,\n enable_thinking: false,\n })\n\n model.runner.reset(0)\n const deadline = BgTimer.setTimeout(() => {\n console.warn(`${LOG_TAG}: generation deadline hit, stopping`)\n model.runner.stop()\n }, GENERATION_DEADLINE_MS)\n let generated: {text: string; stats: LLMGenerationStats}\n const startedAt = Date.now()\n try {\n generated = await model.generate(\n model.runner,\n prompt,\n {maxNewTokens, temperature: request.temperature ?? 0.7},\n model.stopTokens,\n )\n } finally {\n BgTimer.clearTimeout(deadline)\n }\n\n const elapsedMs = Date.now() - startedAt\n console.log(\n `${LOG_TAG}: ${model.modelId} prompt=${generated.stats.numPromptTokens} out=${generated.stats.numGeneratedTokens} tokens in ${elapsedMs} ms`,\n )\n\n const seed = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`\n const completion = finalizeLocalResult(\n {\n text: generated.text,\n promptTokens: generated.stats.numPromptTokens,\n generatedTokens: generated.stats.numGeneratedTokens,\n maxNewTokens,\n },\n {jsonMode, toolsOffered: Boolean(tools), makeToolCallId: (index) => `local_${seed}_${index}`},\n )\n return {...completion, modelId: model.modelId}\n } finally {\n this.armIdleTimer()\n }\n }\n\n private async ensureLoaded(): Promise<LoadedModel> {\n const modelId = localLlmModelManager.selectedModelId()\n if (this.loaded?.modelId === modelId) return this.loaded\n if (this.loading) return this.loading\n\n this.loading = this.load(modelId)\n .then((model) => {\n this.loaded = model\n return model\n })\n .finally(() => {\n this.loading = null\n })\n return this.loading\n }\n\n private async load(modelId: LocalLlmModelId): Promise<LoadedModel> {\n // Runs inside the request queue, so nothing is generating; unload() would\n // wait on that same queue.\n this.releaseRunner()\n if (!(await localLlmModelManager.isModelAvailable(modelId))) {\n throw new Error(`on-device model ${modelId} is not downloaded`)\n }\n const rne = await this.runtime()\n const paths = localLlmModelManager.paths(modelId)\n\n const tokenizerConfig = rne.llm.parseTokenizerConfig(JSON.parse(await RNFS.readFile(paths.tokenizerConfig, \"utf8\")))\n const template = new Template(tokenizerConfig.chatTemplate)\n\n const startedAt = Date.now()\n const createRunner = rne.wrapAsync(rne.llm.createLLMRunner)\n const runner = await createRunner(paths.model, paths.tokenizer, [])\n const maxSeqLen = runner.getKVCacheState().maxSeqLen\n console.log(`${LOG_TAG}: loaded ${modelId} (context ${maxSeqLen}) in ${Date.now() - startedAt} ms`)\n\n return {\n modelId,\n runner,\n template,\n stopTokens: [...tokenizerConfig.stopTokens],\n maxSeqLen,\n generate: rne.wrapAsync(generateWorklet),\n }\n }\n\n private async runtime(): Promise<Rne> {\n if (this.rne) return this.rne\n const rne: Rne = await import(\"react-native-executorch\")\n // Software Mansion's anonymous download analytics; the host has its own telemetry.\n rne.setTelemetryEnabled(false)\n this.rne = rne\n return rne\n }\n\n private armIdleTimer(): void {\n this.clearIdleTimer()\n this.idleTimer = BgTimer.setTimeout(() => {\n this.idleTimer = null\n console.log(`${LOG_TAG}: idle, unloading ${this.loaded?.modelId ?? \"nothing\"}`)\n void this.unload()\n }, IDLE_UNLOAD_MS)\n }\n\n private clearIdleTimer(): void {\n if (this.idleTimer) {\n BgTimer.clearTimeout(this.idleTimer)\n this.idleTimer = null\n }\n }\n}\n\nconst localLlmProvider = LocalLlmProvider.getInstance()\nexport {LocalLlmProvider}\nexport default localLlmProvider\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassly/engine",
|
|
3
|
-
"version": "0.3.4-dev.
|
|
3
|
+
"version": "0.3.4-dev.676",
|
|
4
4
|
"description": "Glassly Engine — miniapp registry, WebView bridge, and miniapp store",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -97,17 +97,17 @@
|
|
|
97
97
|
"registry": "https://registry.npmjs.org/"
|
|
98
98
|
},
|
|
99
99
|
"dependencies": {
|
|
100
|
-
"@glassly/bluetooth-sdk": "0.3.4-dev.
|
|
101
|
-
"@glassly/cloud-client": "0.3.4-dev.
|
|
102
|
-
"@glassly/cloud-protocol": "0.3.4-dev.
|
|
103
|
-
"@glassly/crust": "0.3.4-dev.
|
|
104
|
-
"@glassly/miniapp": "0.3.4-dev.
|
|
100
|
+
"@glassly/bluetooth-sdk": "0.3.4-dev.676",
|
|
101
|
+
"@glassly/cloud-client": "0.3.4-dev.676",
|
|
102
|
+
"@glassly/cloud-protocol": "0.3.4-dev.676",
|
|
103
|
+
"@glassly/crust": "0.3.4-dev.676",
|
|
104
|
+
"@glassly/miniapp": "0.3.4-dev.676",
|
|
105
105
|
"buffer": "^6.0.3",
|
|
106
106
|
"events": "^3.3.0",
|
|
107
107
|
"react-native-marked": "8.1.1",
|
|
108
108
|
"semver": "^7.7.4",
|
|
109
109
|
"typesafe-ts": "^1.7.1",
|
|
110
|
-
"@glassly/llm-providers": "0.3.4-dev.
|
|
110
|
+
"@glassly/llm-providers": "0.3.4-dev.676",
|
|
111
111
|
"@huggingface/jinja": "^0.5.9",
|
|
112
112
|
"jsonrepair": "^3.12.0"
|
|
113
113
|
},
|
|
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
|
|
|
12
12
|
export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
|
|
13
13
|
"schemaVersion": 1,
|
|
14
14
|
"familyBaseVersion": "0.3.4",
|
|
15
|
-
"releaseIdentity": "0.3.4-dev.
|
|
16
|
-
"releaseSetId": "glassly-0.3.4-dev.
|
|
17
|
-
"sourceCommit": "
|
|
15
|
+
"releaseIdentity": "0.3.4-dev.676",
|
|
16
|
+
"releaseSetId": "glassly-0.3.4-dev.676",
|
|
17
|
+
"sourceCommit": "9066b8df01df6448c03c1765e1dbc11965bcaf6c",
|
|
18
18
|
"otaManifestUrl": null,
|
|
19
19
|
"otaManifestSha256": null
|
|
20
20
|
})
|
|
@@ -18,6 +18,7 @@ import * as RNFS from "@dr.pogodin/react-native-fs"
|
|
|
18
18
|
import {Template} from "@huggingface/jinja"
|
|
19
19
|
|
|
20
20
|
import type {LlmCompleteRequest} from "@glassly/cloud-protocol/llm"
|
|
21
|
+
import {BgTimer} from "../utils/timers"
|
|
21
22
|
import localLlmModelManager, {type LocalLlmModelId} from "./LocalLlmModelManager"
|
|
22
23
|
import {
|
|
23
24
|
buildTemplateMessages,
|
|
@@ -90,7 +91,7 @@ class LocalLlmProvider {
|
|
|
90
91
|
private loaded: LoadedModel | null = null
|
|
91
92
|
private loading: Promise<LoadedModel> | null = null
|
|
92
93
|
private queue: Promise<unknown> = Promise.resolve()
|
|
93
|
-
private idleTimer:
|
|
94
|
+
private idleTimer: number | null = null
|
|
94
95
|
private rne: Rne | null = null
|
|
95
96
|
|
|
96
97
|
private constructor() {
|
|
@@ -175,7 +176,7 @@ class LocalLlmProvider {
|
|
|
175
176
|
})
|
|
176
177
|
|
|
177
178
|
model.runner.reset(0)
|
|
178
|
-
const deadline = setTimeout(() => {
|
|
179
|
+
const deadline = BgTimer.setTimeout(() => {
|
|
179
180
|
console.warn(`${LOG_TAG}: generation deadline hit, stopping`)
|
|
180
181
|
model.runner.stop()
|
|
181
182
|
}, GENERATION_DEADLINE_MS)
|
|
@@ -189,7 +190,7 @@ class LocalLlmProvider {
|
|
|
189
190
|
model.stopTokens,
|
|
190
191
|
)
|
|
191
192
|
} finally {
|
|
192
|
-
clearTimeout(deadline)
|
|
193
|
+
BgTimer.clearTimeout(deadline)
|
|
193
194
|
}
|
|
194
195
|
|
|
195
196
|
const elapsedMs = Date.now() - startedAt
|
|
@@ -269,7 +270,7 @@ class LocalLlmProvider {
|
|
|
269
270
|
|
|
270
271
|
private armIdleTimer(): void {
|
|
271
272
|
this.clearIdleTimer()
|
|
272
|
-
this.idleTimer = setTimeout(() => {
|
|
273
|
+
this.idleTimer = BgTimer.setTimeout(() => {
|
|
273
274
|
this.idleTimer = null
|
|
274
275
|
console.log(`${LOG_TAG}: idle, unloading ${this.loaded?.modelId ?? "nothing"}`)
|
|
275
276
|
void this.unload()
|
|
@@ -278,7 +279,7 @@ class LocalLlmProvider {
|
|
|
278
279
|
|
|
279
280
|
private clearIdleTimer(): void {
|
|
280
281
|
if (this.idleTimer) {
|
|
281
|
-
clearTimeout(this.idleTimer)
|
|
282
|
+
BgTimer.clearTimeout(this.idleTimer)
|
|
282
283
|
this.idleTimer = null
|
|
283
284
|
}
|
|
284
285
|
}
|