pi-free 2.0.13 → 2.0.14
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/README.md +4 -1
- package/config.ts +15 -0
- package/constants.ts +3 -0
- package/index.ts +135 -0
- package/lib/built-in-toggle.ts +4 -4
- package/lib/probe-cache.ts +86 -0
- package/lib/registry.ts +25 -3
- package/lib/telemetry.ts +328 -0
- package/lib/util.ts +10 -1
- package/package.json +1 -1
- package/provider-failover/benchmark-lookup.ts +94 -8
- package/provider-failover/benchmarks-chunk-0.ts +599 -890
- package/provider-failover/benchmarks-chunk-1.ts +655 -924
- package/provider-failover/benchmarks-chunk-2.ts +675 -966
- package/provider-failover/benchmarks-chunk-3.ts +676 -967
- package/provider-failover/benchmarks-chunk-4.ts +704 -954
- package/provider-failover/benchmarks-chunk-5.ts +1301 -0
- package/provider-failover/hardcoded-benchmarks.ts +9 -3
- package/providers/cline/cline-models.ts +196 -68
- package/providers/dynamic-built-in/index.ts +1 -1
- package/providers/kilo/kilo.ts +2 -2
- package/providers/model-fetcher.ts +3 -1
- package/providers/nvidia/nvidia.ts +47 -15
- package/providers/ollama/ollama.ts +103 -46
- package/providers/opencode-session.ts +398 -371
- package/providers/qwen/qwen.ts +2 -2
- package/providers/routeway/routeway.ts +213 -0
package/providers/qwen/qwen.ts
CHANGED
|
@@ -108,7 +108,7 @@ export default async function qwenProvider(pi: ExtensionAPI) {
|
|
|
108
108
|
function registerProvider(m = models) {
|
|
109
109
|
pi.registerProvider(PROVIDER_QWEN, {
|
|
110
110
|
baseUrl: DEFAULT_BASE_URL,
|
|
111
|
-
apiKey: "QWEN_API_KEY",
|
|
111
|
+
apiKey: "$QWEN_API_KEY",
|
|
112
112
|
api: "openai-completions" as const,
|
|
113
113
|
headers: {
|
|
114
114
|
"User-Agent": "pi-free",
|
|
@@ -125,7 +125,7 @@ export default async function qwenProvider(pi: ExtensionAPI) {
|
|
|
125
125
|
const reRegister = createReRegister(pi, {
|
|
126
126
|
providerId: PROVIDER_QWEN,
|
|
127
127
|
baseUrl: DEFAULT_BASE_URL,
|
|
128
|
-
apiKey: "QWEN_API_KEY",
|
|
128
|
+
apiKey: "$QWEN_API_KEY",
|
|
129
129
|
oauth: oauthConfig as any,
|
|
130
130
|
});
|
|
131
131
|
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routeway AI Provider Extension
|
|
3
|
+
*
|
|
4
|
+
* Routeway exposes an OpenAI-compatible chat completions API with a model
|
|
5
|
+
* catalog that includes free models marked by a `:free` suffix and zero token
|
|
6
|
+
* pricing.
|
|
7
|
+
*
|
|
8
|
+
* API: https://api.routeway.ai/v1
|
|
9
|
+
* Models: /v1/models
|
|
10
|
+
* Docs: https://docs.routeway.ai
|
|
11
|
+
*
|
|
12
|
+
* Setup:
|
|
13
|
+
* ROUTEWAY_API_KEY=sk-...
|
|
14
|
+
* # or add routeway_api_key to ~/.pi/free.json
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
ExtensionAPI,
|
|
19
|
+
ProviderModelConfig,
|
|
20
|
+
} from "@earendil-works/pi-coding-agent";
|
|
21
|
+
import { getRoutewayApiKey, getRoutewayShowPaid } from "../../config.ts";
|
|
22
|
+
import {
|
|
23
|
+
BASE_URL_ROUTEWAY,
|
|
24
|
+
DEFAULT_FETCH_TIMEOUT_MS,
|
|
25
|
+
PROVIDER_ROUTEWAY,
|
|
26
|
+
} from "../../constants.ts";
|
|
27
|
+
import { applyHidden } from "../../config.ts";
|
|
28
|
+
import { createLogger } from "../../lib/logger.ts";
|
|
29
|
+
import {
|
|
30
|
+
getProxyModelCompat,
|
|
31
|
+
isLikelyReasoningModel,
|
|
32
|
+
} from "../../lib/provider-compat.ts";
|
|
33
|
+
import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
|
|
34
|
+
import { cleanModelName, fetchWithRetry } from "../../lib/util.ts";
|
|
35
|
+
import { createReRegister, setupProvider } from "../../provider-helper.ts";
|
|
36
|
+
|
|
37
|
+
const _logger = createLogger("routeway");
|
|
38
|
+
|
|
39
|
+
interface RoutewayPrice {
|
|
40
|
+
unit?: string;
|
|
41
|
+
price_per_million_t?: number;
|
|
42
|
+
price_per_token_usd?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface RoutewayModel {
|
|
46
|
+
id: string;
|
|
47
|
+
name?: string;
|
|
48
|
+
short_name?: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
context_length?: number;
|
|
51
|
+
available?: boolean;
|
|
52
|
+
type?: string;
|
|
53
|
+
endpoints?: string[];
|
|
54
|
+
pricing?: {
|
|
55
|
+
input?: RoutewayPrice;
|
|
56
|
+
output?: RoutewayPrice;
|
|
57
|
+
caching?: { read?: RoutewayPrice; write?: RoutewayPrice };
|
|
58
|
+
};
|
|
59
|
+
supported_parameters?: string[];
|
|
60
|
+
capabilities?: {
|
|
61
|
+
vision?: boolean;
|
|
62
|
+
function_call?: boolean;
|
|
63
|
+
reasoning?: boolean;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parsePricePerToken(price: RoutewayPrice | undefined): number {
|
|
68
|
+
if (!price) return 0;
|
|
69
|
+
if (typeof price.price_per_token_usd === "string") {
|
|
70
|
+
const parsed = Number.parseFloat(price.price_per_token_usd);
|
|
71
|
+
if (!Number.isNaN(parsed)) return parsed;
|
|
72
|
+
}
|
|
73
|
+
if (typeof price.price_per_million_t === "number") {
|
|
74
|
+
return price.price_per_million_t / 1_000_000;
|
|
75
|
+
}
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isChatModel(model: RoutewayModel): boolean {
|
|
80
|
+
return (
|
|
81
|
+
model.available !== false &&
|
|
82
|
+
(model.type === "chat.completions" ||
|
|
83
|
+
(model.endpoints ?? []).includes("/v1/chat/completions"))
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function mapRoutewayModel(
|
|
88
|
+
model: RoutewayModel,
|
|
89
|
+
): ProviderModelConfig & { _pricingKnown?: boolean } {
|
|
90
|
+
const rawName = model.short_name || model.name || model.id;
|
|
91
|
+
const name = cleanModelName(rawName);
|
|
92
|
+
const inputCost = parsePricePerToken(model.pricing?.input);
|
|
93
|
+
const outputCost = parsePricePerToken(model.pricing?.output);
|
|
94
|
+
const cacheRead = parsePricePerToken(model.pricing?.caching?.read);
|
|
95
|
+
const cacheWrite = parsePricePerToken(model.pricing?.caching?.write);
|
|
96
|
+
const hasPricing = !!(model.pricing?.input || model.pricing?.output);
|
|
97
|
+
const reasoning =
|
|
98
|
+
model.capabilities?.reasoning === true ||
|
|
99
|
+
(model.supported_parameters ?? []).includes("reasoning_effort") ||
|
|
100
|
+
isLikelyReasoningModel({ id: model.id, name });
|
|
101
|
+
const free = inputCost === 0 && outputCost === 0;
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
id: model.id,
|
|
105
|
+
name: `${name} (Routeway)${free ? "" : " 💰"}`,
|
|
106
|
+
reasoning,
|
|
107
|
+
input: model.capabilities?.vision ? ["text", "image"] : ["text"],
|
|
108
|
+
cost: {
|
|
109
|
+
input: inputCost,
|
|
110
|
+
output: outputCost,
|
|
111
|
+
cacheRead,
|
|
112
|
+
cacheWrite,
|
|
113
|
+
},
|
|
114
|
+
contextWindow: model.context_length ?? 128_000,
|
|
115
|
+
maxTokens: 16_384,
|
|
116
|
+
compat: getProxyModelCompat({ id: model.id, name }),
|
|
117
|
+
_pricingKnown: hasPricing,
|
|
118
|
+
} as ProviderModelConfig & { _pricingKnown?: boolean };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function fetchRoutewayModels(
|
|
122
|
+
apiKey: string,
|
|
123
|
+
): Promise<ProviderModelConfig[]> {
|
|
124
|
+
_logger.info("[routeway] Fetching models from Routeway API...");
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const response = await fetchWithRetry(
|
|
128
|
+
`${BASE_URL_ROUTEWAY}/models`,
|
|
129
|
+
{
|
|
130
|
+
headers: {
|
|
131
|
+
Authorization: `Bearer ${apiKey}`,
|
|
132
|
+
Accept: "application/json",
|
|
133
|
+
"Content-Type": "application/json",
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
3,
|
|
137
|
+
1000,
|
|
138
|
+
DEFAULT_FETCH_TIMEOUT_MS,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (!response.ok) {
|
|
142
|
+
throw new Error(`Routeway API error: ${response.status}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const json = (await response.json()) as { data?: RoutewayModel[] };
|
|
146
|
+
const models = (json.data ?? []).filter(isChatModel);
|
|
147
|
+
|
|
148
|
+
_logger.info(`[routeway] Fetched ${models.length} chat models`);
|
|
149
|
+
return applyHidden(models.map(mapRoutewayModel), PROVIDER_ROUTEWAY);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
_logger.error("[routeway] Failed to fetch models", {
|
|
152
|
+
error: error instanceof Error ? error.message : String(error),
|
|
153
|
+
});
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export default async function routewayProvider(pi: ExtensionAPI) {
|
|
159
|
+
const apiKey = getRoutewayApiKey();
|
|
160
|
+
|
|
161
|
+
if (!apiKey) {
|
|
162
|
+
_logger.info(
|
|
163
|
+
"[routeway] Skipping — ROUTEWAY_API_KEY not set. Sign up at https://routeway.ai/",
|
|
164
|
+
);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const allModels = await fetchRoutewayModels(apiKey);
|
|
169
|
+
|
|
170
|
+
if (allModels.length === 0) {
|
|
171
|
+
_logger.warn("[routeway] No chat models available");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const freeModels = allModels.filter((m) =>
|
|
176
|
+
isFreeModel({ ...m, provider: PROVIDER_ROUTEWAY }, allModels),
|
|
177
|
+
);
|
|
178
|
+
const stored = { free: freeModels, all: allModels };
|
|
179
|
+
|
|
180
|
+
_logger.info(
|
|
181
|
+
`[routeway] Registered ${allModels.length} models (${freeModels.length} free)`,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const reRegister = createReRegister(pi, {
|
|
185
|
+
providerId: PROVIDER_ROUTEWAY,
|
|
186
|
+
baseUrl: BASE_URL_ROUTEWAY,
|
|
187
|
+
apiKey,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
registerWithGlobalToggle(PROVIDER_ROUTEWAY, stored, reRegister, true);
|
|
191
|
+
|
|
192
|
+
setupProvider(
|
|
193
|
+
pi,
|
|
194
|
+
{
|
|
195
|
+
providerId: PROVIDER_ROUTEWAY,
|
|
196
|
+
initialShowPaid: getRoutewayShowPaid(),
|
|
197
|
+
tosUrl: "https://routeway.ai/terms",
|
|
198
|
+
reRegister: (models, _stored) => {
|
|
199
|
+
if (_stored) {
|
|
200
|
+
stored.free = _stored.free;
|
|
201
|
+
stored.all = _stored.all;
|
|
202
|
+
}
|
|
203
|
+
reRegister(models);
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
stored,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const showPaid = getRoutewayShowPaid();
|
|
210
|
+
const initialModels =
|
|
211
|
+
showPaid && stored.all.length > 0 ? stored.all : freeModels;
|
|
212
|
+
reRegister(initialModels);
|
|
213
|
+
}
|