pi-free 2.0.14 → 2.1.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 +90 -0
- package/README.md +64 -78
- package/banner.svg +21 -36
- package/config.ts +123 -9
- package/constants.ts +3 -9
- package/index.ts +14 -15
- package/lib/built-in-toggle.ts +29 -16
- package/lib/json-persistence.ts +90 -22
- package/lib/logger.ts +21 -12
- package/lib/model-detection.ts +2 -12
- package/lib/model-enhancer.ts +11 -2
- package/lib/model-metadata.ts +387 -0
- package/lib/open-browser.ts +74 -24
- package/lib/paths.ts +90 -0
- package/lib/probe-cache.ts +19 -19
- package/lib/provider-cache.ts +74 -28
- package/lib/provider-compat.ts +58 -9
- package/lib/provider-probe.ts +188 -0
- package/lib/registry.ts +1 -5
- package/lib/session-start-metrics.ts +46 -0
- package/lib/telemetry.ts +115 -86
- package/lib/types.ts +22 -2
- package/lib/util.ts +80 -21
- package/package.json +7 -2
- package/provider-failover/benchmark-lookup.ts +17 -5
- package/provider-helper.ts +11 -2
- package/providers/cline/cline-models.ts +12 -2
- package/providers/cline/cline-xml-bridge.ts +974 -0
- package/providers/cline/cline.ts +67 -176
- package/providers/crofai/crofai.ts +6 -1
- package/providers/deepinfra/deepinfra.ts +69 -2
- package/providers/dynamic-built-in/index.ts +237 -2
- package/providers/kilo/kilo-models.ts +3 -1
- package/providers/kilo/kilo.ts +268 -41
- package/providers/model-fetcher.ts +18 -55
- package/providers/novita/novita.ts +69 -2
- package/providers/ollama/ollama.ts +48 -24
- package/providers/opencode-session.ts +67 -2
- package/providers/routeway/routeway.ts +188 -2
- package/providers/sambanova/sambanova.ts +67 -1
- package/providers/together/together.ts +69 -2
- package/providers/tokenrouter/tokenrouter.ts +378 -0
- package/providers/zenmux/zenmux.ts +6 -1
- package/scripts/check-extensions.mjs +32 -16
- package/providers/nvidia/nvidia.ts +0 -504
package/providers/cline/cline.ts
CHANGED
|
@@ -15,15 +15,26 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import type { OAuthCredentials } from "@earendil-works/pi-ai";
|
|
18
|
-
import type {
|
|
18
|
+
import type {
|
|
19
|
+
ExtensionAPI,
|
|
20
|
+
ProviderModelConfig,
|
|
21
|
+
} from "@earendil-works/pi-coding-agent";
|
|
19
22
|
import { getClineShowPaid } from "../../config.ts";
|
|
20
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";
|
|
21
30
|
import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
|
|
31
|
+
import { wrapSessionStartHandler } from "../../lib/session-start-metrics.ts";
|
|
22
32
|
import { createToggleState } from "../../lib/toggle-state.ts";
|
|
23
33
|
import { logWarning } from "../../lib/util.ts";
|
|
24
34
|
import { enhanceWithCI } from "../../provider-helper.ts";
|
|
25
35
|
import { loginCline, refreshClineToken } from "./cline-auth.ts";
|
|
26
36
|
import { fetchClineModels } from "./cline-models.ts";
|
|
37
|
+
import { streamClineXml } from "./cline-xml-bridge.ts";
|
|
27
38
|
|
|
28
39
|
// =============================================================================
|
|
29
40
|
// Cline API headers (must match real Cline VS Code extension exactly)
|
|
@@ -68,153 +79,26 @@ function toApiKey(credentials: OAuthCredentials): string {
|
|
|
68
79
|
return token.startsWith("workos:") ? token : `workos:${token}`;
|
|
69
80
|
}
|
|
70
81
|
|
|
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 findLastClineWrappedMessage(messages: any[]): {
|
|
142
|
-
index: number;
|
|
143
|
-
transcript: string;
|
|
144
|
-
} {
|
|
145
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
146
|
-
if (messages[i]?.role !== "user") continue;
|
|
147
|
-
if (!isClineWrapped(messages[i]?.content)) continue;
|
|
148
|
-
return { index: i, transcript: extractTaskBody(messages[i].content) };
|
|
149
|
-
}
|
|
150
|
-
return { index: -1, transcript: "" };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function buildTranscriptParts(
|
|
154
|
-
messages: any[],
|
|
155
|
-
startIdx: number,
|
|
156
|
-
baseTranscript: string,
|
|
157
|
-
): string[] {
|
|
158
|
-
const parts: string[] = baseTranscript ? [baseTranscript] : [];
|
|
159
|
-
|
|
160
|
-
for (let i = startIdx; i < messages.length; i++) {
|
|
161
|
-
const msg = messages[i];
|
|
162
|
-
const role = msg?.role ?? "user";
|
|
163
|
-
if (role === "system") continue;
|
|
164
|
-
if (role === "user" && isClineWrapped(msg?.content)) continue;
|
|
165
|
-
const text = extractText(msg?.content).trim();
|
|
166
|
-
if (!text) continue;
|
|
167
|
-
|
|
168
|
-
if (role === "tool") {
|
|
169
|
-
parts.push(`<tool_result>\n${text}\n</tool_result>`);
|
|
170
|
-
} else if (role !== "assistant") {
|
|
171
|
-
parts.push(`[${role}]\n${text}`);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return parts;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function buildCollapsedMessage(messages: any[], transcript: string): any[] {
|
|
179
|
-
const collapsed: any[] = [];
|
|
180
|
-
const systemMsg = messages.find((m: any) => m?.role === "system");
|
|
181
|
-
if (systemMsg) {
|
|
182
|
-
const systemText = extractText(systemMsg.content);
|
|
183
|
-
if (systemText) collapsed.push({ role: "system", content: systemText });
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
collapsed.push({
|
|
187
|
-
role: "user",
|
|
188
|
-
content: [
|
|
189
|
-
{ type: "text", text: `<task>\n${transcript}\n</task>` },
|
|
190
|
-
{ type: "text", text: TASK_PROGRESS_BLOCK },
|
|
191
|
-
{ type: "text", text: buildEnvironmentDetails() },
|
|
192
|
-
],
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
return collapsed;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function shapeMessagesForCline(messages: any[]): any[] {
|
|
199
|
-
const { index: lastWrappedIdx, transcript: baseTranscript } =
|
|
200
|
-
findLastClineWrappedMessage(messages);
|
|
201
|
-
|
|
202
|
-
const startIdx = lastWrappedIdx >= 0 ? lastWrappedIdx + 1 : 0;
|
|
203
|
-
const parts = buildTranscriptParts(messages, startIdx, baseTranscript);
|
|
204
|
-
const transcript = parts.join("\n\n").trim() || "(no conversation yet)";
|
|
205
|
-
|
|
206
|
-
return buildCollapsedMessage(messages, transcript);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
82
|
// =============================================================================
|
|
210
83
|
// Extension entry point
|
|
211
84
|
// =============================================================================
|
|
212
85
|
|
|
213
86
|
export default async function clineProvider(pi: ExtensionAPI) {
|
|
214
|
-
let allModels
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
87
|
+
let allModels: ProviderModelConfig[];
|
|
88
|
+
const cachedModels = loadProviderCache(PROVIDER_CLINE);
|
|
89
|
+
if (cachedModels && cachedModels.length > 0) {
|
|
90
|
+
allModels = cachedModels;
|
|
91
|
+
} else {
|
|
92
|
+
allModels = await fetchClineModels(false).catch((err) => {
|
|
93
|
+
logWarning("cline", "Failed to fetch models at startup", err);
|
|
94
|
+
return [];
|
|
95
|
+
});
|
|
96
|
+
if (allModels.length > 0) {
|
|
97
|
+
saveProviderCache(PROVIDER_CLINE, allModels).catch((err) => {
|
|
98
|
+
logWarning("cline", "Failed to save model cache", err);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
218
102
|
let freeModels = allModels.filter((m) =>
|
|
219
103
|
isFreeModel({ ...m, provider: PROVIDER_CLINE }, allModels),
|
|
220
104
|
);
|
|
@@ -228,9 +112,11 @@ export default async function clineProvider(pi: ExtensionAPI) {
|
|
|
228
112
|
const reRegister = (m: typeof allModels) => {
|
|
229
113
|
pi.registerProvider(PROVIDER_CLINE, {
|
|
230
114
|
baseUrl: BASE_URL_CLINE,
|
|
231
|
-
api: "
|
|
115
|
+
api: "cline-xml-tools" as const,
|
|
232
116
|
authHeader: false,
|
|
233
117
|
headers: buildClineHeaders(),
|
|
118
|
+
streamSimple: (model, context, options) =>
|
|
119
|
+
streamClineXml(model as any, context, options, buildClineHeaders()),
|
|
234
120
|
models: enhanceWithCI(m),
|
|
235
121
|
oauth: {
|
|
236
122
|
name: "Cline",
|
|
@@ -241,12 +127,23 @@ export default async function clineProvider(pi: ExtensionAPI) {
|
|
|
241
127
|
});
|
|
242
128
|
};
|
|
243
129
|
|
|
130
|
+
const applyModelList = (models: ProviderModelConfig[]) => {
|
|
131
|
+
allModels = models;
|
|
132
|
+
freeModels = allModels.filter((m) =>
|
|
133
|
+
isFreeModel({ ...m, provider: PROVIDER_CLINE }, allModels),
|
|
134
|
+
);
|
|
135
|
+
stored.all = allModels;
|
|
136
|
+
stored.free = freeModels;
|
|
137
|
+
toggleState.setModels(stored);
|
|
138
|
+
toggleState.applyCurrent(reRegister);
|
|
139
|
+
};
|
|
140
|
+
|
|
244
141
|
registerWithGlobalToggle(PROVIDER_CLINE, stored, (m) => reRegister(m), false);
|
|
245
142
|
toggleState.applyCurrent(reRegister);
|
|
246
143
|
|
|
247
144
|
pi.registerCommand("toggle-cline", {
|
|
248
145
|
description: "Toggle between free and all Cline models",
|
|
249
|
-
handler:
|
|
146
|
+
handler: (_args, ctx) => {
|
|
250
147
|
const applied = toggleState.toggle(reRegister);
|
|
251
148
|
const freeCount = stored.free.length;
|
|
252
149
|
const paidCount = stored.all.length - freeCount;
|
|
@@ -262,6 +159,7 @@ export default async function clineProvider(pi: ExtensionAPI) {
|
|
|
262
159
|
"info",
|
|
263
160
|
);
|
|
264
161
|
}
|
|
162
|
+
return Promise.resolve();
|
|
265
163
|
},
|
|
266
164
|
});
|
|
267
165
|
|
|
@@ -288,41 +186,34 @@ export default async function clineProvider(pi: ExtensionAPI) {
|
|
|
288
186
|
ctx.ui.setStatus(`${PROVIDER_CLINE}-status`, status);
|
|
289
187
|
});
|
|
290
188
|
|
|
291
|
-
pi.on("before_agent_start",
|
|
189
|
+
pi.on("before_agent_start", (_event, ctx) => {
|
|
292
190
|
if (ctx.model?.provider !== PROVIDER_CLINE) return;
|
|
293
191
|
_currentTaskId = generateUlid();
|
|
294
192
|
toggleState.applyCurrent(reRegister);
|
|
295
193
|
});
|
|
296
194
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
try {
|
|
305
|
-
const fresh = await fetchClineModels(false);
|
|
306
|
-
if (fresh.length > 0) {
|
|
307
|
-
allModels = fresh;
|
|
308
|
-
freeModels = allModels.filter((m) =>
|
|
309
|
-
isFreeModel({ ...m, provider: PROVIDER_CLINE }, allModels),
|
|
310
|
-
);
|
|
311
|
-
stored.all = allModels;
|
|
312
|
-
stored.free = freeModels;
|
|
313
|
-
toggleState.setModels(stored);
|
|
314
|
-
toggleState.applyCurrent(reRegister);
|
|
315
|
-
if (ctx.model?.provider === PROVIDER_CLINE) {
|
|
316
|
-
const freeCount = stored.free.length;
|
|
317
|
-
const paidCount = stored.all.length - freeCount;
|
|
318
|
-
ctx.ui.notify(
|
|
319
|
-
`Cline: ${freeCount} free, ${paidCount} paid models available`,
|
|
320
|
-
"info",
|
|
321
|
-
);
|
|
322
|
-
}
|
|
195
|
+
let refreshInFlight: Promise<void> | undefined;
|
|
196
|
+
pi.on(
|
|
197
|
+
"session_start",
|
|
198
|
+
wrapSessionStartHandler("cline", () => {
|
|
199
|
+
if (refreshInFlight) return Promise.resolve();
|
|
200
|
+
if (isProviderCacheFresh(PROVIDER_CLINE, DEFAULT_PROVIDER_CACHE_TTL_MS)) {
|
|
201
|
+
return Promise.resolve();
|
|
323
202
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
203
|
+
|
|
204
|
+
refreshInFlight = fetchClineModels(false)
|
|
205
|
+
.then(async (fresh) => {
|
|
206
|
+
if (fresh.length === 0) return;
|
|
207
|
+
await saveProviderCache(PROVIDER_CLINE, fresh);
|
|
208
|
+
applyModelList(fresh);
|
|
209
|
+
})
|
|
210
|
+
.catch((err) => {
|
|
211
|
+
logWarning("cline", "Failed to refresh models at session start", err);
|
|
212
|
+
})
|
|
213
|
+
.finally(() => {
|
|
214
|
+
refreshInFlight = undefined;
|
|
215
|
+
});
|
|
216
|
+
return Promise.resolve();
|
|
217
|
+
}),
|
|
218
|
+
);
|
|
328
219
|
}
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
PROVIDER_CROFAI,
|
|
29
29
|
} from "../../constants.ts";
|
|
30
30
|
import { createLogger } from "../../lib/logger.ts";
|
|
31
|
+
import { safeEnrichModelsWithModelsDev } from "../../lib/model-metadata.ts";
|
|
31
32
|
import {
|
|
32
33
|
getProxyModelCompat,
|
|
33
34
|
isLikelyReasoningModel,
|
|
@@ -98,7 +99,7 @@ async function fetchCrofaiModels(
|
|
|
98
99
|
|
|
99
100
|
_logger.info(`[crofai] Fetched ${models.length} models`);
|
|
100
101
|
|
|
101
|
-
|
|
102
|
+
const mapped = models
|
|
102
103
|
.filter((m) => m.id)
|
|
103
104
|
.map((m): ProviderModelConfig => {
|
|
104
105
|
const name = m.name || m.id;
|
|
@@ -125,6 +126,10 @@ async function fetchCrofaiModels(
|
|
|
125
126
|
m.pricing?.cache_prompt !== undefined,
|
|
126
127
|
} as ProviderModelConfig & { _pricingKnown?: boolean };
|
|
127
128
|
});
|
|
129
|
+
|
|
130
|
+
return await safeEnrichModelsWithModelsDev(mapped, {
|
|
131
|
+
providerId: PROVIDER_CROFAI,
|
|
132
|
+
});
|
|
128
133
|
}
|
|
129
134
|
|
|
130
135
|
// =============================================================================
|
|
@@ -40,12 +40,15 @@ import {
|
|
|
40
40
|
PROVIDER_DEEPINFRA,
|
|
41
41
|
} from "../../constants.ts";
|
|
42
42
|
import { createLogger } from "../../lib/logger.ts";
|
|
43
|
+
import { safeEnrichModelsWithModelsDev } from "../../lib/model-metadata.ts";
|
|
43
44
|
import {
|
|
44
45
|
getProxyModelCompat,
|
|
45
46
|
isLikelyReasoningModel,
|
|
46
47
|
} from "../../lib/provider-compat.ts";
|
|
48
|
+
import { createProviderProbe } from "../../lib/provider-probe.ts";
|
|
47
49
|
import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
|
|
48
|
-
import {
|
|
50
|
+
import { wrapSessionStartHandler } from "../../lib/session-start-metrics.ts";
|
|
51
|
+
import { fetchWithRetry, fetchWithTimeout } from "../../lib/util.ts";
|
|
49
52
|
import { createReRegister, setupProvider } from "../../provider-helper.ts";
|
|
50
53
|
|
|
51
54
|
const _logger = createLogger("deepinfra");
|
|
@@ -99,7 +102,7 @@ async function fetchDeepinfraModels(
|
|
|
99
102
|
|
|
100
103
|
_logger.info(`[deepinfra] Fetched ${models.length} models`);
|
|
101
104
|
|
|
102
|
-
|
|
105
|
+
const mapped = models
|
|
103
106
|
.filter((m) => {
|
|
104
107
|
const id = m.id.toLowerCase();
|
|
105
108
|
// Filter out non-chat models
|
|
@@ -139,6 +142,10 @@ async function fetchDeepinfraModels(
|
|
|
139
142
|
_pricingKnown: meta?.pricing !== undefined,
|
|
140
143
|
} as ProviderModelConfig & { _pricingKnown?: boolean };
|
|
141
144
|
});
|
|
145
|
+
|
|
146
|
+
return await safeEnrichModelsWithModelsDev(mapped, {
|
|
147
|
+
providerId: PROVIDER_DEEPINFRA,
|
|
148
|
+
});
|
|
142
149
|
}
|
|
143
150
|
|
|
144
151
|
// =============================================================================
|
|
@@ -205,4 +212,64 @@ export default async function deepinfraProvider(pi: ExtensionAPI) {
|
|
|
205
212
|
// Initial registration — DeepInfra is a trial-credit provider,
|
|
206
213
|
// so always show all models. Users see them immediately on setup.
|
|
207
214
|
reRegister(allModels);
|
|
215
|
+
|
|
216
|
+
// ── Probe support ──────────────────────────────────────────────
|
|
217
|
+
const probe = createProviderProbe({
|
|
218
|
+
providerId: PROVIDER_DEEPINFRA,
|
|
219
|
+
probeModel: async (_apiKey: string, modelId: string) => {
|
|
220
|
+
try {
|
|
221
|
+
const response = await fetchWithTimeout(
|
|
222
|
+
`${BASE_URL_DEEPINFRA}/chat/completions`,
|
|
223
|
+
{
|
|
224
|
+
method: "POST",
|
|
225
|
+
headers: {
|
|
226
|
+
Authorization: `Bearer ${apiKey}`,
|
|
227
|
+
"Content-Type": "application/json",
|
|
228
|
+
"User-Agent": "pi-free-providers",
|
|
229
|
+
},
|
|
230
|
+
body: JSON.stringify({
|
|
231
|
+
model: modelId,
|
|
232
|
+
messages: [{ role: "user", content: "hi" }],
|
|
233
|
+
max_tokens: 1,
|
|
234
|
+
}),
|
|
235
|
+
},
|
|
236
|
+
10_000,
|
|
237
|
+
);
|
|
238
|
+
if (response.status === 404 || response.status >= 500) return "broken";
|
|
239
|
+
if (response.status === 429) return "ok";
|
|
240
|
+
if (response.ok) return "ok";
|
|
241
|
+
return "ok";
|
|
242
|
+
} catch {
|
|
243
|
+
return "unknown";
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Probe command
|
|
249
|
+
pi.registerCommand(`probe-${PROVIDER_DEEPINFRA}`, {
|
|
250
|
+
description: "Test all DeepInfra models for availability",
|
|
251
|
+
handler: async (_args, ctx) => {
|
|
252
|
+
ctx.ui.notify(`Probing ${allModels.length} DeepInfra models…`, "info");
|
|
253
|
+
const broken = await probe.run(apiKey, allModels, {
|
|
254
|
+
onBroken: (ids) => {
|
|
255
|
+
ctx.ui.notify(
|
|
256
|
+
`Found ${ids.length} broken models (auto-hidden):\n${ids.join("\n")}`,
|
|
257
|
+
"warning",
|
|
258
|
+
);
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
if (broken.length === 0) {
|
|
262
|
+
ctx.ui.notify("All DeepInfra models are accessible ✅", "info");
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Lazy auto-probe on first session_start
|
|
268
|
+
pi.on(
|
|
269
|
+
"session_start",
|
|
270
|
+
wrapSessionStartHandler(
|
|
271
|
+
`${PROVIDER_DEEPINFRA}-auto-probe`,
|
|
272
|
+
probe.autoProbeHandler(apiKey, freeModels),
|
|
273
|
+
),
|
|
274
|
+
);
|
|
208
275
|
}
|