pi-free 1.0.8 → 2.0.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/CHANGELOG.md +107 -1
- package/README.md +95 -46
- package/config.ts +165 -120
- package/constants.ts +22 -61
- package/index.ts +186 -0
- package/lib/json-persistence.ts +11 -10
- package/lib/logger.ts +2 -2
- package/lib/model-enhancer.ts +20 -20
- package/lib/open-browser.ts +41 -0
- package/lib/provider-cache.ts +106 -0
- package/lib/registry.ts +144 -0
- package/package.json +67 -82
- package/provider-factory.ts +25 -41
- package/provider-failover/benchmark-lookup.ts +247 -0
- package/provider-failover/benchmarks-chunk-0.ts +2010 -0
- package/provider-failover/benchmarks-chunk-1.ts +1988 -0
- package/provider-failover/benchmarks-chunk-2.ts +2010 -0
- package/provider-failover/benchmarks-chunk-3.ts +2010 -0
- package/provider-failover/benchmarks-chunk-4.ts +1969 -0
- package/provider-failover/hardcoded-benchmarks.ts +22 -10025
- package/provider-helper.ts +38 -37
- package/providers/{cline-auth.ts → cline/cline-auth.ts} +2 -2
- package/providers/cline/cline-models.ts +128 -0
- package/providers/{cline.ts → cline/cline.ts} +300 -257
- package/providers/cloudflare/cloudflare.ts +368 -0
- package/providers/dynamic-built-in/index.ts +513 -0
- package/providers/{kilo-auth.ts → kilo/kilo-auth.ts} +3 -20
- package/providers/{kilo-models.ts → kilo/kilo-models.ts} +2 -2
- package/providers/kilo/kilo.ts +235 -0
- package/providers/{modal.ts → modal/modal.ts} +4 -3
- package/providers/{nvidia.ts → nvidia/nvidia.ts} +152 -113
- package/providers/ollama/ollama.ts +172 -0
- package/providers/opencode-session.ts +34 -34
- package/providers/{qwen-auth.ts → qwen/qwen-auth.ts} +24 -40
- package/providers/{qwen-models.ts → qwen/qwen-models.ts} +101 -95
- package/providers/qwen/qwen.ts +202 -0
- package/provider-failover/auto-switch.ts +0 -350
- package/provider-failover/errors.ts +0 -275
- package/provider-failover/index.ts +0 -238
- package/providers/cline-models.ts +0 -77
- package/providers/factory.ts +0 -125
- package/providers/fireworks.ts +0 -49
- package/providers/go.ts +0 -216
- package/providers/kilo.ts +0 -146
- package/providers/mistral.ts +0 -144
- package/providers/ollama.ts +0 -113
- package/providers/openrouter.ts +0 -175
- package/providers/qwen.ts +0 -127
- package/providers/zen.ts +0 -371
- package/usage/commands.ts +0 -17
- package/usage/cumulative.ts +0 -193
- package/usage/formatters.ts +0 -115
- package/usage/index.ts +0 -46
- package/usage/limits.ts +0 -148
- package/usage/metrics.ts +0 -222
- package/usage/sessions.ts +0 -355
- package/usage/store.ts +0 -99
- package/usage/tracking.ts +0 -329
- package/usage/types.ts +0 -26
- package/usage/widget.ts +0 -90
- package/widget/data.ts +0 -113
- package/widget/format.ts +0 -26
- package/widget/render.ts +0 -117
|
@@ -1,257 +1,300 @@
|
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
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
|
-
"X-
|
|
54
|
-
"X-
|
|
55
|
-
"X-
|
|
56
|
-
"X-
|
|
57
|
-
"X-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
.
|
|
111
|
-
.
|
|
112
|
-
.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
texts.some((t) =>
|
|
125
|
-
texts.some((t) => t.includes("
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
let
|
|
142
|
-
let
|
|
143
|
-
|
|
144
|
-
if (messages[i]?.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
if (role === "
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
{ type: "text", text:
|
|
183
|
-
{ type: "text", text:
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
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 toggle (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 { BASE_URL_CLINE, PROVIDER_CLINE } from "../../constants.ts";
|
|
20
|
+
import { registerWithGlobalToggle } from "../../lib/registry.ts";
|
|
21
|
+
import { logWarning } from "../../lib/util.ts";
|
|
22
|
+
import { enhanceWithCI } from "../../provider-helper.ts";
|
|
23
|
+
import { loginCline, refreshClineToken } from "./cline-auth.ts";
|
|
24
|
+
import { fetchClineModels } from "./cline-models.ts";
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Cline API headers (must match real Cline VS Code extension exactly)
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
const VS_CODE_VERSION = "1.109.3";
|
|
31
|
+
const CLINE_EXTENSION_VERSION = "3.76.0";
|
|
32
|
+
let _currentTaskId = generateUlid();
|
|
33
|
+
|
|
34
|
+
function generateUlid(): string {
|
|
35
|
+
const CHARS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
let ts = "";
|
|
38
|
+
let t = now;
|
|
39
|
+
for (let i = 0; i < 10; i++) {
|
|
40
|
+
ts = CHARS[t % 32] + ts;
|
|
41
|
+
t = Math.floor(t / 32);
|
|
42
|
+
}
|
|
43
|
+
const rand = new Uint8Array(16);
|
|
44
|
+
crypto.getRandomValues(rand);
|
|
45
|
+
let r = "";
|
|
46
|
+
for (let i = 0; i < 16; i++) r += CHARS[rand[i] % 32];
|
|
47
|
+
return ts + r;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function buildClineHeaders(): Record<string, string> {
|
|
51
|
+
return {
|
|
52
|
+
"HTTP-Referer": "https://cline.bot",
|
|
53
|
+
"X-Title": "Cline",
|
|
54
|
+
"X-Task-ID": _currentTaskId,
|
|
55
|
+
"X-PLATFORM": "Visual Studio Code",
|
|
56
|
+
"X-PLATFORM-VERSION": VS_CODE_VERSION,
|
|
57
|
+
"X-CLIENT-TYPE": "VSCode Extension",
|
|
58
|
+
"X-CLIENT-VERSION": CLINE_EXTENSION_VERSION,
|
|
59
|
+
"X-CORE-VERSION": CLINE_EXTENSION_VERSION,
|
|
60
|
+
"X-Is-Multiroot": "false",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function toApiKey(credentials: OAuthCredentials): string {
|
|
65
|
+
const token = credentials.access;
|
|
66
|
+
return token.startsWith("workos:") ? token : `workos:${token}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// Context shaping — Cline's API requires a specific message envelope
|
|
71
|
+
// =============================================================================
|
|
72
|
+
|
|
73
|
+
const TASK_PROGRESS_BLOCK = `
|
|
74
|
+
# task_progress List (Optional - Plan Mode)
|
|
75
|
+
|
|
76
|
+
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.
|
|
77
|
+
|
|
78
|
+
1. To create or update a todo list, include the task_progress parameter in the next tool call
|
|
79
|
+
2. Review each item and update its status:
|
|
80
|
+
- Mark completed items with: - [x]
|
|
81
|
+
- Keep incomplete items as: - [ ]
|
|
82
|
+
3. Modify the list as needed
|
|
83
|
+
4. Ensure the list accurately reflects the current state`;
|
|
84
|
+
|
|
85
|
+
function buildEnvironmentDetails(): string {
|
|
86
|
+
const cwd = process.cwd();
|
|
87
|
+
return `<environmentDetails>
|
|
88
|
+
# Visual Studio Code Visible Files
|
|
89
|
+
(No visible files)
|
|
90
|
+
|
|
91
|
+
# Visual Studio Code Open Tabs
|
|
92
|
+
(No open tabs)
|
|
93
|
+
|
|
94
|
+
# Current Working Directory (${cwd}) Files
|
|
95
|
+
(No files)
|
|
96
|
+
|
|
97
|
+
# Context Window Usage
|
|
98
|
+
0 / 204.8K tokens used (0%)
|
|
99
|
+
|
|
100
|
+
# Current Mode
|
|
101
|
+
PLAN MODE
|
|
102
|
+
</environmentDetails>`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function extractText(content: unknown): string {
|
|
106
|
+
if (typeof content === "string") return content.trim();
|
|
107
|
+
if (Array.isArray(content)) {
|
|
108
|
+
return (content as any[])
|
|
109
|
+
.filter((p: any) => p?.type === "text" && typeof p?.text === "string")
|
|
110
|
+
.map((p: any) => p.text)
|
|
111
|
+
.join("\n\n")
|
|
112
|
+
.trim();
|
|
113
|
+
}
|
|
114
|
+
return "";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isClineWrapped(content: unknown): boolean {
|
|
118
|
+
if (!Array.isArray(content)) return false;
|
|
119
|
+
const texts = (content as any[])
|
|
120
|
+
.filter((p: any) => p?.type === "text" && typeof p?.text === "string")
|
|
121
|
+
.map((p: any) => p.text as string);
|
|
122
|
+
return (
|
|
123
|
+
texts.some((t) => /<task>[\s\S]*<\/task>/.test(t)) &&
|
|
124
|
+
texts.some((t) => t.includes("task_progress List")) &&
|
|
125
|
+
texts.some((t) => t.includes("<environmentDetails>"))
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function extractTaskBody(content: unknown): string {
|
|
130
|
+
if (!Array.isArray(content)) return "";
|
|
131
|
+
for (const p of content as any[]) {
|
|
132
|
+
if (p?.type !== "text" || typeof p?.text !== "string") continue;
|
|
133
|
+
const m = p.text.match(/<task>\s*([\s\S]*?)\s*<\/task>/);
|
|
134
|
+
if (m?.[1]) return m[1].trim();
|
|
135
|
+
}
|
|
136
|
+
return "";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function shapeMessagesForCline(messages: any[]): any[] {
|
|
140
|
+
let lastWrappedIdx = -1;
|
|
141
|
+
let baseTranscript = "";
|
|
142
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
143
|
+
if (messages[i]?.role !== "user") continue;
|
|
144
|
+
if (!isClineWrapped(messages[i]?.content)) continue;
|
|
145
|
+
lastWrappedIdx = i;
|
|
146
|
+
baseTranscript = extractTaskBody(messages[i].content);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const parts: string[] = baseTranscript ? [baseTranscript] : [];
|
|
151
|
+
const startIdx = lastWrappedIdx >= 0 ? lastWrappedIdx + 1 : 0;
|
|
152
|
+
|
|
153
|
+
for (let i = startIdx; i < messages.length; i++) {
|
|
154
|
+
const msg = messages[i];
|
|
155
|
+
const role = msg?.role ?? "user";
|
|
156
|
+
if (role === "system") continue;
|
|
157
|
+
if (role === "user" && isClineWrapped(msg?.content)) continue;
|
|
158
|
+
const text = extractText(msg?.content).trim();
|
|
159
|
+
if (!text) continue;
|
|
160
|
+
|
|
161
|
+
if (role === "tool") {
|
|
162
|
+
parts.push(`<tool_result>\n${text}\n</tool_result>`);
|
|
163
|
+
} else if (role !== "assistant") {
|
|
164
|
+
parts.push(`[${role}]\n${text}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const transcript = parts.join("\n\n").trim() || "(no conversation yet)";
|
|
169
|
+
const envDetails = buildEnvironmentDetails();
|
|
170
|
+
|
|
171
|
+
const collapsed: any[] = [];
|
|
172
|
+
const systemMsg = messages.find((m: any) => m?.role === "system");
|
|
173
|
+
if (systemMsg) {
|
|
174
|
+
const systemText = extractText(systemMsg.content);
|
|
175
|
+
if (systemText) collapsed.push({ role: "system", content: systemText });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
collapsed.push({
|
|
179
|
+
role: "user",
|
|
180
|
+
content: [
|
|
181
|
+
{ type: "text", text: `<task>\n${transcript}\n</task>` },
|
|
182
|
+
{ type: "text", text: TASK_PROGRESS_BLOCK },
|
|
183
|
+
{ type: "text", text: envDetails },
|
|
184
|
+
],
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return collapsed;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// =============================================================================
|
|
191
|
+
// Extension entry point
|
|
192
|
+
// =============================================================================
|
|
193
|
+
|
|
194
|
+
export default async function (pi: ExtensionAPI) {
|
|
195
|
+
// Fetch ALL models from OpenRouter (free and paid)
|
|
196
|
+
// The global /free toggle will filter based on cost.input
|
|
197
|
+
let allModels = await fetchClineModels(false).catch((err) => {
|
|
198
|
+
logWarning("cline", "Failed to fetch models at startup", err);
|
|
199
|
+
return [];
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Also fetch free-only list for the global toggle's free filter
|
|
203
|
+
let freeModels = allModels.filter((m) => m.cost.input === 0);
|
|
204
|
+
|
|
205
|
+
// Create re-register function for global toggle
|
|
206
|
+
const reRegister = (m: typeof allModels) => {
|
|
207
|
+
pi.registerProvider(PROVIDER_CLINE, {
|
|
208
|
+
baseUrl: BASE_URL_CLINE,
|
|
209
|
+
api: "openai-completions" as const,
|
|
210
|
+
authHeader: false,
|
|
211
|
+
headers: buildClineHeaders(),
|
|
212
|
+
models: enhanceWithCI(m),
|
|
213
|
+
oauth: {
|
|
214
|
+
name: "Cline",
|
|
215
|
+
login: loginCline,
|
|
216
|
+
refreshToken: refreshClineToken,
|
|
217
|
+
getApiKey: toApiKey,
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Register with global toggle (separate free and all lists)
|
|
223
|
+
registerWithGlobalToggle(
|
|
224
|
+
PROVIDER_CLINE,
|
|
225
|
+
{ free: freeModels, all: allModels },
|
|
226
|
+
(m) => reRegister(m),
|
|
227
|
+
false, // no key until OAuth
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
// Initial registration with all models
|
|
231
|
+
reRegister(allModels);
|
|
232
|
+
|
|
233
|
+
// Per-provider toggle command (works independently of global /free)
|
|
234
|
+
let showPaidModels = false;
|
|
235
|
+
let currentModels = allModels;
|
|
236
|
+
pi.registerCommand("cline-toggle", {
|
|
237
|
+
description: "Toggle between free and all Cline models",
|
|
238
|
+
handler: async (_args, ctx) => {
|
|
239
|
+
showPaidModels = !showPaidModels;
|
|
240
|
+
|
|
241
|
+
// Determine which models to show
|
|
242
|
+
const modelsToShow =
|
|
243
|
+
showPaidModels && allModels.length > 0 ? allModels : freeModels;
|
|
244
|
+
|
|
245
|
+
currentModels = modelsToShow;
|
|
246
|
+
reRegister(modelsToShow);
|
|
247
|
+
|
|
248
|
+
const freeCount = freeModels.length;
|
|
249
|
+
const paidCount = allModels.length - freeCount;
|
|
250
|
+
|
|
251
|
+
if (showPaidModels && allModels.length > 0) {
|
|
252
|
+
ctx.ui.notify(
|
|
253
|
+
`cline: showing all ${allModels.length} models (${freeCount} free, ${paidCount} paid)`,
|
|
254
|
+
"info",
|
|
255
|
+
);
|
|
256
|
+
} else {
|
|
257
|
+
ctx.ui.notify(
|
|
258
|
+
`cline: showing ${freeCount} free models (${paidCount} paid hidden)`,
|
|
259
|
+
"info",
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Cline-specific: refresh task ID and re-register headers before each agent run
|
|
266
|
+
pi.on("before_agent_start", async (_event, ctx) => {
|
|
267
|
+
if (ctx.model?.provider !== PROVIDER_CLINE) return;
|
|
268
|
+
_currentTaskId = generateUlid();
|
|
269
|
+
reRegister(freeModels);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Cline-specific: shape messages to Cline's expected envelope format
|
|
273
|
+
pi.on("context", async (event, ctx) => {
|
|
274
|
+
if (ctx.model?.provider !== PROVIDER_CLINE) return;
|
|
275
|
+
const sourceMessages = Array.isArray(event.messages) ? event.messages : [];
|
|
276
|
+
return { messages: shapeMessagesForCline(sourceMessages) };
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Cline-specific: refresh model list at session start
|
|
280
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
281
|
+
try {
|
|
282
|
+
const fresh = await fetchClineModels(false);
|
|
283
|
+
if (fresh.length > 0) {
|
|
284
|
+
allModels = fresh;
|
|
285
|
+
freeModels = allModels.filter((m) => m.cost.input === 0);
|
|
286
|
+
reRegister(allModels);
|
|
287
|
+
if (ctx.model?.provider === PROVIDER_CLINE) {
|
|
288
|
+
const freeCount = freeModels.length;
|
|
289
|
+
const paidCount = allModels.length - freeCount;
|
|
290
|
+
ctx.ui.notify(
|
|
291
|
+
`Cline: ${freeCount} free, ${paidCount} paid models available`,
|
|
292
|
+
"info",
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
} catch (err) {
|
|
297
|
+
logWarning("cline", "Failed to refresh models at session start", err);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|