@evandu/super-pi 0.1.120 → 0.1.121
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.
|
@@ -30,5 +30,5 @@
|
|
|
30
30
|
"src/client/types.ts": "7654e95b65053d639634e0bbee2b4524831571ec59c79d4b8384d1eeb662b0cf",
|
|
31
31
|
"src/client/vite-env.d.ts": "65996936fbb042915f7b74a200fcdde7e410f32a669b1ab9597cfaa4b0faddb5"
|
|
32
32
|
},
|
|
33
|
-
"recordedAt": "2026-10-23T06:
|
|
33
|
+
"recordedAt": "2026-10-23T06:47:25.078Z"
|
|
34
34
|
}
|
|
@@ -49,7 +49,7 @@ export declare class ProviderService {
|
|
|
49
49
|
* asks for it to go.
|
|
50
50
|
*/
|
|
51
51
|
set(input: unknown): Promise<PublicProvider[]>;
|
|
52
|
-
/** Removes a provider
|
|
52
|
+
/** Removes a provider from Pi's models.json and auth.json. */
|
|
53
53
|
remove(providerId: string): Promise<PublicProvider[]>;
|
|
54
54
|
/**
|
|
55
55
|
* Removes ONE model from a provider, leaving the provider and its other models alone.
|
|
@@ -101,7 +101,7 @@ export class ProviderService {
|
|
|
101
101
|
await this.afterChange();
|
|
102
102
|
return publicProviders(next);
|
|
103
103
|
}
|
|
104
|
-
/** Removes a provider
|
|
104
|
+
/** Removes a provider from Pi's models.json and auth.json. */
|
|
105
105
|
async remove(providerId) {
|
|
106
106
|
const previous = await this.registry.load();
|
|
107
107
|
const existing = previous.find((provider) => provider.id === providerId);
|
|
@@ -62,6 +62,7 @@ export type ProviderDefinition = {
|
|
|
62
62
|
api: string;
|
|
63
63
|
/** The API key. Stored, never broadcast. */
|
|
64
64
|
apiKey?: string;
|
|
65
|
+
hasAuthKey?: boolean;
|
|
65
66
|
models: ProviderModelDefinition[];
|
|
66
67
|
};
|
|
67
68
|
/** One model, as the browser sees it. No key, no nothing secret - there is nothing secret here. */
|
|
@@ -116,36 +117,19 @@ export declare class ProviderRegistry {
|
|
|
116
117
|
/** Super Pi's own registry: the providers it manages, with their keys. */
|
|
117
118
|
private get registryPath();
|
|
118
119
|
/**
|
|
119
|
-
* Reads
|
|
120
|
-
*
|
|
121
|
-
* A missing file is an empty registry, not an error: nobody has added a provider yet. A file that
|
|
122
|
-
* cannot be parsed IS an error, because reading it as empty would let the next write replace
|
|
123
|
-
* somebody's keys with nothing.
|
|
120
|
+
* Reads all configured providers directly from Pi's models.json and auth.json.
|
|
121
|
+
* Super Pi acts as the direct visual management plane for Pi's model configurations.
|
|
124
122
|
*/
|
|
125
123
|
load(): Promise<ProviderDefinition[]>;
|
|
126
124
|
/** Writes the registry. The key lives here, so the file is 0600. */
|
|
127
125
|
save(providers: ProviderDefinition[]): Promise<void>;
|
|
128
126
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* `previousIds` are the providers Super Pi managed before this change: they are the only ones
|
|
132
|
-
* whose entries may be REMOVED. An id that is in Pi's `models.json` and not in `previousIds` belongs
|
|
133
|
-
* to the person who wrote that file, and touching it would be Super Pi editing a provider it does
|
|
134
|
-
* not own - so the write is refused with a message naming the id.
|
|
127
|
+
* Directly updates Pi's models.json and auth.json to match the new set of providers.
|
|
128
|
+
* Full visual editor semantics: any provider in models.json is editable and removable.
|
|
135
129
|
*/
|
|
136
130
|
applyToPi(previous: ProviderDefinition[], next: ProviderDefinition[]): Promise<void>;
|
|
137
131
|
/**
|
|
138
132
|
* Writes the keys into Pi's `auth.json`.
|
|
139
|
-
*
|
|
140
|
-
* A credential is REMOVED only when it is the one Super Pi wrote: the same provider id and the
|
|
141
|
-
* same key. A person who replaced our key with their own - or who ran Pi's `/login` and put an
|
|
142
|
-
* OAuth token there - keeps it. Deleting somebody else's credential because they removed our
|
|
143
|
-
* provider would be the worst version of this feature.
|
|
144
|
-
*
|
|
145
|
-
* TWO CASES, and the second is the one that is easy to miss: the provider can be gone ENTIRELY
|
|
146
|
-
* (it was removed), or it can still be there with its key GONE (the person asked for the key to be
|
|
147
|
-
* cleared). Both have to take the credential with them, or "clear the key" would leave a working
|
|
148
|
-
* key sitting in Pi's auth file.
|
|
149
133
|
*/
|
|
150
134
|
private applyKeysToPi;
|
|
151
135
|
}
|
package/dist/server/providers.js
CHANGED
|
@@ -224,32 +224,65 @@ export class ProviderRegistry {
|
|
|
224
224
|
return join(this.dataDir, "providers.json");
|
|
225
225
|
}
|
|
226
226
|
/**
|
|
227
|
-
* Reads
|
|
228
|
-
*
|
|
229
|
-
* A missing file is an empty registry, not an error: nobody has added a provider yet. A file that
|
|
230
|
-
* cannot be parsed IS an error, because reading it as empty would let the next write replace
|
|
231
|
-
* somebody's keys with nothing.
|
|
227
|
+
* Reads all configured providers directly from Pi's models.json and auth.json.
|
|
228
|
+
* Super Pi acts as the direct visual management plane for Pi's model configurations.
|
|
232
229
|
*/
|
|
233
230
|
async load() {
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
if
|
|
239
|
-
|
|
240
|
-
|
|
231
|
+
const modelsPath = piModelsPath(this.piDir);
|
|
232
|
+
const authPath = piAuthPath(this.piDir);
|
|
233
|
+
const piModels = (await readJsonFile(modelsPath))?.providers ?? {};
|
|
234
|
+
const piAuth = (await readJsonFile(authPath)) ?? {};
|
|
235
|
+
// Backup registry from Super Pi data dir if needed
|
|
236
|
+
const rawBackup = await readJsonFile(this.registryPath);
|
|
237
|
+
const backupList = rawBackup && typeof rawBackup === "object" && Array.isArray(rawBackup.providers)
|
|
238
|
+
? rawBackup.providers.filter((p) => Boolean(p) && typeof p === "object")
|
|
239
|
+
: [];
|
|
240
|
+
const backupMap = new Map(backupList.map((p) => [p.id, p]));
|
|
241
|
+
const providers = [];
|
|
242
|
+
const seen = new Set();
|
|
243
|
+
for (const [pId, pData] of Object.entries(piModels)) {
|
|
244
|
+
if (!pData || typeof pData !== "object")
|
|
245
|
+
continue;
|
|
246
|
+
seen.add(pId);
|
|
247
|
+
const rec = pData;
|
|
248
|
+
const backup = backupMap.get(pId);
|
|
249
|
+
const baseUrl = typeof rec.baseUrl === "string" ? rec.baseUrl : (backup?.baseUrl ?? "");
|
|
250
|
+
const api = typeof rec.api === "string" ? rec.api : (backup?.api ?? "openai-completions");
|
|
251
|
+
const name = typeof rec.name === "string" ? rec.name : (backup?.name ?? pId);
|
|
252
|
+
const rawModels = Array.isArray(rec.models) ? rec.models : (backup?.models ?? []);
|
|
253
|
+
const models = rawModels.map((m) => ({
|
|
254
|
+
id: typeof m?.id === "string" ? m.id : String(m),
|
|
255
|
+
name: typeof m?.name === "string" ? m.name : m?.id,
|
|
256
|
+
reasoning: Boolean(m?.reasoning),
|
|
257
|
+
imageInput: Array.isArray(m?.input) ? m.input.includes("image") : false,
|
|
258
|
+
contextWindow: typeof m?.contextWindow === "number" ? m.contextWindow : undefined,
|
|
259
|
+
maxTokens: typeof m?.maxTokens === "number" ? m.maxTokens : undefined,
|
|
260
|
+
}));
|
|
261
|
+
const apiKey = backup?.apiKey;
|
|
262
|
+
providers.push({
|
|
263
|
+
id: pId,
|
|
264
|
+
name,
|
|
265
|
+
baseUrl,
|
|
266
|
+
api,
|
|
267
|
+
...(apiKey ? { apiKey } : {}),
|
|
268
|
+
hasAuthKey: Boolean(piAuth[pId]?.key || backup?.apiKey),
|
|
269
|
+
models,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
for (const b of backupList) {
|
|
273
|
+
if (!seen.has(b.id)) {
|
|
274
|
+
providers.push(b);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return providers;
|
|
241
278
|
}
|
|
242
279
|
/** Writes the registry. The key lives here, so the file is 0600. */
|
|
243
280
|
async save(providers) {
|
|
244
281
|
await writeJsonAtomic(this.registryPath, { providers });
|
|
245
282
|
}
|
|
246
283
|
/**
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* `previousIds` are the providers Super Pi managed before this change: they are the only ones
|
|
250
|
-
* whose entries may be REMOVED. An id that is in Pi's `models.json` and not in `previousIds` belongs
|
|
251
|
-
* to the person who wrote that file, and touching it would be Super Pi editing a provider it does
|
|
252
|
-
* not own - so the write is refused with a message naming the id.
|
|
284
|
+
* Directly updates Pi's models.json and auth.json to match the new set of providers.
|
|
285
|
+
* Full visual editor semantics: any provider in models.json is editable and removable.
|
|
253
286
|
*/
|
|
254
287
|
async applyToPi(previous, next) {
|
|
255
288
|
const previousIds = new Set(previous.map((provider) => provider.id));
|
|
@@ -257,15 +290,9 @@ export class ProviderRegistry {
|
|
|
257
290
|
const modelsPath = piModelsPath(this.piDir);
|
|
258
291
|
const existing = await readJsonFile(modelsPath) ?? {};
|
|
259
292
|
const existingProviders = existing.providers ?? {};
|
|
260
|
-
for (const provider of next) {
|
|
261
|
-
if (existingProviders[provider.id] !== undefined && !previousIds.has(provider.id)) {
|
|
262
|
-
throw new Error(`Pi 的 models.json 里已经有供应商「${provider.id}」,而它不是 Super Pi 添加的,所以没有动它。请换一个供应商 id,或者先在 Pi 的模型配置文件里删掉它。`);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
293
|
const mergedProviders = { ...existingProviders };
|
|
266
294
|
for (const provider of next)
|
|
267
295
|
mergedProviders[provider.id] = toPiProvider(provider);
|
|
268
|
-
// The ones we managed and no longer do.
|
|
269
296
|
for (const id of previousIds)
|
|
270
297
|
if (!nextIds.has(id))
|
|
271
298
|
delete mergedProviders[id];
|
|
@@ -274,16 +301,6 @@ export class ProviderRegistry {
|
|
|
274
301
|
}
|
|
275
302
|
/**
|
|
276
303
|
* Writes the keys into Pi's `auth.json`.
|
|
277
|
-
*
|
|
278
|
-
* A credential is REMOVED only when it is the one Super Pi wrote: the same provider id and the
|
|
279
|
-
* same key. A person who replaced our key with their own - or who ran Pi's `/login` and put an
|
|
280
|
-
* OAuth token there - keeps it. Deleting somebody else's credential because they removed our
|
|
281
|
-
* provider would be the worst version of this feature.
|
|
282
|
-
*
|
|
283
|
-
* TWO CASES, and the second is the one that is easy to miss: the provider can be gone ENTIRELY
|
|
284
|
-
* (it was removed), or it can still be there with its key GONE (the person asked for the key to be
|
|
285
|
-
* cleared). Both have to take the credential with them, or "clear the key" would leave a working
|
|
286
|
-
* key sitting in Pi's auth file.
|
|
287
304
|
*/
|
|
288
305
|
async applyKeysToPi(previous, next) {
|
|
289
306
|
const authPath = piAuthPath(this.piDir);
|
|
@@ -295,7 +312,6 @@ export class ProviderRegistry {
|
|
|
295
312
|
}
|
|
296
313
|
for (const provider of previous) {
|
|
297
314
|
const updated = nextById.get(provider.id);
|
|
298
|
-
// The key is still wanted (the provider is unchanged, or it has a new one) - nothing to do.
|
|
299
315
|
if (updated && updated.apiKey)
|
|
300
316
|
continue;
|
|
301
317
|
if (!updated && !provider.apiKey)
|
|
@@ -314,7 +330,7 @@ export function publicProviders(providers) {
|
|
|
314
330
|
name: provider.name ?? provider.id,
|
|
315
331
|
baseUrl: provider.baseUrl,
|
|
316
332
|
api: provider.api,
|
|
317
|
-
hasKey: Boolean(provider.apiKey),
|
|
333
|
+
hasKey: Boolean(provider.apiKey || provider.hasAuthKey),
|
|
318
334
|
models: provider.models.map(model => ({
|
|
319
335
|
id: model.id,
|
|
320
336
|
name: model.name ?? model.id,
|