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.
@@ -4,7 +4,7 @@
4
4
  * Used for failover when providers hit rate limits.
5
5
  */
6
6
 
7
- import type { Model } from "@earendil-works/pi-ai";
7
+ import type { Model } from "@earendil-works/pi-ai/compat";
8
8
  import type { ProviderModelConfig } from "./types.ts";
9
9
 
10
10
  export interface ModelInfo {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-free",
3
- "version": "2.2.7",
3
+ "version": "2.2.8",
4
4
  "type": "module",
5
5
  "description": "AI model providers for Pi with free model filtering and dynamic model fetching",
6
6
  "keywords": [
@@ -56,14 +56,14 @@
56
56
  "smoke:openmodel": "tsx scripts/smoke-openmodel-wire-format.ts"
57
57
  },
58
58
  "peerDependencies": {
59
- "@earendil-works/pi-ai": ">=0.80.0",
60
- "@earendil-works/pi-coding-agent": ">=0.80.0",
61
- "@earendil-works/pi-tui": ">=0.80.0"
59
+ "@earendil-works/pi-ai": ">=0.80.8",
60
+ "@earendil-works/pi-coding-agent": ">=0.80.8",
61
+ "@earendil-works/pi-tui": ">=0.80.8"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@vitest/ui": "^4.1.10",
65
65
  "tsx": "^4.23.0",
66
- "typescript": "^6.0.3",
66
+ "typescript": "^7.0.2",
67
67
  "vitest": "^4.1.10"
68
68
  },
69
69
  "pi": {
@@ -24,6 +24,33 @@ import { enhanceModelNameWithCodingIndex } from "./provider-failover/benchmark-l
24
24
 
25
25
  const _logger = createLogger("provider-helper");
26
26
 
27
+ interface ContextWithOAuthStatus {
28
+ model?: unknown;
29
+ modelRegistry?: {
30
+ isUsingOAuth?: (model: unknown) => boolean;
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Check OAuth status through Pi's ModelRegistry compatibility facade.
36
+ * Newer Pi versions no longer expose authStorage on the context.
37
+ */
38
+ export function isCurrentModelOAuth(ctx: unknown): boolean {
39
+ if (!ctx || typeof ctx !== "object") return false;
40
+ const context = ctx as ContextWithOAuthStatus;
41
+ return Boolean(
42
+ context.model && context.modelRegistry?.isUsingOAuth?.(context.model),
43
+ );
44
+ }
45
+
46
+ export function isOAuthCredential(
47
+ credential: unknown,
48
+ ): credential is { type: "oauth"; access: string } {
49
+ if (!credential || typeof credential !== "object") return false;
50
+ const candidate = credential as { type?: unknown; access?: unknown };
51
+ return candidate.type === "oauth" && typeof candidate.access === "string";
52
+ }
53
+
27
54
  // =============================================================================
28
55
  // Types
29
56
  // =============================================================================
@@ -124,11 +151,7 @@ export async function loadCachedOrFetchModels(
124
151
  const ttlMs = options?.ttlMs ?? DEFAULT_PROVIDER_CACHE_TTL_MS;
125
152
  const cached = loadProviderCache(providerId);
126
153
 
127
- if (
128
- cached &&
129
- cached.length > 0 &&
130
- isProviderCacheFresh(providerId, ttlMs)
131
- ) {
154
+ if (cached && cached.length > 0 && isProviderCacheFresh(providerId, ttlMs)) {
132
155
  return cached;
133
156
  }
134
157
 
@@ -336,8 +359,7 @@ export function setupProvider(
336
359
  if (tosShown || ctx.model?.provider !== providerId) return;
337
360
  tosShown = true;
338
361
  if (config.hasKey) return;
339
- const cred = ctx.modelRegistry.authStorage.get(providerId);
340
- if (cred?.type === "oauth") return;
362
+ if (isCurrentModelOAuth(ctx)) return;
341
363
  ctx.ui.notify(
342
364
  `Using ${providerId} free models. Set API key for paid access. Terms: ${tosUrl}`,
343
365
  "info",
@@ -1,270 +1,270 @@
1
- /**
2
- * AnyAPI provider extension.
3
- *
4
- * AnyAPI is an OpenAI-compatible gateway with a free plan and a catalog of
5
- * explicitly free models. It exposes the catalog at /v1/models and routes
6
- * chat requests through /v1/chat/completions.
7
- *
8
- * Setup:
9
- * ANYAPI_API_KEY=...
10
- * # or add anyapi_api_key to ~/.pi/free.json
11
- */
12
-
13
- import type {
14
- ExtensionAPI,
15
- ProviderModelConfig,
16
- } from "@earendil-works/pi-coding-agent";
17
- import {
18
- applyHidden,
19
- getAnyapiApiKey,
20
- getAnyapiShowPaid,
21
- } from "../../config.ts";
22
- import {
23
- BASE_URL_ANYAPI,
24
- DEFAULT_FETCH_TIMEOUT_MS,
25
- PROVIDER_ANYAPI,
26
- } from "../../constants.ts";
27
- import { createLogger } from "../../lib/logger.ts";
28
- import { loadProviderCache } from "../../lib/provider-cache.ts";
29
- import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
30
- import { safeEnrichModelsWithModelsDev } from "../../lib/model-metadata.ts";
31
- import { fetchWithRetry, mapOpenRouterModel } from "../../lib/util.ts";
32
- import {
33
- createReRegister,
34
- loadCachedOrFetchModels,
35
- setupProvider,
36
- } from "../../provider-helper.ts";
37
-
38
- const _logger = createLogger("anyapi");
39
-
40
- interface AnyApiModel {
41
- id: string;
42
- name?: string;
43
- context_length?: number;
44
- max_completion_tokens?: number | null;
45
- top_provider?: {
46
- context_length?: number | null;
47
- max_completion_tokens?: number | null;
48
- };
49
- pricing?: {
50
- prompt?: string | number | null;
51
- completion?: string | number | null;
52
- input_cache_read?: string | number | null;
53
- input_cache_write?: string | number | null;
54
- };
55
- architecture?: {
56
- input_modalities?: string[] | null;
57
- output_modalities?: string[] | null;
58
- };
59
- supported_parameters?: string[] | null;
60
- tags?: string[];
61
- isFree?: boolean;
62
- }
63
-
64
- const ANYAPI_METADATA_VERSION = 1;
65
-
66
- type AnyApiProviderModel = ProviderModelConfig & {
67
- _pricingKnown?: boolean;
68
- _freeKnown?: boolean;
69
- _isFree?: boolean;
70
- _anyapiMetadataVersion?: number;
71
- };
72
-
73
- function hasPricing(model: AnyApiModel): boolean {
74
- return (
75
- (model.pricing?.prompt !== null && model.pricing?.prompt !== undefined) ||
76
- (model.pricing?.completion !== null &&
77
- model.pricing?.completion !== undefined) ||
78
- (model.pricing?.input_cache_read !== null &&
79
- model.pricing?.input_cache_read !== undefined) ||
80
- (model.pricing?.input_cache_write !== null &&
81
- model.pricing?.input_cache_write !== undefined)
82
- );
83
- }
84
-
85
- function normalizePrice(
86
- value: string | number | null | undefined,
87
- ): string | null {
88
- return value === null || value === undefined ? null : String(value);
89
- }
90
-
91
- /**
92
- * Detect AnyAPI's explicitly free model labels without treating every model
93
- * with omitted pricing as free. The API may also expose an authoritative flag
94
- * or zero pricing, both of which are handled here.
95
- */
96
- export function isAnyApiFreeModel(model: AnyApiModel): boolean {
97
- if (typeof model.isFree === "boolean") return model.isFree;
98
-
99
- const label = `${model.id} ${model.name ?? ""}`.toLowerCase();
100
- if (/\bfree\b/.test(label)) return true;
101
-
102
- if (!hasPricing(model)) return false;
103
- const input = Number(model.pricing?.prompt);
104
- const output = Number(model.pricing?.completion);
105
- return input === 0 && output === 0;
106
- }
107
-
108
- export function mapAnyApiModel(model: AnyApiModel): AnyApiProviderModel {
109
- const name = model.name ?? model.id;
110
- const pricingKnown = hasPricing(model);
111
- const freeKnown =
112
- typeof model.isFree === "boolean" ||
113
- /\bfree\b/i.test(`${model.id} ${name}`) ||
114
- (pricingKnown && isAnyApiFreeModel(model));
115
-
116
- const tags = model.tags ?? [];
117
- const supportsReasoning =
118
- tags.includes("reasoning") || tags.includes("chat_completions:reasoning");
119
- const supportsVision =
120
- tags.includes("vision") || tags.includes("chat_completions:vision");
121
-
122
- const mapped = mapOpenRouterModel({
123
- ...model,
124
- name,
125
- supported_parameters:
126
- model.supported_parameters ?? (supportsReasoning ? ["reasoning"] : []),
127
- architecture: model.architecture ?? {
128
- input_modalities: supportsVision ? ["text", "image"] : ["text"],
129
- output_modalities: ["text"],
130
- },
131
- pricing: model.pricing
132
- ? {
133
- prompt: normalizePrice(model.pricing.prompt),
134
- completion: normalizePrice(model.pricing.completion),
135
- input_cache_read: normalizePrice(model.pricing.input_cache_read),
136
- input_cache_write: normalizePrice(model.pricing.input_cache_write),
137
- }
138
- : undefined,
139
- });
140
-
141
- return {
142
- ...mapped,
143
- _pricingKnown: pricingKnown,
144
- ...(freeKnown && {
145
- _freeKnown: true,
146
- _isFree: isAnyApiFreeModel(model),
147
- }),
148
- };
149
- }
150
-
151
- function isTextModel(model: AnyApiModel): boolean {
152
- const outputModalities = model.architecture?.output_modalities ?? [];
153
- if (outputModalities.length > 0 && !outputModalities.includes("text")) {
154
- return false;
155
- }
156
-
157
- const tags = model.tags;
158
- if (!tags || tags.length === 0) return true;
159
- return tags.some((tag) => tag.startsWith("chat_completions:"));
160
- }
161
-
162
- async function fetchAnyApiModels(
163
- apiKey: string,
164
- ): Promise<AnyApiProviderModel[]> {
165
- const response = await fetchWithRetry(
166
- `${BASE_URL_ANYAPI}/models`,
167
- {
168
- headers: {
169
- Authorization: `Bearer ${apiKey}`,
170
- Accept: "application/json",
171
- "Content-Type": "application/json",
172
- },
173
- },
174
- 3,
175
- 1000,
176
- DEFAULT_FETCH_TIMEOUT_MS,
177
- );
178
-
179
- if (!response.ok) {
180
- throw new Error(
181
- `AnyAPI API error: ${response.status} ${response.statusText}`,
182
- );
183
- }
184
-
185
- const json = (await response.json()) as { data?: AnyApiModel[] };
186
- const models = (json.data ?? []).flatMap((model) => {
187
- if (!model.id || !isTextModel(model)) return [];
188
- return [mapAnyApiModel(model)];
189
- });
190
-
191
- _logger.info(`[anyapi] Fetched ${models.length} text models`);
192
-
193
- // AnyAPI's /models response omits context and output limits for its
194
- // catalog. Use the global models.dev catalog so canonical model IDs such
195
- // as qwen/qwen3-coder:free do not fall back to the generic 4096-token
196
- // defaults in mapOpenRouterModel. The AnyAPI-scoped models.dev entry only
197
- // contains a small paid subset and does not cover the free catalog.
198
- const enriched = await safeEnrichModelsWithModelsDev(models);
199
- return applyHidden(
200
- enriched.map((model) => ({
201
- ...model,
202
- _anyapiMetadataVersion: ANYAPI_METADATA_VERSION,
203
- })),
204
- PROVIDER_ANYAPI,
205
- ) as AnyApiProviderModel[];
206
- }
207
-
208
- export default async function anyapiProvider(pi: ExtensionAPI) {
209
- const apiKey = getAnyapiApiKey();
210
- if (!apiKey) {
211
- _logger.info("[anyapi] Skipping — ANYAPI_API_KEY not set.");
212
- return;
213
- }
214
-
215
- const cachedModels = loadProviderCache(PROVIDER_ANYAPI) as
216
- | AnyApiProviderModel[]
217
- | undefined;
218
- const needsMetadataMigration = cachedModels?.some(
219
- (model) => model._anyapiMetadataVersion !== ANYAPI_METADATA_VERSION,
220
- );
221
- const allModels = await loadCachedOrFetchModels(
222
- PROVIDER_ANYAPI,
223
- () => fetchAnyApiModels(apiKey),
224
- // Force one refresh for caches written before context metadata was added;
225
- // subsequent warm startups continue using the normal one-hour cache.
226
- needsMetadataMigration ? { ttlMs: -1 } : undefined,
227
- );
228
- if (allModels.length === 0) {
229
- _logger.warn("[anyapi] No text models available");
230
- return;
231
- }
232
-
233
- const freeModels = allModels.filter((model) =>
234
- isFreeModel({ ...model, provider: PROVIDER_ANYAPI }, allModels),
235
- );
236
- const stored = { free: freeModels, all: allModels };
237
-
238
- _logger.info(
239
- `[anyapi] Registered ${allModels.length} models (${freeModels.length} free)`,
240
- );
241
-
242
- const reRegister = createReRegister(pi, {
243
- providerId: PROVIDER_ANYAPI,
244
- baseUrl: BASE_URL_ANYAPI,
245
- apiKey,
246
- });
247
-
248
- registerWithGlobalToggle(PROVIDER_ANYAPI, stored, reRegister, true);
249
-
250
- setupProvider(
251
- pi,
252
- {
253
- providerId: PROVIDER_ANYAPI,
254
- initialShowPaid: getAnyapiShowPaid(),
255
- hasKey: true,
256
- tosUrl: "https://anyapi.ai/terms-of-service",
257
- reRegister: (models, current) => {
258
- if (current) {
259
- stored.free = current.free;
260
- stored.all = current.all;
261
- }
262
- reRegister(models);
263
- },
264
- },
265
- stored,
266
- );
267
-
268
- const showPaid = getAnyapiShowPaid();
269
- reRegister(showPaid ? stored.all : stored.free);
270
- }
1
+ /**
2
+ * AnyAPI provider extension.
3
+ *
4
+ * AnyAPI is an OpenAI-compatible gateway with a free plan and a catalog of
5
+ * explicitly free models. It exposes the catalog at /v1/models and routes
6
+ * chat requests through /v1/chat/completions.
7
+ *
8
+ * Setup:
9
+ * ANYAPI_API_KEY=...
10
+ * # or add anyapi_api_key to ~/.pi/free.json
11
+ */
12
+
13
+ import type {
14
+ ExtensionAPI,
15
+ ProviderModelConfig,
16
+ } from "@earendil-works/pi-coding-agent";
17
+ import {
18
+ applyHidden,
19
+ getAnyapiApiKey,
20
+ getAnyapiShowPaid,
21
+ } from "../../config.ts";
22
+ import {
23
+ BASE_URL_ANYAPI,
24
+ DEFAULT_FETCH_TIMEOUT_MS,
25
+ PROVIDER_ANYAPI,
26
+ } from "../../constants.ts";
27
+ import { createLogger } from "../../lib/logger.ts";
28
+ import { loadProviderCache } from "../../lib/provider-cache.ts";
29
+ import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
30
+ import { safeEnrichModelsWithModelsDev } from "../../lib/model-metadata.ts";
31
+ import { fetchWithRetry, mapOpenRouterModel } from "../../lib/util.ts";
32
+ import {
33
+ createReRegister,
34
+ loadCachedOrFetchModels,
35
+ setupProvider,
36
+ } from "../../provider-helper.ts";
37
+
38
+ const _logger = createLogger("anyapi");
39
+
40
+ interface AnyApiModel {
41
+ id: string;
42
+ name?: string;
43
+ context_length?: number;
44
+ max_completion_tokens?: number | null;
45
+ top_provider?: {
46
+ context_length?: number | null;
47
+ max_completion_tokens?: number | null;
48
+ };
49
+ pricing?: {
50
+ prompt?: string | number | null;
51
+ completion?: string | number | null;
52
+ input_cache_read?: string | number | null;
53
+ input_cache_write?: string | number | null;
54
+ };
55
+ architecture?: {
56
+ input_modalities?: string[] | null;
57
+ output_modalities?: string[] | null;
58
+ };
59
+ supported_parameters?: string[] | null;
60
+ tags?: string[];
61
+ isFree?: boolean;
62
+ }
63
+
64
+ const ANYAPI_METADATA_VERSION = 1;
65
+
66
+ type AnyApiProviderModel = ProviderModelConfig & {
67
+ _pricingKnown?: boolean;
68
+ _freeKnown?: boolean;
69
+ _isFree?: boolean;
70
+ _anyapiMetadataVersion?: number;
71
+ };
72
+
73
+ function hasPricing(model: AnyApiModel): boolean {
74
+ return (
75
+ (model.pricing?.prompt !== null && model.pricing?.prompt !== undefined) ||
76
+ (model.pricing?.completion !== null &&
77
+ model.pricing?.completion !== undefined) ||
78
+ (model.pricing?.input_cache_read !== null &&
79
+ model.pricing?.input_cache_read !== undefined) ||
80
+ (model.pricing?.input_cache_write !== null &&
81
+ model.pricing?.input_cache_write !== undefined)
82
+ );
83
+ }
84
+
85
+ function normalizePrice(
86
+ value: string | number | null | undefined,
87
+ ): string | null {
88
+ return value === null || value === undefined ? null : String(value);
89
+ }
90
+
91
+ /**
92
+ * Detect AnyAPI's explicitly free model labels without treating every model
93
+ * with omitted pricing as free. The API may also expose an authoritative flag
94
+ * or zero pricing, both of which are handled here.
95
+ */
96
+ export function isAnyApiFreeModel(model: AnyApiModel): boolean {
97
+ if (typeof model.isFree === "boolean") return model.isFree;
98
+
99
+ const label = `${model.id} ${model.name ?? ""}`.toLowerCase();
100
+ if (/\bfree\b/.test(label)) return true;
101
+
102
+ if (!hasPricing(model)) return false;
103
+ const input = Number(model.pricing?.prompt);
104
+ const output = Number(model.pricing?.completion);
105
+ return input === 0 && output === 0;
106
+ }
107
+
108
+ export function mapAnyApiModel(model: AnyApiModel): AnyApiProviderModel {
109
+ const name = model.name ?? model.id;
110
+ const pricingKnown = hasPricing(model);
111
+ const freeKnown =
112
+ typeof model.isFree === "boolean" ||
113
+ /\bfree\b/i.test(`${model.id} ${name}`) ||
114
+ (pricingKnown && isAnyApiFreeModel(model));
115
+
116
+ const tags = model.tags ?? [];
117
+ const supportsReasoning =
118
+ tags.includes("reasoning") || tags.includes("chat_completions:reasoning");
119
+ const supportsVision =
120
+ tags.includes("vision") || tags.includes("chat_completions:vision");
121
+
122
+ const mapped = mapOpenRouterModel({
123
+ ...model,
124
+ name,
125
+ supported_parameters:
126
+ model.supported_parameters ?? (supportsReasoning ? ["reasoning"] : []),
127
+ architecture: model.architecture ?? {
128
+ input_modalities: supportsVision ? ["text", "image"] : ["text"],
129
+ output_modalities: ["text"],
130
+ },
131
+ pricing: model.pricing
132
+ ? {
133
+ prompt: normalizePrice(model.pricing.prompt),
134
+ completion: normalizePrice(model.pricing.completion),
135
+ input_cache_read: normalizePrice(model.pricing.input_cache_read),
136
+ input_cache_write: normalizePrice(model.pricing.input_cache_write),
137
+ }
138
+ : undefined,
139
+ });
140
+
141
+ return {
142
+ ...mapped,
143
+ _pricingKnown: pricingKnown,
144
+ ...(freeKnown && {
145
+ _freeKnown: true,
146
+ _isFree: isAnyApiFreeModel(model),
147
+ }),
148
+ };
149
+ }
150
+
151
+ function isTextModel(model: AnyApiModel): boolean {
152
+ const outputModalities = model.architecture?.output_modalities ?? [];
153
+ if (outputModalities.length > 0 && !outputModalities.includes("text")) {
154
+ return false;
155
+ }
156
+
157
+ const tags = model.tags;
158
+ if (!tags || tags.length === 0) return true;
159
+ return tags.some((tag) => tag.startsWith("chat_completions:"));
160
+ }
161
+
162
+ async function fetchAnyApiModels(
163
+ apiKey: string,
164
+ ): Promise<AnyApiProviderModel[]> {
165
+ const response = await fetchWithRetry(
166
+ `${BASE_URL_ANYAPI}/models`,
167
+ {
168
+ headers: {
169
+ Authorization: `Bearer ${apiKey}`,
170
+ Accept: "application/json",
171
+ "Content-Type": "application/json",
172
+ },
173
+ },
174
+ 3,
175
+ 1000,
176
+ DEFAULT_FETCH_TIMEOUT_MS,
177
+ );
178
+
179
+ if (!response.ok) {
180
+ throw new Error(
181
+ `AnyAPI API error: ${response.status} ${response.statusText}`,
182
+ );
183
+ }
184
+
185
+ const json = (await response.json()) as { data?: AnyApiModel[] };
186
+ const models = (json.data ?? []).flatMap((model) => {
187
+ if (!model.id || !isTextModel(model)) return [];
188
+ return [mapAnyApiModel(model)];
189
+ });
190
+
191
+ _logger.info(`[anyapi] Fetched ${models.length} text models`);
192
+
193
+ // AnyAPI's /models response omits context and output limits for its
194
+ // catalog. Use the global models.dev catalog so canonical model IDs such
195
+ // as qwen/qwen3-coder:free do not fall back to the generic 4096-token
196
+ // defaults in mapOpenRouterModel. The AnyAPI-scoped models.dev entry only
197
+ // contains a small paid subset and does not cover the free catalog.
198
+ const enriched = await safeEnrichModelsWithModelsDev(models);
199
+ return applyHidden(
200
+ enriched.map((model) => ({
201
+ ...model,
202
+ _anyapiMetadataVersion: ANYAPI_METADATA_VERSION,
203
+ })),
204
+ PROVIDER_ANYAPI,
205
+ ) as AnyApiProviderModel[];
206
+ }
207
+
208
+ export default async function anyapiProvider(pi: ExtensionAPI) {
209
+ const apiKey = getAnyapiApiKey();
210
+ if (!apiKey) {
211
+ _logger.info("[anyapi] Skipping — ANYAPI_API_KEY not set.");
212
+ return;
213
+ }
214
+
215
+ const cachedModels = loadProviderCache(PROVIDER_ANYAPI) as
216
+ | AnyApiProviderModel[]
217
+ | undefined;
218
+ const needsMetadataMigration = cachedModels?.some(
219
+ (model) => model._anyapiMetadataVersion !== ANYAPI_METADATA_VERSION,
220
+ );
221
+ const allModels = await loadCachedOrFetchModels(
222
+ PROVIDER_ANYAPI,
223
+ () => fetchAnyApiModels(apiKey),
224
+ // Force one refresh for caches written before context metadata was added;
225
+ // subsequent warm startups continue using the normal one-hour cache.
226
+ needsMetadataMigration ? { ttlMs: -1 } : undefined,
227
+ );
228
+ if (allModels.length === 0) {
229
+ _logger.warn("[anyapi] No text models available");
230
+ return;
231
+ }
232
+
233
+ const freeModels = allModels.filter((model) =>
234
+ isFreeModel({ ...model, provider: PROVIDER_ANYAPI }, allModels),
235
+ );
236
+ const stored = { free: freeModels, all: allModels };
237
+
238
+ _logger.info(
239
+ `[anyapi] Registered ${allModels.length} models (${freeModels.length} free)`,
240
+ );
241
+
242
+ const reRegister = createReRegister(pi, {
243
+ providerId: PROVIDER_ANYAPI,
244
+ baseUrl: BASE_URL_ANYAPI,
245
+ apiKey,
246
+ });
247
+
248
+ registerWithGlobalToggle(PROVIDER_ANYAPI, stored, reRegister, true);
249
+
250
+ setupProvider(
251
+ pi,
252
+ {
253
+ providerId: PROVIDER_ANYAPI,
254
+ initialShowPaid: getAnyapiShowPaid(),
255
+ hasKey: true,
256
+ tosUrl: "https://anyapi.ai/terms-of-service",
257
+ reRegister: (models, current) => {
258
+ if (current) {
259
+ stored.free = current.free;
260
+ stored.all = current.all;
261
+ }
262
+ reRegister(models);
263
+ },
264
+ },
265
+ stored,
266
+ );
267
+
268
+ const showPaid = getAnyapiShowPaid();
269
+ reRegister(showPaid ? stored.all : stored.free);
270
+ }