@ynhcj/xiaoyi-channel 0.0.91-beta → 0.0.92-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 +96 -10
- package/package.json +1 -1
package/dist/src/provider.js
CHANGED
|
@@ -9,6 +9,60 @@
|
|
|
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. */
|
|
27
|
+
function getRetryDelayMs(attempt) {
|
|
28
|
+
// attempt 1→10s, 2→20s, 3→40s, 4+→60s
|
|
29
|
+
if (attempt <= RETRY_DELAYS_MS.length)
|
|
30
|
+
return RETRY_DELAYS_MS[attempt - 1];
|
|
31
|
+
return RETRY_DELAYS_MS[RETRY_DELAYS_MS.length - 1]; // 60s cap
|
|
32
|
+
}
|
|
33
|
+
function sleep(ms) {
|
|
34
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build a minimal EventStream-compatible object that replays a single
|
|
38
|
+
* done/error event. This avoids importing @mariozechner/pi-ai at runtime
|
|
39
|
+
* (the package is not available in the extension sandbox).
|
|
40
|
+
*/
|
|
41
|
+
function buildReplayStream(result) {
|
|
42
|
+
let settled = false;
|
|
43
|
+
const queued = [
|
|
44
|
+
result.stopReason === "error"
|
|
45
|
+
? { type: "error", reason: "error", error: result }
|
|
46
|
+
: { type: "done", reason: result.stopReason, message: result },
|
|
47
|
+
];
|
|
48
|
+
return {
|
|
49
|
+
result: () => Promise.resolve(result),
|
|
50
|
+
push: () => { },
|
|
51
|
+
end: () => { },
|
|
52
|
+
[Symbol.asyncIterator]: () => {
|
|
53
|
+
return {
|
|
54
|
+
next: async () => {
|
|
55
|
+
if (settled || queued.length === 0) {
|
|
56
|
+
settled = true;
|
|
57
|
+
return { value: undefined, done: true };
|
|
58
|
+
}
|
|
59
|
+
settled = true;
|
|
60
|
+
return { value: queued.shift(), done: false };
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
12
66
|
/**
|
|
13
67
|
* Dynamic header keys injected via extraParams and forwarded to the HTTP request.
|
|
14
68
|
* Correspond to the three fields written to .xiaoyiruntime:
|
|
@@ -73,10 +127,12 @@ export const xiaoyiProvider = {
|
|
|
73
127
|
},
|
|
74
128
|
/**
|
|
75
129
|
* Wrap the stream function to inject dynamic headers into every
|
|
76
|
-
* HTTP request to the model provider
|
|
130
|
+
* HTTP request to the model provider, and retry on retryable errors
|
|
131
|
+
* (server_error / rate_limit_error) with backoff: 10s, 20s, 40s, 60s (cap).
|
|
77
132
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
133
|
+
* The retry loop awaits stream.result() to detect errors before deciding
|
|
134
|
+
* whether to retry. This keeps the agent loop waiting (no timeout risk
|
|
135
|
+
* since the default agent timeout is 48 hours).
|
|
80
136
|
*/
|
|
81
137
|
wrapStreamFn: (ctx) => {
|
|
82
138
|
const underlying = ctx.streamFn;
|
|
@@ -135,21 +191,51 @@ export const xiaoyiProvider = {
|
|
|
135
191
|
if (sessionCtx?.deviceType) {
|
|
136
192
|
const rawDevice = sessionCtx.deviceType;
|
|
137
193
|
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
|
|
194
|
+
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
195
|
context.systemPrompt = (context.systemPrompt ?? "") + deviceSection;
|
|
140
196
|
}
|
|
141
|
-
|
|
197
|
+
// ── Retry loop ─────────────────────────────────────────
|
|
198
|
+
for (let attempt = 0; attempt < MAX_RETRY_ATTEMPTS; attempt++) {
|
|
199
|
+
const stream = await underlying(model, context, {
|
|
200
|
+
...options,
|
|
201
|
+
headers: {
|
|
202
|
+
...options?.headers,
|
|
203
|
+
...dynamicHeaders,
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
// Wait for the stream to settle (done or error) to inspect the result.
|
|
207
|
+
// stream.result() resolves to the final AssistantMessage (even on error).
|
|
208
|
+
const result = await stream.result();
|
|
209
|
+
// Check if this is a retryable error
|
|
210
|
+
if (result.stopReason === "error" && isRetryableProviderError(result.errorMessage)) {
|
|
211
|
+
const delayMs = getRetryDelayMs(attempt);
|
|
212
|
+
console.log(`[xiaoyiprovider] retryable error (attempt ${attempt + 1}/${MAX_RETRY_ATTEMPTS}): ` +
|
|
213
|
+
`${result.errorMessage} — retrying in ${delayMs}ms`);
|
|
214
|
+
await sleep(delayMs);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
// Success or non-retryable error — log and return
|
|
218
|
+
if (result.stopReason === "error") {
|
|
219
|
+
console.log(`[xiaoyiprovider] non-retryable error: ${result.errorMessage}`);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
console.log(`[xiaoyiprovider] stream completed, usage: input=${result.usage?.input} output=${result.usage?.output}`);
|
|
223
|
+
}
|
|
224
|
+
// The original stream has already been consumed by result().
|
|
225
|
+
// Build a replay stream that delivers the final result.
|
|
226
|
+
return buildReplayStream(result);
|
|
227
|
+
}
|
|
228
|
+
// All retries exhausted — return the last attempt's real error via a new stream
|
|
229
|
+
console.log(`[xiaoyiprovider] all ${MAX_RETRY_ATTEMPTS} retries exhausted, surfacing last error`);
|
|
230
|
+
const lastStream = await underlying(model, context, {
|
|
142
231
|
...options,
|
|
143
232
|
headers: {
|
|
144
233
|
...options?.headers,
|
|
145
234
|
...dynamicHeaders,
|
|
146
235
|
},
|
|
147
236
|
});
|
|
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;
|
|
237
|
+
const lastResult = await lastStream.result();
|
|
238
|
+
return buildReplayStream(lastResult);
|
|
153
239
|
};
|
|
154
240
|
},
|
|
155
241
|
};
|