@genui-a3/create 0.1.7 → 0.1.9

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.
@@ -0,0 +1,226 @@
1
+ # Resilience
2
+
3
+ A3 providers include built-in resilience: automatic retries with backoff, per-request and total timeouts, and model fallback.
4
+ When a request fails, the provider retries against the same model before falling back to the next model in your list — all with zero configuration required.
5
+
6
+ ## How it works
7
+
8
+ When you call `sendRequest` or `sendRequestStream`, the provider delegates to `executeWithFallback`, which runs this loop:
9
+
10
+ ```text
11
+ for each model (in priority order):
12
+ for each attempt (1 … 1 + maxAttempts):
13
+ 1. Check total timeout — abort if exceeded
14
+ 2. Build an AbortSignal combining per-request timeout + total timeout
15
+ 3. Call the provider action with (model, signal)
16
+ 4. On success → return result
17
+ 5. On failure:
18
+ a. Record the error
19
+ b. If retryable and attempts remain → backoff delay → retry same model
20
+ c. Otherwise → move to next model
21
+
22
+ All models exhausted → throw A3ResilienceError
23
+ Total timeout exceeded → throw A3TimeoutError
24
+ ```
25
+
26
+ ## Defaults
27
+
28
+ With zero configuration, every provider gets:
29
+
30
+ | Setting | Default |
31
+ |---|---|
32
+ | `retry.maxAttempts` | `2` (3 total attempts per model) |
33
+ | `retry.retryOn` | `'transient'` |
34
+ | `backoff.strategy` | `'exponential'` |
35
+ | `backoff.baseDelayMs` | `500` |
36
+ | `backoff.maxDelayMs` | `30000` |
37
+ | `backoff.jitter` | `true` |
38
+ | `timeout.requestTimeoutMs` | `undefined` (SDK default) |
39
+ | `timeout.totalTimeoutMs` | `undefined` (no limit) |
40
+ | `isRetryableError` | Built-in classifier |
41
+
42
+ These defaults are exported as `DEFAULT_RESILIENCE_CONFIG` from `@genui-a3/core`.
43
+
44
+ ## Configuration examples
45
+
46
+ Pass a `resilience` object when creating a provider.
47
+ All fields are optional — unspecified fields keep their defaults.
48
+
49
+ ### Minimal — just increase retries
50
+
51
+ ```typescript
52
+ const provider = createBedrockProvider({
53
+ models: ['us.anthropic.claude-sonnet-4-5-20250929-v1:0'],
54
+ resilience: {
55
+ retry: { maxAttempts: 3 },
56
+ },
57
+ })
58
+ ```
59
+
60
+ ### Medium — linear backoff with timeouts
61
+
62
+ ```typescript
63
+ const provider = createOpenAIProvider({
64
+ models: ['gpt-4o', 'gpt-4o-mini'],
65
+ resilience: {
66
+ retry: { maxAttempts: 2 },
67
+ backoff: { strategy: 'linear', baseDelayMs: 1000 },
68
+ timeout: {
69
+ requestTimeoutMs: 30_000,
70
+ totalTimeoutMs: 90_000,
71
+ },
72
+ },
73
+ })
74
+ ```
75
+
76
+ ### Maximum — full control
77
+
78
+ ```typescript
79
+ const provider = createBedrockProvider({
80
+ models: [
81
+ 'us.anthropic.claude-sonnet-4-5-20250929-v1:0',
82
+ 'us.anthropic.claude-haiku-4-5-20251001-v1:0',
83
+ ],
84
+ resilience: {
85
+ retry: { maxAttempts: 5, retryOn: 'all' },
86
+ backoff: {
87
+ strategy: 'exponential',
88
+ baseDelayMs: 200,
89
+ maxDelayMs: 10_000,
90
+ jitter: true,
91
+ },
92
+ timeout: {
93
+ requestTimeoutMs: 15_000,
94
+ totalTimeoutMs: 120_000,
95
+ },
96
+ isRetryableError: (error) => {
97
+ // Custom logic — retry everything except auth errors
98
+ return !error.message.includes('401')
99
+ },
100
+ },
101
+ })
102
+ ```
103
+
104
+ ### Disable retries entirely
105
+
106
+ ```typescript
107
+ const provider = createBedrockProvider({
108
+ models: ['us.anthropic.claude-sonnet-4-5-20250929-v1:0'],
109
+ resilience: {
110
+ retry: false,
111
+ },
112
+ })
113
+ ```
114
+
115
+ With `retry: false`, each model is attempted exactly once with no backoff.
116
+ Model fallback still applies if you provide multiple models.
117
+
118
+ ## Backoff strategies
119
+
120
+ | Strategy | Formula | Example (baseDelay=500ms) |
121
+ |---|---|---|
122
+ | `'exponential'` (default) | `baseDelay * 2^attempt` | 500ms, 1000ms, 2000ms, 4000ms… |
123
+ | `'linear'` | `baseDelay * (attempt + 1)` | 500ms, 1000ms, 1500ms, 2000ms… |
124
+ | `'fixed'` | `baseDelay` | 500ms, 500ms, 500ms, 500ms… |
125
+
126
+ All strategies are capped at `maxDelayMs`.
127
+ When `jitter` is `true`, the actual delay is randomized between `0` and the calculated value.
128
+
129
+ ## Error classification
130
+
131
+ The built-in `isRetryableError` classifier determines which errors are transient (safe to retry) and which are permanent (skip to next model).
132
+
133
+ **Retryable (transient):**
134
+
135
+ - Network errors: `ECONNRESET`, `ECONNREFUSED`, `ECONNABORTED`, `ETIMEDOUT`, `ENETUNREACH`, `EPIPE`, `EHOSTUNREACH`
136
+ - Throttling: messages containing `throttl`, `rate limit`, `too many requests`, `request limit`, `quota`
137
+ - Timeouts: messages containing `timeout` or `timed out`
138
+ - HTTP status codes: `408` (Request Timeout), `429` (Too Many Requests), `500+` (Server Errors, including `529` Overloaded)
139
+ - AWS SDK v3: reads `$metadata.httpStatusCode` automatically
140
+
141
+ **Not retryable:**
142
+
143
+ - `AbortError` / `TimeoutError` (intentional cancellation by the timeout system)
144
+ - `400` Bad Request, `401` Unauthorized, `403` Forbidden, `404` Not Found
145
+ - Any error not matching the patterns above
146
+
147
+ When `retryOn` is set to `'all'`, the classifier is bypassed and every error triggers a retry (up to `maxAttempts`).
148
+
149
+ ## Error handling
150
+
151
+ When all models and retries are exhausted, A3 throws typed errors that preserve the full failure history.
152
+
153
+ ### A3ResilienceError
154
+
155
+ Thrown when every model has failed.
156
+ Contains an `errors` array with one entry per failed attempt.
157
+
158
+ ```typescript
159
+ import { A3ResilienceError } from '@genui-a3/core'
160
+
161
+ try {
162
+ await provider.sendRequest(request)
163
+ } catch (err) {
164
+ if (err instanceof A3ResilienceError) {
165
+ for (const entry of err.errors) {
166
+ console.log(`${entry.model} attempt ${entry.attempt}: ${entry.error.message}`)
167
+ }
168
+ }
169
+ }
170
+ ```
171
+
172
+ Each `ResilienceErrorEntry` contains:
173
+
174
+ - `model` — the model identifier that was attempted
175
+ - `attempt` — 1-based attempt number within that model's retry cycle
176
+ - `error` — the original `Error` object
177
+
178
+ ### A3TimeoutError
179
+
180
+ Thrown when `totalTimeoutMs` is exceeded.
181
+ Extends `A3ResilienceError`, so it includes the same `errors` array and can be caught by either type.
182
+
183
+ ```typescript
184
+ import { A3TimeoutError, A3ResilienceError } from '@genui-a3/core'
185
+
186
+ try {
187
+ await provider.sendRequest(request)
188
+ } catch (err) {
189
+ if (err instanceof A3TimeoutError) {
190
+ console.log('Total timeout exceeded')
191
+ } else if (err instanceof A3ResilienceError) {
192
+ console.log('All models exhausted')
193
+ }
194
+ }
195
+ ```
196
+
197
+ ## Custom error classifier
198
+
199
+ Override the built-in classification by providing an `isRetryableError` function.
200
+ When provided, this replaces the default classifier entirely.
201
+
202
+ ```typescript
203
+ const provider = createBedrockProvider({
204
+ models: ['us.anthropic.claude-sonnet-4-5-20250929-v1:0'],
205
+ resilience: {
206
+ isRetryableError: (error) => {
207
+ // Only retry rate-limit errors
208
+ return error.message.includes('429') || error.message.includes('Too Many Requests')
209
+ },
210
+ },
211
+ })
212
+ ```
213
+
214
+ The custom classifier is ignored when `retryOn` is set to `'all'`.
215
+
216
+ ## Key files
217
+
218
+ | File | Description |
219
+ |---|---|
220
+ | `src/types/resilience.ts` | `ResilienceConfig`, `RetryConfig`, `BackoffConfig`, `TimeoutConfig`, `ResolvedResilienceConfig` |
221
+ | `src/utils/resilience/defaults.ts` | `DEFAULT_RESILIENCE_CONFIG`, `resolveResilienceConfig()` |
222
+ | `src/utils/resilience/errorClassification.ts` | `isRetryableError()` — built-in error classifier |
223
+ | `src/errors/resilience.ts` | `A3ResilienceError`, `A3TimeoutError`, `ResilienceErrorEntry` |
224
+ | `providers/utils/executeWithFallback.ts` | `executeWithFallback()` — core execution loop |
225
+ | `providers/utils/backoff.ts` | `calculateBackoff()`, `sleep()` |
226
+ | `src/index.ts` | Public exports: `resolveResilienceConfig`, `DEFAULT_RESILIENCE_CONFIG`, `isRetryableError`, `A3ResilienceError`, `A3TimeoutError` |
@@ -11,8 +11,8 @@
11
11
  "@ag-ui/encoder": "0.0.45",
12
12
  "@emotion/react": "11.14.0",
13
13
  "@emotion/styled": "11.14.1",
14
- "@genui-a3/core": "^0.1.10",
15
- "@genui-a3/providers": "^0.0.2",
14
+ "@genui-a3/core": "^0.1.18",
15
+ "@genui-a3/providers": "^0.0.6",
16
16
  "@mui/icons-material": "7.3.7",
17
17
  "@mui/material": "7.3.7",
18
18
  "next": "16.1.6",