pi-free 2.2.7 → 2.2.8
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/config.ts +815 -821
- package/constants.ts +113 -124
- package/lib/built-in-toggle.ts +426 -426
- package/lib/model-detection.ts +1 -1
- package/package.json +5 -5
- package/provider-helper.ts +29 -7
- package/providers/anyapi/anyapi.ts +270 -270
- package/providers/cline/cline-auth.ts +473 -473
- package/providers/cline/cline-xml-bridge.ts +1506 -1506
- package/providers/cline/cline.ts +205 -205
- package/providers/dynamic-built-in/index.ts +718 -718
- package/providers/kilo/kilo-auth.ts +152 -155
- package/providers/kilo/kilo.ts +13 -7
- package/providers/opencode-session.ts +514 -514
- package/providers/qoder/auth.ts +548 -548
- package/providers/qoder/qoder.ts +119 -119
- package/providers/qoder/stream.ts +585 -585
- package/providers/qoder/thinking-parser.ts +251 -251
- package/providers/qoder/transform.ts +192 -192
- package/providers/tokenrouter/tokenrouter.ts +636 -636
- package/providers/qwen/qwen-auth.ts +0 -416
- package/providers/qwen/qwen-models.ts +0 -101
- package/providers/qwen/qwen.ts +0 -200
|
@@ -1,155 +1,152 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Kilo device authorization flow and token management.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
OAuthCredentials,
|
|
7
|
-
OAuthLoginCallbacks,
|
|
8
|
-
} from "@earendil-works/pi-ai";
|
|
9
|
-
import {
|
|
10
|
-
KILO_POLL_INTERVAL_MS,
|
|
11
|
-
KILO_TOKEN_EXPIRATION_MS,
|
|
12
|
-
} from "../../constants.ts";
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (response.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
callbacks.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
"Kilo token expired. Please run /login kilo to re-authenticate.",
|
|
154
|
-
);
|
|
155
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Kilo device authorization flow and token management.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
OAuthCredentials,
|
|
7
|
+
OAuthLoginCallbacks,
|
|
8
|
+
} from "@earendil-works/pi-ai/compat";
|
|
9
|
+
import {
|
|
10
|
+
KILO_POLL_INTERVAL_MS,
|
|
11
|
+
KILO_TOKEN_EXPIRATION_MS,
|
|
12
|
+
} from "../../constants.ts";
|
|
13
|
+
import { openBrowser } from "../../lib/open-browser.ts";
|
|
14
|
+
|
|
15
|
+
const KILO_API_BASE = process.env.KILO_API_URL || "https://api.kilo.ai";
|
|
16
|
+
const DEVICE_AUTH_ENDPOINT = `${KILO_API_BASE}/api/device-auth/codes`;
|
|
17
|
+
const PROFILE_ENDPOINT = `${KILO_API_BASE}/api/profile`;
|
|
18
|
+
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Balance & Rate Limit
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
export async function fetchKiloBalance(token: string): Promise<number | null> {
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(`${PROFILE_ENDPOINT}/balance`, {
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${token}`,
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
if (!response.ok) return null;
|
|
32
|
+
const data = (await response.json()) as { balance?: number };
|
|
33
|
+
return data.balance ?? null;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function formatCredits(balance: number): string {
|
|
40
|
+
return balance >= 1000
|
|
41
|
+
? `$${(balance / 1000).toFixed(1)}k`
|
|
42
|
+
: `$${balance.toFixed(2)}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// Device auth
|
|
47
|
+
// =============================================================================
|
|
48
|
+
|
|
49
|
+
interface DeviceAuthResponse {
|
|
50
|
+
code: string;
|
|
51
|
+
verificationUrl: string;
|
|
52
|
+
expiresIn: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface DeviceAuthPollResponse {
|
|
56
|
+
status: "pending" | "approved" | "denied" | "expired";
|
|
57
|
+
token?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
if (signal?.aborted) {
|
|
63
|
+
reject(new Error("Login cancelled"));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const timeout = setTimeout(resolve, ms);
|
|
67
|
+
signal?.addEventListener(
|
|
68
|
+
"abort",
|
|
69
|
+
() => {
|
|
70
|
+
clearTimeout(timeout);
|
|
71
|
+
reject(new Error("Login cancelled"));
|
|
72
|
+
},
|
|
73
|
+
{ once: true },
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function initiateDeviceAuth(): Promise<DeviceAuthResponse> {
|
|
79
|
+
const response = await fetch(DEVICE_AUTH_ENDPOINT, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: { "Content-Type": "application/json" },
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
response.status === 429
|
|
86
|
+
? "Too many pending authorization requests. Please try again later."
|
|
87
|
+
: `Failed to initiate device authorization: ${response.status}`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return (await response.json()) as DeviceAuthResponse;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function pollDeviceAuth(code: string): Promise<DeviceAuthPollResponse> {
|
|
94
|
+
const response = await fetch(`${DEVICE_AUTH_ENDPOINT}/${code}`);
|
|
95
|
+
if (response.status === 202) return { status: "pending" };
|
|
96
|
+
if (response.status === 403) return { status: "denied" };
|
|
97
|
+
if (response.status === 410) return { status: "expired" };
|
|
98
|
+
if (!response.ok)
|
|
99
|
+
throw new Error(`Failed to poll device authorization: ${response.status}`);
|
|
100
|
+
return (await response.json()) as DeviceAuthPollResponse;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function loginKilo(
|
|
104
|
+
callbacks: OAuthLoginCallbacks,
|
|
105
|
+
): Promise<OAuthCredentials> {
|
|
106
|
+
callbacks.onProgress?.("Initiating device authorization...");
|
|
107
|
+
const { code, verificationUrl, expiresIn } = await initiateDeviceAuth();
|
|
108
|
+
|
|
109
|
+
callbacks.onAuth({
|
|
110
|
+
url: verificationUrl,
|
|
111
|
+
instructions: `Enter code: ${code}`,
|
|
112
|
+
});
|
|
113
|
+
openBrowser(verificationUrl);
|
|
114
|
+
callbacks.onProgress?.("Waiting for browser authorization...");
|
|
115
|
+
|
|
116
|
+
const deadline = Date.now() + expiresIn * 1000;
|
|
117
|
+
while (Date.now() < deadline) {
|
|
118
|
+
if (callbacks.signal?.aborted) throw new Error("Login cancelled");
|
|
119
|
+
await abortableSleep(KILO_POLL_INTERVAL_MS, callbacks.signal);
|
|
120
|
+
|
|
121
|
+
const result = await pollDeviceAuth(code);
|
|
122
|
+
if (result.status === "approved") {
|
|
123
|
+
if (!result.token)
|
|
124
|
+
throw new Error("Authorization approved but no token received");
|
|
125
|
+
callbacks.onProgress?.("Login successful!");
|
|
126
|
+
return {
|
|
127
|
+
refresh: result.token,
|
|
128
|
+
access: result.token,
|
|
129
|
+
expires: Date.now() + KILO_TOKEN_EXPIRATION_MS,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (result.status === "denied")
|
|
133
|
+
throw new Error("Authorization denied by user.");
|
|
134
|
+
if (result.status === "expired")
|
|
135
|
+
throw new Error("Authorization code expired. Please try again.");
|
|
136
|
+
|
|
137
|
+
const remaining = Math.ceil((deadline - Date.now()) / 1000);
|
|
138
|
+
callbacks.onProgress?.(
|
|
139
|
+
`Waiting for browser authorization... (${remaining}s remaining)`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
throw new Error("Authentication timed out. Please try again.");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function refreshKiloToken(
|
|
146
|
+
credentials: OAuthCredentials,
|
|
147
|
+
): Promise<OAuthCredentials> {
|
|
148
|
+
if (credentials.expires > Date.now()) return credentials;
|
|
149
|
+
throw new Error(
|
|
150
|
+
"Kilo token expired. Please run /login kilo to re-authenticate.",
|
|
151
|
+
);
|
|
152
|
+
}
|
package/providers/kilo/kilo.ts
CHANGED
|
@@ -12,10 +12,15 @@
|
|
|
12
12
|
* # Free models visible immediately; /login kilo for paid access
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import type { Api, Model, OAuthCredentials } from "@earendil-works/pi-ai";
|
|
16
15
|
import type {
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
Api,
|
|
17
|
+
Model,
|
|
18
|
+
OAuthCredentials,
|
|
19
|
+
} from "@earendil-works/pi-ai/compat";
|
|
20
|
+
import {
|
|
21
|
+
readStoredCredential,
|
|
22
|
+
type ExtensionAPI,
|
|
23
|
+
type ProviderModelConfig,
|
|
19
24
|
} from "@earendil-works/pi-coding-agent";
|
|
20
25
|
import {
|
|
21
26
|
getKiloApiKey,
|
|
@@ -33,6 +38,7 @@ import {
|
|
|
33
38
|
createCtxReRegister,
|
|
34
39
|
createReRegister,
|
|
35
40
|
enhanceWithCI,
|
|
41
|
+
isOAuthCredential,
|
|
36
42
|
loadCachedOrFetchModels,
|
|
37
43
|
type StoredModels,
|
|
38
44
|
} from "../../provider-helper.ts";
|
|
@@ -344,8 +350,8 @@ export default async function kiloProvider(pi: ExtensionAPI) {
|
|
|
344
350
|
// ToS notice (once)
|
|
345
351
|
if (tosShown) return;
|
|
346
352
|
tosShown = true;
|
|
347
|
-
const cred =
|
|
348
|
-
if (cred
|
|
353
|
+
const cred = readStoredCredential(PROVIDER_KILO);
|
|
354
|
+
if (isOAuthCredential(cred)) return;
|
|
349
355
|
const paidCount = allModels.length - freeModels.length;
|
|
350
356
|
if (paidCount > 0) {
|
|
351
357
|
ctx.ui.notify(
|
|
@@ -442,8 +448,8 @@ export default async function kiloProvider(pi: ExtensionAPI) {
|
|
|
442
448
|
pi.on(
|
|
443
449
|
"session_start",
|
|
444
450
|
wrapSessionStartHandler("kilo", (_event, ctx) => {
|
|
445
|
-
const cred =
|
|
446
|
-
if (cred
|
|
451
|
+
const cred = readStoredCredential(PROVIDER_KILO);
|
|
452
|
+
if (!isOAuthCredential(cred) || refreshInFlight) return Promise.resolve();
|
|
447
453
|
|
|
448
454
|
refreshInFlight = fetchKiloModels({ token: cred.access, freeOnly: false })
|
|
449
455
|
.then((newModels) => {
|