pi-cliproxyapi-native 0.2.1 → 0.3.0
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/README.md +19 -6
- package/extensions/index.ts +2 -0
- package/package.json +4 -4
- package/src/compaction.ts +1 -1
- package/src/retry.ts +99 -0
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ Chat uses Pi's native API adapters and model metadata, with availability from yo
|
|
|
17
17
|
- [Chat models](#chat-models)
|
|
18
18
|
- [Refresh](#refresh)
|
|
19
19
|
- [Wait for quota reset](#wait-for-quota-reset)
|
|
20
|
+
- [Retry failed turns](#retry-failed-turns)
|
|
20
21
|
- [Compaction](#compaction)
|
|
21
22
|
- [Aliases and model limits](#aliases-and-model-limits)
|
|
22
23
|
- [Backend routes](#backend-routes)
|
|
@@ -27,15 +28,15 @@ Chat uses Pi's native API adapters and model metadata, with availability from yo
|
|
|
27
28
|
|
|
28
29
|
## Install
|
|
29
30
|
|
|
30
|
-
The extension targets Pi 1.
|
|
31
|
+
The extension targets Pi 1.4.0 and CLIProxyAPI 8.0.38, with Node.js 22.19 or newer. Run CLIProxyAPI with your upstream accounts configured before connecting Pi. The extension uses Pi's host-provided packages, adds no runtime dependencies, and needs no compilation.
|
|
31
32
|
|
|
32
33
|
If you installed `pi-cliproxy-native`, remove it before installing the renamed package. Both register the same `cliproxyapi` provider.
|
|
33
34
|
|
|
34
35
|
```sh
|
|
35
|
-
pi install npm:pi-cliproxyapi-native@0.
|
|
36
|
+
pi install npm:pi-cliproxyapi-native@0.3.0
|
|
36
37
|
```
|
|
37
38
|
|
|
38
|
-
For Git installation, use `pi install git:github.com/hawkff/pi-cliproxyapi-native@v0.
|
|
39
|
+
For Git installation, use `pi install git:github.com/hawkff/pi-cliproxyapi-native@v0.3.0`. For a local checkout, use `pi install /path/to/pi-cliproxyapi-native`.
|
|
39
40
|
|
|
40
41
|
The default proxy address is `http://localhost:8317`. For another address, set the [connection](#connection) before logging in. Restart Pi, then run:
|
|
41
42
|
|
|
@@ -49,7 +50,7 @@ Enter your CLIProxyAPI client key at login. Pi validates it against the proxy be
|
|
|
49
50
|
|
|
50
51
|
**Use Pi's built-in `/model` picker for chat.** Choose a `cliproxyapi/` entry there. This extension's `/cli:model` picker is only for CLIProxyAPI image and video models. Selecting a media model does not change chat or generate anything.
|
|
51
52
|
|
|
52
|
-
Enable only one extension that registers the `cliproxyapi` provider ID.
|
|
53
|
+
Enable only one extension that registers the `cliproxyapi` provider ID. Model discovery leaves other providers' model lists unchanged. [Delayed retries](#retry-failed-turns) apply to all providers.
|
|
53
54
|
|
|
54
55
|
## Connection
|
|
55
56
|
|
|
@@ -224,11 +225,23 @@ Explicit `api/` routes skip live Claude subscription lookup, even with a managem
|
|
|
224
225
|
|
|
225
226
|
Other chat routes retain the response-based fallback: `Retry-After` on HTTP 429 or a recognized quota HTTP 403, or `reset_seconds` in a `model_cooldown` error. Google's native adapter exposes only the structured error body. Those waits share the same seven-day deadline but cannot inspect live weekly usage. Image and video generation still do not retry.
|
|
226
227
|
|
|
228
|
+
### Retry failed turns
|
|
229
|
+
|
|
230
|
+
Keep Pi open in terminal or RPC mode to retry failed model turns across all providers, including those outside CLIProxyAPI. After Pi finishes its built-in retries and recovery, the extension waits ten minutes and adds a visible continuation message to resume the unfinished task. Another failed turn starts another ten-minute wait. Successful turns, tool errors, and deliberate cancellations do not start a wait.
|
|
231
|
+
|
|
232
|
+
Delayed retries are on by default. Use `/auto-retry` to see the status, `/auto-retry off` to cancel the pending retry and disable future ones, or `/auto-retry on` to enable retries for future failures. Reloading the extension restores the default.
|
|
233
|
+
|
|
234
|
+
Escape or Ctrl+C cancels a pending retry in the terminal. New input, a model change, manual compaction, or session navigation also cancels it. Reloading or closing Pi discards pending retries. One-shot print and JSON runs do not schedule them.
|
|
235
|
+
|
|
236
|
+
Retries preserve the conversation rather than replaying the original prompt. They do not fix credentials or increase quotas, and resumed work can incur provider charges. Use `/auto-retry off` if a persistent error needs manual correction.
|
|
237
|
+
|
|
227
238
|
### Compaction
|
|
228
239
|
|
|
229
|
-
For automatic compaction and `/compact`, the extension limits medium or higher thinking to low without changing your chat thinking level. It uses Pi's default thinking budgets for these requests.
|
|
240
|
+
For automatic compaction and `/compact`, the extension limits medium or higher thinking to low without changing your chat thinking level. It uses Pi's default thinking budgets for these requests. Without an explicit output cap, summaries use the model's output limit. Pi's native adapters reduce the output allowance when needed to fit the remaining context space.
|
|
241
|
+
|
|
242
|
+
If a caller supplies a smaller cap and the summary hits it, the extension retries once with more output room, up to the model's output limit. An empty or thinking-only response gets one retry, including at the model's output limit.
|
|
230
243
|
|
|
231
|
-
Pi still selects which messages to summarize and retains recent history, previous summaries, custom instructions, and file tracking. Only the final response reaches the compactor, with usage from
|
|
244
|
+
Pi still selects which messages to summarize and retains recent history, previous summaries, custom instructions, and file tracking. Only the final response reaches the compactor, with usage from all attempts. Compaction fails rather than saving an incomplete summary. Cancellation, tool calls, and provider failures do not trigger these output retries; Pi keeps its configured transient-error retries and the extension keeps its quota handling.
|
|
232
245
|
|
|
233
246
|
Authentication failures, exhausted quotas, and inputs that exceed the model's context window still need correction. Run `/compact` after resolving the cause. The extension does not truncate history to force a summary or switch models. CLIProxyAPI's Responses compaction endpoint is separate from Pi's text-summary workflow.
|
|
234
247
|
|
package/extensions/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { PROVIDER_ID, parseConfig } from "../src/config.ts";
|
|
|
5
5
|
import { registerMediaTools } from "../src/media.ts";
|
|
6
6
|
import { registerModelPicker } from "../src/picker.ts";
|
|
7
7
|
import { createCliproxyProvider } from "../src/provider.ts";
|
|
8
|
+
import { registerAutoRetry } from "../src/retry.ts";
|
|
8
9
|
|
|
9
10
|
export default async function (pi: ExtensionAPI) {
|
|
10
11
|
let contents = "{}";
|
|
@@ -62,6 +63,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
62
63
|
compactionSignals,
|
|
63
64
|
),
|
|
64
65
|
);
|
|
66
|
+
registerAutoRetry(pi);
|
|
65
67
|
registerMediaTools(pi, config);
|
|
66
68
|
registerModelPicker(pi, config);
|
|
67
69
|
pi.registerCommand("cliproxyapi-refresh", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cliproxyapi-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLIProxyAPI chat discovery and image/video generation with Pi's native adapters",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@biomejs/biome": "^2.5.13",
|
|
42
|
-
"@earendil-works/pi-ai": "^1.
|
|
43
|
-
"@earendil-works/pi-coding-agent": "^1.
|
|
44
|
-
"@earendil-works/pi-tui": "^1.
|
|
42
|
+
"@earendil-works/pi-ai": "^1.4.0",
|
|
43
|
+
"@earendil-works/pi-coding-agent": "^1.4.0",
|
|
44
|
+
"@earendil-works/pi-tui": "^1.4.0",
|
|
45
45
|
"@types/node": "^24.13.4",
|
|
46
46
|
"typescript": "5.9.3"
|
|
47
47
|
},
|
package/src/compaction.ts
CHANGED
|
@@ -10,7 +10,7 @@ export function withCompactionRecovery(
|
|
|
10
10
|
return lazyStream(model, async () => ({
|
|
11
11
|
async *[Symbol.asyncIterator]() {
|
|
12
12
|
const limit = model.maxTokens > 0 ? model.maxTokens : 32768;
|
|
13
|
-
let maxTokens = Math.max(1, Math.min(options.maxTokens ??
|
|
13
|
+
let maxTokens = Math.max(1, Math.min(options.maxTokens ?? limit, limit));
|
|
14
14
|
const reasoning =
|
|
15
15
|
options.reasoning && !["minimal", "low"].includes(options.reasoning) ? "low" : options.reasoning;
|
|
16
16
|
const run = () =>
|
package/src/retry.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { matchesKey } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
4
|
+
export function registerAutoRetry(pi: ExtensionAPI) {
|
|
5
|
+
let enabled = true;
|
|
6
|
+
let failed = false;
|
|
7
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
8
|
+
let waitingContext: ExtensionContext | undefined;
|
|
9
|
+
let removeInputListener: (() => void) | undefined;
|
|
10
|
+
|
|
11
|
+
function cancel() {
|
|
12
|
+
clearTimeout(timer);
|
|
13
|
+
timer = undefined;
|
|
14
|
+
failed = false;
|
|
15
|
+
removeInputListener?.();
|
|
16
|
+
removeInputListener = undefined;
|
|
17
|
+
waitingContext?.ui.setStatus("cliproxyapi-auto-retry", undefined);
|
|
18
|
+
waitingContext = undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
pi.on("agent_end", (event) => {
|
|
22
|
+
failed = event.messages.findLast((message) => message.role === "assistant")?.stopReason === "error";
|
|
23
|
+
});
|
|
24
|
+
pi.on("agent_settled", (event, ctx) => {
|
|
25
|
+
const retry = enabled && failed && !event.aborted;
|
|
26
|
+
cancel();
|
|
27
|
+
// One-shot modes exit after settlement.
|
|
28
|
+
if (!retry || ctx.mode === "print" || ctx.mode === "json") return;
|
|
29
|
+
|
|
30
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
31
|
+
const leafId = ctx.sessionManager.getLeafId();
|
|
32
|
+
const retryAt = new Date(Date.now() + 10 * 60 * 1000).toLocaleTimeString();
|
|
33
|
+
waitingContext = ctx;
|
|
34
|
+
ctx.ui.setStatus("cliproxyapi-auto-retry", `Auto-retry at ${retryAt} · /auto-retry off to cancel`);
|
|
35
|
+
const notice = `Model request failed. Retrying in 10 minutes at ${retryAt}. Use /auto-retry off to cancel.`;
|
|
36
|
+
ctx.ui.notify(notice, "warning");
|
|
37
|
+
if (ctx.mode === "tui") {
|
|
38
|
+
removeInputListener = ctx.ui.onTerminalInput((data) => {
|
|
39
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) cancel();
|
|
40
|
+
return undefined;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
timer = setTimeout(
|
|
44
|
+
() => {
|
|
45
|
+
cancel();
|
|
46
|
+
if (
|
|
47
|
+
!ctx.isIdle() ||
|
|
48
|
+
ctx.hasPendingMessages() ||
|
|
49
|
+
ctx.sessionManager.getSessionId() !== sessionId ||
|
|
50
|
+
ctx.sessionManager.getLeafId() !== leafId
|
|
51
|
+
)
|
|
52
|
+
return;
|
|
53
|
+
pi.sendMessage(
|
|
54
|
+
{
|
|
55
|
+
customType: "auto-retry",
|
|
56
|
+
content: "Retry the failed request and continue the unfinished task from where it stopped.",
|
|
57
|
+
display: true,
|
|
58
|
+
},
|
|
59
|
+
{ triggerTurn: true },
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
10 * 60 * 1000,
|
|
63
|
+
);
|
|
64
|
+
timer.unref();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
pi.on("input", cancel);
|
|
68
|
+
pi.on("before_agent_start", cancel);
|
|
69
|
+
pi.on("agent_start", cancel);
|
|
70
|
+
pi.on("model_select", cancel);
|
|
71
|
+
pi.on("user_bash", cancel);
|
|
72
|
+
pi.on("session_before_compact", (event) => {
|
|
73
|
+
if (event.reason === "manual") cancel();
|
|
74
|
+
});
|
|
75
|
+
pi.on("session_before_switch", cancel);
|
|
76
|
+
pi.on("session_before_fork", cancel);
|
|
77
|
+
pi.on("session_before_tree", cancel);
|
|
78
|
+
pi.on("session_start", cancel);
|
|
79
|
+
pi.on("session_shutdown", cancel);
|
|
80
|
+
|
|
81
|
+
pi.registerCommand("auto-retry", {
|
|
82
|
+
description: "Control 10-minute retries for failed model requests across all providers",
|
|
83
|
+
async handler(args, ctx) {
|
|
84
|
+
const action = args.trim();
|
|
85
|
+
if (action !== "" && action !== "on" && action !== "off") {
|
|
86
|
+
ctx.ui.notify("Usage: /auto-retry [on|off]", "error");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (action === "off") {
|
|
90
|
+
enabled = false;
|
|
91
|
+
cancel();
|
|
92
|
+
} else if (action === "on") enabled = true;
|
|
93
|
+
ctx.ui.notify(
|
|
94
|
+
`Auto-retry is ${enabled ? "on" : "off"}.${timer ? " A retry is pending." : " No retry is pending."}`,
|
|
95
|
+
"info",
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|