@glassly/engine 0.3.3-dev.670 → 0.3.4-dev.674
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 +38 -14
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +3 -0
- package/build/engine.js.map +1 -1
- package/build/facades/ai.d.ts +32 -0
- package/build/facades/ai.d.ts.map +1 -0
- package/build/facades/ai.js +38 -0
- package/build/facades/ai.js.map +1 -0
- package/build/facades/glasses.d.ts +30 -24
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +49 -30
- package/build/facades/glasses.js.map +1 -1
- package/build/facades/glassesSettings.d.ts.map +1 -1
- package/build/facades/glassesSettings.js +1 -0
- package/build/facades/glassesSettings.js.map +1 -1
- package/build/facades/pairing.d.ts +6 -5
- package/build/facades/pairing.d.ts.map +1 -1
- package/build/facades/pairing.js +14 -14
- package/build/facades/pairing.js.map +1 -1
- package/build/generated/releaseMetadata.js +4 -4
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +5 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/services/DeviceEventRouter.d.ts.map +1 -1
- package/build/services/DeviceEventRouter.js +9 -1
- package/build/services/DeviceEventRouter.js.map +1 -1
- package/build/services/GestureDotsService.d.ts +4 -3
- package/build/services/GestureDotsService.d.ts.map +1 -1
- package/build/services/GestureDotsService.js +17 -12
- package/build/services/GestureDotsService.js.map +1 -1
- package/build/services/GlassesTouchInput.d.ts.map +1 -1
- package/build/services/GlassesTouchInput.js +11 -9
- package/build/services/GlassesTouchInput.js.map +1 -1
- package/build/services/LlmRouting.d.ts +35 -0
- package/build/services/LlmRouting.d.ts.map +1 -0
- package/build/services/LlmRouting.js +25 -0
- package/build/services/LlmRouting.js.map +1 -0
- package/build/services/LocalLlmModelManager.d.ts +79 -0
- package/build/services/LocalLlmModelManager.d.ts.map +1 -0
- package/build/services/LocalLlmModelManager.js +278 -0
- package/build/services/LocalLlmModelManager.js.map +1 -0
- package/build/services/LocalLlmPrompt.d.ts +90 -0
- package/build/services/LocalLlmPrompt.d.ts.map +1 -0
- package/build/services/LocalLlmPrompt.js +189 -0
- package/build/services/LocalLlmPrompt.js.map +1 -0
- package/build/services/LocalLlmProvider.d.ts +35 -0
- package/build/services/LocalLlmProvider.d.ts.map +1 -0
- package/build/services/LocalLlmProvider.js +217 -0
- package/build/services/LocalLlmProvider.js.map +1 -0
- package/build/services/LocalMiniappRuntime.d.ts +9 -1
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +91 -17
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/STTModelManager.d.ts +25 -2
- package/build/services/STTModelManager.d.ts.map +1 -1
- package/build/services/STTModelManager.js +137 -6
- package/build/services/STTModelManager.js.map +1 -1
- package/build/services/controllers.d.ts +21 -0
- package/build/services/controllers.d.ts.map +1 -0
- package/build/services/controllers.js +62 -0
- package/build/services/controllers.js.map +1 -0
- package/build/services/effectiveCapabilities.d.ts.map +1 -1
- package/build/services/effectiveCapabilities.js +18 -24
- package/build/services/effectiveCapabilities.js.map +1 -1
- package/build/services/neuralBand/NeuralBandInput.d.ts +17 -6
- package/build/services/neuralBand/NeuralBandInput.d.ts.map +1 -1
- package/build/services/neuralBand/NeuralBandInput.js +40 -23
- package/build/services/neuralBand/NeuralBandInput.js.map +1 -1
- package/build/services/neuralBand/NeuralBandSettingsSync.d.ts.map +1 -1
- package/build/services/neuralBand/NeuralBandSettingsSync.js +23 -26
- package/build/services/neuralBand/NeuralBandSettingsSync.js.map +1 -1
- package/build/services/settingsBridge.d.ts +26 -18
- package/build/services/settingsBridge.d.ts.map +1 -1
- package/build/services/settingsBridge.js +36 -15
- package/build/services/settingsBridge.js.map +1 -1
- package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
- package/build/stores/bluetoothSettingKeys.js +3 -1
- package/build/stores/bluetoothSettingKeys.js.map +1 -1
- package/build/stores/core.d.ts.map +1 -1
- package/build/stores/core.js +0 -1
- package/build/stores/core.js.map +1 -1
- package/build/stores/glasses.d.ts.map +1 -1
- package/build/stores/glasses.js +2 -6
- package/build/stores/glasses.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +28 -2
- package/build/stores/settings.js.map +1 -1
- package/package.json +12 -8
- package/src/engine.ts +3 -0
- package/src/facades/ai.ts +43 -0
- package/src/facades/glasses.ts +60 -31
- package/src/facades/glassesSettings.ts +1 -0
- package/src/facades/pairing.ts +18 -14
- package/src/generated/releaseMetadata.ts +4 -4
- package/src/index.ts +16 -2
- package/src/services/DeviceEventRouter.ts +8 -1
- package/src/services/GestureDotsService.ts +20 -14
- package/src/services/GlassesTouchInput.ts +11 -9
- package/src/services/LlmRouting.ts +49 -0
- package/src/services/LocalLlmModelManager.ts +327 -0
- package/src/services/LocalLlmPrompt.ts +246 -0
- package/src/services/LocalLlmProvider.ts +289 -0
- package/src/services/LocalMiniappRuntime.ts +137 -21
- package/src/services/STTModelManager.ts +140 -8
- package/src/services/controllers.ts +77 -0
- package/src/services/effectiveCapabilities.ts +16 -32
- package/src/services/neuralBand/NeuralBandInput.ts +50 -26
- package/src/services/neuralBand/NeuralBandSettingsSync.ts +28 -26
- package/src/services/settingsBridge.ts +60 -22
- package/src/stores/bluetoothSettingKeys.ts +3 -1
- package/src/stores/core.ts +0 -1
- package/src/stores/glasses.ts +2 -6
- package/src/stores/settings.ts +28 -2
|
@@ -34,6 +34,9 @@ import { markTranscriptSourceRecent, useTranscriptionDebugStore } from "../store
|
|
|
34
34
|
import { resolveMapboxRenderToken } from "./MapboxTokenService";
|
|
35
35
|
import { createAnthropicProvider, createGeminiProvider, createOpenAiProvider, } from "@glassly/llm-providers";
|
|
36
36
|
import { normalizeAiTier, resolveAiTier } from "./AiTierResolution";
|
|
37
|
+
import { decideLlmRoute, localLlmReason } from "./LlmRouting";
|
|
38
|
+
import localLlmModelManager from "./LocalLlmModelManager";
|
|
39
|
+
import localLlmProvider from "./LocalLlmProvider";
|
|
37
40
|
import { HttpError } from "@glassly/cloud-client";
|
|
38
41
|
import localDisplayManager from "./LocalDisplayManager";
|
|
39
42
|
import sttModelManager from "./STTModelManager";
|
|
@@ -45,7 +48,8 @@ import dashboardOpenTrace from "./debug/DashboardOpenTrace";
|
|
|
45
48
|
import { isAppForegrounded, isPhoneActive, onAppForegroundChange, onPhoneActivityChange } from "./PhoneActivity";
|
|
46
49
|
import { installedNotificationApps } from "./PhoneNotificationsSync";
|
|
47
50
|
import { MiniappIcons } from "./MiniappIcons";
|
|
48
|
-
import {
|
|
51
|
+
import { pairedControllersFrom, readyControllersKey } from "./controllers";
|
|
52
|
+
import { DERIVED_SETTINGS_KEYS, SETTINGS_BRIDGE_KEYS, controllersSettingFrom, isDerivedSettingsKey, selectSettingsBridgeKeys, validateSettingsSet, } from "./settingsBridge";
|
|
49
53
|
import { mayUsePermission } from "./miniappPermissions";
|
|
50
54
|
import { activeDashboard, addDashboardApp, displayViewsFor, isDashboardApp, isOverlayApp, onOverlayAppsChange, removeDashboardApp, setOverlayViewer, } from "./pages/overlayApps";
|
|
51
55
|
import { pagesEligible } from "./pages/pageEligibility";
|
|
@@ -262,6 +266,7 @@ function directLlmProvider(name) {
|
|
|
262
266
|
directLlmProviders.set(name, built);
|
|
263
267
|
return built;
|
|
264
268
|
}
|
|
269
|
+
const QUOTA_EXCEEDED_MESSAGE = "Monthly AI credit is used up. Upgrade in Settings > Membership, or add your own API key in Settings > API Keys.";
|
|
265
270
|
/** The user's saved key (Settings > API Keys) for an LLM provider, "" if none. */
|
|
266
271
|
function userLlmKey(provider) {
|
|
267
272
|
const raw = useSettingsStore.getState().getSetting(MINIAPP_READABLE_API_KEYS[provider]);
|
|
@@ -2201,11 +2206,10 @@ class LocalMiniappRuntime {
|
|
|
2201
2206
|
return isPhoneActive();
|
|
2202
2207
|
case "foreground_app_on_screen":
|
|
2203
2208
|
return localDisplayManager.foregroundAppOnScreen();
|
|
2204
|
-
case "
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
return input2dFrom(useGlassesStore.getState(), controller);
|
|
2209
|
+
case "controllers": {
|
|
2210
|
+
const glasses = useGlassesStore.getState();
|
|
2211
|
+
const records = pairedControllersFrom(useSettingsStore.getState().getSetting(SETTINGS.controllers.key));
|
|
2212
|
+
return controllersSettingFrom(records, glasses.controllers, glasses);
|
|
2209
2213
|
}
|
|
2210
2214
|
}
|
|
2211
2215
|
}
|
|
@@ -2254,7 +2258,7 @@ class LocalMiniappRuntime {
|
|
|
2254
2258
|
/**
|
|
2255
2259
|
* Push bridged settings as they change: stored keys from the store, the
|
|
2256
2260
|
* derived keys whenever their inputs move (phone activity, the display,
|
|
2257
|
-
* the apps store, the
|
|
2261
|
+
* the apps store, the controllers, the dashboard role). Subscribers declared
|
|
2258
2262
|
* PLATFORM_SETTINGS (gated at SUBSCRIBE), so this is a no-op fan-out for
|
|
2259
2263
|
* everyone else.
|
|
2260
2264
|
*/
|
|
@@ -2292,8 +2296,8 @@ class LocalMiniappRuntime {
|
|
|
2292
2296
|
localDisplayManager.onActiveDisplayChange(pushDerived);
|
|
2293
2297
|
useAppStatusStore.subscribe(pushDerived);
|
|
2294
2298
|
onOverlayAppsChange(pushDerived);
|
|
2295
|
-
useGlassesStore.subscribe((s) => `${s.ringConnected}:${s.ringBatteryLevel}:${s.
|
|
2296
|
-
useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.
|
|
2299
|
+
useGlassesStore.subscribe((s) => `${s.ringConnected}:${s.ringBatteryLevel}:${s.ringCharging}:${JSON.stringify(s.controllers ?? {})}`, pushDerived);
|
|
2300
|
+
useSettingsStore.subscribe((s) => JSON.stringify(s.getSetting(SETTINGS.controllers.key) ?? null), pushDerived);
|
|
2297
2301
|
}
|
|
2298
2302
|
/**
|
|
2299
2303
|
* Native nav UI feed (display.sendNativeNav*). Gated on the device's nav
|
|
@@ -3529,15 +3533,29 @@ class LocalMiniappRuntime {
|
|
|
3529
3533
|
// here (the user's Settings > AI Models dropdowns, or "auto" from which
|
|
3530
3534
|
// keys are saved — see AiTierResolution). Legacy provider/model pins from
|
|
3531
3535
|
// pre-tier miniapps are deliberately stripped, not honored.
|
|
3532
|
-
const { tier: rawTier, provider: _legacyProvider, model: _legacyModel, ...neutralRequest } = request;
|
|
3536
|
+
const { tier: rawTier, provider: _legacyProvider, model: _legacyModel, forceLocal: rawForceLocal, localFallback: rawLocalFallback, ...neutralRequest } = request;
|
|
3533
3537
|
const tier = normalizeAiTier(rawTier);
|
|
3534
3538
|
const resolved = resolveAiTier(tier, tierSettingValue(tier), savedLlmKeys());
|
|
3539
|
+
// ON-DEVICE ROUTING — the request's forceLocal / localFallback flags
|
|
3540
|
+
// against the primary path's health (see LlmRouting). The primary is the
|
|
3541
|
+
// vendor-direct call when a user key is saved, else the cloud relay.
|
|
3542
|
+
const directKey = resolved.provider ? userLlmKey(resolved.provider) : "";
|
|
3543
|
+
const forceLocal = rawForceLocal === true;
|
|
3544
|
+
const localFallback = rawLocalFallback === true;
|
|
3545
|
+
const cloudConnected = cloudClientService.isConnected();
|
|
3546
|
+
const cloudDisabled = cloudClientService.isCloudDisabled();
|
|
3547
|
+
const primaryUp = directKey.length > 0 || (cloudConnected && !cloudDisabled && !this.llmQuotaBlocked);
|
|
3548
|
+
const route = decideLlmRoute({ forceLocal, localFallback, primaryUp });
|
|
3549
|
+
if (route === "local") {
|
|
3550
|
+
const reason = localLlmReason({ forceLocal, cloudDisabled, cloudConnected, quotaBlocked: this.llmQuotaBlocked });
|
|
3551
|
+
await this.serveLocalAi(packageName, requestId, neutralRequest, reason, null);
|
|
3552
|
+
return;
|
|
3553
|
+
}
|
|
3535
3554
|
try {
|
|
3536
3555
|
// Direct when the resolved provider has a user key (BYO bill, unmetered,
|
|
3537
3556
|
// works with the cloud disabled); otherwise relay to the cloud LLM
|
|
3538
3557
|
// service, forwarding the resolved pin so platform-funded calls land on
|
|
3539
3558
|
// the same model the user chose.
|
|
3540
|
-
const directKey = resolved.provider ? userLlmKey(resolved.provider) : "";
|
|
3541
3559
|
if (resolved.provider && directKey) {
|
|
3542
3560
|
const result = await directLlmProvider(resolved.provider).complete({
|
|
3543
3561
|
...neutralRequest,
|
|
@@ -3560,17 +3578,30 @@ class LocalMiniappRuntime {
|
|
|
3560
3578
|
// is spent. Distinct code (miniapps may fall back to a local path) and
|
|
3561
3579
|
// a host hook so the app can offer the upgrade paywall — the miniapp
|
|
3562
3580
|
// itself must never own that conversation.
|
|
3563
|
-
|
|
3581
|
+
const quotaExceeded = err instanceof HttpError && (err.code === "quota_exceeded" || err.status === 402);
|
|
3582
|
+
if (quotaExceeded && !this.llmQuotaBlocked) {
|
|
3564
3583
|
console.warn(`${LOG_TAG}: ai.complete quota exceeded for ${packageName}`);
|
|
3565
3584
|
this.llmQuotaBlocked = true;
|
|
3566
3585
|
this.onQuotaExceeded?.("llm");
|
|
3586
|
+
}
|
|
3587
|
+
const message = err instanceof Error ? err.message : "ai.complete failed";
|
|
3588
|
+
// localFallback: the same request, on-device. The primary's error
|
|
3589
|
+
// stands only when the phone cannot serve it either.
|
|
3590
|
+
if (route === "primary_then_local") {
|
|
3591
|
+
console.warn(`${LOG_TAG}: ai.complete primary failed for ${packageName} (${message}); trying on-device`);
|
|
3592
|
+
await this.serveLocalAi(packageName, requestId, neutralRequest, quotaExceeded ? "llm_quota" : "primary_failed", {
|
|
3593
|
+
code: quotaExceeded ? MiniappErrorCode.QUOTA_EXCEEDED : MiniappErrorCode.INTERNAL,
|
|
3594
|
+
message: quotaExceeded ? QUOTA_EXCEEDED_MESSAGE : message,
|
|
3595
|
+
});
|
|
3596
|
+
return;
|
|
3597
|
+
}
|
|
3598
|
+
if (quotaExceeded) {
|
|
3567
3599
|
this.sendResult(packageName, requestId, false, undefined, {
|
|
3568
3600
|
code: MiniappErrorCode.QUOTA_EXCEEDED,
|
|
3569
|
-
message:
|
|
3601
|
+
message: QUOTA_EXCEEDED_MESSAGE,
|
|
3570
3602
|
});
|
|
3571
3603
|
return;
|
|
3572
3604
|
}
|
|
3573
|
-
const message = err instanceof Error ? err.message : "ai.complete failed";
|
|
3574
3605
|
console.warn(`${LOG_TAG}: ai.complete failed for ${packageName}: ${message}`);
|
|
3575
3606
|
this.sendResult(packageName, requestId, false, undefined, {
|
|
3576
3607
|
code: MiniappErrorCode.INTERNAL,
|
|
@@ -3578,6 +3609,50 @@ class LocalMiniappRuntime {
|
|
|
3578
3609
|
});
|
|
3579
3610
|
}
|
|
3580
3611
|
}
|
|
3612
|
+
/**
|
|
3613
|
+
* Serve one ai.complete on the phone's own model (LocalLlmProvider). With
|
|
3614
|
+
* no model on disk the download starts (or continues) in the background
|
|
3615
|
+
* and the miniapp gets AI_LOCAL_UNAVAILABLE naming the state — unless
|
|
3616
|
+
* `primaryError` is given (a failed fallback), in which case the primary's
|
|
3617
|
+
* own error is what the miniapp sees.
|
|
3618
|
+
*/
|
|
3619
|
+
async serveLocalAi(packageName, requestId, request, reason, primaryError) {
|
|
3620
|
+
const modelId = localLlmModelManager.selectedModelId();
|
|
3621
|
+
if (!(await localLlmModelManager.ensureSelectedModel())) {
|
|
3622
|
+
const status = localLlmModelManager.getStatus();
|
|
3623
|
+
const state = status?.modelId === modelId && status.stage === "downloading"
|
|
3624
|
+
? `is downloading (${status.percent}%)`
|
|
3625
|
+
: status?.modelId === modelId && status.stage === "failed"
|
|
3626
|
+
? `failed to download (${status.error ?? "unknown error"})`
|
|
3627
|
+
: "is not downloaded yet";
|
|
3628
|
+
console.warn(`${LOG_TAG}: ai.complete on-device for ${packageName} (${reason}): model ${modelId} ${state}`);
|
|
3629
|
+
this.sendResult(packageName, requestId, false, undefined, primaryError ?? {
|
|
3630
|
+
code: MiniappErrorCode.AI_LOCAL_UNAVAILABLE,
|
|
3631
|
+
message: `The on-device model (${localLlmModelManager.model(modelId).label}) ${state}. Manage it in Settings > AI Models.`,
|
|
3632
|
+
});
|
|
3633
|
+
return;
|
|
3634
|
+
}
|
|
3635
|
+
try {
|
|
3636
|
+
// webSearch is a vendor server-side grant; the phone has none.
|
|
3637
|
+
const { webSearch: _webSearch, ...localRequest } = request;
|
|
3638
|
+
const result = await localLlmProvider.complete(localRequest);
|
|
3639
|
+
console.log(`${LOG_TAG}: ai.complete served on-device for ${packageName} (${reason}, ${result.modelId})`);
|
|
3640
|
+
this.sendResult(packageName, requestId, true, {
|
|
3641
|
+
text: result.text,
|
|
3642
|
+
stopReason: result.stopReason,
|
|
3643
|
+
...(result.toolCalls ? { toolCalls: result.toolCalls } : {}),
|
|
3644
|
+
usage: result.usage,
|
|
3645
|
+
provider: "device",
|
|
3646
|
+
model: result.modelId,
|
|
3647
|
+
billedToUserKey: false,
|
|
3648
|
+
});
|
|
3649
|
+
}
|
|
3650
|
+
catch (err) {
|
|
3651
|
+
const message = err instanceof Error ? err.message : "on-device completion failed";
|
|
3652
|
+
console.warn(`${LOG_TAG}: ai.complete on-device failed for ${packageName}: ${message}`);
|
|
3653
|
+
this.sendResult(packageName, requestId, false, undefined, primaryError ?? { code: MiniappErrorCode.AI_LOCAL_UNAVAILABLE, message });
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3581
3656
|
/**
|
|
3582
3657
|
* ai.generate — agentic visual generation. Always relayed to the cloud
|
|
3583
3658
|
* runtime (the server drives the provider's agentic loop; there is no
|
|
@@ -4640,9 +4715,8 @@ class LocalMiniappRuntime {
|
|
|
4640
4715
|
this.capabilitiesWired = true;
|
|
4641
4716
|
const broadcast = () => this.broadcastCapabilities();
|
|
4642
4717
|
// Connection, model and firmware also decide the dashboard view's eligibility;
|
|
4643
|
-
//
|
|
4644
|
-
useGlassesStore.subscribe((s) => `${s.sceneCanvasWidth ?? 0}x${s.sceneCanvasHeight ?? 0}:${s.sceneVector === true}:${s.firmwareVersion}:${s.connection.state}:${s.deviceModel}:${s.
|
|
4645
|
-
useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.default_controller.key), broadcast);
|
|
4718
|
+
// connected controllers add their input surfaces to `button`.
|
|
4719
|
+
useGlassesStore.subscribe((s) => `${s.sceneCanvasWidth ?? 0}x${s.sceneCanvasHeight ?? 0}:${s.sceneVector === true}:${s.firmwareVersion}:${s.connection.state}:${s.deviceModel}:${readyControllersKey(s.controllers)}`, broadcast);
|
|
4646
4720
|
// The debug knob flips hasNativeNavigation on custom firmware.
|
|
4647
4721
|
useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.cfw_native_navigation.key), broadcast);
|
|
4648
4722
|
// The dashboard master switch flips the dashboard view.
|