pi-free 2.0.2 → 2.0.5
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 +84 -12
- package/README.md +44 -97
- package/banner.svg +132 -0
- package/config.ts +24 -52
- package/constants.ts +6 -0
- package/index.ts +175 -148
- package/lib/built-in-toggle.ts +40 -1
- package/lib/model-detection.ts +176 -139
- 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 +200 -144
- package/lib/types.ts +101 -108
- package/lib/util.ts +262 -256
- package/package.json +9 -8
- package/provider-failover/benchmark-lookup.ts +191 -140
- package/provider-helper.ts +19 -1
- package/providers/cline/cline-auth.ts +473 -473
- package/providers/cline/cline.ts +58 -14
- package/providers/crofai/crofai.ts +170 -0
- package/providers/dynamic-built-in/index.ts +260 -308
- package/providers/kilo/kilo-auth.ts +155 -155
- package/providers/kilo/kilo.ts +263 -235
- package/providers/nvidia/nvidia.ts +474 -415
- package/providers/ollama/ollama.ts +295 -280
- package/providers/opencode-session.ts +3 -4
- package/providers/qwen/qwen-models.ts +101 -101
- package/providers/qwen/qwen.ts +47 -49
- package/providers/zenmux/zenmux.ts +176 -0
- package/scripts/check-extensions.mjs +71 -55
- package/provider-factory.ts +0 -207
- package/providers/cloudflare/cloudflare.ts +0 -526
- package/providers/modal/modal.ts +0 -47
package/lib/model-detection.ts
CHANGED
|
@@ -60,6 +60,83 @@ export function toProviderModelInfo(model: ProviderModelConfig): ModelInfo {
|
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// Shared helpers for model family detection
|
|
65
|
+
// =============================================================================
|
|
66
|
+
|
|
67
|
+
const VERSION_RE = /^v?\d+(\.\d+)?$/;
|
|
68
|
+
const ROUTER_RE = /\b(?:router|auto)\b/;
|
|
69
|
+
const SKIP_PARTS = new Set([
|
|
70
|
+
"latest",
|
|
71
|
+
"preview",
|
|
72
|
+
"rc",
|
|
73
|
+
"beta",
|
|
74
|
+
"alpha",
|
|
75
|
+
"dev",
|
|
76
|
+
"free",
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
interface BrandMapping {
|
|
80
|
+
keywords: string[];
|
|
81
|
+
familyId: string;
|
|
82
|
+
familyName: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const BRAND_MAPPINGS: BrandMapping[] = [
|
|
86
|
+
{ keywords: ["claude"], familyId: "claude", familyName: "Claude" },
|
|
87
|
+
{ keywords: ["deepseek"], familyId: "deepseek", familyName: "DeepSeek" },
|
|
88
|
+
{ keywords: ["gemini"], familyId: "gemini", familyName: "Gemini" },
|
|
89
|
+
{ keywords: ["gpt"], familyId: "gpt", familyName: "GPT" },
|
|
90
|
+
{ keywords: ["llama"], familyId: "llama", familyName: "Llama" },
|
|
91
|
+
{ keywords: ["minimax"], familyId: "minimax", familyName: "MiniMax" },
|
|
92
|
+
{ keywords: ["qwen"], familyId: "qwen", familyName: "Qwen" },
|
|
93
|
+
{ keywords: ["nemotron"], familyId: "nemotron", familyName: "Nemotron" },
|
|
94
|
+
{ keywords: ["kimi", "moonshot"], familyId: "kimi", familyName: "Kimi" },
|
|
95
|
+
{ keywords: ["glm", "chatglm"], familyId: "glm", familyName: "GLM" },
|
|
96
|
+
{ keywords: ["mistral"], familyId: "mistral", familyName: "Mistral" },
|
|
97
|
+
{ keywords: ["arcee", "trinity"], familyId: "arcee", familyName: "Arcee" },
|
|
98
|
+
{ keywords: ["o1", "o3"], familyId: "openai-o", familyName: "OpenAI o" },
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
const PROVIDER_MAPPINGS: Record<
|
|
102
|
+
string,
|
|
103
|
+
{ familyId: string; familyName: string }
|
|
104
|
+
> = {
|
|
105
|
+
minimax: { familyId: "minimax", familyName: "MiniMax" },
|
|
106
|
+
minimaxai: { familyId: "minimax", familyName: "MiniMax" },
|
|
107
|
+
deepseek: { familyId: "deepseek", familyName: "DeepSeek" },
|
|
108
|
+
nvidia: { familyId: "nemotron", familyName: "Nemotron" },
|
|
109
|
+
moonshot: { familyId: "kimi", familyName: "Kimi" },
|
|
110
|
+
zhipu: { familyId: "glm", familyName: "GLM" },
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
function capitalize(s: string): string {
|
|
114
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function findBrandInText(
|
|
118
|
+
text: string,
|
|
119
|
+
): { familyId: string; familyName: string } | null {
|
|
120
|
+
for (const mapping of BRAND_MAPPINGS) {
|
|
121
|
+
for (const keyword of mapping.keywords) {
|
|
122
|
+
if (text.includes(keyword)) {
|
|
123
|
+
return { familyId: mapping.familyId, familyName: mapping.familyName };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function findBrandInParts(
|
|
131
|
+
parts: string[],
|
|
132
|
+
): { familyId: string; familyName: string } | null {
|
|
133
|
+
for (const part of parts) {
|
|
134
|
+
const result = findBrandInText(part);
|
|
135
|
+
if (result) return result;
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
63
140
|
/**
|
|
64
141
|
* Detect the model family from a model's ID or name.
|
|
65
142
|
* Returns the family ID and display name.
|
|
@@ -72,116 +149,41 @@ export function detectModelFamily(
|
|
|
72
149
|
const fullText = `${id} ${name}`;
|
|
73
150
|
|
|
74
151
|
// Router models (gateways to free models) - group into "other"
|
|
75
|
-
if (
|
|
152
|
+
if (ROUTER_RE.test(fullText) || id === "kilo-auto/free") {
|
|
76
153
|
return { familyId: "other", familyName: "Other" };
|
|
77
154
|
}
|
|
78
155
|
|
|
79
|
-
// Known brand keywords
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
familyId: string;
|
|
83
|
-
familyName: string;
|
|
84
|
-
}[] = [
|
|
85
|
-
{ keywords: ["claude"], familyId: "claude", familyName: "Claude" },
|
|
86
|
-
{ keywords: ["deepseek"], familyId: "deepseek", familyName: "DeepSeek" },
|
|
87
|
-
{ keywords: ["gemini"], familyId: "gemini", familyName: "Gemini" },
|
|
88
|
-
{ keywords: ["gpt"], familyId: "gpt", familyName: "GPT" },
|
|
89
|
-
{ keywords: ["llama"], familyId: "llama", familyName: "Llama" },
|
|
90
|
-
{ keywords: ["minimax"], familyId: "minimax", familyName: "MiniMax" },
|
|
91
|
-
{ keywords: ["qwen"], familyId: "qwen", familyName: "Qwen" },
|
|
92
|
-
{ keywords: ["nemotron"], familyId: "nemotron", familyName: "Nemotron" },
|
|
93
|
-
{ keywords: ["kimi", "moonshot"], familyId: "kimi", familyName: "Kimi" },
|
|
94
|
-
{ keywords: ["glm", "chatglm"], familyId: "glm", familyName: "GLM" },
|
|
95
|
-
{ keywords: ["mistral"], familyId: "mistral", familyName: "Mistral" },
|
|
96
|
-
{ keywords: ["arcee", "trinity"], familyId: "arcee", familyName: "Arcee" },
|
|
97
|
-
{ keywords: ["o1", "o3"], familyId: "openai-o", familyName: "OpenAI o" },
|
|
98
|
-
];
|
|
99
|
-
|
|
100
|
-
// Check for known brands in ID or name
|
|
101
|
-
for (const mapping of brandMappings) {
|
|
102
|
-
for (const keyword of mapping.keywords) {
|
|
103
|
-
if (fullText.includes(keyword)) {
|
|
104
|
-
return { familyId: mapping.familyId, familyName: mapping.familyName };
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
156
|
+
// Known brand keywords in full text
|
|
157
|
+
const brandFromText = findBrandInText(fullText);
|
|
158
|
+
if (brandFromText) return brandFromText;
|
|
108
159
|
|
|
109
160
|
// Provider-specific fallbacks for models without brand in ID/name
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
minimaxai: { familyId: "minimax", familyName: "MiniMax" },
|
|
113
|
-
deepseek: { familyId: "deepseek", familyName: "DeepSeek" },
|
|
114
|
-
nvidia: { familyId: "nemotron", familyName: "Nemotron" },
|
|
115
|
-
moonshot: { familyId: "kimi", familyName: "Kimi" },
|
|
116
|
-
zhipu: { familyId: "glm", familyName: "GLM" },
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
if (providerMappings[model.provider]) {
|
|
120
|
-
return providerMappings[model.provider];
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Helper to find brand in ID parts
|
|
124
|
-
function findBrandInParts(parts: string[]): { familyId: string; familyName: string } | null {
|
|
125
|
-
for (const part of parts) {
|
|
126
|
-
for (const mapping of brandMappings) {
|
|
127
|
-
for (const keyword of mapping.keywords) {
|
|
128
|
-
if (part.includes(keyword)) {
|
|
129
|
-
return { familyId: mapping.familyId, familyName: mapping.familyName };
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
161
|
+
const providerResult = PROVIDER_MAPPINGS[model.provider];
|
|
162
|
+
if (providerResult) return providerResult;
|
|
136
163
|
|
|
137
|
-
//
|
|
164
|
+
// Fallback: try to identify brand from model ID structure
|
|
138
165
|
const parts = id.split(/[-_:.@]/);
|
|
139
166
|
const firstPart = parts[0];
|
|
140
167
|
|
|
141
|
-
|
|
142
|
-
if (
|
|
143
|
-
const brandFromParts = findBrandInParts(parts.slice(1));
|
|
144
|
-
if (brandFromParts) {
|
|
145
|
-
return brandFromParts;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// If ID has multiple parts, check ALL parts for brand keywords
|
|
150
|
-
if (parts.length > 1) {
|
|
151
|
-
const brandFromParts = findBrandInParts(parts);
|
|
152
|
-
if (brandFromParts) {
|
|
153
|
-
return brandFromParts;
|
|
154
|
-
}
|
|
168
|
+
const brandFromParts = findBrandInParts(parts);
|
|
169
|
+
if (brandFromParts) return brandFromParts;
|
|
155
170
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
familyId: firstPart,
|
|
160
|
-
familyName: firstPart.charAt(0).toUpperCase() + firstPart.slice(1),
|
|
161
|
-
};
|
|
162
|
-
}
|
|
171
|
+
// Use first part as brand if it looks brand-like
|
|
172
|
+
if (firstPart && !VERSION_RE.test(firstPart)) {
|
|
173
|
+
return { familyId: firstPart, familyName: capitalize(firstPart) };
|
|
163
174
|
}
|
|
164
175
|
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
!/^v?\d+(\.\d+)?$/.test(part) &&
|
|
172
|
-
!["latest", "preview", "rc", "beta", "alpha", "dev", "free"].includes(part)
|
|
173
|
-
) {
|
|
174
|
-
return {
|
|
175
|
-
familyId: part,
|
|
176
|
-
familyName: part.charAt(0).toUpperCase() + part.slice(1),
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
}
|
|
176
|
+
// First non-version, non-skip part
|
|
177
|
+
const nonVersion = parts.find(
|
|
178
|
+
(p) => p && !VERSION_RE.test(p) && !SKIP_PARTS.has(p),
|
|
179
|
+
);
|
|
180
|
+
if (nonVersion) {
|
|
181
|
+
return { familyId: nonVersion, familyName: capitalize(nonVersion) };
|
|
180
182
|
}
|
|
181
183
|
|
|
182
184
|
return {
|
|
183
185
|
familyId: firstPart || id,
|
|
184
|
-
familyName: (firstPart || id)
|
|
186
|
+
familyName: capitalize(firstPart || id),
|
|
185
187
|
};
|
|
186
188
|
}
|
|
187
189
|
|
|
@@ -189,21 +191,83 @@ export function detectModelFamily(
|
|
|
189
191
|
* Normalize a model name for comparison by removing provider-specific suffixes
|
|
190
192
|
* and common qualifiers. This helps detect when the same model is offered by
|
|
191
193
|
* multiple providers with slightly different naming.
|
|
194
|
+
*
|
|
195
|
+
* Uses string operations instead of regex backtracking to avoid ReDoS warnings.
|
|
192
196
|
*/
|
|
193
197
|
export function normalizeModelName(name: string): string {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
);
|
|
198
|
+
const suffixes = ["(free)", "(cline)", "-free", "free"];
|
|
199
|
+
let normalized = name.toLowerCase().trimEnd();
|
|
200
|
+
|
|
201
|
+
// Remove common literal suffixes — simple string ops, no regex backtracking
|
|
202
|
+
for (const suffix of suffixes) {
|
|
203
|
+
while (normalized.endsWith(suffix)) {
|
|
204
|
+
normalized = normalized.slice(0, -suffix.length).trimEnd();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// CI score suffix — regex with disjoint char classes (linear)
|
|
209
|
+
normalized = normalized.replace(/\(ci:\s*[\d.]+\)$/, "").trimEnd();
|
|
210
|
+
normalized = normalized.replace(/\[ci:\s*[\d.]+\]$/, "").trimEnd();
|
|
211
|
+
|
|
212
|
+
// Remove any trailing parenthetical — non-regex loop
|
|
213
|
+
while (normalized.endsWith(")")) {
|
|
214
|
+
const idx = normalized.lastIndexOf("(", normalized.length - 1);
|
|
215
|
+
if (idx === -1) break;
|
|
216
|
+
normalized = normalized.slice(0, idx).trimEnd();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return normalized.trim();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Try to merge a model into another family if its normalized name
|
|
224
|
+
* matches a model in a different family.
|
|
225
|
+
*/
|
|
226
|
+
function tryMergeFamily(
|
|
227
|
+
byFamily: Map<string, ModelInfo[]>,
|
|
228
|
+
nameToFamilyId: Map<string, string>,
|
|
229
|
+
familyId: string,
|
|
230
|
+
model: ModelInfo,
|
|
231
|
+
): boolean {
|
|
232
|
+
const normalizedName = normalizeModelName(model.name || model.id);
|
|
233
|
+
if (!normalizedName) return false;
|
|
234
|
+
|
|
235
|
+
const existingFamilyForName = nameToFamilyId.get(normalizedName);
|
|
236
|
+
if (!existingFamilyForName || existingFamilyForName === familyId) {
|
|
237
|
+
nameToFamilyId.set(normalizedName, familyId);
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Same model name found in different family - merge them
|
|
242
|
+
const targetFamily = byFamily.get(existingFamilyForName);
|
|
243
|
+
const sourceFamily = byFamily.get(familyId);
|
|
244
|
+
if (!targetFamily || !sourceFamily) return false;
|
|
245
|
+
|
|
246
|
+
targetFamily.push(...sourceFamily);
|
|
247
|
+
byFamily.delete(familyId);
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Build a sorted list of ModelFamily from a by-family grouping map.
|
|
253
|
+
*/
|
|
254
|
+
function buildFamiliesList(byFamily: Map<string, ModelInfo[]>): ModelFamily[] {
|
|
255
|
+
const families: ModelFamily[] = [];
|
|
256
|
+
for (const [id, familyModels] of byFamily) {
|
|
257
|
+
const firstModel = familyModels[0]!;
|
|
258
|
+
const familyInfo = detectModelFamily(firstModel)!;
|
|
259
|
+
|
|
260
|
+
families.push({
|
|
261
|
+
id,
|
|
262
|
+
displayName: familyInfo.familyName,
|
|
263
|
+
models: familyModels.sort(
|
|
264
|
+
(a, b) =>
|
|
265
|
+
a.provider.localeCompare(b.provider) || b.id.localeCompare(a.id),
|
|
266
|
+
),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return families.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
|
207
271
|
}
|
|
208
272
|
|
|
209
273
|
/**
|
|
@@ -214,6 +278,7 @@ export function getModelFamilies(models: ModelInfo[]): ModelFamily[] {
|
|
|
214
278
|
const byFamily = new Map<string, ModelInfo[]>();
|
|
215
279
|
const nameToFamilyId = new Map<string, string>();
|
|
216
280
|
|
|
281
|
+
// First pass: group models by detected family
|
|
217
282
|
for (const model of models) {
|
|
218
283
|
const family = detectModelFamily(model);
|
|
219
284
|
if (!family) continue;
|
|
@@ -223,46 +288,18 @@ export function getModelFamilies(models: ModelInfo[]): ModelFamily[] {
|
|
|
223
288
|
byFamily.set(family.familyId, existing);
|
|
224
289
|
}
|
|
225
290
|
|
|
226
|
-
// Second pass: merge families
|
|
291
|
+
// Second pass: merge families whose models have the same normalized name
|
|
227
292
|
const familyIds = [...byFamily.keys()];
|
|
228
293
|
for (const familyId of familyIds) {
|
|
229
294
|
const familyModels = byFamily.get(familyId);
|
|
230
295
|
if (!familyModels) continue;
|
|
231
296
|
|
|
232
297
|
for (const model of familyModels) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const existingFamilyForName = nameToFamilyId.get(normalizedName);
|
|
237
|
-
if (existingFamilyForName && existingFamilyForName !== familyId) {
|
|
238
|
-
// Same model name found in different family - merge them
|
|
239
|
-
const targetFamily = byFamily.get(existingFamilyForName);
|
|
240
|
-
const sourceFamily = byFamily.get(familyId);
|
|
241
|
-
if (targetFamily && sourceFamily) {
|
|
242
|
-
targetFamily.push(...sourceFamily);
|
|
243
|
-
byFamily.delete(familyId);
|
|
244
|
-
break;
|
|
245
|
-
}
|
|
246
|
-
} else {
|
|
247
|
-
nameToFamilyId.set(normalizedName, familyId);
|
|
298
|
+
if (tryMergeFamily(byFamily, nameToFamilyId, familyId, model)) {
|
|
299
|
+
break;
|
|
248
300
|
}
|
|
249
301
|
}
|
|
250
302
|
}
|
|
251
303
|
|
|
252
|
-
|
|
253
|
-
for (const [id, familyModels] of byFamily) {
|
|
254
|
-
const firstModel = familyModels[0]!;
|
|
255
|
-
const familyInfo = detectModelFamily(firstModel)!;
|
|
256
|
-
|
|
257
|
-
families.push({
|
|
258
|
-
id,
|
|
259
|
-
displayName: familyInfo.familyName,
|
|
260
|
-
models: familyModels.sort(
|
|
261
|
-
(a, b) =>
|
|
262
|
-
a.provider.localeCompare(b.provider) || b.id.localeCompare(a.id),
|
|
263
|
-
),
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return families.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
|
304
|
+
return buildFamiliesList(byFamily);
|
|
268
305
|
}
|
package/lib/model-enhancer.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Model name enhancement helper
|
|
3
|
-
* Adds Coding Index scores to model names for display in /model
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent";
|
|
7
|
-
import { enhanceModelNameWithCodingIndex } from "../provider-failover/benchmark-lookup.ts";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Enhance model names with Coding Index scores
|
|
11
|
-
* Use this before registering providers to show CI in /model list
|
|
12
|
-
*/
|
|
13
|
-
export function enhanceModelsWithCodingIndex(
|
|
14
|
-
models: ProviderModelConfig[],
|
|
15
|
-
): ProviderModelConfig[] {
|
|
16
|
-
return models.map((m) => ({
|
|
17
|
-
...m,
|
|
18
|
-
name: enhanceModelNameWithCodingIndex(m.name, m.id),
|
|
19
|
-
}));
|
|
20
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Model name enhancement helper
|
|
3
|
+
* Adds Coding Index scores to model names for display in /model
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent";
|
|
7
|
+
import { enhanceModelNameWithCodingIndex } from "../provider-failover/benchmark-lookup.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Enhance model names with Coding Index scores
|
|
11
|
+
* Use this before registering providers to show CI in /model list
|
|
12
|
+
*/
|
|
13
|
+
export function enhanceModelsWithCodingIndex(
|
|
14
|
+
models: ProviderModelConfig[],
|
|
15
|
+
): ProviderModelConfig[] {
|
|
16
|
+
return models.map((m) => ({
|
|
17
|
+
...m,
|
|
18
|
+
name: enhanceModelNameWithCodingIndex(m.name, m.id),
|
|
19
|
+
}));
|
|
20
|
+
}
|
package/lib/open-browser.ts
CHANGED
|
@@ -25,7 +25,7 @@ export function openBrowser(url: string): void {
|
|
|
25
25
|
"-NoProfile",
|
|
26
26
|
"-NonInteractive",
|
|
27
27
|
"-Command",
|
|
28
|
-
`Start-Process "${url.replace(/"/g,
|
|
28
|
+
`Start-Process "${url.replace(/[\\"]/g, "\\$&")}"`,
|
|
29
29
|
],
|
|
30
30
|
{ detached: true, shell: false, windowsHide: true },
|
|
31
31
|
).unref();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export interface ProviderModelIdentity {
|
|
4
|
+
id: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const DEEPSEEK_PROXY_COMPAT: NonNullable<ProviderModelConfig["compat"]> =
|
|
9
|
+
{
|
|
10
|
+
supportsStore: false,
|
|
11
|
+
supportsDeveloperRole: false,
|
|
12
|
+
supportsReasoningEffort: true,
|
|
13
|
+
requiresReasoningContentOnAssistantMessages: true,
|
|
14
|
+
thinkingFormat: "deepseek",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function isDeepSeekModel(model: ProviderModelIdentity): boolean {
|
|
18
|
+
const haystack = `${model.id} ${model.name ?? ""}`.toLowerCase();
|
|
19
|
+
return haystack.includes("deepseek");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function isLikelyReasoningModel(model: ProviderModelIdentity): boolean {
|
|
23
|
+
const haystack = `${model.id} ${model.name ?? ""}`.toLowerCase();
|
|
24
|
+
return (
|
|
25
|
+
isDeepSeekModel(model) ||
|
|
26
|
+
haystack.includes("thinking") ||
|
|
27
|
+
haystack.includes("reasoning") ||
|
|
28
|
+
haystack.includes("reasoner") ||
|
|
29
|
+
haystack.includes("r1") ||
|
|
30
|
+
haystack.includes("qwq")
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* For gateway/proxy providers that mask the upstream DeepSeek base URL,
|
|
36
|
+
* add explicit compat so pi-ai preserves and replays reasoning_content.
|
|
37
|
+
*/
|
|
38
|
+
export function getProxyModelCompat(
|
|
39
|
+
model: ProviderModelIdentity,
|
|
40
|
+
): ProviderModelConfig["compat"] | undefined {
|
|
41
|
+
if (isDeepSeekModel(model)) {
|
|
42
|
+
return DEEPSEEK_PROXY_COMPAT;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|