@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.
- package/CHANGELOG.md +146 -0
- package/README.md +65 -24
- package/lib/commonjs/config/behaviorConfig.js +283 -0
- package/lib/commonjs/config/behaviorConfig.js.map +1 -0
- package/lib/commonjs/contract/gateway.js +60 -0
- package/lib/commonjs/contract/gateway.js.map +1 -0
- package/lib/commonjs/core/aiClient.js +330 -0
- package/lib/commonjs/core/aiClient.js.map +1 -0
- package/lib/commonjs/core/quotaRequest.js +105 -0
- package/lib/commonjs/core/quotaRequest.js.map +1 -0
- package/lib/commonjs/diagnostics/errors.js +108 -0
- package/lib/commonjs/diagnostics/errors.js.map +1 -0
- package/lib/commonjs/diagnostics/messageKeys.js +68 -0
- package/lib/commonjs/diagnostics/messageKeys.js.map +1 -0
- package/lib/commonjs/index.js +102 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/react/hooks.js +170 -0
- package/lib/commonjs/react/hooks.js.map +1 -0
- package/lib/commonjs/transport/sseClient.js +151 -0
- package/lib/commonjs/transport/sseClient.js.map +1 -0
- package/lib/commonjs/transport/timeouts.js +46 -0
- package/lib/commonjs/transport/timeouts.js.map +1 -0
- package/lib/module/config/behaviorConfig.js +275 -0
- package/lib/module/config/behaviorConfig.js.map +1 -0
- package/lib/module/contract/gateway.js +56 -0
- package/lib/module/contract/gateway.js.map +1 -0
- package/lib/module/core/aiClient.js +327 -0
- package/lib/module/core/aiClient.js.map +1 -0
- package/lib/module/core/quotaRequest.js +102 -0
- package/lib/module/core/quotaRequest.js.map +1 -0
- package/lib/module/diagnostics/errors.js +99 -0
- package/lib/module/diagnostics/errors.js.map +1 -0
- package/lib/module/diagnostics/messageKeys.js +61 -0
- package/lib/module/diagnostics/messageKeys.js.map +1 -0
- package/lib/module/index.js +19 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/react/hooks.js +164 -0
- package/lib/module/react/hooks.js.map +1 -0
- package/lib/module/transport/sseClient.js +146 -0
- package/lib/module/transport/sseClient.js.map +1 -0
- package/lib/module/transport/timeouts.js +41 -0
- package/lib/module/transport/timeouts.js.map +1 -0
- package/lib/typescript/commonjs/config/behaviorConfig.d.ts +119 -0
- package/lib/typescript/commonjs/config/behaviorConfig.d.ts.map +1 -0
- package/lib/typescript/commonjs/contract/gateway.d.ts +64 -0
- package/lib/typescript/commonjs/contract/gateway.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/aiClient.d.ts +104 -0
- package/lib/typescript/commonjs/core/aiClient.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/quotaRequest.d.ts +45 -0
- package/lib/typescript/commonjs/core/quotaRequest.d.ts.map +1 -0
- package/lib/typescript/commonjs/diagnostics/errors.d.ts +38 -0
- package/lib/typescript/commonjs/diagnostics/errors.d.ts.map +1 -0
- package/lib/typescript/commonjs/diagnostics/messageKeys.d.ts +46 -0
- package/lib/typescript/commonjs/diagnostics/messageKeys.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +21 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/react/hooks.d.ts +37 -0
- package/lib/typescript/commonjs/react/hooks.d.ts.map +1 -0
- package/lib/typescript/commonjs/transport/sseClient.d.ts +57 -0
- package/lib/typescript/commonjs/transport/sseClient.d.ts.map +1 -0
- package/lib/typescript/commonjs/transport/timeouts.d.ts +28 -0
- package/lib/typescript/commonjs/transport/timeouts.d.ts.map +1 -0
- package/lib/typescript/module/config/behaviorConfig.d.ts +119 -0
- package/lib/typescript/module/config/behaviorConfig.d.ts.map +1 -0
- package/lib/typescript/module/contract/gateway.d.ts +64 -0
- package/lib/typescript/module/contract/gateway.d.ts.map +1 -0
- package/lib/typescript/module/core/aiClient.d.ts +104 -0
- package/lib/typescript/module/core/aiClient.d.ts.map +1 -0
- package/lib/typescript/module/core/quotaRequest.d.ts +45 -0
- package/lib/typescript/module/core/quotaRequest.d.ts.map +1 -0
- package/lib/typescript/module/diagnostics/errors.d.ts +38 -0
- package/lib/typescript/module/diagnostics/errors.d.ts.map +1 -0
- package/lib/typescript/module/diagnostics/messageKeys.d.ts +46 -0
- package/lib/typescript/module/diagnostics/messageKeys.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +21 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/react/hooks.d.ts +37 -0
- package/lib/typescript/module/react/hooks.d.ts.map +1 -0
- package/lib/typescript/module/transport/sseClient.d.ts +57 -0
- package/lib/typescript/module/transport/sseClient.d.ts.map +1 -0
- package/lib/typescript/module/transport/timeouts.d.ts +28 -0
- package/lib/typescript/module/transport/timeouts.d.ts.map +1 -0
- package/package.json +40 -12
- package/skills/sdk-rn-ai/SKILL.md +43 -4
- package/src/config/behaviorConfig.ts +158 -37
- package/src/contract/gateway.ts +91 -0
- package/src/core/aiClient.ts +194 -96
- package/src/core/quotaRequest.ts +125 -0
- package/src/diagnostics/errors.ts +85 -23
- package/src/diagnostics/messageKeys.ts +3 -0
- package/src/index.ts +28 -1
- package/src/react/hooks.ts +86 -14
- 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
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
25
|
+
npm install @chipmobilesdk/rn-ai
|
|
23
26
|
```
|
|
24
27
|
|
|
25
|
-
|
|
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
|
-
//
|
|
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
|
|
87
|
-
on `401
|
|
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 | `
|
|
94
|
-
| `idleMs` — silence between two stream chunks | 30 s | `
|
|
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 | `
|
|
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
|
-
`
|
|
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
|
-
- **
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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`, `
|
|
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":"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","ignoreList":[]}
|