@chipmobilesdk/rn-ai 0.3.0 → 0.5.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.
Files changed (97) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/README.md +65 -24
  3. package/lib/commonjs/config/behaviorConfig.js +283 -0
  4. package/lib/commonjs/config/behaviorConfig.js.map +1 -0
  5. package/lib/commonjs/contract/gateway.js +60 -0
  6. package/lib/commonjs/contract/gateway.js.map +1 -0
  7. package/lib/commonjs/core/aiClient.js +330 -0
  8. package/lib/commonjs/core/aiClient.js.map +1 -0
  9. package/lib/commonjs/core/quotaRequest.js +105 -0
  10. package/lib/commonjs/core/quotaRequest.js.map +1 -0
  11. package/lib/commonjs/diagnostics/errors.js +108 -0
  12. package/lib/commonjs/diagnostics/errors.js.map +1 -0
  13. package/lib/commonjs/diagnostics/messageKeys.js +68 -0
  14. package/lib/commonjs/diagnostics/messageKeys.js.map +1 -0
  15. package/lib/commonjs/index.js +102 -0
  16. package/lib/commonjs/index.js.map +1 -0
  17. package/lib/commonjs/package.json +1 -0
  18. package/lib/commonjs/react/hooks.js +170 -0
  19. package/lib/commonjs/react/hooks.js.map +1 -0
  20. package/lib/commonjs/transport/sseClient.js +151 -0
  21. package/lib/commonjs/transport/sseClient.js.map +1 -0
  22. package/lib/commonjs/transport/timeouts.js +46 -0
  23. package/lib/commonjs/transport/timeouts.js.map +1 -0
  24. package/lib/module/config/behaviorConfig.js +275 -0
  25. package/lib/module/config/behaviorConfig.js.map +1 -0
  26. package/lib/module/contract/gateway.js +56 -0
  27. package/lib/module/contract/gateway.js.map +1 -0
  28. package/lib/module/core/aiClient.js +327 -0
  29. package/lib/module/core/aiClient.js.map +1 -0
  30. package/lib/module/core/quotaRequest.js +102 -0
  31. package/lib/module/core/quotaRequest.js.map +1 -0
  32. package/lib/module/diagnostics/errors.js +99 -0
  33. package/lib/module/diagnostics/errors.js.map +1 -0
  34. package/lib/module/diagnostics/messageKeys.js +61 -0
  35. package/lib/module/diagnostics/messageKeys.js.map +1 -0
  36. package/lib/module/index.js +19 -0
  37. package/lib/module/index.js.map +1 -0
  38. package/lib/module/package.json +1 -0
  39. package/lib/module/react/hooks.js +164 -0
  40. package/lib/module/react/hooks.js.map +1 -0
  41. package/lib/module/transport/sseClient.js +146 -0
  42. package/lib/module/transport/sseClient.js.map +1 -0
  43. package/lib/module/transport/timeouts.js +41 -0
  44. package/lib/module/transport/timeouts.js.map +1 -0
  45. package/lib/typescript/commonjs/config/behaviorConfig.d.ts +119 -0
  46. package/lib/typescript/commonjs/config/behaviorConfig.d.ts.map +1 -0
  47. package/lib/typescript/commonjs/contract/gateway.d.ts +64 -0
  48. package/lib/typescript/commonjs/contract/gateway.d.ts.map +1 -0
  49. package/lib/typescript/commonjs/core/aiClient.d.ts +104 -0
  50. package/lib/typescript/commonjs/core/aiClient.d.ts.map +1 -0
  51. package/lib/typescript/commonjs/core/quotaRequest.d.ts +45 -0
  52. package/lib/typescript/commonjs/core/quotaRequest.d.ts.map +1 -0
  53. package/lib/typescript/commonjs/diagnostics/errors.d.ts +38 -0
  54. package/lib/typescript/commonjs/diagnostics/errors.d.ts.map +1 -0
  55. package/lib/typescript/commonjs/diagnostics/messageKeys.d.ts +46 -0
  56. package/lib/typescript/commonjs/diagnostics/messageKeys.d.ts.map +1 -0
  57. package/lib/typescript/commonjs/index.d.ts +21 -0
  58. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  59. package/lib/typescript/commonjs/package.json +1 -0
  60. package/lib/typescript/commonjs/react/hooks.d.ts +37 -0
  61. package/lib/typescript/commonjs/react/hooks.d.ts.map +1 -0
  62. package/lib/typescript/commonjs/transport/sseClient.d.ts +57 -0
  63. package/lib/typescript/commonjs/transport/sseClient.d.ts.map +1 -0
  64. package/lib/typescript/commonjs/transport/timeouts.d.ts +28 -0
  65. package/lib/typescript/commonjs/transport/timeouts.d.ts.map +1 -0
  66. package/lib/typescript/module/config/behaviorConfig.d.ts +119 -0
  67. package/lib/typescript/module/config/behaviorConfig.d.ts.map +1 -0
  68. package/lib/typescript/module/contract/gateway.d.ts +64 -0
  69. package/lib/typescript/module/contract/gateway.d.ts.map +1 -0
  70. package/lib/typescript/module/core/aiClient.d.ts +104 -0
  71. package/lib/typescript/module/core/aiClient.d.ts.map +1 -0
  72. package/lib/typescript/module/core/quotaRequest.d.ts +45 -0
  73. package/lib/typescript/module/core/quotaRequest.d.ts.map +1 -0
  74. package/lib/typescript/module/diagnostics/errors.d.ts +38 -0
  75. package/lib/typescript/module/diagnostics/errors.d.ts.map +1 -0
  76. package/lib/typescript/module/diagnostics/messageKeys.d.ts +46 -0
  77. package/lib/typescript/module/diagnostics/messageKeys.d.ts.map +1 -0
  78. package/lib/typescript/module/index.d.ts +21 -0
  79. package/lib/typescript/module/index.d.ts.map +1 -0
  80. package/lib/typescript/module/package.json +1 -0
  81. package/lib/typescript/module/react/hooks.d.ts +37 -0
  82. package/lib/typescript/module/react/hooks.d.ts.map +1 -0
  83. package/lib/typescript/module/transport/sseClient.d.ts +57 -0
  84. package/lib/typescript/module/transport/sseClient.d.ts.map +1 -0
  85. package/lib/typescript/module/transport/timeouts.d.ts +28 -0
  86. package/lib/typescript/module/transport/timeouts.d.ts.map +1 -0
  87. package/package.json +40 -12
  88. package/skills/sdk-rn-ai/SKILL.md +43 -4
  89. package/src/config/behaviorConfig.ts +158 -37
  90. package/src/contract/gateway.ts +91 -0
  91. package/src/core/aiClient.ts +194 -96
  92. package/src/core/quotaRequest.ts +125 -0
  93. package/src/diagnostics/errors.ts +85 -23
  94. package/src/diagnostics/messageKeys.ts +3 -0
  95. package/src/index.ts +28 -1
  96. package/src/react/hooks.ts +86 -14
  97. package/src/transport/sseClient.ts +58 -11
package/CHANGELOG.md CHANGED
@@ -1,5 +1,151 @@
1
1
  # @chipmobilesdk/rn-ai
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Every peer range has an upper bound, and no package declares a peer it does not use. ([#21](https://github.com/operationd8979/chip-mobile-sdk/pull/21))
8
+
9
+ A peer range is now a caret, a tilde or an explicit `<`, except for `react` and
10
+ `react-native`: an app brings exactly one of each, so a floor is the only useful thing
11
+ to say about them. Every other peer is a library the app installs beside us, and an
12
+ open `>=` was a promise about versions nobody had run — mmkv 3→4 lost entitlements and
13
+ pending purchases exactly that way.
14
+
15
+ **Four of the new ceilings exclude a version that is already published**, so an app
16
+ that took the open range at its word may now see a peer warning. The new bound is the
17
+ major this release is tested against, which is the thing the old range failed to say:
18
+
19
+ | Peer | Was | Now | Published above the ceiling |
20
+ |---|---|---|---|
21
+ | `react-native-app-auth` (rn-auth) | `>=8` | `^8.0.0` | 9.0.0 |
22
+ | `react-native-keychain` (rn-auth) | `>=10` | `^10.0.0` | 11.0.0 |
23
+ | `@op-engineering/op-sqlite` (rn-local-db) | `>=17.1.0` | `^17.1.0` | 18.x, 19.x |
24
+ | `@react-native-firebase/messaging` (rn-notification) | `>=26.0.0` | `^26.0.0` | 27.x |
25
+
26
+ The others — `react-native-vision-camera` `^5.1.1`, `react-native-svg` `^15.15.0`,
27
+ `react-native-unistyles` `^3.2.0`, `@dr.pogodin/react-native-fs` `^2.39.2`,
28
+ `react-native-quick-crypto` `^1.0.0`, `react-native-notify-kit` `^10.5.0`,
29
+ `react-native-localize` `^3.7.0`, `react-native-mmkv` `^4.0.0` — exclude nothing that
30
+ exists today. rn-payment's mmkv range was already bounded (`>=4.0.0 <5`) and is now
31
+ written as the caret that means the same thing.
32
+
33
+ Peers nothing imports are gone. `rn-theme`, `rn-local-db` and `rn-notification` asked
34
+ for `react` without having a component or a hook between them; `rn-auth`, `rn-ai` and
35
+ `rn-i18n` asked for `react-native` without touching a React Native API (rn-auth reaches
36
+ both native modules through `lazyPeer`, rn-i18n formats on `Intl`); `rn-notification`
37
+ asked for `@react-native-firebase/app`, which messaging declares itself. `rn-ai` no
38
+ longer peers on `@chipmobilesdk/rn-auth` at all — it asks for a `TokenProvider`, a
39
+ two-method interface rn-auth's `auth` satisfies, so an app with its own token source
40
+ installs nothing extra. An app that enables push or signs in through rn-auth installs
41
+ the same packages as before; it is the manifest that stopped overstating.
42
+
43
+ `react-native-localize` is declared optional, which is what the code already did: the
44
+ time-zone reader goes through `lazyPeer` and falls back to `Intl` without it. The
45
+ `peerDependenciesMeta` entries that said `optional: false` are gone — that is npm's
46
+ default, and writing it down only invited the question of which peers were undecided.
47
+
48
+ `npm run check:manifests` now fails on an unbounded peer range, a peer nothing in
49
+ `src/` imports, and a `peerDependenciesMeta` entry for either, so the policy holds
50
+ without anyone remembering it.
51
+
52
+ - Every package now ships built JavaScript with declaration files, instead of asking the
53
+ app to compile its TypeScript. ([#22](https://github.com/operationd8979/chip-mobile-sdk/pull/22))
54
+
55
+ Each tarball carries `lib/commonjs`, `lib/module` and `lib/typescript` (built with
56
+ `react-native-builder-bob`), and `src/` alongside them. What that changes for a
57
+ consumer:
58
+
59
+ - **Nothing to add to `transformIgnorePatterns`.** `require` resolves to the CommonJS
60
+ build, so an app's Jest loads this package without being told to transform it. That
61
+ was the one thing every consuming app had to know about us (DEP-04).
62
+ - **Typechecking reads our `.d.ts`** rather than compiling our source under our
63
+ `tsconfig`. A consumer's stricter — or looser — settings no longer meet our code.
64
+ - **`import` resolves to ESM**, so a bundler can tree-shake what it does not use.
65
+
66
+ No exported name changed, and no subpath moved: `.`, `./testing`, `./push`,
67
+ `./advanced`, `./app-auth`, `./keychain` and rn-core's twelve subpaths resolve exactly
68
+ as before, each now with its own declarations for both module systems. publint and
69
+ `attw --profile node16` pass on all twelve packages, which they could not before.
70
+
71
+ Inside the monorepo nothing changed either, and that is deliberate: every subpath also
72
+ declares a private `chipmobilesdk-source` condition pointing at its TypeScript, and
73
+ Metro, Jest and `tsc` here ask for that condition first. The demo app bundles 371
74
+ modules straight from `packages/*/src`, so editing a package still changes the app with
75
+ no build step, and coverage is still measured on the source. The condition is named for
76
+ this organization rather than using the conventional `source` because third-party
77
+ packages declare that one too — `@dr.pogodin/react-native-fs` does — and asking for it
78
+ pulled their sources into our stricter compile.
79
+
80
+ `npm run build:packages` builds everything, `prepack` rebuilds before any `npm pack`,
81
+ and `lib/` is git-ignored: the published bytes are produced by the release, never
82
+ committed. `npm run check:manifests` now fails a package whose `main`, `module`,
83
+ `types`, `source` condition, `files`, build script or `prepack` do not match this shape.
84
+
85
+ ### Patch Changes
86
+
87
+ - The token source is documented as the interface it has always been. `createAiClient`
88
+ takes `auth: TokenProvider` — `requestAccessToken(resourceKey, { forceRefresh })` and
89
+ nothing else — which rn-auth's controller satisfies structurally, so passing `auth`
90
+ straight in keeps working. rn-auth stopped being a peer of this package in the same
91
+ release; an app with its own token source implements two methods' worth of interface
92
+ and installs nothing extra. The skill says so where it used to leave rn-auth implied,
93
+ and the demo app carries the assignment as a compile-time assertion, so a rename on
94
+ either side now fails `tsc` instead of surfacing at runtime. ([#21](https://github.com/operationd8979/chip-mobile-sdk/pull/21))
95
+
96
+ - Each package's public API and its weight are now recorded in the repository, so a change
97
+ to either is reviewed rather than discovered. ([#23](https://github.com/operationd8979/chip-mobile-sdk/pull/23))
98
+
99
+ `packages/<pkg>/etc/*.api.md` is an api-extractor report per entrypoint — twenty-seven
100
+ of them, generated from the declarations the package publishes and committed. A renamed
101
+ type, a widened union, one more optional field: all of it shows up in a pull request's
102
+ diff. `npm run check:api` fails when a report is out of date and `npm run api:update`
103
+ rewrites them.
104
+
105
+ A report that moves now needs a **minor or major** changeset for that package.
106
+ `check:changesets` enforces it, because the report is generated from what the package
107
+ publishes, and a patch release that moves the public surface is how a consumer's build
108
+ breaks on `npm update`. A report added for the first time is a baseline, not a change.
109
+ `./testing` deliberately has no report: test doubles are not a surface a release is
110
+ gated on.
111
+
112
+ `npm run check:size` holds every entrypoint to a budget in `.size-limit.json`, measured
113
+ on the ESM build with peers and rn-core treated as external — they are what an app
114
+ installs beside us, not our weight. Each budget started at today's measurement plus five
115
+ per cent, so growth becomes a decision someone makes instead of a drift nobody sees. The
116
+ current figures run from 500 B (`rn-core/lazy`) to 19 kB (rn-notification).
117
+
118
+ No behaviour or API changed in this release; the entry is here so the changelog records
119
+ when the reports and the budgets began.
120
+ - Updated dependencies ([#22](https://github.com/operationd8979/chip-mobile-sdk/pull/22), [#23](https://github.com/operationd8979/chip-mobile-sdk/pull/23)):
121
+ - @chipmobilesdk/rn-core@0.2.0
122
+
123
+ ## 0.4.0
124
+
125
+ ### Minor Changes
126
+
127
+ - A 401 retry now gets a token the provider had to fetch again. `TokenProvider.requestAccessToken` takes `{ forceRefresh }`, which rn-auth implements by dropping the cached token for that resource, so the retry no longer re-sends the token the gateway had just refused — a cached token is still unexpired by the device's clock when the server rejects it. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
128
+
129
+ Failures an app can tell apart. `network` is a request that never arrived (no connection, a timeout, a dropped stream) and is retryable; `session-expired` is a session the user must renew. `AiError.cause` carries the code from the layer underneath — the gateway's own code, or rn-auth's — so a screen can explain *why*, and `retryAfterSeconds` is read from the `Retry-After` header when the body omits it. Both new codes have user copy, and the gateway's full code list is mapped rather than guessed from the status.
130
+
131
+ The remote behavior config is checked before it is trusted. `configUrl` and the document's `api.baseUrl` must be https (localhost excepted), the document must name the app it is for, its `schemaVersion` must be one this release understands, and `allowedHosts` pins where the client may send its bearer token. A broken TTL no longer means a revalidation on every request, and a retry count is bounded. A not-modified answer against a cache that is no longer there asks again without the ETag instead of reporting the 304 as a failure.
132
+
133
+ `configCacheFromKeyValue(port)` makes a durable config cache out of rn-core's `KeyValuePort`, so a second launch uses the last-good document instead of waiting for the network.
134
+
135
+ One quota figure: a chat's `done` event and `getQuota()` both record it on the client (`lastQuota()`), so `useAiQuota` and `useAiChat` cannot disagree, and a quota response that does not match the contract is refused rather than displayed. `getQuota()` recovers like a chat: a designated 404/410 reloads the configuration and retries, and a 401 retries once.
136
+
137
+ Streamed text reaches the screen on an interval (50 ms by default, `useAiChat(client, { flushIntervalMs })`) rather than once per token, and whatever arrived is flushed with the terminal state, so a half-answer stays on screen when a stream fails. A stream framed with CRLF is parsed incrementally, as the SSE specification allows. The transport seam is a published `StreamTransport` port instead of the internal reader's signature.
138
+
139
+ - The gateway↔client contract has one definition. `GatewayErrorCode`, `GATEWAY_ERROR_STATUS`, `QUOTA_HEADERS`, `GatewayErrorBody`, `GatewayStreamEvent`, `GatewayQuota`, `GatewayUsage` and `SSE_DONE_SENTINEL` are exported from this package, and the gateway service imports the same types, so an error code, a header name or a stream event added on one side does not compile until the other handles it. The three codes the gateway could already answer with and the client had never heard of — `BAD_REQUEST`, `ENDPOINT_RETIRED`, `INTERNAL` — now map to a typed error instead of a guess from the HTTP status. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
140
+
141
+ ### Patch Changes
142
+
143
+ - The gateway service (not published to npm) now shares one credential across the process and refuses unauthenticated requests before reading storage. No client-facing change; the entry is here so the release notes record the service work that went with this version. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
144
+
145
+ - Internal: no module in these packages is over 500 lines any more. The camera controller's session I/O moved to `sessionRuntime` and its contract to `contract.ts`; the Play adapter's shape translation to `iapShapes`; the purchase flow's failure mapping and its receipt-validation hook to modules of their own; the AI client's quota request to `quotaRequest`; the in-memory adapter's attachments and the fake ads engine's clock likewise. Behaviour is unchanged — the splits are by responsibility, and every package's suite passes as it stood. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
146
+
147
+ The repository also measures what it promised: `npm run perf` prints the median of nine runs of the deterministic hot paths against written budgets (recurrence expansion, theme construction, translation lookup, chart geometry, filter compilation), the demo app has a Performance screen reporting startup phases, time to interactive and the same benchmarks on Hermes, and `npm run check:file-size` keeps the limit from drifting. The budgets and the measured figures are recorded in `docs/standards.md` §VI.
148
+
3
149
  ## 0.3.0
4
150
 
5
151
  ### Minor Changes
package/README.md CHANGED
@@ -12,17 +12,23 @@ OpenAI directly and hold no AI credentials.
12
12
  - **Entrypoint**: `@chipmobilesdk/rn-ai`
13
13
  - **Native code**: none
14
14
 
15
- - Contract: [rn-ai-public-api](../../specs/005-azure-ai-gateway/contracts/rn-ai-public-api.md)
16
- - Gateway API: [gateway-api](../../specs/005-azure-ai-gateway/contracts/gateway-api.md)
17
- - Behavior config: [app-behavior-config](../../specs/005-azure-ai-gateway/contracts/app-behavior-config.md)
15
+ - **Gateway contract**: the error codes, quota headers and stream events this
16
+ client and the gateway share are exported from this package
17
+ (`GatewayErrorCode`, `QUOTA_HEADERS`, `GatewayStreamEvent`), and the gateway
18
+ imports the same definitions — see `## Public API`. The feature's original
19
+ contract documents live in the repository under
20
+ `specs/005-azure-ai-gateway/legacy/contracts/` and are not shipped here.
18
21
 
19
22
  ## Install
20
23
 
21
24
  ```bash
22
- npm install @chipmobilesdk/rn-ai @chipmobilesdk/rn-auth
25
+ npm install @chipmobilesdk/rn-ai
23
26
  ```
24
27
 
25
- Peers: `react >=19`, `react-native >=0.85`, `@chipmobilesdk/rn-auth >=0.2.0`.
28
+ The one peer is `react >=19`. `@chipmobilesdk/rn-auth` is no longer a peer: this
29
+ package asks for a `TokenProvider`, a two-method interface rn-auth's `auth`
30
+ happens to satisfy, so an app with its own token source installs nothing extra.
31
+ Install rn-auth as well if you want it to be the token source.
26
32
  **Zero runtime dependencies**; streaming uses React Native's standard
27
33
  `XMLHttpRequest` (no native code, Hermes-compatible, iOS + Android).
28
34
 
@@ -39,7 +45,10 @@ resources: [
39
45
  2. Create the client (one per app):
40
46
 
41
47
  ```ts
42
- import { createAiClient } from '@chipmobilesdk/rn-ai';
48
+ import { configCacheFromKeyValue, createAiClient } from '@chipmobilesdk/rn-ai';
49
+ import { openDurable } from '@chipmobilesdk/rn-core';
50
+ import { createMMKV } from 'react-native-mmkv';
51
+
43
52
  import { auth } from './auth/setup';
44
53
 
45
54
  export const aiClient = createAiClient({
@@ -47,10 +56,21 @@ export const aiClient = createAiClient({
47
56
  configUrl: 'https://<config-host>/appconfig/config/scan-food.json',
48
57
  resourceKey: 'ai-gateway',
49
58
  auth,
50
- // storage: inject a persistent AiConfigCache for offline launches
59
+ // Cache the behavior config where it survives a restart, so the next launch
60
+ // does not wait for the network. Any `AiConfigCache` will do; this one is
61
+ // rn-core's key-value port over MMKV.
62
+ storage: configCacheFromKeyValue(openDurable(() => createMMKV({ id: 'app.ai' }))),
63
+ // Optional: pin the hosts the remote config may point the client at.
64
+ allowedHosts: ['gw.example.com'],
51
65
  });
52
66
  ```
53
67
 
68
+ The config document decides where this client sends its bearer token, so both
69
+ `configUrl` and the document's `api.baseUrl` must be `https` (plain `http` is
70
+ accepted only for `localhost`), the document must name this `appId`, and its
71
+ `schemaVersion` must be one this release understands. Anything else is a
72
+ `config-unavailable` error rather than a request to an unvetted host.
73
+
54
74
  3. Use the hooks:
55
75
 
56
76
  ```tsx
@@ -83,30 +103,46 @@ send({
83
103
  waiting `recovery.retryBackoffMs[n]` (the last step repeats, each capped at
84
104
  30 s), at most `recovery.maxSilentRetries` times. A chat cancelled during the
85
105
  wait is not retried.
86
- - **Auth**: acquires tokens via rn-auth (`requestAccessToken('ai-gateway')`);
87
- on `401`, reacquires a token and retries once.
106
+ - **Auth**: acquires tokens via rn-auth
107
+ (`requestAccessToken('ai-gateway')`); on `401` it asks for a token the
108
+ provider had to fetch again (`{ forceRefresh: true }`) and retries once. The
109
+ cached token is unexpired by the device's clock after a 401, so without that
110
+ the retry would re-send the token the gateway just refused.
111
+ - **Quota**: one figure, wherever it came from. A chat's `done` event and
112
+ `getQuota()` both record it on the client, so `useAiQuota` and `useAiChat`
113
+ never disagree, and a quota display does not go stale when a chat finishes.
114
+ `getQuota()` recovers like a chat does: a designated `404`/`410` reloads the
115
+ configuration and retries, and a `401` retries once with a fresh token.
88
116
  - **Timeouts**: every network call is bounded. Override any of them with
89
117
  `createAiClient({ timeouts: { ... } })`:
90
118
 
91
119
  | | Default | Fails with |
92
120
  |---|---|---|
93
- | `connectMs` — chat request sent → headers | 30 s | `upstream-error`, `retryable: true` |
94
- | `idleMs` — silence between two stream chunks | 30 s | `upstream-error`, `retryable: true` |
121
+ | `connectMs` — chat request sent → headers | 30 s | `network`, `retryable: true` |
122
+ | `idleMs` — silence between two stream chunks | 30 s | `network`, `retryable: true` |
95
123
  | `configMs` — behavior-config fetch | 10 s | stale cache if any, else `config-unavailable` |
96
- | `quotaMs` — quota fetch | 10 s | `upstream-error`, `retryable: true` |
124
+ | `quotaMs` — quota fetch | 10 s | `network`, `retryable: true` |
97
125
 
98
126
  The gateway sends headers only after the model answers, so `connectMs` also
99
127
  covers time to first token. A dropped connection (HTTP status 0) is likewise
100
- `upstream-error` with `retryable: true`.
128
+ `network` with `retryable: true`: the request never reached the service, which
129
+ is a different thing to tell the user than a service that failed.
101
130
  - **Callbacks**: each `chat()` delivers exactly one of `onDone` / `onError`,
102
131
  and nothing after `cancel()`. An unexpected exception anywhere on the way
103
132
  (including in your own `onDelta`) is reported as `internal`, never dropped.
104
133
  - **Rate limits**: `429` surfaces as `status: 'rate-limited'` with `retryAt`;
105
134
  the SDK never hammers and never enforces limits locally (display only).
106
- - **Typed errors** (`AiError.code`): `auth-invalid`, `not-entitled`,
107
- `user-blocked`, `app-unknown`, `rate-limited`, `upstream-busy`,
108
- `payload-too-large`, `modality-not-allowed`, `config-unavailable`,
109
- `upstream-error`, `internal`.
135
+ - **Streamed text** reaches the screen on an interval (50 ms by default), not
136
+ once per token: `useAiChat(client, { flushIntervalMs })` tunes it, and `0`
137
+ renders per token. Whatever had arrived is always flushed with the terminal
138
+ state, so a half-answer stays on screen when a stream fails.
139
+ - **Typed errors** (`AiError.code`): `auth-invalid`, `session-expired`,
140
+ `not-entitled`, `user-blocked`, `app-unknown`, `rate-limited`,
141
+ `upstream-busy`, `payload-too-large`, `modality-not-allowed`,
142
+ `config-unavailable`, `network`, `upstream-error`, `internal`.
143
+ `AiError.cause` carries the code the layer underneath used — the gateway's own
144
+ code, or rn-auth's — so a screen can say *why*. `retryAfterSeconds` is read
145
+ from the body, or from the `Retry-After` header when the body omits it.
110
146
  - **Copy keys**: `ai.*` defaults in `aiMessages` — map through your i18n.
111
147
 
112
148
  ## Public API
@@ -114,13 +150,22 @@ send({
114
150
  Everything is exported from `@chipmobilesdk/rn-ai`; internal `config/`,
115
151
  `transport/`, `core/`, `react/`, and `diagnostics/` paths are not public.
116
152
 
117
- - Client: `createAiClient`, `AiClient`, `AiClientConfig`, `TokenProvider`
153
+ - Client: `createAiClient`, `AiClient`, `AiClientConfig`, `TokenProvider`,
154
+ `AccessTokenRequestOptions`
118
155
  - Chat: `ChatRequest`, `ChatMessage`, `ContentPart`, `ChatStreamHandlers`,
119
156
  `ChatHandle`, `Usage`
120
157
  - Behavior and cache: `AppBehaviorConfig`, `AiBehaviorState`, `BehaviorState`,
121
- `AiConfigCache`, `QuotaSnapshot`
158
+ `AiConfigCache`, `configCacheFromKeyValue`, `SUPPORTED_SCHEMA_VERSION`,
159
+ `QuotaSnapshot`
160
+ - Transport: `StreamTransport` and its `SseRequest` / `SseHandlers` /
161
+ `SseHandle` / `SseResponseInfo` types. `createAiClient({ transport })` takes
162
+ any implementation; the default is an XHR reader.
163
+ - The gateway contract, shared with the service so both sides cannot drift:
164
+ `GatewayErrorCode`, `GatewayErrorBody`, `GatewayQuota`, `GatewayUsage`,
165
+ `GatewayStreamEvent`, `GATEWAY_ERROR_STATUS`, `QUOTA_HEADERS`,
166
+ `SSE_DONE_SENTINEL`
122
167
  - React: `useAiChat`, `useAiQuota`, `useAiBehaviorState`, `AiChatStatus`,
123
- `UseAiChatResult`, `UseAiQuotaResult`. `useAiBehaviorState(client)` re-renders when
168
+ `UseAiChatOptions`, `UseAiChatResult`, `UseAiQuotaResult`. `useAiBehaviorState(client)` re-renders when
124
169
  the config loads or the `aiEnabled` kill switch flips; `client.subscribe` and
125
170
  `client.getBehaviorState()` give the same outside React.
126
171
  - Diagnostics and copy: `AiError`, `AiErrorCode`, `isAiError`, `aiMessages`,
@@ -144,10 +189,6 @@ Everything is exported from `@chipmobilesdk/rn-ai`; internal `config/`,
144
189
  React ≥19 · React Native ≥0.85 (Hermes) · iOS + Android · Node ≥22.11 for
145
190
  tooling.
146
191
 
147
- The repository contains `src/screens/AiDemoScreen.tsx` and its component test.
148
- The current demo sidebar does not expose that screen yet, so it must be wired
149
- into `ScreenKey`, `Sidebar`, and `ActiveScreen` before it is reachable in-app.
150
-
151
192
  ## Validation
152
193
 
153
194
  Run from the repository root:
@@ -0,0 +1,283 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.SUPPORTED_SCHEMA_VERSION = exports.MemoryConfigCache = exports.BehaviorConfigManager = void 0;
7
+ exports.configCacheFromKeyValue = configCacheFromKeyValue;
8
+ exports.isTrustedUrl = isTrustedUrl;
9
+ exports.parseBehaviorConfig = parseBehaviorConfig;
10
+ var _rnCore = require("@chipmobilesdk/rn-core");
11
+ var _errors = require("../diagnostics/errors.js");
12
+ var _timeouts = require("../transport/timeouts.js");
13
+ /** App behavior configuration (contracts/app-behavior-config.md). */
14
+
15
+ /** Persistence port; default is in-memory (inject a durable store for offline launch). */
16
+
17
+ class MemoryConfigCache {
18
+ map = new Map();
19
+ async get(key) {
20
+ return this.map.get(key) ?? null;
21
+ }
22
+ async set(key, value) {
23
+ this.map.set(key, value);
24
+ }
25
+ }
26
+
27
+ /**
28
+ * An `AiConfigCache` over rn-core's synchronous `KeyValuePort` (AI-10).
29
+ *
30
+ * Without a durable cache every launch waits for the network before the first
31
+ * chat, which is exactly what the cache-first design exists to avoid. Open the
32
+ * port the way the app opens its other durable state, for example
33
+ * `openDurable(() => createMMKV({ id: 'app' }))`.
34
+ */
35
+ exports.MemoryConfigCache = MemoryConfigCache;
36
+ function configCacheFromKeyValue(port) {
37
+ return {
38
+ async get(key) {
39
+ return port.getString(key) ?? null;
40
+ },
41
+ async set(key, value) {
42
+ port.set(key, value);
43
+ }
44
+ };
45
+ }
46
+ /** The newest document layout this release understands. */
47
+ const SUPPORTED_SCHEMA_VERSION = exports.SUPPORTED_SCHEMA_VERSION = 1;
48
+
49
+ /** Bounds on the remote-configured recovery, so a bad document cannot stall a chat. */
50
+ const MAX_SILENT_RETRIES = 5;
51
+ const MIN_REFRESH_TTL_SECONDS = 30;
52
+ const DEFAULT_REFRESH_TTL_SECONDS = 21_600;
53
+ const numbers = value => Array.isArray(value) ? value.filter(n => Number.isFinite(n)) : undefined;
54
+ const positiveInt = (value, fallback) => typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
55
+ function invalid(reason) {
56
+ throw new _errors.AiError('config-unavailable', `behavior config ${reason}`);
57
+ }
58
+
59
+ /** https, or http on a development machine: a bearer token travels to this host. */
60
+ function isTrustedUrl(url) {
61
+ if (/^https:\/\//i.test(url)) {
62
+ return true;
63
+ }
64
+ return /^http:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?(\/|$)/i.test(url);
65
+ }
66
+ function hostOf(url) {
67
+ return url.replace(/^[a-z]+:\/\//i, '').split(/[/:?#]/)[0].toLowerCase();
68
+ }
69
+ /**
70
+ * Parse + validate; unknown fields ignored (forward compatibility).
71
+ *
72
+ * Every field this package acts on is checked here, because the document is
73
+ * remote and the alternative is a TypeError inside an XHR callback, a revalidate
74
+ * on every single request, or a bearer token sent to a host nobody vetted
75
+ * (AI-06, AI-07).
76
+ */
77
+ function parseBehaviorConfig(json, options = {}) {
78
+ let raw;
79
+ try {
80
+ raw = JSON.parse(json);
81
+ } catch {
82
+ invalid('is not valid JSON');
83
+ }
84
+ const api = raw.api;
85
+ if (typeof raw.appId !== 'string' || typeof api?.baseUrl !== 'string' || !Array.isArray(api.scopes)) {
86
+ invalid('is missing required fields');
87
+ }
88
+ const schemaVersion = positiveInt(raw.schemaVersion, 1);
89
+ if (schemaVersion > SUPPORTED_SCHEMA_VERSION) {
90
+ // A newer layout may mean something different by the same field names.
91
+ invalid(`declares schema version ${schemaVersion}, newer than ${SUPPORTED_SCHEMA_VERSION}`);
92
+ }
93
+ if (options.expectedAppId !== undefined && raw.appId !== options.expectedAppId) {
94
+ // The cache key and the request path carry the app id; a document for
95
+ // another app would send this app's traffic somewhere else entirely.
96
+ invalid(`is for another app (${raw.appId})`);
97
+ }
98
+ const baseUrl = api.baseUrl.replace(/\/$/, '');
99
+ if (!isTrustedUrl(baseUrl)) {
100
+ invalid('names a baseUrl that is not https');
101
+ }
102
+ if (options.allowedHosts && !options.allowedHosts.includes(hostOf(baseUrl))) {
103
+ invalid(`names a baseUrl outside the allowed hosts (${hostOf(baseUrl)})`);
104
+ }
105
+ const recovery = raw.recovery ?? {};
106
+ const features = raw.features ?? {};
107
+ return {
108
+ schemaVersion,
109
+ appId: raw.appId,
110
+ configVersion: positiveInt(raw.configVersion, 0),
111
+ // A TTL that was NaN made `isFresh` false forever, so every request
112
+ // revalidated over the network (AI-06).
113
+ refreshTtlSeconds: Math.max(MIN_REFRESH_TTL_SECONDS, positiveInt(raw.refreshTtlSeconds, DEFAULT_REFRESH_TTL_SECONDS)),
114
+ api: {
115
+ baseUrl,
116
+ scopes: api.scopes.filter(s => typeof s === 'string'),
117
+ supportsStreaming: api.supportsStreaming !== false
118
+ },
119
+ limitsDisplay: raw.limitsDisplay,
120
+ // Typed here, once: a remote document with `reloadOnHttpStatus: "404"` used to
121
+ // throw a TypeError inside the XHR error callback, where nothing caught it.
122
+ recovery: {
123
+ reloadOnHttpStatus: numbers(recovery.reloadOnHttpStatus) ?? [404, 410],
124
+ maxSilentRetries: Math.min(MAX_SILENT_RETRIES, Number.isFinite(recovery.maxSilentRetries) ? Math.max(0, Number(recovery.maxSilentRetries)) : 1),
125
+ retryBackoffMs: numbers(recovery.retryBackoffMs) ?? [500, 2000]
126
+ },
127
+ features: {
128
+ aiEnabled: features.aiEnabled !== false
129
+ }
130
+ };
131
+ }
132
+ /**
133
+ * Cache-first behavior config (FR-018): launch never blocks on the network;
134
+ * TTL revalidation uses ETag; designated errors force a reload.
135
+ */
136
+ class BehaviorConfigManager {
137
+ /**
138
+ * One cache read and one network fetch at a time (AI-08): a cold start makes a
139
+ * chat and a quota call together, and each used to fetch the config itself.
140
+ */
141
+ cacheRead = (0, _rnCore.createSingleFlight)();
142
+ networkFetch = (0, _rnCore.createSingleFlight)();
143
+ currentState = 'unloaded';
144
+ stateListeners = (0, _rnCore.createListenerSet)();
145
+
146
+ /** Where the config came from, or why there is none. */
147
+ get state() {
148
+ return this.currentState;
149
+ }
150
+
151
+ /** Called whenever `state` changes (AI-09). */
152
+ subscribe(listener) {
153
+ return this.stateListeners.add(listener);
154
+ }
155
+ setState(next) {
156
+ if (next === this.currentState) return;
157
+ this.currentState = next;
158
+ this.stateListeners.notify();
159
+ }
160
+ constructor(deps) {
161
+ this.deps = deps;
162
+ if (!isTrustedUrl(deps.configUrl)) {
163
+ // The document decides where the tokens go, so where it comes from
164
+ // matters as much (AI-07).
165
+ throw new _errors.AiError('config-unavailable', 'configUrl must be https');
166
+ }
167
+ this.cacheKey = `rn-ai:config:${deps.appId}`;
168
+ this.cache = deps.cache ?? new MemoryConfigCache();
169
+ this.fetchFn = deps.fetchFn ?? fetch;
170
+ this.now = deps.now ?? Date.now;
171
+ }
172
+ parse(json) {
173
+ return parseBehaviorConfig(json, {
174
+ expectedAppId: this.deps.appId,
175
+ ...(this.deps.allowedHosts ? {
176
+ allowedHosts: this.deps.allowedHosts
177
+ } : {})
178
+ });
179
+ }
180
+ isFresh(envelope, config) {
181
+ return this.now() - envelope.fetchedAt < config.refreshTtlSeconds * 1000;
182
+ }
183
+ loadFromCache() {
184
+ return this.current ? Promise.resolve() : this.cacheRead.run(() => this.readCache());
185
+ }
186
+ async readCache() {
187
+ if (this.current) return;
188
+ const stored = await this.cache.get(this.cacheKey).catch(() => null);
189
+ if (!stored) return;
190
+ try {
191
+ const envelope = JSON.parse(stored);
192
+ this.current = {
193
+ config: this.parse(envelope.configJson),
194
+ envelope
195
+ };
196
+ this.setState('ready-cache');
197
+ } catch {
198
+ // corrupted cache entries are ignored, not fatal
199
+ }
200
+ }
201
+ fetchNetwork() {
202
+ return this.networkFetch.run(() => this.fetchFromNetwork());
203
+ }
204
+
205
+ /** One attempt; `conditional` sends the stored ETag when there is one. */
206
+ async fetchOnce(conditional) {
207
+ const headers = {};
208
+ const etag = this.current?.envelope.etag;
209
+ if (conditional && etag) {
210
+ headers['If-None-Match'] = etag;
211
+ }
212
+ // Bounded: with no cache, a hung fetch used to block every chat and quota call.
213
+ return (0, _timeouts.withTimeout)(this.deps.timeoutMs ?? _timeouts.DEFAULT_AI_TIMEOUTS.configMs, async signal => {
214
+ const response = await this.fetchFn(this.deps.configUrl, {
215
+ headers,
216
+ signal
217
+ });
218
+ return {
219
+ status: response.status,
220
+ text: response.ok && response.status !== 304 ? await response.text() : '',
221
+ etag: response.headers.get('etag') ?? undefined
222
+ };
223
+ }, () => new _errors.AiError('config-unavailable', 'config fetch timed out'));
224
+ }
225
+ async fetchFromNetwork() {
226
+ let result = await this.fetchOnce(true);
227
+ if (result.status === 304) {
228
+ if (this.current) {
229
+ this.current.envelope.fetchedAt = this.now();
230
+ await this.persist(this.current.envelope);
231
+ this.setState('ready-network');
232
+ return this.current.config;
233
+ }
234
+ // Not-modified against a cache that is no longer there (it was cleared,
235
+ // or it failed to parse). Asking again without the ETag is the answer;
236
+ // reporting `config fetch failed: 304` was not (AI-17).
237
+ result = await this.fetchOnce(false);
238
+ }
239
+ if (result.status < 200 || result.status >= 300) {
240
+ throw new _errors.AiError('config-unavailable', `config fetch failed: ${result.status}`);
241
+ }
242
+ const config = this.parse(result.text);
243
+ const envelope = {
244
+ configJson: result.text,
245
+ etag: result.etag,
246
+ fetchedAt: this.now()
247
+ };
248
+ this.current = {
249
+ config,
250
+ envelope
251
+ };
252
+ await this.persist(envelope);
253
+ this.setState('ready-network');
254
+ return config;
255
+ }
256
+ async persist(envelope) {
257
+ await this.cache.set(this.cacheKey, JSON.stringify(envelope)).catch(() => undefined);
258
+ }
259
+
260
+ /**
261
+ * Cache-first read. Fresh cache returns immediately; stale cache returns
262
+ * immediately too while a background revalidation runs; no cache at all
263
+ * falls through to the network (or `config-unavailable`).
264
+ */
265
+ async getConfig(options = {}) {
266
+ await this.loadFromCache();
267
+ if (!options.forceReload && this.current) {
268
+ if (!this.isFresh(this.current.envelope, this.current.config)) {
269
+ this.fetchNetwork().catch(() => undefined); // background revalidate
270
+ }
271
+ return this.current.config;
272
+ }
273
+ try {
274
+ return await this.fetchNetwork();
275
+ } catch (e) {
276
+ if (this.current) return this.current.config; // stale beats broken
277
+ this.setState('unavailable');
278
+ throw e instanceof _errors.AiError ? e : new _errors.AiError('config-unavailable', String(e));
279
+ }
280
+ }
281
+ }
282
+ exports.BehaviorConfigManager = BehaviorConfigManager;
283
+ //# sourceMappingURL=behaviorConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_rnCore","require","_errors","_timeouts","MemoryConfigCache","map","Map","get","key","set","value","exports","configCacheFromKeyValue","port","getString","SUPPORTED_SCHEMA_VERSION","MAX_SILENT_RETRIES","MIN_REFRESH_TTL_SECONDS","DEFAULT_REFRESH_TTL_SECONDS","numbers","Array","isArray","filter","n","Number","isFinite","undefined","positiveInt","fallback","Math","floor","invalid","reason","AiError","isTrustedUrl","url","test","hostOf","replace","split","toLowerCase","parseBehaviorConfig","json","options","raw","JSON","parse","api","appId","baseUrl","scopes","schemaVersion","expectedAppId","allowedHosts","includes","recovery","features","configVersion","refreshTtlSeconds","max","s","supportsStreaming","limitsDisplay","reloadOnHttpStatus","maxSilentRetries","min","retryBackoffMs","aiEnabled","BehaviorConfigManager","cacheRead","createSingleFlight","networkFetch","currentState","stateListeners","createListenerSet","state","subscribe","listener","add","setState","next","notify","constructor","deps","configUrl","cacheKey","cache","fetchFn","fetch","now","Date","isFresh","envelope","config","fetchedAt","loadFromCache","current","Promise","resolve","run","readCache","stored","catch","configJson","fetchNetwork","fetchFromNetwork","fetchOnce","conditional","headers","etag","withTimeout","timeoutMs","DEFAULT_AI_TIMEOUTS","configMs","signal","response","status","text","ok","result","persist","stringify","getConfig","forceReload","e","String"],"sourceRoot":"../../../src","sources":["config/behaviorConfig.ts"],"mappings":";;;;;;;;;AAAA,IAAAA,OAAA,GAAAC,OAAA;AAEA,IAAAC,OAAA,GAAAD,OAAA;AACA,IAAAE,SAAA,GAAAF,OAAA;AAEA;;AAgBA;;AAMO,MAAMG,iBAAiB,CAA0B;EAC/CC,GAAG,GAAG,IAAIC,GAAG,CAAiB,CAAC;EACvC,MAAMC,GAAGA,CAACC,GAAW,EAA0B;IAC9C,OAAO,IAAI,CAACH,GAAG,CAACE,GAAG,CAACC,GAAG,CAAC,IAAI,IAAI;EACjC;EACA,MAAMC,GAAGA,CAACD,GAAW,EAAEE,KAAa,EAAiB;IACpD,IAAI,CAACL,GAAG,CAACI,GAAG,CAACD,GAAG,EAAEE,KAAK,CAAC;EACzB;AACD;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPAC,OAAA,CAAAP,iBAAA,GAAAA,iBAAA;AAQO,SAASQ,uBAAuBA,CAACC,IAAkB,EAAiB;EAC1E,OAAO;IACN,MAAMN,GAAGA,CAACC,GAAG,EAAE;MACd,OAAOK,IAAI,CAACC,SAAS,CAACN,GAAG,CAAC,IAAI,IAAI;IACnC,CAAC;IACD,MAAMC,GAAGA,CAACD,GAAG,EAAEE,KAAK,EAAE;MACrBG,IAAI,CAACJ,GAAG,CAACD,GAAG,EAAEE,KAAK,CAAC;IACrB;EACD,CAAC;AACF;AAIA;AACO,MAAMK,wBAAwB,GAAAJ,OAAA,CAAAI,wBAAA,GAAG,CAAC;;AAEzC;AACA,MAAMC,kBAAkB,GAAG,CAAC;AAC5B,MAAMC,uBAAuB,GAAG,EAAE;AAClC,MAAMC,2BAA2B,GAAG,MAAM;AAE1C,MAAMC,OAAO,GAAIT,KAAc,IAC9BU,KAAK,CAACC,OAAO,CAACX,KAAK,CAAC,GAAGA,KAAK,CAACY,MAAM,CAAEC,CAAC,IAAkBC,MAAM,CAACC,QAAQ,CAACF,CAAC,CAAC,CAAC,GAAGG,SAAS;AAExF,MAAMC,WAAW,GAAGA,CAACjB,KAAc,EAAEkB,QAAgB,KACpD,OAAOlB,KAAK,KAAK,QAAQ,IAAIc,MAAM,CAACC,QAAQ,CAACf,KAAK,CAAC,IAAIA,KAAK,GAAG,CAAC,GAAGmB,IAAI,CAACC,KAAK,CAACpB,KAAK,CAAC,GAAGkB,QAAQ;AAEhG,SAASG,OAAOA,CAACC,MAAc,EAAS;EACvC,MAAM,IAAIC,eAAO,CAAC,oBAAoB,EAAE,mBAAmBD,MAAM,EAAE,CAAC;AACrE;;AAEA;AACO,SAASE,YAAYA,CAACC,GAAW,EAAW;EAClD,IAAI,cAAc,CAACC,IAAI,CAACD,GAAG,CAAC,EAAE;IAC7B,OAAO,IAAI;EACZ;EACA,OAAO,0DAA0D,CAACC,IAAI,CAACD,GAAG,CAAC;AAC5E;AAEA,SAASE,MAAMA,CAACF,GAAW,EAAU;EACpC,OAAOA,GAAG,CACRG,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAC5BC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAClBC,WAAW,CAAC,CAAC;AAChB;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,mBAAmBA,CAClCC,IAAY,EACZC,OAAmC,GAAG,CAAC,CAAC,EACpB;EACpB,IAAIC,GAA4B;EAChC,IAAI;IACHA,GAAG,GAAGC,IAAI,CAACC,KAAK,CAACJ,IAAI,CAA4B;EAClD,CAAC,CAAC,MAAM;IACPX,OAAO,CAAC,mBAAmB,CAAC;EAC7B;EACA,MAAMgB,GAAG,GAAGH,GAAG,CAACG,GAAoD;EACpE,IACC,OAAOH,GAAG,CAACI,KAAK,KAAK,QAAQ,IAC7B,OAAOD,GAAG,EAAEE,OAAO,KAAK,QAAQ,IAChC,CAAC7B,KAAK,CAACC,OAAO,CAAC0B,GAAG,CAACG,MAAM,CAAC,EACzB;IACDnB,OAAO,CAAC,4BAA4B,CAAC;EACtC;EAEA,MAAMoB,aAAa,GAAGxB,WAAW,CAACiB,GAAG,CAACO,aAAa,EAAE,CAAC,CAAC;EACvD,IAAIA,aAAa,GAAGpC,wBAAwB,EAAE;IAC7C;IACAgB,OAAO,CAAC,2BAA2BoB,aAAa,gBAAgBpC,wBAAwB,EAAE,CAAC;EAC5F;EACA,IAAI4B,OAAO,CAACS,aAAa,KAAK1B,SAAS,IAAIkB,GAAG,CAACI,KAAK,KAAKL,OAAO,CAACS,aAAa,EAAE;IAC/E;IACA;IACArB,OAAO,CAAC,uBAAuBa,GAAG,CAACI,KAAK,GAAG,CAAC;EAC7C;EAEA,MAAMC,OAAO,GAAGF,GAAG,CAACE,OAAO,CAACX,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;EAC9C,IAAI,CAACJ,YAAY,CAACe,OAAO,CAAC,EAAE;IAC3BlB,OAAO,CAAC,mCAAmC,CAAC;EAC7C;EACA,IAAIY,OAAO,CAACU,YAAY,IAAI,CAACV,OAAO,CAACU,YAAY,CAACC,QAAQ,CAACjB,MAAM,CAACY,OAAO,CAAC,CAAC,EAAE;IAC5ElB,OAAO,CAAC,8CAA8CM,MAAM,CAACY,OAAO,CAAC,GAAG,CAAC;EAC1E;EAEA,MAAMM,QAAQ,GAAIX,GAAG,CAACW,QAAQ,IAAI,CAAC,CAA4C;EAC/E,MAAMC,QAAQ,GAAIZ,GAAG,CAACY,QAAQ,IAAI,CAAC,CAA4C;EAC/E,OAAO;IACNL,aAAa;IACbH,KAAK,EAAEJ,GAAG,CAACI,KAAK;IAChBS,aAAa,EAAE9B,WAAW,CAACiB,GAAG,CAACa,aAAa,EAAE,CAAC,CAAC;IAChD;IACA;IACAC,iBAAiB,EAAE7B,IAAI,CAAC8B,GAAG,CAC1B1C,uBAAuB,EACvBU,WAAW,CAACiB,GAAG,CAACc,iBAAiB,EAAExC,2BAA2B,CAC/D,CAAC;IACD6B,GAAG,EAAE;MACJE,OAAO;MACPC,MAAM,EAAEH,GAAG,CAACG,MAAM,CAAC5B,MAAM,CAAEsC,CAAC,IAAkB,OAAOA,CAAC,KAAK,QAAQ,CAAC;MACpEC,iBAAiB,EAAEd,GAAG,CAACc,iBAAiB,KAAK;IAC9C,CAAC;IACDC,aAAa,EAAElB,GAAG,CAACkB,aAAmD;IACtE;IACA;IACAP,QAAQ,EAAE;MACTQ,kBAAkB,EAAE5C,OAAO,CAACoC,QAAQ,CAACQ,kBAAkB,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC;MACtEC,gBAAgB,EAAEnC,IAAI,CAACoC,GAAG,CACzBjD,kBAAkB,EAClBQ,MAAM,CAACC,QAAQ,CAAC8B,QAAQ,CAACS,gBAAgB,CAAC,GACvCnC,IAAI,CAAC8B,GAAG,CAAC,CAAC,EAAEnC,MAAM,CAAC+B,QAAQ,CAACS,gBAAgB,CAAC,CAAC,GAC9C,CACJ,CAAC;MACDE,cAAc,EAAE/C,OAAO,CAACoC,QAAQ,CAACW,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI;IAC/D,CAAC;IACDV,QAAQ,EAAE;MAAEW,SAAS,EAAEX,QAAQ,CAACW,SAAS,KAAK;IAAM;EACrD,CAAC;AACF;AAoBA;AACA;AACA;AACA;AACO,MAAMC,qBAAqB,CAAC;EAMlC;AACD;AACA;AACA;EACkBC,SAAS,GAAG,IAAAC,0BAAkB,EAAO,CAAC;EACtCC,YAAY,GAAG,IAAAD,0BAAkB,EAAoB,CAAC;EAC/DE,YAAY,GAAkB,UAAU;EAC/BC,cAAc,GAAG,IAAAC,yBAAiB,EAAK,CAAC;;EAEzD;EACA,IAAIC,KAAKA,CAAA,EAAkB;IAC1B,OAAO,IAAI,CAACH,YAAY;EACzB;;EAEA;EACAI,SAASA,CAACC,QAAoB,EAAc;IAC3C,OAAO,IAAI,CAACJ,cAAc,CAACK,GAAG,CAACD,QAAQ,CAAC;EACzC;EAEQE,QAAQA,CAACC,IAAmB,EAAQ;IAC3C,IAAIA,IAAI,KAAK,IAAI,CAACR,YAAY,EAAE;IAChC,IAAI,CAACA,YAAY,GAAGQ,IAAI;IACxB,IAAI,CAACP,cAAc,CAACQ,MAAM,CAAC,CAAC;EAC7B;EAEAC,WAAWA,CAASC,IAAwB,EAAE;IAAA,KAA1BA,IAAwB,GAAxBA,IAAwB;IAC3C,IAAI,CAACjD,YAAY,CAACiD,IAAI,CAACC,SAAS,CAAC,EAAE;MAClC;MACA;MACA,MAAM,IAAInD,eAAO,CAAC,oBAAoB,EAAE,yBAAyB,CAAC;IACnE;IACA,IAAI,CAACoD,QAAQ,GAAG,gBAAgBF,IAAI,CAACnC,KAAK,EAAE;IAC5C,IAAI,CAACsC,KAAK,GAAGH,IAAI,CAACG,KAAK,IAAI,IAAIlF,iBAAiB,CAAC,CAAC;IAClD,IAAI,CAACmF,OAAO,GAAGJ,IAAI,CAACI,OAAO,IAAIC,KAAK;IACpC,IAAI,CAACC,GAAG,GAAGN,IAAI,CAACM,GAAG,IAAIC,IAAI,CAACD,GAAG;EAChC;EAEQ3C,KAAKA,CAACJ,IAAY,EAAqB;IAC9C,OAAOD,mBAAmB,CAACC,IAAI,EAAE;MAChCU,aAAa,EAAE,IAAI,CAAC+B,IAAI,CAACnC,KAAK;MAC9B,IAAI,IAAI,CAACmC,IAAI,CAAC9B,YAAY,GAAG;QAAEA,YAAY,EAAE,IAAI,CAAC8B,IAAI,CAAC9B;MAAa,CAAC,GAAG,CAAC,CAAC;IAC3E,CAAC,CAAC;EACH;EAEQsC,OAAOA,CAACC,QAAuB,EAAEC,MAAyB,EAAW;IAC5E,OAAO,IAAI,CAACJ,GAAG,CAAC,CAAC,GAAGG,QAAQ,CAACE,SAAS,GAAGD,MAAM,CAACnC,iBAAiB,GAAG,IAAI;EACzE;EAEQqC,aAAaA,CAAA,EAAkB;IACtC,OAAO,IAAI,CAACC,OAAO,GAAGC,OAAO,CAACC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC7B,SAAS,CAAC8B,GAAG,CAAC,MAAM,IAAI,CAACC,SAAS,CAAC,CAAC,CAAC;EACrF;EAEA,MAAcA,SAASA,CAAA,EAAkB;IACxC,IAAI,IAAI,CAACJ,OAAO,EAAE;IAClB,MAAMK,MAAM,GAAG,MAAM,IAAI,CAACf,KAAK,CAAC/E,GAAG,CAAC,IAAI,CAAC8E,QAAQ,CAAC,CAACiB,KAAK,CAAC,MAAM,IAAI,CAAC;IACpE,IAAI,CAACD,MAAM,EAAE;IACb,IAAI;MACH,MAAMT,QAAQ,GAAG/C,IAAI,CAACC,KAAK,CAACuD,MAAM,CAAkB;MACpD,IAAI,CAACL,OAAO,GAAG;QAAEH,MAAM,EAAE,IAAI,CAAC/C,KAAK,CAAC8C,QAAQ,CAACW,UAAU,CAAC;QAAEX;MAAS,CAAC;MACpE,IAAI,CAACb,QAAQ,CAAC,aAAa,CAAC;IAC7B,CAAC,CAAC,MAAM;MACP;IAAA;EAEF;EAEQyB,YAAYA,CAAA,EAA+B;IAClD,OAAO,IAAI,CAACjC,YAAY,CAAC4B,GAAG,CAAC,MAAM,IAAI,CAACM,gBAAgB,CAAC,CAAC,CAAC;EAC5D;;EAEA;EACA,MAAcC,SAASA,CACtBC,WAAoB,EACuC;IAC3D,MAAMC,OAA+B,GAAG,CAAC,CAAC;IAC1C,MAAMC,IAAI,GAAG,IAAI,CAACb,OAAO,EAAEJ,QAAQ,CAACiB,IAAI;IACxC,IAAIF,WAAW,IAAIE,IAAI,EAAE;MACxBD,OAAO,CAAC,eAAe,CAAC,GAAGC,IAAI;IAChC;IACA;IACA,OAAO,IAAAC,qBAAW,EACjB,IAAI,CAAC3B,IAAI,CAAC4B,SAAS,IAAIC,6BAAmB,CAACC,QAAQ,EACnD,MAAMC,MAAM,IAAI;MACf,MAAMC,QAAQ,GAAG,MAAM,IAAI,CAAC5B,OAAO,CAAC,IAAI,CAACJ,IAAI,CAACC,SAAS,EAAE;QAAEwB,OAAO;QAAEM;MAAO,CAAC,CAAC;MAC7E,OAAO;QACNE,MAAM,EAAED,QAAQ,CAACC,MAAM;QACvBC,IAAI,EAAEF,QAAQ,CAACG,EAAE,IAAIH,QAAQ,CAACC,MAAM,KAAK,GAAG,GAAG,MAAMD,QAAQ,CAACE,IAAI,CAAC,CAAC,GAAG,EAAE;QACzER,IAAI,EAAEM,QAAQ,CAACP,OAAO,CAACrG,GAAG,CAAC,MAAM,CAAC,IAAImB;MACvC,CAAC;IACF,CAAC,EACD,MAAM,IAAIO,eAAO,CAAC,oBAAoB,EAAE,wBAAwB,CACjE,CAAC;EACF;EAEA,MAAcwE,gBAAgBA,CAAA,EAA+B;IAC5D,IAAIc,MAAM,GAAG,MAAM,IAAI,CAACb,SAAS,CAAC,IAAI,CAAC;IACvC,IAAIa,MAAM,CAACH,MAAM,KAAK,GAAG,EAAE;MAC1B,IAAI,IAAI,CAACpB,OAAO,EAAE;QACjB,IAAI,CAACA,OAAO,CAACJ,QAAQ,CAACE,SAAS,GAAG,IAAI,CAACL,GAAG,CAAC,CAAC;QAC5C,MAAM,IAAI,CAAC+B,OAAO,CAAC,IAAI,CAACxB,OAAO,CAACJ,QAAQ,CAAC;QACzC,IAAI,CAACb,QAAQ,CAAC,eAAe,CAAC;QAC9B,OAAO,IAAI,CAACiB,OAAO,CAACH,MAAM;MAC3B;MACA;MACA;MACA;MACA0B,MAAM,GAAG,MAAM,IAAI,CAACb,SAAS,CAAC,KAAK,CAAC;IACrC;IACA,IAAIa,MAAM,CAACH,MAAM,GAAG,GAAG,IAAIG,MAAM,CAACH,MAAM,IAAI,GAAG,EAAE;MAChD,MAAM,IAAInF,eAAO,CAAC,oBAAoB,EAAE,wBAAwBsF,MAAM,CAACH,MAAM,EAAE,CAAC;IACjF;IACA,MAAMvB,MAAM,GAAG,IAAI,CAAC/C,KAAK,CAACyE,MAAM,CAACF,IAAI,CAAC;IACtC,MAAMzB,QAAuB,GAAG;MAC/BW,UAAU,EAAEgB,MAAM,CAACF,IAAI;MACvBR,IAAI,EAAEU,MAAM,CAACV,IAAI;MACjBf,SAAS,EAAE,IAAI,CAACL,GAAG,CAAC;IACrB,CAAC;IACD,IAAI,CAACO,OAAO,GAAG;MAAEH,MAAM;MAAED;IAAS,CAAC;IACnC,MAAM,IAAI,CAAC4B,OAAO,CAAC5B,QAAQ,CAAC;IAC5B,IAAI,CAACb,QAAQ,CAAC,eAAe,CAAC;IAC9B,OAAOc,MAAM;EACd;EAEA,MAAc2B,OAAOA,CAAC5B,QAAuB,EAAiB;IAC7D,MAAM,IAAI,CAACN,KAAK,CAAC7E,GAAG,CAAC,IAAI,CAAC4E,QAAQ,EAAExC,IAAI,CAAC4E,SAAS,CAAC7B,QAAQ,CAAC,CAAC,CAACU,KAAK,CAAC,MAAM5E,SAAS,CAAC;EACrF;;EAEA;AACD;AACA;AACA;AACA;EACC,MAAMgG,SAASA,CAAC/E,OAAkC,GAAG,CAAC,CAAC,EAA8B;IACpF,MAAM,IAAI,CAACoD,aAAa,CAAC,CAAC;IAC1B,IAAI,CAACpD,OAAO,CAACgF,WAAW,IAAI,IAAI,CAAC3B,OAAO,EAAE;MACzC,IAAI,CAAC,IAAI,CAACL,OAAO,CAAC,IAAI,CAACK,OAAO,CAACJ,QAAQ,EAAE,IAAI,CAACI,OAAO,CAACH,MAAM,CAAC,EAAE;QAC9D,IAAI,CAACW,YAAY,CAAC,CAAC,CAACF,KAAK,CAAC,MAAM5E,SAAS,CAAC,CAAC,CAAC;MAC7C;MACA,OAAO,IAAI,CAACsE,OAAO,CAACH,MAAM;IAC3B;IACA,IAAI;MACH,OAAO,MAAM,IAAI,CAACW,YAAY,CAAC,CAAC;IACjC,CAAC,CAAC,OAAOoB,CAAC,EAAE;MACX,IAAI,IAAI,CAAC5B,OAAO,EAAE,OAAO,IAAI,CAACA,OAAO,CAACH,MAAM,CAAC,CAAC;MAC9C,IAAI,CAACd,QAAQ,CAAC,aAAa,CAAC;MAC5B,MAAM6C,CAAC,YAAY3F,eAAO,GAAG2F,CAAC,GAAG,IAAI3F,eAAO,CAAC,oBAAoB,EAAE4F,MAAM,CAACD,CAAC,CAAC,CAAC;IAC9E;EACD;AACD;AAACjH,OAAA,CAAAyD,qBAAA,GAAAA,qBAAA","ignoreList":[]}