pi-freeflow 1.15.0 → 1.15.1
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 +12 -0
- package/package.json +1 -1
- package/src/relay.ts +57 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.15.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Requests now roll over to the next relay when the current one answers with a disabled-deployment verdict, instead of failing on the first relay and requiring a manual switch. Other payment and quota refusals still surface immediately.
|
|
8
|
+
|
|
9
|
+
## 1.15.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Free-tier requests now use native session identifiers and an up-to-date client version, after the upstream gateway began rejecting older formats with a free-tier error. `/freeflow test` also gains an end-to-end chat check that verifies a relay can actually run inference, not just list models.
|
|
14
|
+
|
|
3
15
|
## 1.14.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-freeflow",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.15.
|
|
4
|
+
"version": "1.15.1",
|
|
5
5
|
"description": "Thin provider for OMP/Pi — model list + dumb relay proxy + log; host pi-ai owns thinking/normalization",
|
|
6
6
|
"main": "extensions/index.ts",
|
|
7
7
|
"types": "src/index.ts",
|
package/src/relay.ts
CHANGED
|
@@ -38,6 +38,26 @@ export function isRetriableStatus(status: number): boolean {
|
|
|
38
38
|
(status >= 520 && status <= 530)
|
|
39
39
|
);
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
/** Vercel edge markers identifying a relay-host (not upstream) verdict. */
|
|
43
|
+
function hasVercelEdgeMarkers(res: Response): boolean {
|
|
44
|
+
return (
|
|
45
|
+
Boolean(res.headers.get("x-vercel-error")) ||
|
|
46
|
+
Boolean(res.headers.get("x-vercel-id")) ||
|
|
47
|
+
res.headers.get("server")?.toLowerCase().includes("vercel") === true
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Gated 402 roll predicate: a 402 is a relay-host failure only when it
|
|
53
|
+
* carries Vercel edge markers or a DEPLOYMENT_DISABLED body match.
|
|
54
|
+
* Generic 402s (payment/quota) carry neither and must surface immediately.
|
|
55
|
+
*/
|
|
56
|
+
function isRelayDeploymentDisabled(res: Response, bodyText: string | null): boolean {
|
|
57
|
+
if (res.status !== 402) return false;
|
|
58
|
+
if (hasVercelEdgeMarkers(res)) return true;
|
|
59
|
+
return bodyText !== null && bodyText.includes("DEPLOYMENT_DISABLED");
|
|
60
|
+
}
|
|
41
61
|
/**
|
|
42
62
|
* Per-conversation reasoning affinity. Callers sending caller-bound reasoning
|
|
43
63
|
* pass the relay that issued it; `onServed` reports the relay that actually
|
|
@@ -230,6 +250,43 @@ export async function relayFetch(
|
|
|
230
250
|
}
|
|
231
251
|
continue;
|
|
232
252
|
}
|
|
253
|
+
// Relay deployment disabled (402 DEPLOYMENT_DISABLED from Vercel):
|
|
254
|
+
// A disabled deployment is a relay-host failure, so roll to the next
|
|
255
|
+
// relay. A blanket 402 must NOT roll: generic 402s are payment/quota
|
|
256
|
+
// verdicts that must surface immediately, so gate strictly on Vercel
|
|
257
|
+
// edge markers or a DEPLOYMENT_DISABLED body match and otherwise fall
|
|
258
|
+
// through to the terminal path below. The body is peeked via a clone so
|
|
259
|
+
// the downstream stream stays intact; on any clone/read failure decide
|
|
260
|
+
// on headers alone (headerless failure reads as a generic 402).
|
|
261
|
+
if (res.status === 402) {
|
|
262
|
+
let disabledBody: string | null = null;
|
|
263
|
+
try {
|
|
264
|
+
disabledBody = (await res.clone().text()).slice(0, 8192);
|
|
265
|
+
} catch {
|
|
266
|
+
disabledBody = null;
|
|
267
|
+
}
|
|
268
|
+
if (isRelayDeploymentDisabled(res, disabledBody)) {
|
|
269
|
+
markRelayFailure(targetUrl, 402, "Deployment disabled (DEPLOYMENT_DISABLED) on relay host");
|
|
270
|
+
lastResponse?.body?.cancel().catch(() => {});
|
|
271
|
+
lastResponse = res;
|
|
272
|
+
lastResponseRelay = targetUrl;
|
|
273
|
+
log(
|
|
274
|
+
"warn",
|
|
275
|
+
`relay ${targetUrl} returned 402 DEPLOYMENT_DISABLED in ${elapsed}s — rolling to next relay`,
|
|
276
|
+
{ upstream: url },
|
|
277
|
+
rid,
|
|
278
|
+
);
|
|
279
|
+
const now = Date.now();
|
|
280
|
+
if (now - lastRollNotify > ROLL_NOTIFY_MS) {
|
|
281
|
+
lastRollNotify = now;
|
|
282
|
+
const ui = getStatusUi();
|
|
283
|
+
if (ui?.notify) {
|
|
284
|
+
ui.notify(`relay ${shortRelayLabel(targetUrl)} failed (HTTP 402) — rolled to next relay`, "warning");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
233
290
|
if (isRetriableStatus(res.status)) {
|
|
234
291
|
markRelayFailure(targetUrl, res.status);
|
|
235
292
|
lastResponse?.body?.cancel().catch(() => {});
|