@ateam-ai-mobile/expo 0.1.21 → 0.1.23

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.
Files changed (33) hide show
  1. package/dist/aiLive/provider.d.mts +10 -4
  2. package/dist/aiLive/provider.d.ts +10 -4
  3. package/dist/aiLive/provider.js +119 -43
  4. package/dist/aiLive/provider.js.map +1 -1
  5. package/dist/aiLive/provider.mjs +5 -3
  6. package/dist/bootstrap.js +120 -46
  7. package/dist/bootstrap.js.map +1 -1
  8. package/dist/bootstrap.mjs +4 -4
  9. package/dist/capabilities/index.js +19 -1
  10. package/dist/capabilities/index.js.map +1 -1
  11. package/dist/capabilities/index.mjs +21 -3
  12. package/dist/capabilities/index.mjs.map +1 -1
  13. package/dist/{chunk-BAZFMTCC.mjs → chunk-4HJ5FO3S.mjs} +2 -2
  14. package/dist/{chunk-C62NQA4F.mjs → chunk-GGEFDRQQ.mjs} +2 -2
  15. package/dist/chunk-GGEFDRQQ.mjs.map +1 -0
  16. package/dist/{chunk-OEX4OKYQ.mjs → chunk-NH3CHSLR.mjs} +2 -2
  17. package/dist/{chunk-JUBLG3PN.mjs → chunk-PV2EHH2P.mjs} +119 -44
  18. package/dist/chunk-PV2EHH2P.mjs.map +1 -0
  19. package/dist/device/createDeviceProvider.mjs +3 -3
  20. package/dist/device-bridge/index.js +1 -1
  21. package/dist/device-bridge/index.js.map +1 -1
  22. package/dist/device-bridge/index.mjs +2 -2
  23. package/dist/push/silentSync.js.map +1 -1
  24. package/dist/push/silentSync.mjs +1 -1
  25. package/package.json +4 -4
  26. package/src/aiLive/provider.ts +138 -49
  27. package/src/aiLive/streamProtocol.ts +2 -2
  28. package/src/capabilities/store.ts +21 -1
  29. package/src/device-bridge/capabilities.ts +2 -1
  30. package/dist/chunk-C62NQA4F.mjs.map +0 -1
  31. package/dist/chunk-JUBLG3PN.mjs.map +0 -1
  32. /package/dist/{chunk-BAZFMTCC.mjs.map → chunk-4HJ5FO3S.mjs.map} +0 -0
  33. /package/dist/{chunk-OEX4OKYQ.mjs.map → chunk-NH3CHSLR.mjs.map} +0 -0
package/dist/bootstrap.js CHANGED
@@ -176,7 +176,7 @@ __export(bootstrap_exports, {
176
176
  });
177
177
  module.exports = __toCommonJS(bootstrap_exports);
178
178
  var import_core3 = require("@ateam-ai-mobile/core");
179
- var import_plugin_runtime3 = require("@ateam-ai-mobile/plugin-runtime");
179
+ var import_plugin_runtime4 = require("@ateam-ai-mobile/plugin-runtime");
180
180
  var import_ui2 = require("@ateam-ai-mobile/ui");
181
181
 
182
182
  // src/audio/index.ts
@@ -2925,6 +2925,7 @@ function createDeviceProvider(config2) {
2925
2925
 
2926
2926
  // src/aiLive/provider.ts
2927
2927
  var import_core2 = require("@ateam-ai-mobile/core");
2928
+ var import_plugin_runtime3 = require("@ateam-ai-mobile/plugin-runtime");
2928
2929
 
2929
2930
  // src/aiLive/streamProtocol.ts
2930
2931
  var STREAM_PATH = "/ws/stream";
@@ -2953,8 +2954,8 @@ function encodeFrame({ seq, tCapture, payload }) {
2953
2954
  out.set(payload, HEADER_BYTES);
2954
2955
  return out;
2955
2956
  }
2956
- function openMessage(kind) {
2957
- return JSON.stringify({ type: "open", kind });
2957
+ function openMessage(kind, params) {
2958
+ return JSON.stringify(params ? { type: "open", kind, params } : { type: "open", kind });
2958
2959
  }
2959
2960
  var CLOSE_MESSAGE = JSON.stringify({ type: "close" });
2960
2961
  function parseServerEvent(text2) {
@@ -3002,22 +3003,14 @@ function base64ToBytes(b64) {
3002
3003
  }
3003
3004
 
3004
3005
  // src/aiLive/provider.ts
3005
- var PHONE_SLICE_USD = 0.5;
3006
3006
  var LIVE_KIND = "ai-models.live";
3007
3007
  var FRESH_MS2 = 3e3;
3008
3008
  var MAX_BUFFERED = 512 * 1024;
3009
3009
  var DEFAULT_CAMERA = { fps: 1, maxPx: 768 };
3010
- var SESSION_UP = /* @__PURE__ */ new Set(["starting", "running", "degraded"]);
3011
- async function defaultCallCp(method, params = {}) {
3012
- const res = await (0, import_core2.mcpToolCall)("cp.ai_models_api", { method, params });
3013
- if (res?.error) throw new Error(res.error.message || "AI Models refused the request");
3014
- const out = (0, import_core2.unwrapMcpResult)(res?.result);
3015
- if (res?.result?.isError) throw new Error(typeof out === "string" ? out : out?.message || out?.error || "AI Models refused the request");
3016
- if (out && typeof out === "object" && out.ok === false) throw new Error(out.message || out.error || "AI Models refused the request");
3017
- return out;
3018
- }
3010
+ var OPEN_TIMEOUT_MS = 2e4;
3011
+ var CLOSE_WAIT_MS = 3e3;
3012
+ var isEpisode = (d) => !!d && typeof d === "object" && typeof d.type === "string" && typeof d.class === "string" && typeof d.episodeId === "string" && typeof d.actorId === "string" && d.actorId.length > 0 && Number.isFinite(d.at);
3019
3013
  var defaultAiLiveDeps = () => ({
3020
- callCp: defaultCallCp,
3021
3014
  WebSocketImpl: globalThis.WebSocket,
3022
3015
  wsBase: import_core2.getCoreWsUrl,
3023
3016
  token: import_core2.getToken,
@@ -3025,7 +3018,9 @@ var defaultAiLiveDeps = () => ({
3025
3018
  jpegById,
3026
3019
  now: Date.now,
3027
3020
  setInterval: (fn, ms) => setInterval(fn, ms),
3028
- clearInterval: (h) => clearInterval(h)
3021
+ clearInterval: (h) => clearInterval(h),
3022
+ setTimeout: (fn, ms) => setTimeout(fn, ms),
3023
+ clearTimeout: (h) => clearTimeout(h)
3029
3024
  });
3030
3025
  var idle = () => ({ state: "idle", camera: null, sent: 0, skipped: 0, lastEpisode: null, lastError: null });
3031
3026
  function createAiLiveProvider(overrides = {}) {
@@ -3035,7 +3030,11 @@ function createAiLiveProvider(overrides = {}) {
3035
3030
  let ws = null;
3036
3031
  let loop = null;
3037
3032
  let visual = null;
3038
- let startedSession = false;
3033
+ let allowed = () => false;
3034
+ let stopReason = null;
3035
+ let tearingDown = null;
3036
+ const waitingToStart = /* @__PURE__ */ new Map();
3037
+ const closedOf = /* @__PURE__ */ new WeakMap();
3039
3038
  let closingOnPurpose = false;
3040
3039
  let seq = 0;
3041
3040
  let busy = false;
@@ -3088,6 +3087,12 @@ function createAiLiveProvider(overrides = {}) {
3088
3087
  const g = gen;
3089
3088
  const sock = ws;
3090
3089
  const current = () => g === gen && ws === sock && sock?.readyState === 1;
3090
+ const consentGone = () => {
3091
+ if (allowed()) return false;
3092
+ if (g === gen) void teardown("stopped", import_plugin_runtime3.CONSENT_TAKEN_BACK);
3093
+ return true;
3094
+ };
3095
+ if (consentGone()) return;
3091
3096
  if (busy || !current()) {
3092
3097
  set2({ skipped: snap.skipped + 1 });
3093
3098
  return;
@@ -3108,7 +3113,7 @@ function createAiLiveProvider(overrides = {}) {
3108
3113
  return;
3109
3114
  }
3110
3115
  const b64 = await deps.jpegById(id, camera.maxPx, 0.7);
3111
- if (!current()) return;
3116
+ if (!current() || consentGone()) return;
3112
3117
  const frame = encodeFrame({ seq: mySeq, tCapture: t, payload: base64ToBytes(b64) });
3113
3118
  sock.send(frame.buffer);
3114
3119
  lastFrameId = id;
@@ -3132,22 +3137,42 @@ function createAiLiveProvider(overrides = {}) {
3132
3137
  const sock = new deps.WebSocketImpl(url, [SUBPROTOCOL, `adas.bearer.${token}`]);
3133
3138
  ws = sock;
3134
3139
  sock.binaryType = "arraybuffer";
3140
+ let markClosed = () => {
3141
+ };
3142
+ closedOf.set(sock, new Promise((r) => {
3143
+ markClosed = r;
3144
+ }));
3145
+ const gaveUp = deps.setTimeout(() => {
3146
+ if (opened) return;
3147
+ reject(new Error("Core did not answer in time. Try again."));
3148
+ try {
3149
+ sock.close();
3150
+ } catch {
3151
+ }
3152
+ }, OPEN_TIMEOUT_MS);
3135
3153
  sock.onopen = () => {
3136
3154
  try {
3137
- sock.send(openMessage(LIVE_KIND));
3155
+ sock.send(openMessage(LIVE_KIND, { session: "own", source: "phone" }));
3138
3156
  } catch (e) {
3139
3157
  reject(e);
3140
3158
  }
3141
3159
  };
3142
3160
  sock.onmessage = (m) => {
3161
+ if (ws !== sock) return;
3143
3162
  const ev = parseServerEvent(m?.data);
3144
3163
  if (!ev) return;
3164
+ if (ev.event === "session" && !opened) {
3165
+ const c = ev.data?.camera;
3166
+ camera = c && Number(c.fps) > 0 && Number(c.maxPx) > 0 ? { fps: Number(c.fps), maxPx: Number(c.maxPx) } : DEFAULT_CAMERA;
3167
+ return;
3168
+ }
3145
3169
  if (ev.event === "opened" && !opened) {
3146
3170
  opened = true;
3171
+ deps.clearTimeout(gaveUp);
3147
3172
  resolve();
3148
3173
  return;
3149
3174
  }
3150
- if (ev.event === "episode" && ev.data && typeof ev.data === "object") {
3175
+ if (ev.event === "episode" && isEpisode(ev.data)) {
3151
3176
  const episode = ev.data;
3152
3177
  set2({ lastEpisode: episode });
3153
3178
  for (const cb of owner && episodeCbs.get(owner) || []) {
@@ -3161,8 +3186,10 @@ function createAiLiveProvider(overrides = {}) {
3161
3186
  sock.onerror = () => {
3162
3187
  };
3163
3188
  sock.onclose = (c) => {
3189
+ markClosed();
3164
3190
  const why = describeClose(Number(c?.code), c?.reason);
3165
3191
  if (!opened) {
3192
+ deps.clearTimeout(gaveUp);
3166
3193
  reject(new Error(why));
3167
3194
  return;
3168
3195
  }
@@ -3171,9 +3198,20 @@ function createAiLiveProvider(overrides = {}) {
3171
3198
  };
3172
3199
  });
3173
3200
  }
3174
- async function teardown(state, lastError) {
3175
- const hadSession = startedSession;
3176
- startedSession = false;
3201
+ function teardown(state, lastError) {
3202
+ if (tearingDown) return tearingDown;
3203
+ const p = teardownNow(state, lastError);
3204
+ tearingDown = p;
3205
+ void p.then(() => {
3206
+ }, () => {
3207
+ }).then(() => {
3208
+ if (tearingDown === p) tearingDown = null;
3209
+ });
3210
+ return p;
3211
+ }
3212
+ async function teardownNow(state, lastError) {
3213
+ stopReason = lastError;
3214
+ allowed = () => false;
3177
3215
  gen++;
3178
3216
  stopTimers();
3179
3217
  const sock = ws;
@@ -3190,21 +3228,40 @@ function createAiLiveProvider(overrides = {}) {
3190
3228
  closingOnPurpose = false;
3191
3229
  const v = visual;
3192
3230
  visual = null;
3193
- try {
3194
- await v?.stop?.();
3195
- } catch {
3196
- }
3197
- if (hadSession) {
3198
- try {
3199
- await deps.callCp("liveStop");
3200
- } catch {
3201
- }
3202
- }
3231
+ let gaveUp = null;
3232
+ const closed = sock && closedOf.get(sock);
3233
+ await Promise.all([
3234
+ (async () => {
3235
+ try {
3236
+ await v?.stop?.();
3237
+ } catch {
3238
+ }
3239
+ })(),
3240
+ closed ? Promise.race([closed, new Promise((r) => {
3241
+ gaveUp = deps.setTimeout(r, CLOSE_WAIT_MS);
3242
+ })]) : Promise.resolve()
3243
+ ]);
3244
+ if (gaveUp != null) deps.clearTimeout(gaveUp);
3203
3245
  owner = null;
3204
3246
  set2({ state, lastError });
3205
3247
  }
3206
3248
  return {
3207
3249
  async start(opts, who, ctx) {
3250
+ if (tearingDown) {
3251
+ const wait = { cancelled: false };
3252
+ waitingToStart.set(who, wait);
3253
+ try {
3254
+ while (tearingDown) {
3255
+ try {
3256
+ await tearingDown;
3257
+ } catch {
3258
+ }
3259
+ }
3260
+ } finally {
3261
+ if (waitingToStart.get(who) === wait) waitingToStart.delete(who);
3262
+ }
3263
+ if (wait.cancelled) throw Object.assign(new Error("aiLive: stopped before it started."), { code: "STOPPED" });
3264
+ }
3208
3265
  if (owner && owner !== who) throw Object.assign(new Error("aiLive: another screen is sending to AI right now. Close it first."), { code: "CONFLICT" });
3209
3266
  if (owner === who) return snap;
3210
3267
  const cam = opts?.camera === "front" ? "front" : "back";
@@ -3212,35 +3269,52 @@ function createAiLiveProvider(overrides = {}) {
3212
3269
  owner = who;
3213
3270
  lastOwner = who;
3214
3271
  visual = ctx.visual;
3272
+ allowed = typeof ctx.allowed === "function" ? ctx.allowed : () => false;
3215
3273
  gen++;
3216
3274
  seq = 0;
3217
3275
  lastFrameId = null;
3218
3276
  busy = false;
3277
+ const g = gen;
3278
+ const cameraOfScreen = ctx.visual;
3219
3279
  snap = { ...idle(), camera: cam };
3220
3280
  set2({ state: "starting" });
3281
+ const stillStarting = async () => {
3282
+ if (g === gen && !allowed()) await teardown("stopped", import_plugin_runtime3.CONSENT_TAKEN_BACK);
3283
+ if (g !== gen) throw Object.assign(new Error(stopReason || "aiLive: stopped before it started sending."), { code: "STOPPED" });
3284
+ };
3221
3285
  try {
3222
- let session = (await deps.callCp("liveStatus"))?.session;
3223
- if (!SESSION_UP.has(session?.state)) {
3224
- session = (await deps.callCp("liveStart", { source: "phone", budgetSliceUsd: PHONE_SLICE_USD }))?.session;
3225
- startedSession = true;
3226
- }
3227
- const c = session?.camera;
3228
- camera = c && Number(c.fps) > 0 && Number(c.maxPx) > 0 ? { fps: Number(c.fps), maxPx: Number(c.maxPx) } : DEFAULT_CAMERA;
3229
- await visual.start({ intent: "stream", facing: cam });
3286
+ await stillStarting();
3287
+ camera = DEFAULT_CAMERA;
3288
+ if (g !== gen) throw Object.assign(new Error(stopReason || "aiLive: stopped before it started sending."), { code: "STOPPED" });
3230
3289
  await openSocket();
3290
+ await stillStarting();
3291
+ await cameraOfScreen.start({ intent: "stream", facing: cam });
3292
+ if (g !== gen) {
3293
+ try {
3294
+ await cameraOfScreen.stop?.();
3295
+ } catch {
3296
+ }
3297
+ }
3298
+ await stillStarting();
3299
+ if (g !== gen) throw Object.assign(new Error(stopReason || "aiLive: stopped before it started sending."), { code: "STOPPED" });
3231
3300
  loop = deps.setInterval(() => {
3232
3301
  void slot();
3233
3302
  }, Math.max(200, Math.round(1e3 / camera.fps)));
3234
3303
  set2({ state: "sending" });
3235
3304
  return snap;
3236
3305
  } catch (e) {
3306
+ if (g !== gen) {
3307
+ throw e?.code === "STOPPED" ? e : Object.assign(new Error(stopReason || "aiLive: stopped before it started sending."), { code: "STOPPED" });
3308
+ }
3237
3309
  await teardown("error", String(e?.message || e));
3238
3310
  throw e;
3239
3311
  }
3240
3312
  },
3241
- async stop(who) {
3313
+ async stop(who, why) {
3314
+ const waiting = waitingToStart.get(who);
3315
+ if (waiting) waiting.cancelled = true;
3242
3316
  if (owner !== who) return;
3243
- await teardown("stopped", null);
3317
+ await teardown("stopped", why ?? null);
3244
3318
  },
3245
3319
  getSnapshot: (who) => lastOwner === who ? snap : idle(),
3246
3320
  onEpisode: (cb, who) => add(episodeCbs, who, cb),
@@ -3342,7 +3416,7 @@ function bootstrapExpoAdas(opts) {
3342
3416
  deviceProvider: createDeviceProvider({ reachabilityUrl: opts.reachabilityUrl })
3343
3417
  });
3344
3418
  for (const plugin of opts.nativePlugins ?? []) {
3345
- for (const id of plugin.ids) (0, import_plugin_runtime3.registerNativePlugin)(id, plugin.resolver);
3419
+ for (const id of plugin.ids) (0, import_plugin_runtime4.registerNativePlugin)(id, plugin.resolver);
3346
3420
  }
3347
3421
  const relay = opts.urls.relay;
3348
3422
  (0, import_ui2.setPluginWebViewExtraHosts)(() => relay ? [relay()] : []);
@@ -3356,11 +3430,11 @@ function bootstrapExpoAdas(opts) {
3356
3430
  const onReach = opts.onReachabilityChange;
3357
3431
  (0, import_core3.setAcsReachabilityReporter)({ up: () => onReach(true), down: () => onReach(false) });
3358
3432
  }
3359
- (0, import_plugin_runtime3.setDeviceStateProvider)(
3433
+ (0, import_plugin_runtime4.setDeviceStateProvider)(
3360
3434
  createDeviceState({ connectivity: connectivity_exports, location: location_exports })
3361
3435
  );
3362
- (0, import_plugin_runtime3.setAiLiveProvider)(createAiLiveProvider());
3363
- (0, import_plugin_runtime3.registerPlatformDeviceTools)();
3436
+ (0, import_plugin_runtime4.setAiLiveProvider)(createAiLiveProvider());
3437
+ (0, import_plugin_runtime4.registerPlatformDeviceTools)();
3364
3438
  (0, import_ui2.setUIActionNavigator)(opts.navigateToPlugin);
3365
3439
  (0, import_core3.initAdas)({
3366
3440
  coreUrl: opts.urls.core,