pi-free 2.0.1 → 2.0.4
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 +179 -3
- package/README.md +495 -393
- package/config.ts +46 -54
- package/constants.ts +6 -0
- package/index.ts +39 -12
- package/lib/built-in-toggle.ts +63 -43
- package/lib/model-enhancer.ts +20 -20
- package/lib/open-browser.ts +1 -1
- package/lib/provider-compat.ts +46 -0
- package/lib/registry.ts +193 -144
- package/lib/toggle-state.ts +86 -0
- package/lib/types.ts +101 -108
- package/package.json +8 -8
- package/provider-failover/benchmark-lookup.ts +637 -247
- package/provider-helper.ts +279 -260
- package/providers/cline/cline-auth.ts +473 -473
- package/providers/cline/cline-models.ts +129 -128
- package/providers/cline/cline.ts +311 -298
- package/providers/crofai/crofai.ts +170 -0
- package/providers/dynamic-built-in/index.ts +259 -308
- package/providers/kilo/kilo-auth.ts +155 -155
- package/providers/kilo/kilo-models.ts +2 -1
- package/providers/kilo/kilo.ts +263 -235
- package/providers/nvidia/nvidia.ts +476 -152
- package/providers/ollama/ollama.ts +130 -7
- package/providers/opencode-session.ts +3 -4
- package/providers/qwen/qwen-models.ts +101 -101
- package/providers/zenmux/zenmux.ts +176 -0
- package/scripts/check-extensions.mjs +64 -55
- package/provider-factory.ts +0 -207
- package/providers/cloudflare/cloudflare.ts +0 -368
- package/providers/modal/modal.ts +0 -44
package/providers/cline/cline.ts
CHANGED
|
@@ -1,298 +1,311 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cline Provider Extension
|
|
3
|
-
*
|
|
4
|
-
* Provides access to Cline's free models (via their OpenRouter gateway).
|
|
5
|
-
* Free model list is fetched from Cline's GitHub source — no account needed to browse.
|
|
6
|
-
* Run /login cline to authenticate and make API calls.
|
|
7
|
-
*
|
|
8
|
-
* Auth flow based on pi-cline's proven implementation.
|
|
9
|
-
*
|
|
10
|
-
* Responds to global free-only filter (though Cline only provides free models without auth).
|
|
11
|
-
*
|
|
12
|
-
* Usage:
|
|
13
|
-
* pi install git:github.com/apmantza/pi-free
|
|
14
|
-
* # Models appear immediately; run /login cline to start chatting
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import type { OAuthCredentials } from "@mariozechner/pi-ai";
|
|
18
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
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
|
-
"X-
|
|
56
|
-
"X-
|
|
57
|
-
"X-
|
|
58
|
-
"X-
|
|
59
|
-
"X-
|
|
60
|
-
"X-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
.
|
|
112
|
-
.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
texts.some((t) => t
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
parts.push(
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
{ type: "text", text:
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
// =============================================================================
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
let allModels = await fetchClineModels(false).catch((err) => {
|
|
198
|
-
logWarning("cline", "Failed to fetch models at startup", err);
|
|
199
|
-
return [];
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Cline Provider Extension
|
|
3
|
+
*
|
|
4
|
+
* Provides access to Cline's free models (via their OpenRouter gateway).
|
|
5
|
+
* Free model list is fetched from Cline's GitHub source — no account needed to browse.
|
|
6
|
+
* Run /login cline to authenticate and make API calls.
|
|
7
|
+
*
|
|
8
|
+
* Auth flow based on pi-cline's proven implementation.
|
|
9
|
+
*
|
|
10
|
+
* Responds to global free-only filter (though Cline only provides free models without auth).
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* pi install git:github.com/apmantza/pi-free
|
|
14
|
+
* # Models appear immediately; run /login cline to start chatting
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { OAuthCredentials } from "@mariozechner/pi-ai";
|
|
18
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
19
|
+
import { getClineShowPaid } from "../../config.ts";
|
|
20
|
+
import { BASE_URL_CLINE, PROVIDER_CLINE } from "../../constants.ts";
|
|
21
|
+
import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
|
|
22
|
+
import { createToggleState } from "../../lib/toggle-state.ts";
|
|
23
|
+
import { logWarning } from "../../lib/util.ts";
|
|
24
|
+
import { enhanceWithCI } from "../../provider-helper.ts";
|
|
25
|
+
import { loginCline, refreshClineToken } from "./cline-auth.ts";
|
|
26
|
+
import { fetchClineModels } from "./cline-models.ts";
|
|
27
|
+
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Cline API headers (must match real Cline VS Code extension exactly)
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
const VS_CODE_VERSION = "1.109.3";
|
|
33
|
+
const CLINE_EXTENSION_VERSION = "3.76.0";
|
|
34
|
+
let _currentTaskId = generateUlid();
|
|
35
|
+
|
|
36
|
+
function generateUlid(): string {
|
|
37
|
+
const CHARS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
let ts = "";
|
|
40
|
+
let t = now;
|
|
41
|
+
for (let i = 0; i < 10; i++) {
|
|
42
|
+
ts = CHARS[t % 32] + ts;
|
|
43
|
+
t = Math.floor(t / 32);
|
|
44
|
+
}
|
|
45
|
+
const rand = new Uint8Array(16);
|
|
46
|
+
crypto.getRandomValues(rand);
|
|
47
|
+
let r = "";
|
|
48
|
+
for (let i = 0; i < 16; i++) r += CHARS[rand[i] % 32];
|
|
49
|
+
return ts + r;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildClineHeaders(): Record<string, string> {
|
|
53
|
+
return {
|
|
54
|
+
"HTTP-Referer": "https://cline.bot",
|
|
55
|
+
"X-Title": "Cline",
|
|
56
|
+
"X-Task-ID": _currentTaskId,
|
|
57
|
+
"X-PLATFORM": "Visual Studio Code",
|
|
58
|
+
"X-PLATFORM-VERSION": VS_CODE_VERSION,
|
|
59
|
+
"X-CLIENT-TYPE": "VSCode Extension",
|
|
60
|
+
"X-CLIENT-VERSION": CLINE_EXTENSION_VERSION,
|
|
61
|
+
"X-CORE-VERSION": CLINE_EXTENSION_VERSION,
|
|
62
|
+
"X-Is-Multiroot": "false",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function toApiKey(credentials: OAuthCredentials): string {
|
|
67
|
+
const token = credentials.access;
|
|
68
|
+
return token.startsWith("workos:") ? token : `workos:${token}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// =============================================================================
|
|
72
|
+
// Context shaping — Cline's API requires a specific message envelope
|
|
73
|
+
// =============================================================================
|
|
74
|
+
|
|
75
|
+
const TASK_PROGRESS_BLOCK = `
|
|
76
|
+
# task_progress List (Optional - Plan Mode)
|
|
77
|
+
|
|
78
|
+
While in PLAN MODE, if you've outlined concrete steps or requirements for the user, you may include a preliminary todo list using the task_progress parameter.
|
|
79
|
+
|
|
80
|
+
1. To create or update a todo list, include the task_progress parameter in the next tool call
|
|
81
|
+
2. Review each item and update its status:
|
|
82
|
+
- Mark completed items with: - [x]
|
|
83
|
+
- Keep incomplete items as: - [ ]
|
|
84
|
+
3. Modify the list as needed
|
|
85
|
+
4. Ensure the list accurately reflects the current state`;
|
|
86
|
+
|
|
87
|
+
function buildEnvironmentDetails(): string {
|
|
88
|
+
const cwd = process.cwd();
|
|
89
|
+
return `<environmentDetails>
|
|
90
|
+
# Visual Studio Code Visible Files
|
|
91
|
+
(No visible files)
|
|
92
|
+
|
|
93
|
+
# Visual Studio Code Open Tabs
|
|
94
|
+
(No open tabs)
|
|
95
|
+
|
|
96
|
+
# Current Working Directory (${cwd}) Files
|
|
97
|
+
(No files)
|
|
98
|
+
|
|
99
|
+
# Context Window Usage
|
|
100
|
+
0 / 204.8K tokens used (0%)
|
|
101
|
+
|
|
102
|
+
# Current Mode
|
|
103
|
+
PLAN MODE
|
|
104
|
+
</environmentDetails>`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function extractText(content: unknown): string {
|
|
108
|
+
if (typeof content === "string") return content.trim();
|
|
109
|
+
if (Array.isArray(content)) {
|
|
110
|
+
return (content as any[])
|
|
111
|
+
.filter((p: any) => p?.type === "text" && typeof p?.text === "string")
|
|
112
|
+
.map((p: any) => p.text)
|
|
113
|
+
.join("\n\n")
|
|
114
|
+
.trim();
|
|
115
|
+
}
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isClineWrapped(content: unknown): boolean {
|
|
120
|
+
if (!Array.isArray(content)) return false;
|
|
121
|
+
const texts = (content as any[])
|
|
122
|
+
.filter((p: any) => p?.type === "text" && typeof p?.text === "string")
|
|
123
|
+
.map((p: any) => p.text as string);
|
|
124
|
+
return (
|
|
125
|
+
texts.some((t) => /<task>[\s\S]*<\/task>/.test(t)) &&
|
|
126
|
+
texts.some((t) => t.includes("task_progress List")) &&
|
|
127
|
+
texts.some((t) => t.includes("<environmentDetails>"))
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function extractTaskBody(content: unknown): string {
|
|
132
|
+
if (!Array.isArray(content)) return "";
|
|
133
|
+
for (const p of content as any[]) {
|
|
134
|
+
if (p?.type !== "text" || typeof p?.text !== "string") continue;
|
|
135
|
+
const m = p.text.match(/<task>([\s\S]*?)<\/task>/);
|
|
136
|
+
if (m?.[1]) return m[1].trim();
|
|
137
|
+
}
|
|
138
|
+
return "";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function shapeMessagesForCline(messages: any[]): any[] {
|
|
142
|
+
let lastWrappedIdx = -1;
|
|
143
|
+
let baseTranscript = "";
|
|
144
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
145
|
+
if (messages[i]?.role !== "user") continue;
|
|
146
|
+
if (!isClineWrapped(messages[i]?.content)) continue;
|
|
147
|
+
lastWrappedIdx = i;
|
|
148
|
+
baseTranscript = extractTaskBody(messages[i].content);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const parts: string[] = baseTranscript ? [baseTranscript] : [];
|
|
153
|
+
const startIdx = lastWrappedIdx >= 0 ? lastWrappedIdx + 1 : 0;
|
|
154
|
+
|
|
155
|
+
for (let i = startIdx; i < messages.length; i++) {
|
|
156
|
+
const msg = messages[i];
|
|
157
|
+
const role = msg?.role ?? "user";
|
|
158
|
+
if (role === "system") continue;
|
|
159
|
+
if (role === "user" && isClineWrapped(msg?.content)) continue;
|
|
160
|
+
const text = extractText(msg?.content).trim();
|
|
161
|
+
if (!text) continue;
|
|
162
|
+
|
|
163
|
+
if (role === "tool") {
|
|
164
|
+
parts.push(`<tool_result>\n${text}\n</tool_result>`);
|
|
165
|
+
} else if (role !== "assistant") {
|
|
166
|
+
parts.push(`[${role}]\n${text}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const transcript = parts.join("\n\n").trim() || "(no conversation yet)";
|
|
171
|
+
const envDetails = buildEnvironmentDetails();
|
|
172
|
+
|
|
173
|
+
const collapsed: any[] = [];
|
|
174
|
+
const systemMsg = messages.find((m: any) => m?.role === "system");
|
|
175
|
+
if (systemMsg) {
|
|
176
|
+
const systemText = extractText(systemMsg.content);
|
|
177
|
+
if (systemText) collapsed.push({ role: "system", content: systemText });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
collapsed.push({
|
|
181
|
+
role: "user",
|
|
182
|
+
content: [
|
|
183
|
+
{ type: "text", text: `<task>\n${transcript}\n</task>` },
|
|
184
|
+
{ type: "text", text: TASK_PROGRESS_BLOCK },
|
|
185
|
+
{ type: "text", text: envDetails },
|
|
186
|
+
],
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return collapsed;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// =============================================================================
|
|
193
|
+
// Extension entry point
|
|
194
|
+
// =============================================================================
|
|
195
|
+
|
|
196
|
+
export default async function (pi: ExtensionAPI) {
|
|
197
|
+
let allModels = await fetchClineModels(false).catch((err) => {
|
|
198
|
+
logWarning("cline", "Failed to fetch models at startup", err);
|
|
199
|
+
return [];
|
|
200
|
+
});
|
|
201
|
+
let freeModels = allModels.filter((m) =>
|
|
202
|
+
isFreeModel({ ...m, provider: PROVIDER_CLINE }, allModels),
|
|
203
|
+
);
|
|
204
|
+
const stored = { free: freeModels, all: allModels };
|
|
205
|
+
const toggleState = createToggleState({
|
|
206
|
+
providerId: PROVIDER_CLINE,
|
|
207
|
+
initialShowPaid: getClineShowPaid(),
|
|
208
|
+
initialModels: stored,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const reRegister = (m: typeof allModels) => {
|
|
212
|
+
pi.registerProvider(PROVIDER_CLINE, {
|
|
213
|
+
baseUrl: BASE_URL_CLINE,
|
|
214
|
+
api: "openai-completions" as const,
|
|
215
|
+
authHeader: false,
|
|
216
|
+
headers: buildClineHeaders(),
|
|
217
|
+
models: enhanceWithCI(m),
|
|
218
|
+
oauth: {
|
|
219
|
+
name: "Cline",
|
|
220
|
+
login: loginCline,
|
|
221
|
+
refreshToken: refreshClineToken,
|
|
222
|
+
getApiKey: toApiKey,
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
registerWithGlobalToggle(PROVIDER_CLINE, stored, (m) => reRegister(m), false);
|
|
228
|
+
toggleState.applyCurrent(reRegister);
|
|
229
|
+
|
|
230
|
+
pi.registerCommand("toggle-cline", {
|
|
231
|
+
description: "Toggle between free and all Cline models",
|
|
232
|
+
handler: async (_args, ctx) => {
|
|
233
|
+
const applied = toggleState.toggle(reRegister);
|
|
234
|
+
const freeCount = stored.free.length;
|
|
235
|
+
const paidCount = stored.all.length - freeCount;
|
|
236
|
+
|
|
237
|
+
if (applied.mode === "all") {
|
|
238
|
+
ctx.ui.notify(
|
|
239
|
+
`cline: showing all ${stored.all.length} models (${freeCount} free, ${paidCount} paid)`,
|
|
240
|
+
"info",
|
|
241
|
+
);
|
|
242
|
+
} else {
|
|
243
|
+
ctx.ui.notify(
|
|
244
|
+
`cline: showing ${freeCount} free models (${paidCount} paid hidden)`,
|
|
245
|
+
"info",
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// ── Status bar for provider selection ─────────────────────────
|
|
252
|
+
|
|
253
|
+
pi.on("model_select", (_event, ctx) => {
|
|
254
|
+
if (_event.model?.provider !== PROVIDER_CLINE) {
|
|
255
|
+
ctx.ui.setStatus(`${PROVIDER_CLINE}-status`, undefined);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const free = stored.free.length;
|
|
260
|
+
const total = stored.all.length;
|
|
261
|
+
const paid = total - free;
|
|
262
|
+
const mode = toggleState.getCurrentMode();
|
|
263
|
+
let status: string;
|
|
264
|
+
if (paid === 0) {
|
|
265
|
+
status = `cline: ${free} free models`;
|
|
266
|
+
} else if (mode === "all") {
|
|
267
|
+
status = `cline: ${total} models (free + paid)`;
|
|
268
|
+
} else {
|
|
269
|
+
status = `cline: ${free} free \u00b7 ${paid} paid`;
|
|
270
|
+
}
|
|
271
|
+
ctx.ui.setStatus(`${PROVIDER_CLINE}-status`, status);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
pi.on("before_agent_start", async (_event, ctx) => {
|
|
275
|
+
if (ctx.model?.provider !== PROVIDER_CLINE) return;
|
|
276
|
+
_currentTaskId = generateUlid();
|
|
277
|
+
toggleState.applyCurrent(reRegister);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
pi.on("context", async (event, ctx) => {
|
|
281
|
+
if (ctx.model?.provider !== PROVIDER_CLINE) return;
|
|
282
|
+
const sourceMessages = Array.isArray(event.messages) ? event.messages : [];
|
|
283
|
+
return { messages: shapeMessagesForCline(sourceMessages) };
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
287
|
+
try {
|
|
288
|
+
const fresh = await fetchClineModels(false);
|
|
289
|
+
if (fresh.length > 0) {
|
|
290
|
+
allModels = fresh;
|
|
291
|
+
freeModels = allModels.filter((m) =>
|
|
292
|
+
isFreeModel({ ...m, provider: PROVIDER_CLINE }, allModels),
|
|
293
|
+
);
|
|
294
|
+
stored.all = allModels;
|
|
295
|
+
stored.free = freeModels;
|
|
296
|
+
toggleState.setModels(stored);
|
|
297
|
+
toggleState.applyCurrent(reRegister);
|
|
298
|
+
if (ctx.model?.provider === PROVIDER_CLINE) {
|
|
299
|
+
const freeCount = stored.free.length;
|
|
300
|
+
const paidCount = stored.all.length - freeCount;
|
|
301
|
+
ctx.ui.notify(
|
|
302
|
+
`Cline: ${freeCount} free, ${paidCount} paid models available`,
|
|
303
|
+
"info",
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
} catch (err) {
|
|
308
|
+
logWarning("cline", "Failed to refresh models at session start", err);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|