@appilots/sdk 0.4.1 → 0.6.0

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.
@@ -1,4 +1,4 @@
1
- import { useAppilotsContext, setNavigationRef, setCurrentScreen } from './chunk-4GUPZDWT.mjs';
1
+ import { useAppilotsContext, setNavigationRef, setCurrentScreen } from './chunk-R4D34FEW.mjs';
2
2
  import React, { useRef, useCallback, useEffect } from 'react';
3
3
 
4
4
  function getActiveRouteName(state) {
@@ -42,6 +42,21 @@ var StreamAbortError = class extends Error {
42
42
  this.partialContent = partialContent;
43
43
  }
44
44
  };
45
+ var RateLimitedError = class extends Error {
46
+ retryAfterSeconds;
47
+ constructor(message, retryAfterSeconds) {
48
+ super(message);
49
+ this.name = "RateLimitedError";
50
+ this.retryAfterSeconds = retryAfterSeconds;
51
+ }
52
+ };
53
+ function parseRetryAfter(...candidates) {
54
+ for (const candidate of candidates) {
55
+ const seconds = Number(candidate);
56
+ if (Number.isFinite(seconds) && seconds > 0) return Math.ceil(seconds);
57
+ }
58
+ return 60;
59
+ }
45
60
  var CONTINUATION_TIMEOUT_MS = 15e4;
46
61
  var AppilotsClient = class {
47
62
  baseUrl;
@@ -50,6 +65,7 @@ var AppilotsClient = class {
50
65
  timeout;
51
66
  debug;
52
67
  mcpVersion;
68
+ introspectionReporter;
53
69
  user;
54
70
  sessionId = null;
55
71
  /** Guard so identify fires at most once per client instance. */
@@ -60,6 +76,7 @@ var AppilotsClient = class {
60
76
  this.timeout = options.timeout ?? 6e4;
61
77
  this.debug = options.debug ?? false;
62
78
  this.mcpVersion = options.mcpVersion;
79
+ this.introspectionReporter = options.introspectionReporter;
63
80
  this.user = options.user;
64
81
  this.headers = {
65
82
  "Content-Type": "application/json",
@@ -104,6 +121,15 @@ var AppilotsClient = class {
104
121
  }
105
122
  if (!response.ok) {
106
123
  const errorMsg = json?.error?.message ?? json?.error ?? `HTTP ${response.status}`;
124
+ if (response.status === 429) {
125
+ throw new RateLimitedError(
126
+ errorMsg,
127
+ parseRetryAfter(
128
+ json?.error?.retryAfterSeconds,
129
+ response.headers?.get?.("retry-after")
130
+ )
131
+ );
132
+ }
107
133
  throw new Error(errorMsg);
108
134
  }
109
135
  return json.data ?? json;
@@ -162,6 +188,21 @@ var AppilotsClient = class {
162
188
  getSessionId() {
163
189
  return this.sessionId;
164
190
  }
191
+ /**
192
+ * Wire fragment for introspection health. Empty on the happy path —
193
+ * a working client sends nothing, so the field costs a byte only
194
+ * when something is actually wrong. Never throws: a broken reporter
195
+ * must not take the message down with it.
196
+ */
197
+ introspectionFragment() {
198
+ try {
199
+ const report = this.introspectionReporter?.();
200
+ if (!report || report.captured !== false) return {};
201
+ return { introspection: report };
202
+ } catch {
203
+ return {};
204
+ }
205
+ }
165
206
  // ── Messages ──────────────────────────────────────────────────
166
207
  async sendMessage(content, context) {
167
208
  if (!this.sessionId) {
@@ -175,7 +216,8 @@ var AppilotsClient = class {
175
216
  context,
176
217
  // Optional MCP version bundled with this app build. Server
177
218
  // compares vs the active MCP doc to detect stale uploads.
178
- ...this.mcpVersion ? { mcpVersion: this.mcpVersion } : {}
219
+ ...this.mcpVersion ? { mcpVersion: this.mcpVersion } : {},
220
+ ...this.introspectionFragment()
179
221
  })
180
222
  });
181
223
  if (data.sessionId) {
@@ -332,11 +374,25 @@ var AppilotsClient = class {
332
374
  const contentType = String(xhr.getResponseHeader?.("content-type") ?? "");
333
375
  if (xhr.status !== 200 || !contentType.includes("text/event-stream")) {
334
376
  let message2 = `HTTP ${xhr.status || 0}`;
377
+ let retryAfterSeconds;
335
378
  try {
336
379
  const json = JSON.parse(xhr.responseText || "{}");
337
380
  message2 = json?.error?.message ?? json?.error ?? message2;
381
+ retryAfterSeconds = json?.error?.retryAfterSeconds;
338
382
  } catch {
339
383
  }
384
+ if (xhr.status === 429) {
385
+ fail(
386
+ new RateLimitedError(
387
+ message2,
388
+ parseRetryAfter(
389
+ retryAfterSeconds,
390
+ xhr.getResponseHeader?.("retry-after")
391
+ )
392
+ )
393
+ );
394
+ return;
395
+ }
340
396
  fail(
341
397
  sawAnyEvent ? new Error(message2) : new StreamTransportError(message2)
342
398
  );
@@ -402,7 +458,8 @@ var AppilotsClient = class {
402
458
  content,
403
459
  sessionId: this.sessionId,
404
460
  context,
405
- ...this.mcpVersion ? { mcpVersion: this.mcpVersion } : {}
461
+ ...this.mcpVersion ? { mcpVersion: this.mcpVersion } : {},
462
+ ...this.introspectionFragment()
406
463
  })
407
464
  );
408
465
  });
@@ -923,25 +980,41 @@ function detectLocaleFromUserTexts(texts) {
923
980
  var copy = {
924
981
  pt: {
925
982
  rateLimit: "O provedor de IA atingiu o limite de uso durante a automa\xE7\xE3o. Aguarde um momento e pe\xE7a para continuar, ou aumente o limite de tokens por minuto.",
983
+ platformRateLimit: (wait) => `Recebi pedidos demais em pouco tempo e precisei pausar. Tente de novo em ${wait}.`,
926
984
  invalidInput: "N\xE3o consegui continuar a automa\xE7\xE3o por um problema interno de sincroniza\xE7\xE3o. Pode tentar de novo ou fazer essa parte manualmente?",
927
985
  generic: (detail) => `Perdi o fio da automa\xE7\xE3o (${detail}). Pode continuar manualmente ou me pedir de novo?`
928
986
  },
929
987
  es: {
930
988
  rateLimit: "El proveedor de IA alcanz\xF3 su l\xEDmite de uso durante la automatizaci\xF3n. Espera un momento y p\xEDdeme continuar, o aumenta el l\xEDmite de tokens por minuto.",
989
+ platformRateLimit: (wait) => `Recib\xED demasiadas solicitudes en poco tiempo y tuve que pausar. Int\xE9ntalo de nuevo en ${wait}.`,
931
990
  invalidInput: "No pude continuar la automatizaci\xF3n por un problema interno de sincronizaci\xF3n. \xBFPuedes intentarlo de nuevo o hacer esta parte manualmente?",
932
991
  generic: (detail) => `Perd\xED el hilo de la automatizaci\xF3n (${detail}). \xBFPuedes continuar manualmente o ped\xEDrmelo de nuevo?`
933
992
  },
934
993
  fr: {
935
994
  rateLimit: "Le fournisseur d'IA a atteint sa limite d'utilisation pendant l'automatisation. Attendez un instant et demandez-moi de continuer, ou augmentez la limite de tokens par minute.",
995
+ platformRateLimit: (wait) => `J'ai re\xE7u trop de demandes en peu de temps et j'ai d\xFB faire une pause. R\xE9essayez dans ${wait}.`,
936
996
  invalidInput: "Je n'ai pas pu poursuivre l'automatisation \xE0 cause d'un probl\xE8me interne de synchronisation. Pouvez-vous r\xE9essayer ou faire cette \xE9tape manuellement ?",
937
997
  generic: (detail) => `J'ai perdu le fil de l'automatisation (${detail}). Vous pouvez continuer manuellement ou me redemander ?`
938
998
  },
939
999
  en: {
940
1000
  rateLimit: "Your AI provider hit its rate limit during automation. Wait a moment and ask me to continue, or raise your TPM limit.",
1001
+ platformRateLimit: (wait) => `I got too many requests in a short time and had to pause. Try again in ${wait}.`,
941
1002
  invalidInput: "I could not continue the automation because of an internal sync issue. You can try again or take over manually.",
942
1003
  generic: (detail) => `I lost my footing during automation (${detail}). You can take over from here.`
943
1004
  }
944
1005
  };
1006
+ var waitPhrase = {
1007
+ pt: (s) => s < 90 ? `${Math.ceil(s)} segundos` : `cerca de ${Math.ceil(s / 60)} minutos`,
1008
+ es: (s) => s < 90 ? `${Math.ceil(s)} segundos` : `unos ${Math.ceil(s / 60)} minutos`,
1009
+ fr: (s) => s < 90 ? `${Math.ceil(s)} secondes` : `environ ${Math.ceil(s / 60)} minutes`,
1010
+ en: (s) => s < 90 ? `${Math.ceil(s)} seconds` : `about ${Math.ceil(s / 60)} minutes`
1011
+ };
1012
+ function buildRateLimitedMessage(retryAfterSeconds, recentUserTexts) {
1013
+ const locale = detectLocaleFromUserTexts(recentUserTexts);
1014
+ const strings = copy[locale] ?? copy.en;
1015
+ const seconds = Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0 ? retryAfterSeconds : 60;
1016
+ return strings.platformRateLimit(waitPhrase[locale](seconds));
1017
+ }
945
1018
  function buildContinuationFailureMessage(rawError, recentUserTexts) {
946
1019
  const locale = detectLocaleFromUserTexts(recentUserTexts);
947
1020
  const strings = copy[locale] ?? copy.en;
@@ -1545,7 +1618,7 @@ var ChatSessionMachine = class {
1545
1618
  const raw = err instanceof Error ? err.message : "Continuation failed";
1546
1619
  this.warn("ChatSessionMachine: continuation failed \u2014", raw);
1547
1620
  const recentUserTexts = this.state.messages.filter((m) => m.role === "user").slice(-4).map((m) => m.content);
1548
- const friendly = buildContinuationFailureMessage(raw, recentUserTexts);
1621
+ const friendly = err instanceof RateLimitedError ? buildRateLimitedMessage(err.retryAfterSeconds, recentUserTexts) : buildContinuationFailureMessage(raw, recentUserTexts);
1549
1622
  this.addAssistantMessage({
1550
1623
  id: `error_${++this.messageIdCounter}`,
1551
1624
  role: "assistant",
@@ -1707,6 +1780,17 @@ var ChatSessionMachine = class {
1707
1780
  removeStreamingPlaceholder();
1708
1781
  const errorMessage = err instanceof Error ? err.message : "Failed to send message";
1709
1782
  this.setState({ error: errorMessage });
1783
+ if (err instanceof RateLimitedError) {
1784
+ const recentUserTexts = this.state.messages.filter((m) => m.role === "user").slice(-4).map((m) => m.content);
1785
+ this.addAssistantMessage({
1786
+ id: `error_${++this.messageIdCounter}`,
1787
+ role: "assistant",
1788
+ content: buildRateLimitedMessage(err.retryAfterSeconds, recentUserTexts),
1789
+ timestamp: Date.now()
1790
+ });
1791
+ this.setState({ isLoading: false, loadingStatusKey: "thinking" });
1792
+ return;
1793
+ }
1710
1794
  this.addAssistantMessage({
1711
1795
  id: `error_${++this.messageIdCounter}`,
1712
1796
  role: "assistant",
@@ -3628,15 +3712,32 @@ function getGlobalConfig() {
3628
3712
 
3629
3713
  // src/introspection/fiberRoot.ts
3630
3714
  var _fiberRoot = null;
3715
+ var _diagnostics = {
3716
+ captured: false,
3717
+ failureReason: null,
3718
+ reactVersion: null,
3719
+ failureCount: 0
3720
+ };
3631
3721
  function setFiberRoot(fiber) {
3632
3722
  if (_fiberRoot !== fiber) {
3633
3723
  console.log(`[Appilots] Introspection: fiber root captured (type=${typeName(fiber)})`);
3634
3724
  _fiberRoot = fiber;
3635
3725
  }
3726
+ _diagnostics.captured = true;
3636
3727
  }
3637
3728
  function getFiberRoot() {
3638
3729
  return _fiberRoot;
3639
3730
  }
3731
+ function recordIntrospectionFailure(reason, reactVersion) {
3732
+ _diagnostics.failureCount += 1;
3733
+ if (_diagnostics.failureReason === null) {
3734
+ _diagnostics.failureReason = reason;
3735
+ _diagnostics.reactVersion = reactVersion;
3736
+ }
3737
+ }
3738
+ function getIntrospectionDiagnostics() {
3739
+ return { ..._diagnostics };
3740
+ }
3640
3741
  function typeName(fiber) {
3641
3742
  const t = fiber?.type ?? fiber?.elementType;
3642
3743
  if (!t) return "unknown";
@@ -3645,15 +3746,16 @@ function typeName(fiber) {
3645
3746
  if (typeof t === "object") return t.displayName ?? t.render?.displayName ?? t.render?.name ?? "forwardRef/memo";
3646
3747
  return String(t);
3647
3748
  }
3648
-
3649
- // src/introspection/AppilotsFiberRoot.tsx
3650
3749
  var FiberSentinel = class extends React2__default.default.Component {
3651
3750
  componentDidMount() {
3652
3751
  const fiber = this._reactInternals ?? this._reactInternalFiber ?? null;
3653
3752
  if (!fiber) {
3753
+ const reactVersion = React2__default.default.version ?? null;
3654
3754
  console.warn(
3655
- "[Appilots] FiberSentinel: this._reactInternals is undefined; snapshot capture will fall back to render-time owner. React version may be incompatible."
3755
+ `[Appilots] FiberSentinel: this._reactInternals is undefined; snapshot capture will fall back to render-time owner. React version may be incompatible (react=${reactVersion}).`
3656
3756
  );
3757
+ recordIntrospectionFailure("sentinel-missing-internals", reactVersion);
3758
+ this.props.onUnavailable?.({ reactVersion });
3657
3759
  return;
3658
3760
  }
3659
3761
  let top = fiber;
@@ -3664,13 +3766,16 @@ var FiberSentinel = class extends React2__default.default.Component {
3664
3766
  return this.props.children ?? null;
3665
3767
  }
3666
3768
  };
3667
- function AppilotsFiberRoot({ children }) {
3769
+ function AppilotsFiberRoot({
3770
+ children,
3771
+ onUnavailable
3772
+ }) {
3668
3773
  const Internals = React2__default.default.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_IN_THIS_VERSION_OF_REACT;
3669
3774
  const ownerFiber = Internals?.ReactCurrentOwner?.current;
3670
3775
  if (ownerFiber && !getFiberRoot()) {
3671
3776
  setFiberRoot(ownerFiber);
3672
3777
  }
3673
- return /* @__PURE__ */ React2__default.default.createElement(FiberSentinel, null, children);
3778
+ return /* @__PURE__ */ React2__default.default.createElement(FiberSentinel, { onUnavailable }, children);
3674
3779
  }
3675
3780
 
3676
3781
  // src/debug/logger.ts
@@ -3727,6 +3832,16 @@ function AppilotsProvider({ config: configProp, children, client: externalClient
3727
3832
  recordAppilotsEventTrace(event);
3728
3833
  listenersRef.current.forEach((handler) => handler(event));
3729
3834
  }, []);
3835
+ const handleIntrospectionUnavailable = React2.useCallback(
3836
+ ({ reactVersion }) => {
3837
+ emit({
3838
+ type: "sdk:introspection:unavailable",
3839
+ timestamp: Date.now(),
3840
+ data: { reason: "sentinel-missing-internals", reactVersion }
3841
+ });
3842
+ },
3843
+ [emit]
3844
+ );
3730
3845
  const client = React2.useMemo(
3731
3846
  () => externalClient ?? new AppilotsClient({
3732
3847
  projectId: config.projectId,
@@ -3735,7 +3850,9 @@ function AppilotsProvider({ config: configProp, children, client: externalClient
3735
3850
  debug: config.debug,
3736
3851
  appVersion: config.appVersion,
3737
3852
  mcpVersion: config.mcpVersion,
3738
- user: config.user
3853
+ user: config.user,
3854
+ // Only reports a FAILED reading; the happy path sends nothing.
3855
+ introspectionReporter: getIntrospectionDiagnostics
3739
3856
  }),
3740
3857
  [config.projectId, config.apiBaseUrl, config.apiKey, config.debug, config.appVersion, config.mcpVersion, config.user, externalClient]
3741
3858
  );
@@ -3756,7 +3873,7 @@ function AppilotsProvider({ config: configProp, children, client: externalClient
3756
3873
  () => ({ config, client, subscribe, emit, remotePersonalization }),
3757
3874
  [config, client, subscribe, emit, remotePersonalization]
3758
3875
  );
3759
- return /* @__PURE__ */ React2__default.default.createElement(AppilotsContext.Provider, { value }, /* @__PURE__ */ React2__default.default.createElement(AppilotsFiberRoot, null, children));
3876
+ return /* @__PURE__ */ React2__default.default.createElement(AppilotsContext.Provider, { value }, /* @__PURE__ */ React2__default.default.createElement(AppilotsFiberRoot, { onUnavailable: handleIntrospectionUnavailable }, children));
3760
3877
  }
3761
3878
  function useAppilotsContext() {
3762
3879
  const context = React2.useContext(AppilotsContext);
@@ -4281,6 +4398,7 @@ exports.AppilotsProvider = AppilotsProvider;
4281
4398
  exports.AppilotsRegistryProvider = AppilotsRegistryProvider;
4282
4399
  exports.ChatSessionMachine = ChatSessionMachine;
4283
4400
  exports.OPTIONAL_STEP_AUTOMATION_HINT = OPTIONAL_STEP_AUTOMATION_HINT;
4401
+ exports.RateLimitedError = RateLimitedError;
4284
4402
  exports.SDK_VERSION = SDK_VERSION;
4285
4403
  exports._patchJsxRuntimes = _patchJsxRuntimes;
4286
4404
  exports.actionPressTargetId = actionPressTargetId;
@@ -4307,6 +4425,7 @@ exports.getCurrentScreenSignature = getCurrentScreenSignature;
4307
4425
  exports.getDefaultRegistry = getDefaultRegistry;
4308
4426
  exports.getFiberRoot = getFiberRoot;
4309
4427
  exports.getGlobalConfig = getGlobalConfig;
4428
+ exports.getIntrospectionDiagnostics = getIntrospectionDiagnostics;
4310
4429
  exports.getNavigationRef = getNavigationRef;
4311
4430
  exports.getNavigationStateSnapshot = getNavigationStateSnapshot;
4312
4431
  exports.getScreenMetadata = getScreenMetadata;
@@ -4325,6 +4444,7 @@ exports.normalize3 = normalize3;
4325
4444
  exports.parseStableElementId = parseStableElementId;
4326
4445
  exports.probeLoadingState = probeLoadingState;
4327
4446
  exports.recordAppilotsDebugTrace = recordAppilotsDebugTrace;
4447
+ exports.recordIntrospectionFailure = recordIntrospectionFailure;
4328
4448
  exports.registerScreen = registerScreen;
4329
4449
  exports.resolveBaseTheme = resolveBaseTheme;
4330
4450
  exports.routesBelongToSameFeature = routesBelongToSameFeature;