pi-free 2.0.7 → 2.0.9
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 +96 -10
- package/README.md +572 -495
- package/config.ts +58 -11
- package/constants.ts +12 -0
- package/index.ts +67 -3
- package/lib/built-in-toggle.ts +2 -2
- package/lib/model-detection.ts +2 -1
- package/lib/model-enhancer.ts +1 -1
- package/lib/open-browser.ts +1 -1
- package/lib/provider-compat.ts +1 -1
- package/lib/quota-monitor.ts +123 -0
- package/lib/registry.ts +1 -1
- package/lib/types.ts +101 -101
- package/lib/util.ts +460 -351
- package/package.json +4 -4
- package/provider-failover/benchmark-lookup.ts +743 -702
- package/provider-failover/benchmarks-chunk-0.ts +48 -48
- package/provider-failover/benchmarks-chunk-1.ts +44 -44
- package/provider-failover/benchmarks-chunk-2.ts +39 -39
- package/provider-failover/benchmarks-chunk-3.ts +41 -41
- package/provider-failover/benchmarks-chunk-4.ts +33 -33
- package/provider-helper.ts +1 -1
- package/providers/cline/cline-auth.ts +473 -473
- package/providers/cline/cline-models.ts +2 -2
- package/providers/cline/cline.ts +3 -3
- package/providers/codestral/codestral.ts +139 -0
- package/providers/crofai/crofai.ts +14 -85
- package/providers/deepinfra/deepinfra.ts +109 -0
- package/providers/dynamic-built-in/index.ts +1 -1
- package/providers/kilo/kilo-auth.ts +155 -155
- package/providers/kilo/kilo.ts +3 -3
- package/providers/llm7/llm7.ts +156 -0
- package/providers/model-fetcher.ts +2 -2
- package/providers/nvidia/nvidia.ts +5 -5
- package/providers/ollama/ollama.ts +2 -2
- package/providers/opencode-session.ts +1 -1
- package/providers/qwen/qwen-auth.ts +1 -1
- package/providers/qwen/qwen-models.ts +1 -1
- package/providers/qwen/qwen.ts +3 -3
- package/providers/sambanova/sambanova.ts +109 -0
- package/providers/zenmux/zenmux.ts +6 -3
- package/scripts/check-extensions.mjs +6 -4
|
@@ -1,155 +1,155 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Kilo device authorization flow and token management.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
OAuthCredentials,
|
|
7
|
-
OAuthLoginCallbacks,
|
|
8
|
-
} from "@
|
|
9
|
-
import {
|
|
10
|
-
KILO_POLL_INTERVAL_MS,
|
|
11
|
-
KILO_TOKEN_EXPIRATION_MS,
|
|
12
|
-
} from "../../constants.ts";
|
|
13
|
-
import { createLogger } from "../../lib/logger.ts";
|
|
14
|
-
import { openBrowser } from "../../lib/open-browser.ts";
|
|
15
|
-
|
|
16
|
-
const _logger = createLogger("kilo-auth");
|
|
17
|
-
|
|
18
|
-
const KILO_API_BASE = process.env.KILO_API_URL || "https://api.kilo.ai";
|
|
19
|
-
const DEVICE_AUTH_ENDPOINT = `${KILO_API_BASE}/api/device-auth/codes`;
|
|
20
|
-
const PROFILE_ENDPOINT = `${KILO_API_BASE}/api/profile`;
|
|
21
|
-
|
|
22
|
-
// =============================================================================
|
|
23
|
-
// Balance & Rate Limit
|
|
24
|
-
// =============================================================================
|
|
25
|
-
|
|
26
|
-
export async function fetchKiloBalance(token: string): Promise<number | null> {
|
|
27
|
-
try {
|
|
28
|
-
const response = await fetch(`${PROFILE_ENDPOINT}/balance`, {
|
|
29
|
-
headers: {
|
|
30
|
-
Authorization: `Bearer ${token}`,
|
|
31
|
-
"Content-Type": "application/json",
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
if (!response.ok) return null;
|
|
35
|
-
const data = (await response.json()) as { balance?: number };
|
|
36
|
-
return data.balance ?? null;
|
|
37
|
-
} catch {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function formatCredits(balance: number): string {
|
|
43
|
-
return balance >= 1000
|
|
44
|
-
? `$${(balance / 1000).toFixed(1)}k`
|
|
45
|
-
: `$${balance.toFixed(2)}`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// =============================================================================
|
|
49
|
-
// Device auth
|
|
50
|
-
// =============================================================================
|
|
51
|
-
|
|
52
|
-
interface DeviceAuthResponse {
|
|
53
|
-
code: string;
|
|
54
|
-
verificationUrl: string;
|
|
55
|
-
expiresIn: number;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interface DeviceAuthPollResponse {
|
|
59
|
-
status: "pending" | "approved" | "denied" | "expired";
|
|
60
|
-
token?: string;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
64
|
-
return new Promise((resolve, reject) => {
|
|
65
|
-
if (signal?.aborted) {
|
|
66
|
-
reject(new Error("Login cancelled"));
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
const timeout = setTimeout(resolve, ms);
|
|
70
|
-
signal?.addEventListener(
|
|
71
|
-
"abort",
|
|
72
|
-
() => {
|
|
73
|
-
clearTimeout(timeout);
|
|
74
|
-
reject(new Error("Login cancelled"));
|
|
75
|
-
},
|
|
76
|
-
{ once: true },
|
|
77
|
-
);
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function initiateDeviceAuth(): Promise<DeviceAuthResponse> {
|
|
82
|
-
const response = await fetch(DEVICE_AUTH_ENDPOINT, {
|
|
83
|
-
method: "POST",
|
|
84
|
-
headers: { "Content-Type": "application/json" },
|
|
85
|
-
});
|
|
86
|
-
if (!response.ok) {
|
|
87
|
-
throw new Error(
|
|
88
|
-
response.status === 429
|
|
89
|
-
? "Too many pending authorization requests. Please try again later."
|
|
90
|
-
: `Failed to initiate device authorization: ${response.status}`,
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
return (await response.json()) as DeviceAuthResponse;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async function pollDeviceAuth(code: string): Promise<DeviceAuthPollResponse> {
|
|
97
|
-
const response = await fetch(`${DEVICE_AUTH_ENDPOINT}/${code}`);
|
|
98
|
-
if (response.status === 202) return { status: "pending" };
|
|
99
|
-
if (response.status === 403) return { status: "denied" };
|
|
100
|
-
if (response.status === 410) return { status: "expired" };
|
|
101
|
-
if (!response.ok)
|
|
102
|
-
throw new Error(`Failed to poll device authorization: ${response.status}`);
|
|
103
|
-
return (await response.json()) as DeviceAuthPollResponse;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export async function loginKilo(
|
|
107
|
-
callbacks: OAuthLoginCallbacks,
|
|
108
|
-
): Promise<OAuthCredentials> {
|
|
109
|
-
callbacks.onProgress?.("Initiating device authorization...");
|
|
110
|
-
const { code, verificationUrl, expiresIn } = await initiateDeviceAuth();
|
|
111
|
-
|
|
112
|
-
callbacks.onAuth({
|
|
113
|
-
url: verificationUrl,
|
|
114
|
-
instructions: `Enter code: ${code}`,
|
|
115
|
-
});
|
|
116
|
-
openBrowser(verificationUrl);
|
|
117
|
-
callbacks.onProgress?.("Waiting for browser authorization...");
|
|
118
|
-
|
|
119
|
-
const deadline = Date.now() + expiresIn * 1000;
|
|
120
|
-
while (Date.now() < deadline) {
|
|
121
|
-
if (callbacks.signal?.aborted) throw new Error("Login cancelled");
|
|
122
|
-
await abortableSleep(KILO_POLL_INTERVAL_MS, callbacks.signal);
|
|
123
|
-
|
|
124
|
-
const result = await pollDeviceAuth(code);
|
|
125
|
-
if (result.status === "approved") {
|
|
126
|
-
if (!result.token)
|
|
127
|
-
throw new Error("Authorization approved but no token received");
|
|
128
|
-
callbacks.onProgress?.("Login successful!");
|
|
129
|
-
return {
|
|
130
|
-
refresh: result.token,
|
|
131
|
-
access: result.token,
|
|
132
|
-
expires: Date.now() + KILO_TOKEN_EXPIRATION_MS,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
if (result.status === "denied")
|
|
136
|
-
throw new Error("Authorization denied by user.");
|
|
137
|
-
if (result.status === "expired")
|
|
138
|
-
throw new Error("Authorization code expired. Please try again.");
|
|
139
|
-
|
|
140
|
-
const remaining = Math.ceil((deadline - Date.now()) / 1000);
|
|
141
|
-
callbacks.onProgress?.(
|
|
142
|
-
`Waiting for browser authorization... (${remaining}s remaining)`,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
throw new Error("Authentication timed out. Please try again.");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export async function refreshKiloToken(
|
|
149
|
-
credentials: OAuthCredentials,
|
|
150
|
-
): Promise<OAuthCredentials> {
|
|
151
|
-
if (credentials.expires > Date.now()) return credentials;
|
|
152
|
-
throw new Error(
|
|
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";
|
|
9
|
+
import {
|
|
10
|
+
KILO_POLL_INTERVAL_MS,
|
|
11
|
+
KILO_TOKEN_EXPIRATION_MS,
|
|
12
|
+
} from "../../constants.ts";
|
|
13
|
+
import { createLogger } from "../../lib/logger.ts";
|
|
14
|
+
import { openBrowser } from "../../lib/open-browser.ts";
|
|
15
|
+
|
|
16
|
+
const _logger = createLogger("kilo-auth");
|
|
17
|
+
|
|
18
|
+
const KILO_API_BASE = process.env.KILO_API_URL || "https://api.kilo.ai";
|
|
19
|
+
const DEVICE_AUTH_ENDPOINT = `${KILO_API_BASE}/api/device-auth/codes`;
|
|
20
|
+
const PROFILE_ENDPOINT = `${KILO_API_BASE}/api/profile`;
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// Balance & Rate Limit
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
export async function fetchKiloBalance(token: string): Promise<number | null> {
|
|
27
|
+
try {
|
|
28
|
+
const response = await fetch(`${PROFILE_ENDPOINT}/balance`, {
|
|
29
|
+
headers: {
|
|
30
|
+
Authorization: `Bearer ${token}`,
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) return null;
|
|
35
|
+
const data = (await response.json()) as { balance?: number };
|
|
36
|
+
return data.balance ?? null;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function formatCredits(balance: number): string {
|
|
43
|
+
return balance >= 1000
|
|
44
|
+
? `$${(balance / 1000).toFixed(1)}k`
|
|
45
|
+
: `$${balance.toFixed(2)}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// =============================================================================
|
|
49
|
+
// Device auth
|
|
50
|
+
// =============================================================================
|
|
51
|
+
|
|
52
|
+
interface DeviceAuthResponse {
|
|
53
|
+
code: string;
|
|
54
|
+
verificationUrl: string;
|
|
55
|
+
expiresIn: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface DeviceAuthPollResponse {
|
|
59
|
+
status: "pending" | "approved" | "denied" | "expired";
|
|
60
|
+
token?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
if (signal?.aborted) {
|
|
66
|
+
reject(new Error("Login cancelled"));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const timeout = setTimeout(resolve, ms);
|
|
70
|
+
signal?.addEventListener(
|
|
71
|
+
"abort",
|
|
72
|
+
() => {
|
|
73
|
+
clearTimeout(timeout);
|
|
74
|
+
reject(new Error("Login cancelled"));
|
|
75
|
+
},
|
|
76
|
+
{ once: true },
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function initiateDeviceAuth(): Promise<DeviceAuthResponse> {
|
|
82
|
+
const response = await fetch(DEVICE_AUTH_ENDPOINT, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: { "Content-Type": "application/json" },
|
|
85
|
+
});
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
response.status === 429
|
|
89
|
+
? "Too many pending authorization requests. Please try again later."
|
|
90
|
+
: `Failed to initiate device authorization: ${response.status}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return (await response.json()) as DeviceAuthResponse;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function pollDeviceAuth(code: string): Promise<DeviceAuthPollResponse> {
|
|
97
|
+
const response = await fetch(`${DEVICE_AUTH_ENDPOINT}/${code}`);
|
|
98
|
+
if (response.status === 202) return { status: "pending" };
|
|
99
|
+
if (response.status === 403) return { status: "denied" };
|
|
100
|
+
if (response.status === 410) return { status: "expired" };
|
|
101
|
+
if (!response.ok)
|
|
102
|
+
throw new Error(`Failed to poll device authorization: ${response.status}`);
|
|
103
|
+
return (await response.json()) as DeviceAuthPollResponse;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function loginKilo(
|
|
107
|
+
callbacks: OAuthLoginCallbacks,
|
|
108
|
+
): Promise<OAuthCredentials> {
|
|
109
|
+
callbacks.onProgress?.("Initiating device authorization...");
|
|
110
|
+
const { code, verificationUrl, expiresIn } = await initiateDeviceAuth();
|
|
111
|
+
|
|
112
|
+
callbacks.onAuth({
|
|
113
|
+
url: verificationUrl,
|
|
114
|
+
instructions: `Enter code: ${code}`,
|
|
115
|
+
});
|
|
116
|
+
openBrowser(verificationUrl);
|
|
117
|
+
callbacks.onProgress?.("Waiting for browser authorization...");
|
|
118
|
+
|
|
119
|
+
const deadline = Date.now() + expiresIn * 1000;
|
|
120
|
+
while (Date.now() < deadline) {
|
|
121
|
+
if (callbacks.signal?.aborted) throw new Error("Login cancelled");
|
|
122
|
+
await abortableSleep(KILO_POLL_INTERVAL_MS, callbacks.signal);
|
|
123
|
+
|
|
124
|
+
const result = await pollDeviceAuth(code);
|
|
125
|
+
if (result.status === "approved") {
|
|
126
|
+
if (!result.token)
|
|
127
|
+
throw new Error("Authorization approved but no token received");
|
|
128
|
+
callbacks.onProgress?.("Login successful!");
|
|
129
|
+
return {
|
|
130
|
+
refresh: result.token,
|
|
131
|
+
access: result.token,
|
|
132
|
+
expires: Date.now() + KILO_TOKEN_EXPIRATION_MS,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (result.status === "denied")
|
|
136
|
+
throw new Error("Authorization denied by user.");
|
|
137
|
+
if (result.status === "expired")
|
|
138
|
+
throw new Error("Authorization code expired. Please try again.");
|
|
139
|
+
|
|
140
|
+
const remaining = Math.ceil((deadline - Date.now()) / 1000);
|
|
141
|
+
callbacks.onProgress?.(
|
|
142
|
+
`Waiting for browser authorization... (${remaining}s remaining)`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
throw new Error("Authentication timed out. Please try again.");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function refreshKiloToken(
|
|
149
|
+
credentials: OAuthCredentials,
|
|
150
|
+
): Promise<OAuthCredentials> {
|
|
151
|
+
if (credentials.expires > Date.now()) return credentials;
|
|
152
|
+
throw new Error(
|
|
153
|
+
"Kilo token expired. Please run /login kilo to re-authenticate.",
|
|
154
|
+
);
|
|
155
|
+
}
|
package/providers/kilo/kilo.ts
CHANGED
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
* # Free models visible immediately; /login kilo for paid access
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import type { Api, Model, OAuthCredentials } from "@
|
|
15
|
+
import type { Api, Model, OAuthCredentials } from "@earendil-works/pi-ai";
|
|
16
16
|
import type {
|
|
17
17
|
ExtensionAPI,
|
|
18
18
|
ProviderModelConfig,
|
|
19
|
-
} from "@
|
|
19
|
+
} from "@earendil-works/pi-coding-agent";
|
|
20
20
|
import {
|
|
21
21
|
getKiloFreeOnly,
|
|
22
22
|
getKiloShowPaid,
|
|
@@ -44,7 +44,7 @@ const KILO_PROVIDER_CONFIG = {
|
|
|
44
44
|
},
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
export default async function (pi: ExtensionAPI) {
|
|
47
|
+
export default async function kiloProvider(pi: ExtensionAPI) {
|
|
48
48
|
// Try to fetch ALL models at startup (like Cline/OpenRouter)
|
|
49
49
|
// If no API key, this will return free models only
|
|
50
50
|
let allModels: ProviderModelConfig[] = [];
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM7.io Provider Extension
|
|
3
|
+
*
|
|
4
|
+
* LLM7.io is an LLM API gateway that routes requests across multiple
|
|
5
|
+
* providers (OpenAI, Mistral, Google, DeepSeek, Cloudflare, etc.) through
|
|
6
|
+
* a single OpenAI-compatible endpoint.
|
|
7
|
+
*
|
|
8
|
+
* Free tier:
|
|
9
|
+
* - Free token from https://token.llm7.io/
|
|
10
|
+
* - 100 req/hr, 20 req/min, 2 req/s
|
|
11
|
+
* - No credit card required
|
|
12
|
+
*
|
|
13
|
+
* Pro tier ($12/mo):
|
|
14
|
+
* - Higher rate limits, JSON mode, function calling
|
|
15
|
+
* - Access to "pro" routing selector
|
|
16
|
+
*
|
|
17
|
+
* Model selectors (not specific model IDs — LLM7 routes randomly):
|
|
18
|
+
* - "default" — first available free model (free)
|
|
19
|
+
* - "fast" — lowest latency option (free)
|
|
20
|
+
* - "pro" — highest quality, longer reasoning (paid)
|
|
21
|
+
*
|
|
22
|
+
* Endpoint:
|
|
23
|
+
* Chat: https://api.llm7.io/v1/chat/completions
|
|
24
|
+
*
|
|
25
|
+
* Setup:
|
|
26
|
+
* 1. Get free token from https://token.llm7.io/
|
|
27
|
+
* 2. Set LLM7_API_KEY env var (or add to ~/.pi/free.json)
|
|
28
|
+
*
|
|
29
|
+
* Usage:
|
|
30
|
+
* pi install git:github.com/apmantza/pi-free
|
|
31
|
+
* # Set LLM7_API_KEY env var
|
|
32
|
+
* # Models appear in /model selector as "llm7/default", "llm7/fast", "llm7/pro"
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import type {
|
|
36
|
+
ExtensionAPI,
|
|
37
|
+
ProviderModelConfig,
|
|
38
|
+
} from "@earendil-works/pi-coding-agent";
|
|
39
|
+
import { getLlm7ApiKey, getLlm7ShowPaid } from "../../config.ts";
|
|
40
|
+
import { BASE_URL_LLM7, PROVIDER_LLM7 } from "../../constants.ts";
|
|
41
|
+
import { createLogger } from "../../lib/logger.ts";
|
|
42
|
+
import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
|
|
43
|
+
import { createReRegister, setupProvider } from "../../provider-helper.ts";
|
|
44
|
+
|
|
45
|
+
const _logger = createLogger("llm7");
|
|
46
|
+
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// Model Definitions
|
|
49
|
+
// =============================================================================
|
|
50
|
+
|
|
51
|
+
const LLM7_MODELS: ProviderModelConfig[] = [
|
|
52
|
+
{
|
|
53
|
+
id: "default",
|
|
54
|
+
name: "LLM7 Default",
|
|
55
|
+
reasoning: false,
|
|
56
|
+
input: ["text"],
|
|
57
|
+
cost: {
|
|
58
|
+
input: 0,
|
|
59
|
+
output: 0,
|
|
60
|
+
cacheRead: 0,
|
|
61
|
+
cacheWrite: 0,
|
|
62
|
+
},
|
|
63
|
+
contextWindow: 32_000,
|
|
64
|
+
maxTokens: 4_096,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "fast",
|
|
68
|
+
name: "LLM7 Fast",
|
|
69
|
+
reasoning: false,
|
|
70
|
+
input: ["text"],
|
|
71
|
+
cost: {
|
|
72
|
+
input: 0,
|
|
73
|
+
output: 0,
|
|
74
|
+
cacheRead: 0,
|
|
75
|
+
cacheWrite: 0,
|
|
76
|
+
},
|
|
77
|
+
contextWindow: 32_000,
|
|
78
|
+
maxTokens: 4_096,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "pro",
|
|
82
|
+
name: "LLM7 Pro",
|
|
83
|
+
reasoning: false,
|
|
84
|
+
input: ["text"],
|
|
85
|
+
cost: {
|
|
86
|
+
input: 0.3, // Requires $12/mo LLM7 Pro subscription
|
|
87
|
+
output: 0.9,
|
|
88
|
+
cacheRead: 0,
|
|
89
|
+
cacheWrite: 0,
|
|
90
|
+
},
|
|
91
|
+
contextWindow: 32_000,
|
|
92
|
+
maxTokens: 4_096,
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
// =============================================================================
|
|
97
|
+
// Extension Entry Point
|
|
98
|
+
// =============================================================================
|
|
99
|
+
|
|
100
|
+
export default async function llm7Provider(pi: ExtensionAPI) {
|
|
101
|
+
const apiKey = getLlm7ApiKey();
|
|
102
|
+
|
|
103
|
+
if (!apiKey) {
|
|
104
|
+
_logger.info(
|
|
105
|
+
"[llm7] Skipping — LLM7_API_KEY not set. Get a free token at https://token.llm7.io/",
|
|
106
|
+
);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_logger.info("[llm7] Using LLM7_API_KEY");
|
|
111
|
+
|
|
112
|
+
const allModels = LLM7_MODELS;
|
|
113
|
+
const freeModels = allModels.filter((m) =>
|
|
114
|
+
isFreeModel({ ...m, provider: PROVIDER_LLM7 }, allModels),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const stored = { free: freeModels, all: allModels };
|
|
118
|
+
|
|
119
|
+
_logger.info(
|
|
120
|
+
`[llm7] Registered ${allModels.length} models (${freeModels.length} free)`,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Create re-register function
|
|
124
|
+
const reRegister = createReRegister(pi, {
|
|
125
|
+
providerId: PROVIDER_LLM7,
|
|
126
|
+
baseUrl: BASE_URL_LLM7,
|
|
127
|
+
apiKey,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Register with global toggle
|
|
131
|
+
registerWithGlobalToggle(PROVIDER_LLM7, stored, reRegister, true);
|
|
132
|
+
|
|
133
|
+
// Setup provider with toggle command
|
|
134
|
+
setupProvider(
|
|
135
|
+
pi,
|
|
136
|
+
{
|
|
137
|
+
providerId: PROVIDER_LLM7,
|
|
138
|
+
initialShowPaid: getLlm7ShowPaid(),
|
|
139
|
+
tosUrl: "https://llm7.io/",
|
|
140
|
+
reRegister: (models, _stored) => {
|
|
141
|
+
if (_stored) {
|
|
142
|
+
stored.free = _stored.free;
|
|
143
|
+
stored.all = _stored.all;
|
|
144
|
+
}
|
|
145
|
+
reRegister(models);
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
stored,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Initial registration — respect persisted toggle state
|
|
152
|
+
const showPaid = getLlm7ShowPaid();
|
|
153
|
+
const initialModels =
|
|
154
|
+
showPaid && stored.all.length > 0 ? stored.all : freeModels;
|
|
155
|
+
reRegister(initialModels);
|
|
156
|
+
}
|
|
@@ -108,8 +108,8 @@ export async function fetchOpenRouterCompatibleModels(
|
|
|
108
108
|
|
|
109
109
|
// Filter by pricing if freeOnly
|
|
110
110
|
if (freeOnly) {
|
|
111
|
-
const prompt = parseFloat(m.pricing?.prompt ?? "1");
|
|
112
|
-
const completion = parseFloat(m.pricing?.completion ?? "1");
|
|
111
|
+
const prompt = Number.parseFloat(m.pricing?.prompt ?? "1");
|
|
112
|
+
const completion = Number.parseFloat(m.pricing?.completion ?? "1");
|
|
113
113
|
if (prompt !== 0 || completion !== 0) return false;
|
|
114
114
|
}
|
|
115
115
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import type {
|
|
17
17
|
ExtensionAPI,
|
|
18
18
|
ProviderModelConfig,
|
|
19
|
-
} from "@
|
|
19
|
+
} from "@earendil-works/pi-coding-agent";
|
|
20
20
|
import {
|
|
21
21
|
applyHidden,
|
|
22
22
|
getNvidiaApiKey,
|
|
@@ -146,9 +146,9 @@ function inferModelFromId(id: string): ModelsDevModel | null {
|
|
|
146
146
|
const name = id
|
|
147
147
|
.split("/")
|
|
148
148
|
.pop()!
|
|
149
|
-
.
|
|
150
|
-
.
|
|
151
|
-
.
|
|
149
|
+
.replaceAll("-", " ")
|
|
150
|
+
.replaceAll(/\b\w/g, (c) => c.toUpperCase())
|
|
151
|
+
.replaceAll(/\b(\d+(?:\.\d+)?)b\b/gi, "$1B");
|
|
152
152
|
|
|
153
153
|
const hasVision = /vision|multimodal|vl/i.test(id);
|
|
154
154
|
const hasReasoning = /reason|r1|thinking/i.test(id);
|
|
@@ -369,7 +369,7 @@ async function runNvidiaProbe(
|
|
|
369
369
|
);
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
-
export default async function (pi: ExtensionAPI) {
|
|
372
|
+
export default async function nvidiaProvider(pi: ExtensionAPI) {
|
|
373
373
|
const apiKey = getNvidiaApiKey();
|
|
374
374
|
const hasKey = !!apiKey;
|
|
375
375
|
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import type {
|
|
24
24
|
ExtensionAPI,
|
|
25
25
|
ProviderModelConfig,
|
|
26
|
-
} from "@
|
|
26
|
+
} from "@earendil-works/pi-coding-agent";
|
|
27
27
|
import {
|
|
28
28
|
applyHidden,
|
|
29
29
|
getOllamaApiKey,
|
|
@@ -140,7 +140,7 @@ async function fetchOllamaModels(
|
|
|
140
140
|
// Extension Entry Point
|
|
141
141
|
// =============================================================================
|
|
142
142
|
|
|
143
|
-
export default async function (pi: ExtensionAPI) {
|
|
143
|
+
export default async function ollamaProvider(pi: ExtensionAPI) {
|
|
144
144
|
const apiKey = getOllamaApiKey();
|
|
145
145
|
|
|
146
146
|
if (!apiKey) {
|
|
@@ -16,7 +16,7 @@ import crypto from "node:crypto";
|
|
|
16
16
|
import type {
|
|
17
17
|
OAuthCredentials,
|
|
18
18
|
OAuthLoginCallbacks,
|
|
19
|
-
} from "@
|
|
19
|
+
} from "@earendil-works/pi-ai";
|
|
20
20
|
import { createLogger } from "../../lib/logger.ts";
|
|
21
21
|
import { openBrowser } from "../../lib/open-browser.ts";
|
|
22
22
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* This provider remains for backward compatibility but should not be used.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ProviderModelConfig } from "@
|
|
8
|
+
import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { createLogger } from "../../lib/logger.ts";
|
|
10
10
|
|
|
11
11
|
const _logger = createLogger("qwen-models");
|
package/providers/qwen/qwen.ts
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* 1,000 free API calls/day — run /login qwen to authenticate.~~
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { Api, Model, OAuthCredentials } from "@
|
|
14
|
-
import type { ExtensionAPI } from "@
|
|
13
|
+
import type { Api, Model, OAuthCredentials } from "@earendil-works/pi-ai";
|
|
14
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
15
15
|
import { PROVIDER_QWEN, URL_QWEN_TOS } from "../../constants.ts";
|
|
16
16
|
import { createLogger } from "../../lib/logger.ts";
|
|
17
17
|
import { logWarning } from "../../lib/util.ts";
|
|
@@ -59,7 +59,7 @@ const DASHSCOPE_HEADERS = {
|
|
|
59
59
|
// Extension entry point
|
|
60
60
|
// =============================================================================
|
|
61
61
|
|
|
62
|
-
export default async function (pi: ExtensionAPI) {
|
|
62
|
+
export default async function qwenProvider(pi: ExtensionAPI) {
|
|
63
63
|
// DEPRECATION WARNING
|
|
64
64
|
_logger.warn(
|
|
65
65
|
"Qwen provider is deprecated. The 1,000 req/day free tier is no longer available.",
|