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
package/providers/cline/cline.ts
CHANGED
|
@@ -1,205 +1,205 @@
|
|
|
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 "@earendil-works/pi-ai";
|
|
18
|
-
import type {
|
|
19
|
-
ExtensionAPI,
|
|
20
|
-
ProviderModelConfig,
|
|
21
|
-
} from "@earendil-works/pi-coding-agent";
|
|
22
|
-
import { getClineApiKey, getClineShowPaid } from "../../config.ts";
|
|
23
|
-
import { BASE_URL_CLINE, PROVIDER_CLINE } from "../../constants.ts";
|
|
24
|
-
import {
|
|
25
|
-
DEFAULT_PROVIDER_CACHE_TTL_MS,
|
|
26
|
-
isProviderCacheFresh,
|
|
27
|
-
loadProviderCache,
|
|
28
|
-
saveProviderCache,
|
|
29
|
-
} from "../../lib/provider-cache.ts";
|
|
30
|
-
import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
|
|
31
|
-
import { wrapSessionStartHandler } from "../../lib/session-start-metrics.ts";
|
|
32
|
-
import { createToggleState } from "../../lib/toggle-state.ts";
|
|
33
|
-
import { logWarning } from "../../lib/util.ts";
|
|
34
|
-
import { enhanceWithCI } from "../../provider-helper.ts";
|
|
35
|
-
import { loginCline, refreshClineToken } from "./cline-auth.ts";
|
|
36
|
-
import { fetchClineModels } from "./cline-models.ts";
|
|
37
|
-
import { streamClineXml } from "./cline-xml-bridge.ts";
|
|
38
|
-
|
|
39
|
-
// =============================================================================
|
|
40
|
-
// Cline API headers (must match real Cline VS Code extension exactly)
|
|
41
|
-
// =============================================================================
|
|
42
|
-
|
|
43
|
-
const VS_CODE_VERSION = "1.109.3";
|
|
44
|
-
const CLINE_EXTENSION_VERSION = "3.76.0";
|
|
45
|
-
let _currentTaskId = generateUlid();
|
|
46
|
-
|
|
47
|
-
function generateUlid(): string {
|
|
48
|
-
const CHARS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
49
|
-
const now = Date.now();
|
|
50
|
-
let ts = "";
|
|
51
|
-
let t = now;
|
|
52
|
-
for (let i = 0; i < 10; i++) {
|
|
53
|
-
ts = CHARS[t % 32] + ts;
|
|
54
|
-
t = Math.floor(t / 32);
|
|
55
|
-
}
|
|
56
|
-
const rand = new Uint8Array(16);
|
|
57
|
-
crypto.getRandomValues(rand);
|
|
58
|
-
let r = "";
|
|
59
|
-
for (let i = 0; i < 16; i++) r += CHARS[rand[i] % 32];
|
|
60
|
-
return ts + r;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function buildClineHeaders(): Record<string, string> {
|
|
64
|
-
return {
|
|
65
|
-
"HTTP-Referer": "https://cline.bot",
|
|
66
|
-
"X-Title": "Cline",
|
|
67
|
-
"X-Task-ID": _currentTaskId,
|
|
68
|
-
"X-PLATFORM": "Visual Studio Code",
|
|
69
|
-
"X-PLATFORM-VERSION": VS_CODE_VERSION,
|
|
70
|
-
"X-CLIENT-TYPE": "VSCode Extension",
|
|
71
|
-
"X-CLIENT-VERSION": CLINE_EXTENSION_VERSION,
|
|
72
|
-
"X-CORE-VERSION": CLINE_EXTENSION_VERSION,
|
|
73
|
-
"X-Is-Multiroot": "false",
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function toApiKey(credentials: OAuthCredentials): string {
|
|
78
|
-
const token = credentials.access;
|
|
79
|
-
return token.startsWith("workos:") ? token : `workos:${token}`;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// =============================================================================
|
|
83
|
-
// Extension entry point
|
|
84
|
-
// =============================================================================
|
|
85
|
-
|
|
86
|
-
export default async function clineProvider(pi: ExtensionAPI) {
|
|
87
|
-
const clineApiKey = getClineApiKey();
|
|
88
|
-
const useApiKeyAuth = !!clineApiKey;
|
|
89
|
-
|
|
90
|
-
let allModels: ProviderModelConfig[];
|
|
91
|
-
const cachedModels = loadProviderCache(PROVIDER_CLINE);
|
|
92
|
-
if (cachedModels && cachedModels.length > 0) {
|
|
93
|
-
allModels = cachedModels;
|
|
94
|
-
} else {
|
|
95
|
-
allModels = await fetchClineModels(false).catch((err) => {
|
|
96
|
-
logWarning("cline", "Failed to fetch models at startup", err);
|
|
97
|
-
return [];
|
|
98
|
-
});
|
|
99
|
-
if (allModels.length > 0) {
|
|
100
|
-
saveProviderCache(PROVIDER_CLINE, allModels).catch((err) => {
|
|
101
|
-
logWarning("cline", "Failed to save model cache", err);
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
let freeModels = allModels.filter((m) =>
|
|
106
|
-
isFreeModel({ ...m, provider: PROVIDER_CLINE }, allModels),
|
|
107
|
-
);
|
|
108
|
-
const stored = { free: freeModels, all: allModels };
|
|
109
|
-
const toggleState = createToggleState({
|
|
110
|
-
providerId: PROVIDER_CLINE,
|
|
111
|
-
initialShowPaid: getClineShowPaid(),
|
|
112
|
-
initialModels: stored,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
const reRegister = (m: typeof allModels) => {
|
|
116
|
-
pi.registerProvider(PROVIDER_CLINE, {
|
|
117
|
-
baseUrl: BASE_URL_CLINE,
|
|
118
|
-
api: "cline-xml-tools" as const,
|
|
119
|
-
authHeader: false,
|
|
120
|
-
apiKey: clineApiKey,
|
|
121
|
-
headers: buildClineHeaders(),
|
|
122
|
-
streamSimple: (model, context, options) =>
|
|
123
|
-
streamClineXml(model as any, context, options, buildClineHeaders()),
|
|
124
|
-
models: enhanceWithCI(m),
|
|
125
|
-
...(useApiKeyAuth
|
|
126
|
-
? {}
|
|
127
|
-
: {
|
|
128
|
-
oauth: {
|
|
129
|
-
name: "Cline",
|
|
130
|
-
login: loginCline,
|
|
131
|
-
refreshToken: refreshClineToken,
|
|
132
|
-
getApiKey: toApiKey,
|
|
133
|
-
},
|
|
134
|
-
}),
|
|
135
|
-
});
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const applyModelList = (models: ProviderModelConfig[]) => {
|
|
139
|
-
allModels = models;
|
|
140
|
-
freeModels = allModels.filter((m) =>
|
|
141
|
-
isFreeModel({ ...m, provider: PROVIDER_CLINE }, allModels),
|
|
142
|
-
);
|
|
143
|
-
stored.all = allModels;
|
|
144
|
-
stored.free = freeModels;
|
|
145
|
-
toggleState.setModels(stored);
|
|
146
|
-
toggleState.applyCurrent(reRegister);
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
// Register with global toggle system (hasKey=true if API key auth configured)
|
|
150
|
-
registerWithGlobalToggle(PROVIDER_CLINE, stored, (m) => reRegister(m), useApiKeyAuth);
|
|
151
|
-
toggleState.applyCurrent(reRegister);
|
|
152
|
-
|
|
153
|
-
pi.registerCommand("toggle-cline", {
|
|
154
|
-
description: "Toggle between free and all Cline models",
|
|
155
|
-
handler: (_args, ctx) => {
|
|
156
|
-
const applied = toggleState.toggle(reRegister);
|
|
157
|
-
const freeCount = stored.free.length;
|
|
158
|
-
const paidCount = stored.all.length - freeCount;
|
|
159
|
-
|
|
160
|
-
if (applied.mode === "all") {
|
|
161
|
-
ctx.ui.notify(
|
|
162
|
-
`cline: showing all ${stored.all.length} models (${freeCount} free, ${paidCount} paid)`,
|
|
163
|
-
"info",
|
|
164
|
-
);
|
|
165
|
-
} else {
|
|
166
|
-
ctx.ui.notify(
|
|
167
|
-
`cline: showing ${freeCount} free models (${paidCount} paid hidden)`,
|
|
168
|
-
"info",
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
return Promise.resolve();
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
pi.on("before_agent_start", (_event, ctx) => {
|
|
176
|
-
if (ctx.model?.provider !== PROVIDER_CLINE) return;
|
|
177
|
-
_currentTaskId = generateUlid();
|
|
178
|
-
toggleState.applyCurrent(reRegister);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
let refreshInFlight: Promise<void> | undefined;
|
|
182
|
-
pi.on(
|
|
183
|
-
"session_start",
|
|
184
|
-
wrapSessionStartHandler("cline", () => {
|
|
185
|
-
if (refreshInFlight) return Promise.resolve();
|
|
186
|
-
if (isProviderCacheFresh(PROVIDER_CLINE, DEFAULT_PROVIDER_CACHE_TTL_MS)) {
|
|
187
|
-
return Promise.resolve();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
refreshInFlight = fetchClineModels(false)
|
|
191
|
-
.then(async (fresh) => {
|
|
192
|
-
if (fresh.length === 0) return;
|
|
193
|
-
await saveProviderCache(PROVIDER_CLINE, fresh);
|
|
194
|
-
applyModelList(fresh);
|
|
195
|
-
})
|
|
196
|
-
.catch((err) => {
|
|
197
|
-
logWarning("cline", "Failed to refresh models at session start", err);
|
|
198
|
-
})
|
|
199
|
-
.finally(() => {
|
|
200
|
-
refreshInFlight = undefined;
|
|
201
|
-
});
|
|
202
|
-
return Promise.resolve();
|
|
203
|
-
}),
|
|
204
|
-
);
|
|
205
|
-
}
|
|
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 "@earendil-works/pi-ai/compat";
|
|
18
|
+
import type {
|
|
19
|
+
ExtensionAPI,
|
|
20
|
+
ProviderModelConfig,
|
|
21
|
+
} from "@earendil-works/pi-coding-agent";
|
|
22
|
+
import { getClineApiKey, getClineShowPaid } from "../../config.ts";
|
|
23
|
+
import { BASE_URL_CLINE, PROVIDER_CLINE } from "../../constants.ts";
|
|
24
|
+
import {
|
|
25
|
+
DEFAULT_PROVIDER_CACHE_TTL_MS,
|
|
26
|
+
isProviderCacheFresh,
|
|
27
|
+
loadProviderCache,
|
|
28
|
+
saveProviderCache,
|
|
29
|
+
} from "../../lib/provider-cache.ts";
|
|
30
|
+
import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
|
|
31
|
+
import { wrapSessionStartHandler } from "../../lib/session-start-metrics.ts";
|
|
32
|
+
import { createToggleState } from "../../lib/toggle-state.ts";
|
|
33
|
+
import { logWarning } from "../../lib/util.ts";
|
|
34
|
+
import { enhanceWithCI } from "../../provider-helper.ts";
|
|
35
|
+
import { loginCline, refreshClineToken } from "./cline-auth.ts";
|
|
36
|
+
import { fetchClineModels } from "./cline-models.ts";
|
|
37
|
+
import { streamClineXml } from "./cline-xml-bridge.ts";
|
|
38
|
+
|
|
39
|
+
// =============================================================================
|
|
40
|
+
// Cline API headers (must match real Cline VS Code extension exactly)
|
|
41
|
+
// =============================================================================
|
|
42
|
+
|
|
43
|
+
const VS_CODE_VERSION = "1.109.3";
|
|
44
|
+
const CLINE_EXTENSION_VERSION = "3.76.0";
|
|
45
|
+
let _currentTaskId = generateUlid();
|
|
46
|
+
|
|
47
|
+
function generateUlid(): string {
|
|
48
|
+
const CHARS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
let ts = "";
|
|
51
|
+
let t = now;
|
|
52
|
+
for (let i = 0; i < 10; i++) {
|
|
53
|
+
ts = CHARS[t % 32] + ts;
|
|
54
|
+
t = Math.floor(t / 32);
|
|
55
|
+
}
|
|
56
|
+
const rand = new Uint8Array(16);
|
|
57
|
+
crypto.getRandomValues(rand);
|
|
58
|
+
let r = "";
|
|
59
|
+
for (let i = 0; i < 16; i++) r += CHARS[rand[i] % 32];
|
|
60
|
+
return ts + r;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildClineHeaders(): Record<string, string> {
|
|
64
|
+
return {
|
|
65
|
+
"HTTP-Referer": "https://cline.bot",
|
|
66
|
+
"X-Title": "Cline",
|
|
67
|
+
"X-Task-ID": _currentTaskId,
|
|
68
|
+
"X-PLATFORM": "Visual Studio Code",
|
|
69
|
+
"X-PLATFORM-VERSION": VS_CODE_VERSION,
|
|
70
|
+
"X-CLIENT-TYPE": "VSCode Extension",
|
|
71
|
+
"X-CLIENT-VERSION": CLINE_EXTENSION_VERSION,
|
|
72
|
+
"X-CORE-VERSION": CLINE_EXTENSION_VERSION,
|
|
73
|
+
"X-Is-Multiroot": "false",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function toApiKey(credentials: OAuthCredentials): string {
|
|
78
|
+
const token = credentials.access;
|
|
79
|
+
return token.startsWith("workos:") ? token : `workos:${token}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// =============================================================================
|
|
83
|
+
// Extension entry point
|
|
84
|
+
// =============================================================================
|
|
85
|
+
|
|
86
|
+
export default async function clineProvider(pi: ExtensionAPI) {
|
|
87
|
+
const clineApiKey = getClineApiKey();
|
|
88
|
+
const useApiKeyAuth = !!clineApiKey;
|
|
89
|
+
|
|
90
|
+
let allModels: ProviderModelConfig[];
|
|
91
|
+
const cachedModels = loadProviderCache(PROVIDER_CLINE);
|
|
92
|
+
if (cachedModels && cachedModels.length > 0) {
|
|
93
|
+
allModels = cachedModels;
|
|
94
|
+
} else {
|
|
95
|
+
allModels = await fetchClineModels(false).catch((err) => {
|
|
96
|
+
logWarning("cline", "Failed to fetch models at startup", err);
|
|
97
|
+
return [];
|
|
98
|
+
});
|
|
99
|
+
if (allModels.length > 0) {
|
|
100
|
+
saveProviderCache(PROVIDER_CLINE, allModels).catch((err) => {
|
|
101
|
+
logWarning("cline", "Failed to save model cache", err);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
let freeModels = allModels.filter((m) =>
|
|
106
|
+
isFreeModel({ ...m, provider: PROVIDER_CLINE }, allModels),
|
|
107
|
+
);
|
|
108
|
+
const stored = { free: freeModels, all: allModels };
|
|
109
|
+
const toggleState = createToggleState({
|
|
110
|
+
providerId: PROVIDER_CLINE,
|
|
111
|
+
initialShowPaid: getClineShowPaid(),
|
|
112
|
+
initialModels: stored,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const reRegister = (m: typeof allModels) => {
|
|
116
|
+
pi.registerProvider(PROVIDER_CLINE, {
|
|
117
|
+
baseUrl: BASE_URL_CLINE,
|
|
118
|
+
api: "cline-xml-tools" as const,
|
|
119
|
+
authHeader: false,
|
|
120
|
+
apiKey: clineApiKey,
|
|
121
|
+
headers: buildClineHeaders(),
|
|
122
|
+
streamSimple: (model, context, options) =>
|
|
123
|
+
streamClineXml(model as any, context, options, buildClineHeaders()),
|
|
124
|
+
models: enhanceWithCI(m),
|
|
125
|
+
...(useApiKeyAuth
|
|
126
|
+
? {}
|
|
127
|
+
: {
|
|
128
|
+
oauth: {
|
|
129
|
+
name: "Cline",
|
|
130
|
+
login: loginCline,
|
|
131
|
+
refreshToken: refreshClineToken,
|
|
132
|
+
getApiKey: toApiKey,
|
|
133
|
+
},
|
|
134
|
+
}),
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const applyModelList = (models: ProviderModelConfig[]) => {
|
|
139
|
+
allModels = models;
|
|
140
|
+
freeModels = allModels.filter((m) =>
|
|
141
|
+
isFreeModel({ ...m, provider: PROVIDER_CLINE }, allModels),
|
|
142
|
+
);
|
|
143
|
+
stored.all = allModels;
|
|
144
|
+
stored.free = freeModels;
|
|
145
|
+
toggleState.setModels(stored);
|
|
146
|
+
toggleState.applyCurrent(reRegister);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Register with global toggle system (hasKey=true if API key auth configured)
|
|
150
|
+
registerWithGlobalToggle(PROVIDER_CLINE, stored, (m) => reRegister(m), useApiKeyAuth);
|
|
151
|
+
toggleState.applyCurrent(reRegister);
|
|
152
|
+
|
|
153
|
+
pi.registerCommand("toggle-cline", {
|
|
154
|
+
description: "Toggle between free and all Cline models",
|
|
155
|
+
handler: (_args, ctx) => {
|
|
156
|
+
const applied = toggleState.toggle(reRegister);
|
|
157
|
+
const freeCount = stored.free.length;
|
|
158
|
+
const paidCount = stored.all.length - freeCount;
|
|
159
|
+
|
|
160
|
+
if (applied.mode === "all") {
|
|
161
|
+
ctx.ui.notify(
|
|
162
|
+
`cline: showing all ${stored.all.length} models (${freeCount} free, ${paidCount} paid)`,
|
|
163
|
+
"info",
|
|
164
|
+
);
|
|
165
|
+
} else {
|
|
166
|
+
ctx.ui.notify(
|
|
167
|
+
`cline: showing ${freeCount} free models (${paidCount} paid hidden)`,
|
|
168
|
+
"info",
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return Promise.resolve();
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
pi.on("before_agent_start", (_event, ctx) => {
|
|
176
|
+
if (ctx.model?.provider !== PROVIDER_CLINE) return;
|
|
177
|
+
_currentTaskId = generateUlid();
|
|
178
|
+
toggleState.applyCurrent(reRegister);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
let refreshInFlight: Promise<void> | undefined;
|
|
182
|
+
pi.on(
|
|
183
|
+
"session_start",
|
|
184
|
+
wrapSessionStartHandler("cline", () => {
|
|
185
|
+
if (refreshInFlight) return Promise.resolve();
|
|
186
|
+
if (isProviderCacheFresh(PROVIDER_CLINE, DEFAULT_PROVIDER_CACHE_TTL_MS)) {
|
|
187
|
+
return Promise.resolve();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
refreshInFlight = fetchClineModels(false)
|
|
191
|
+
.then(async (fresh) => {
|
|
192
|
+
if (fresh.length === 0) return;
|
|
193
|
+
await saveProviderCache(PROVIDER_CLINE, fresh);
|
|
194
|
+
applyModelList(fresh);
|
|
195
|
+
})
|
|
196
|
+
.catch((err) => {
|
|
197
|
+
logWarning("cline", "Failed to refresh models at session start", err);
|
|
198
|
+
})
|
|
199
|
+
.finally(() => {
|
|
200
|
+
refreshInFlight = undefined;
|
|
201
|
+
});
|
|
202
|
+
return Promise.resolve();
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
}
|