@crossworks/voice-client 0.232.114 → 0.232.123
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/package.json +1 -1
- package/src/adapters/retry.ts +75 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crossworks/voice-client",
|
|
3
|
-
"version": "0.232.
|
|
3
|
+
"version": "0.232.123",
|
|
4
4
|
"description": "Browser-safe surface of the voice/model layer — provider catalogue, model catalogs, audio tags, and the adapter type/metadata contract. Zero deps by design: nothing here may reach the network adapters or node builtins (the jackdaw-repo-split P0 boundary).",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.ts",
|
package/src/adapters/retry.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* OpenRouter is intentionally NOT wrapped — its SDK already retries, and
|
|
20
20
|
* double-wrapping would compound attempt counts. See registry.getChatAdapter.
|
|
21
21
|
*/
|
|
22
|
-
import type { ChatDispatcher, ChatOptions, ChatResult } from './types';
|
|
22
|
+
import type { ChatDispatcher, ChatOptions, ChatResult, ChatStreamSink } from './types';
|
|
23
23
|
|
|
24
24
|
/** Default attempts AFTER the first try (so 2 ⇒ up to 3 total calls). */
|
|
25
25
|
export const DEFAULT_MAX_RETRIES = 2;
|
|
@@ -152,42 +152,86 @@ export interface ChatRetryConfig {
|
|
|
152
152
|
maxDelayMs?: number;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
/** One attempt loop shared by the one-shot and streaming wrappers. `run` is
|
|
156
|
+
* called per attempt; `mayRetry` lets the streaming path veto a replay once
|
|
157
|
+
* output has already reached the user. */
|
|
158
|
+
async function attemptWithRetry<T>(
|
|
159
|
+
adapter: ChatDispatcher,
|
|
160
|
+
opts: ChatOptions,
|
|
161
|
+
config: ChatRetryConfig,
|
|
162
|
+
run: () => Promise<T>,
|
|
163
|
+
mayRetry: () => boolean,
|
|
164
|
+
): Promise<T> {
|
|
165
|
+
const base = config.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
|
|
166
|
+
const max = config.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
167
|
+
const maxRetries = opts.maxRetries ?? config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
168
|
+
let attempt = 0;
|
|
169
|
+
for (;;) {
|
|
170
|
+
try {
|
|
171
|
+
return await run();
|
|
172
|
+
} catch (err) {
|
|
173
|
+
// A caller-aborted signal (user Stop) is not transient: every retry
|
|
174
|
+
// would abort identically after a pointless backoff sleep. Surface it
|
|
175
|
+
// immediately — the tool loop / run-turn recognise the stop.
|
|
176
|
+
if (opts.signal?.aborted) throw err;
|
|
177
|
+
if (!mayRetry()) throw err;
|
|
178
|
+
const { retry, retryAfterMs } = classifyChatError(err);
|
|
179
|
+
if (!retry || attempt >= maxRetries) throw err;
|
|
180
|
+
attempt += 1;
|
|
181
|
+
const delay =
|
|
182
|
+
retryAfterMs != null
|
|
183
|
+
? Math.min(retryAfterMs, RETRY_AFTER_CAP_MS)
|
|
184
|
+
: Math.round(Math.random() * Math.min(max, base * 2 ** (attempt - 1)));
|
|
185
|
+
console.warn(
|
|
186
|
+
`[chat-retry] ${adapter.adapterName} ${opts.model}: ${describeError(err)} — retry ${attempt}/${maxRetries} in ${delay}ms`,
|
|
187
|
+
);
|
|
188
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
155
193
|
/**
|
|
156
|
-
* Wrap a ChatDispatcher with retry/backoff on its `chat`
|
|
157
|
-
* `opts.maxRetries` overrides the config default; 0 disables.
|
|
158
|
-
* dispatcher members (providerId, adapterName, discoverModels,
|
|
159
|
-
* are preserved unchanged.
|
|
194
|
+
* Wrap a ChatDispatcher with retry/backoff on its `chat` AND `chatStream`
|
|
195
|
+
* calls. Per-call `opts.maxRetries` overrides the config default; 0 disables.
|
|
196
|
+
* All other dispatcher members (providerId, adapterName, discoverModels,
|
|
197
|
+
* staticCatalog) are preserved unchanged.
|
|
198
|
+
*
|
|
199
|
+
* Streaming is retried only while nothing has reached the user yet: a 429, a
|
|
200
|
+
* 5xx, a network blip or a connect timeout before the first delta re-sends the
|
|
201
|
+
* request exactly like the one-shot path. Once a delta has been emitted the
|
|
202
|
+
* error surfaces as-is — replaying would repeat text the client already
|
|
203
|
+
* rendered, and the adapters already return the partial on a user Stop.
|
|
204
|
+
* (Before 2026-09-02 only `chat` was wrapped; the tool loop prefers
|
|
205
|
+
* `chatStream` whenever a turn is live, so live turns had no retry at all.)
|
|
160
206
|
*/
|
|
161
207
|
export function withChatRetry(
|
|
162
208
|
adapter: ChatDispatcher,
|
|
163
209
|
config: ChatRetryConfig = {},
|
|
164
210
|
): ChatDispatcher {
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
211
|
+
const chat = (opts: ChatOptions): Promise<ChatResult> =>
|
|
212
|
+
attemptWithRetry(
|
|
213
|
+
adapter,
|
|
214
|
+
opts,
|
|
215
|
+
config,
|
|
216
|
+
() => adapter.chat(opts),
|
|
217
|
+
() => true,
|
|
218
|
+
);
|
|
219
|
+
const inner = adapter.chatStream?.bind(adapter);
|
|
220
|
+
const chatStream = inner
|
|
221
|
+
? (opts: ChatOptions, onDelta: ChatStreamSink): Promise<ChatResult> => {
|
|
222
|
+
let emitted = false;
|
|
223
|
+
const sink: ChatStreamSink = (delta) => {
|
|
224
|
+
emitted = true;
|
|
225
|
+
onDelta(delta);
|
|
226
|
+
};
|
|
227
|
+
return attemptWithRetry(
|
|
228
|
+
adapter,
|
|
229
|
+
opts,
|
|
230
|
+
config,
|
|
231
|
+
() => inner(opts, sink),
|
|
232
|
+
() => !emitted,
|
|
187
233
|
);
|
|
188
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
189
234
|
}
|
|
190
|
-
|
|
191
|
-
};
|
|
192
|
-
return { ...adapter, chat };
|
|
235
|
+
: undefined;
|
|
236
|
+
return { ...adapter, chat, ...(chatStream ? { chatStream } : {}) };
|
|
193
237
|
}
|