@aliou/pi-synthetic 0.10.0 → 0.10.2
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/extensions/command-quotas/command.ts +30 -24
- package/src/extensions/command-quotas/components/quotas-display.ts +30 -3
- package/src/extensions/command-quotas/sub-integration.ts +6 -3
- package/src/extensions/provider/models.ts +24 -4
- package/src/types/quotas.ts +11 -0
- package/src/utils/quotas.ts +60 -7
package/package.json
CHANGED
|
@@ -15,50 +15,56 @@ export function registerQuotasCommand(pi: ExtensionAPI): void {
|
|
|
15
15
|
ctx.ui.notify(MISSING_AUTH_MESSAGE, "warning");
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
|
+
const key: string = apiKey;
|
|
18
19
|
|
|
19
20
|
const result = await ctx.ui.custom<null>((tui, theme, _kb, done) => {
|
|
20
|
-
const
|
|
21
|
+
const controller = new AbortController();
|
|
22
|
+
const component = new QuotasComponent(theme, tui, () => {
|
|
23
|
+
controller.abort();
|
|
24
|
+
done(null);
|
|
25
|
+
});
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
component.setState({ type: "loaded", quotas });
|
|
32
|
-
}
|
|
33
|
-
tui.requestRender();
|
|
34
|
-
})
|
|
35
|
-
.catch(() => {
|
|
27
|
+
async function loadQuotas(): Promise<void> {
|
|
28
|
+
const fetchResult = await fetchQuotas(key, controller.signal);
|
|
29
|
+
if (controller.signal.aborted) return;
|
|
30
|
+
if (fetchResult.success) {
|
|
31
|
+
component.setState({
|
|
32
|
+
type: "loaded",
|
|
33
|
+
quotas: fetchResult.data.quotas,
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
36
|
component.setState({
|
|
37
37
|
type: "error",
|
|
38
|
-
message:
|
|
39
|
-
"Failed to fetch quotas. Check your Synthetic subscription status.",
|
|
38
|
+
message: fetchResult.error.message,
|
|
40
39
|
});
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
}
|
|
41
|
+
tui.requestRender();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
void loadQuotas();
|
|
43
45
|
|
|
44
46
|
return {
|
|
45
47
|
render: (width: number) => component.render(width),
|
|
46
48
|
invalidate: () => component.invalidate(),
|
|
47
49
|
handleInput: (data: string) => component.handleInput(data),
|
|
50
|
+
dispose: () => {
|
|
51
|
+
controller.abort();
|
|
52
|
+
component.destroy();
|
|
53
|
+
},
|
|
48
54
|
};
|
|
49
55
|
});
|
|
50
56
|
|
|
51
|
-
//
|
|
57
|
+
// Non-interactive fallback (RPC, print, JSON modes)
|
|
52
58
|
if (result === undefined) {
|
|
53
|
-
const
|
|
54
|
-
if (!
|
|
59
|
+
const fetchResult = await fetchQuotas(key);
|
|
60
|
+
if (!fetchResult.success) {
|
|
55
61
|
ctx.ui.notify(
|
|
56
|
-
JSON.stringify({ error:
|
|
62
|
+
JSON.stringify({ error: fetchResult.error.message }),
|
|
57
63
|
"error",
|
|
58
64
|
);
|
|
59
65
|
return;
|
|
60
66
|
}
|
|
61
|
-
ctx.ui.notify(JSON.stringify(quotas
|
|
67
|
+
ctx.ui.notify(JSON.stringify(fetchResult.data.quotas), "info");
|
|
62
68
|
}
|
|
63
69
|
},
|
|
64
70
|
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
|
|
3
|
-
import type { Component } from "@mariozechner/pi-tui";
|
|
3
|
+
import type { Component, TUI } from "@mariozechner/pi-tui";
|
|
4
4
|
import {
|
|
5
|
+
Loader,
|
|
5
6
|
matchesKey,
|
|
6
7
|
truncateToWidth,
|
|
7
8
|
visibleWidth,
|
|
@@ -251,14 +252,36 @@ function renderSimpleIndicatorBar(
|
|
|
251
252
|
export class QuotasComponent implements Component {
|
|
252
253
|
private state: QuotasState = { type: "loading" };
|
|
253
254
|
private theme: Theme;
|
|
255
|
+
private tui: TUI;
|
|
254
256
|
private onClose: () => void;
|
|
257
|
+
private loader: Loader | null = null;
|
|
255
258
|
|
|
256
|
-
constructor(theme: Theme, onClose: () => void) {
|
|
259
|
+
constructor(theme: Theme, tui: TUI, onClose: () => void) {
|
|
257
260
|
this.theme = theme;
|
|
261
|
+
this.tui = tui;
|
|
258
262
|
this.onClose = onClose;
|
|
263
|
+
this.startLoader();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private startLoader(): void {
|
|
267
|
+
this.loader = new Loader(
|
|
268
|
+
this.tui,
|
|
269
|
+
(s: string) => this.theme.fg("accent", s),
|
|
270
|
+
(s: string) => this.theme.fg("muted", s),
|
|
271
|
+
"Fetching quotas...",
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
destroy(): void {
|
|
276
|
+
this.loader?.stop();
|
|
277
|
+
this.loader = null;
|
|
259
278
|
}
|
|
260
279
|
|
|
261
280
|
setState(state: QuotasState): void {
|
|
281
|
+
if (this.state.type === "loading" && state.type !== "loading") {
|
|
282
|
+
this.loader?.stop();
|
|
283
|
+
this.loader = null;
|
|
284
|
+
}
|
|
262
285
|
this.state = state;
|
|
263
286
|
}
|
|
264
287
|
|
|
@@ -286,7 +309,11 @@ export class QuotasComponent implements Component {
|
|
|
286
309
|
|
|
287
310
|
switch (this.state.type) {
|
|
288
311
|
case "loading":
|
|
289
|
-
|
|
312
|
+
if (this.loader) {
|
|
313
|
+
lines.push(...this.loader.render(width));
|
|
314
|
+
} else {
|
|
315
|
+
lines.push(this.theme.fg("muted", " Fetching quotas..."));
|
|
316
|
+
}
|
|
290
317
|
break;
|
|
291
318
|
case "error":
|
|
292
319
|
lines.push(this.theme.fg("error", ` ${this.state.message}`));
|
|
@@ -107,10 +107,13 @@ async function emitCurrentUsage(
|
|
|
107
107
|
): Promise<void> {
|
|
108
108
|
const apiKey = await getSyntheticApiKey(authStorage);
|
|
109
109
|
if (!apiKey) return;
|
|
110
|
-
const
|
|
111
|
-
if (!
|
|
110
|
+
const result = await fetchQuotas(apiKey);
|
|
111
|
+
if (!result.success) return;
|
|
112
112
|
pi.events.emit("sub-core:update-current", {
|
|
113
|
-
state: {
|
|
113
|
+
state: {
|
|
114
|
+
provider: "synthetic",
|
|
115
|
+
usage: toUsageSnapshot(result.data.quotas),
|
|
116
|
+
},
|
|
114
117
|
});
|
|
115
118
|
}
|
|
116
119
|
|
|
@@ -36,7 +36,7 @@ const SYNTHETIC_REASONING_EFFORT_MAP = {
|
|
|
36
36
|
} as const;
|
|
37
37
|
|
|
38
38
|
export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
39
|
-
// API: hf:zai-org/GLM-4.7 → ctx=202752,
|
|
39
|
+
// API: hf:zai-org/GLM-4.7 → ctx=202752, multimodal
|
|
40
40
|
{
|
|
41
41
|
id: "hf:zai-org/GLM-4.7",
|
|
42
42
|
name: "zai-org/GLM-4.7",
|
|
@@ -45,11 +45,11 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
45
45
|
supportsReasoningEffort: true,
|
|
46
46
|
reasoningEffortMap: SYNTHETIC_REASONING_EFFORT_MAP,
|
|
47
47
|
},
|
|
48
|
-
input: ["text"],
|
|
48
|
+
input: ["text", "image"],
|
|
49
49
|
cost: {
|
|
50
|
-
input:
|
|
50
|
+
input: 2.19,
|
|
51
51
|
output: 2.19,
|
|
52
|
-
cacheRead:
|
|
52
|
+
cacheRead: 2.19,
|
|
53
53
|
cacheWrite: 0,
|
|
54
54
|
},
|
|
55
55
|
contextWindow: 202752,
|
|
@@ -74,6 +74,26 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
74
74
|
contextWindow: 196608,
|
|
75
75
|
maxTokens: 65536,
|
|
76
76
|
},
|
|
77
|
+
// API: hf:zai-org/GLM-5.1 → ctx=196608, out=65536
|
|
78
|
+
{
|
|
79
|
+
id: "hf:zai-org/GLM-5.1",
|
|
80
|
+
name: "zai-org/GLM-5.1",
|
|
81
|
+
reasoning: true,
|
|
82
|
+
compat: {
|
|
83
|
+
supportsReasoningEffort: true,
|
|
84
|
+
reasoningEffortMap: SYNTHETIC_REASONING_EFFORT_MAP,
|
|
85
|
+
supportsDeveloperRole: false,
|
|
86
|
+
},
|
|
87
|
+
input: ["text"],
|
|
88
|
+
cost: {
|
|
89
|
+
input: 1,
|
|
90
|
+
output: 3,
|
|
91
|
+
cacheRead: 1,
|
|
92
|
+
cacheWrite: 0,
|
|
93
|
+
},
|
|
94
|
+
contextWindow: 196608,
|
|
95
|
+
maxTokens: 65536,
|
|
96
|
+
},
|
|
77
97
|
// API: hf:zai-org/GLM-4.7-Flash → ctx=196608
|
|
78
98
|
{
|
|
79
99
|
id: "hf:zai-org/GLM-4.7-Flash",
|
package/src/types/quotas.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
export type QuotasErrorKind =
|
|
2
|
+
| "cancelled"
|
|
3
|
+
| "timeout"
|
|
4
|
+
| "config"
|
|
5
|
+
| "http"
|
|
6
|
+
| "network";
|
|
7
|
+
|
|
8
|
+
export type QuotasResult =
|
|
9
|
+
| { success: true; data: { quotas: QuotasResponse } }
|
|
10
|
+
| { success: false; error: { message: string; kind: QuotasErrorKind } };
|
|
11
|
+
|
|
1
12
|
export interface QuotasResponse {
|
|
2
13
|
subscription?: {
|
|
3
14
|
limit: number;
|
package/src/utils/quotas.ts
CHANGED
|
@@ -1,20 +1,73 @@
|
|
|
1
|
-
import type { QuotasResponse } from "../types/quotas";
|
|
1
|
+
import type { QuotasResponse, QuotasResult } from "../types/quotas";
|
|
2
|
+
|
|
3
|
+
const FETCH_TIMEOUT_MS = 15_000;
|
|
4
|
+
|
|
5
|
+
function isTimeoutReason(reason: unknown): boolean {
|
|
6
|
+
return (
|
|
7
|
+
(reason instanceof DOMException && reason.name === "TimeoutError") ||
|
|
8
|
+
(reason instanceof Error && reason.name === "TimeoutError")
|
|
9
|
+
);
|
|
10
|
+
}
|
|
2
11
|
|
|
3
12
|
export async function fetchQuotas(
|
|
4
13
|
apiKey: string,
|
|
5
|
-
|
|
6
|
-
|
|
14
|
+
signal?: AbortSignal,
|
|
15
|
+
): Promise<QuotasResult> {
|
|
16
|
+
if (!apiKey) {
|
|
17
|
+
return {
|
|
18
|
+
success: false,
|
|
19
|
+
error: { message: "No API key provided", kind: "config" },
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const signals: AbortSignal[] = [AbortSignal.timeout(FETCH_TIMEOUT_MS)];
|
|
24
|
+
if (signal) signals.push(signal);
|
|
25
|
+
const combined = AbortSignal.any(signals);
|
|
7
26
|
|
|
8
27
|
try {
|
|
9
28
|
const response = await fetch("https://api.synthetic.new/v2/quotas", {
|
|
10
29
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
30
|
+
signal: combined,
|
|
11
31
|
});
|
|
12
32
|
|
|
13
|
-
if (!response.ok)
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
let message = response.statusText;
|
|
35
|
+
try {
|
|
36
|
+
const body = await response.text();
|
|
37
|
+
if (body) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(body) as { error?: string };
|
|
40
|
+
if (parsed.error) message = parsed.error;
|
|
41
|
+
} catch {
|
|
42
|
+
message = body;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
return { success: false, error: { message, kind: "http" } };
|
|
47
|
+
}
|
|
48
|
+
return { success: false, error: { message, kind: "http" } };
|
|
49
|
+
}
|
|
50
|
+
|
|
14
51
|
const data: QuotasResponse = await response.json();
|
|
15
|
-
return data;
|
|
16
|
-
} catch {
|
|
17
|
-
|
|
52
|
+
return { success: true, data: { quotas: data } };
|
|
53
|
+
} catch (err: unknown) {
|
|
54
|
+
const isAbort =
|
|
55
|
+
combined.aborted ||
|
|
56
|
+
(err instanceof DOMException && err.name === "AbortError");
|
|
57
|
+
if (isAbort) {
|
|
58
|
+
if (isTimeoutReason(combined.reason)) {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
error: { message: "Request timed out", kind: "timeout" },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
error: { message: "Request cancelled", kind: "cancelled" },
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
70
|
+
return { success: false, error: { message, kind: "network" } };
|
|
18
71
|
}
|
|
19
72
|
}
|
|
20
73
|
|