@ynhcj/xiaoyi-channel 0.0.91-beta → 0.0.93-beta
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/dist/src/provider.js +97 -10
- package/package.json +1 -1
package/dist/src/provider.js
CHANGED
|
@@ -9,6 +9,61 @@
|
|
|
9
9
|
// models.providers.xiaoyiprovider.models = [...]
|
|
10
10
|
import { createHash } from "crypto";
|
|
11
11
|
import { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
12
|
+
// ── Retry config ──────────────────────────────────────────────
|
|
13
|
+
const RETRY_DELAYS_MS = [10_000, 20_000, 40_000, 60_000];
|
|
14
|
+
const MAX_RETRY_ATTEMPTS = 8;
|
|
15
|
+
/** Check if an errorMessage indicates a retryable provider error by type. */
|
|
16
|
+
function isRetryableProviderError(message) {
|
|
17
|
+
if (!message)
|
|
18
|
+
return false;
|
|
19
|
+
const lower = message.toLowerCase();
|
|
20
|
+
if (lower.includes("server_error"))
|
|
21
|
+
return true;
|
|
22
|
+
if (lower.includes("rate_limit_error"))
|
|
23
|
+
return true;
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
/** Compute retry delay in ms for the given 1-based attempt, with up to 10s jitter. */
|
|
27
|
+
function getRetryDelayMs(attempt) {
|
|
28
|
+
const base = attempt <= RETRY_DELAYS_MS.length
|
|
29
|
+
? RETRY_DELAYS_MS[attempt - 1]
|
|
30
|
+
: RETRY_DELAYS_MS[RETRY_DELAYS_MS.length - 1];
|
|
31
|
+
const jitter = Math.floor(Math.random() * 10_000);
|
|
32
|
+
return base + jitter;
|
|
33
|
+
}
|
|
34
|
+
function sleep(ms) {
|
|
35
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build a minimal EventStream-compatible object that replays a single
|
|
39
|
+
* done/error event. This avoids importing @mariozechner/pi-ai at runtime
|
|
40
|
+
* (the package is not available in the extension sandbox).
|
|
41
|
+
*/
|
|
42
|
+
function buildReplayStream(result) {
|
|
43
|
+
let settled = false;
|
|
44
|
+
const queued = [
|
|
45
|
+
result.stopReason === "error"
|
|
46
|
+
? { type: "error", reason: "error", error: result }
|
|
47
|
+
: { type: "done", reason: result.stopReason, message: result },
|
|
48
|
+
];
|
|
49
|
+
return {
|
|
50
|
+
result: () => Promise.resolve(result),
|
|
51
|
+
push: () => { },
|
|
52
|
+
end: () => { },
|
|
53
|
+
[Symbol.asyncIterator]: () => {
|
|
54
|
+
return {
|
|
55
|
+
next: async () => {
|
|
56
|
+
if (settled || queued.length === 0) {
|
|
57
|
+
settled = true;
|
|
58
|
+
return { value: undefined, done: true };
|
|
59
|
+
}
|
|
60
|
+
settled = true;
|
|
61
|
+
return { value: queued.shift(), done: false };
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
12
67
|
/**
|
|
13
68
|
* Dynamic header keys injected via extraParams and forwarded to the HTTP request.
|
|
14
69
|
* Correspond to the three fields written to .xiaoyiruntime:
|
|
@@ -73,10 +128,12 @@ export const xiaoyiProvider = {
|
|
|
73
128
|
},
|
|
74
129
|
/**
|
|
75
130
|
* Wrap the stream function to inject dynamic headers into every
|
|
76
|
-
* HTTP request to the model provider
|
|
131
|
+
* HTTP request to the model provider, and retry on retryable errors
|
|
132
|
+
* (server_error / rate_limit_error) with backoff: 10s, 20s, 40s, 60s (cap).
|
|
77
133
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
134
|
+
* The retry loop awaits stream.result() to detect errors before deciding
|
|
135
|
+
* whether to retry. This keeps the agent loop waiting (no timeout risk
|
|
136
|
+
* since the default agent timeout is 48 hours).
|
|
80
137
|
*/
|
|
81
138
|
wrapStreamFn: (ctx) => {
|
|
82
139
|
const underlying = ctx.streamFn;
|
|
@@ -135,21 +192,51 @@ export const xiaoyiProvider = {
|
|
|
135
192
|
if (sessionCtx?.deviceType) {
|
|
136
193
|
const rawDevice = sessionCtx.deviceType;
|
|
137
194
|
const displayDevice = (rawDevice === "2in1") ? "鸿蒙PC" : rawDevice;
|
|
138
|
-
const deviceSection = `\n\n## Current User Device Context\nThe current user is using the following device: ${displayDevice}\nYou need to be aware of the user
|
|
195
|
+
const deviceSection = `\n\n## Current User Device Context\nThe current user is using the following device: ${displayDevice}\nYou need to be aware of the user's current device and provide guidance accordingly. If the response involves device-related tools or actions, you must tailor the reply based on the user's current device, using device-specific references such as "saved to the Notes/Calendar on your {deviceType}.\n"`;
|
|
139
196
|
context.systemPrompt = (context.systemPrompt ?? "") + deviceSection;
|
|
140
197
|
}
|
|
141
|
-
|
|
198
|
+
// ── Retry loop ─────────────────────────────────────────
|
|
199
|
+
for (let attempt = 0; attempt < MAX_RETRY_ATTEMPTS; attempt++) {
|
|
200
|
+
const stream = await underlying(model, context, {
|
|
201
|
+
...options,
|
|
202
|
+
headers: {
|
|
203
|
+
...options?.headers,
|
|
204
|
+
...dynamicHeaders,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
// Wait for the stream to settle (done or error) to inspect the result.
|
|
208
|
+
// stream.result() resolves to the final AssistantMessage (even on error).
|
|
209
|
+
const result = await stream.result();
|
|
210
|
+
// Check if this is a retryable error
|
|
211
|
+
if (result.stopReason === "error" && isRetryableProviderError(result.errorMessage)) {
|
|
212
|
+
const delayMs = getRetryDelayMs(attempt);
|
|
213
|
+
console.log(`[xiaoyiprovider] retryable error (attempt ${attempt + 1}/${MAX_RETRY_ATTEMPTS}): ` +
|
|
214
|
+
`${result.errorMessage} — retrying in ${delayMs}ms`);
|
|
215
|
+
await sleep(delayMs);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
// Success or non-retryable error — log and return
|
|
219
|
+
if (result.stopReason === "error") {
|
|
220
|
+
console.log(`[xiaoyiprovider] non-retryable error: ${result.errorMessage}`);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
console.log(`[xiaoyiprovider] stream completed, usage: input=${result.usage?.input} output=${result.usage?.output}`);
|
|
224
|
+
}
|
|
225
|
+
// The original stream has already been consumed by result().
|
|
226
|
+
// Build a replay stream that delivers the final result.
|
|
227
|
+
return buildReplayStream(result);
|
|
228
|
+
}
|
|
229
|
+
// All retries exhausted — return the last attempt's real error via a new stream
|
|
230
|
+
console.log(`[xiaoyiprovider] all ${MAX_RETRY_ATTEMPTS} retries exhausted, surfacing last error`);
|
|
231
|
+
const lastStream = await underlying(model, context, {
|
|
142
232
|
...options,
|
|
143
233
|
headers: {
|
|
144
234
|
...options?.headers,
|
|
145
235
|
...dynamicHeaders,
|
|
146
236
|
},
|
|
147
237
|
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
console.log(`[xiaoyiprovider] stream completed, usage: input=${result.usage?.input} output=${result.usage?.output}`);
|
|
151
|
-
}, (err) => console.log(`[xiaoyiprovider] stream error: ${JSON.stringify(err)}`));
|
|
152
|
-
return stream;
|
|
238
|
+
const lastResult = await lastStream.result();
|
|
239
|
+
return buildReplayStream(lastResult);
|
|
153
240
|
};
|
|
154
241
|
},
|
|
155
242
|
};
|