@sahan-sac/ai-core 0.3.0 → 0.4.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 +21 -0
- package/README.md +123 -120
- package/dist/adapters.d.ts +1 -1
- package/dist/adapters.js +17 -609
- package/dist/availability.js +9 -458
- package/dist/chunk-BK4B76LD.js +621 -0
- package/dist/chunk-BL4J2JNX.js +48 -0
- package/dist/chunk-MRNQPHWI.js +194 -0
- package/dist/chunk-PMC3UWQ2.js +10 -0
- package/dist/chunk-RBSLNDFW.js +61 -0
- package/dist/chunk-SI7JE5BG.js +126 -0
- package/dist/env.d.ts +13 -0
- package/dist/env.js +6 -102
- package/dist/image.js +14 -236
- package/dist/index.js +5 -102
- package/dist/log.js +3 -6
- package/dist/models.js +9 -51
- package/dist/providers.d.ts +31 -1
- package/dist/providers.js +22 -582
- package/dist/vertex.js +4 -43
- package/package.json +2 -2
package/dist/availability.js
CHANGED
|
@@ -1,461 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// Short, latency-sensitive replies: the fastest free model first.
|
|
11
|
-
chat: {
|
|
12
|
-
gemini: "gemini-3.1-flash-lite",
|
|
13
|
-
openrouter: "nvidia/nemotron-3-super-120b-a12b:free",
|
|
14
|
-
nvidia: "nvidia/nemotron-3-super-120b-a12b",
|
|
15
|
-
vertex: "gemini-2.5-flash"
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
var DEFAULT_IMAGE_MODELS = {
|
|
19
|
-
nvidia: "black-forest-labs/flux.1-dev",
|
|
20
|
-
gemini: "gemini-3.1-flash-image",
|
|
21
|
-
vertex: "gemini-3.1-flash-image"
|
|
22
|
-
};
|
|
23
|
-
var pick = (...values) => values.find((value) => value && value.trim()).trim();
|
|
24
|
-
function textModels(env, purpose) {
|
|
25
|
-
const defaults = DEFAULT_TEXT_MODELS[purpose];
|
|
26
|
-
const own = purpose === "blog" ? { gemini: env.BLOG_GEMINI_MODEL, openrouter: env.BLOG_OPENROUTER_MODEL, nvidia: env.BLOG_NVIDIA_MODEL, vertex: env.BLOG_VERTEX_MODEL } : { gemini: env.CHAT_GEMINI_MODEL, openrouter: env.CHAT_OPENROUTER_MODEL, nvidia: env.CHAT_NVIDIA_MODEL, vertex: env.CHAT_VERTEX_MODEL };
|
|
27
|
-
return {
|
|
28
|
-
gemini: pick(own.gemini, env.GEMINI_MODEL, defaults.gemini),
|
|
29
|
-
openrouter: pick(own.openrouter, env.OPENROUTER_MODEL, defaults.openrouter),
|
|
30
|
-
nvidia: pick(own.nvidia, env.NVIDIA_MODEL, defaults.nvidia),
|
|
31
|
-
vertex: pick(own.vertex, env.VERTEX_MODEL, defaults.vertex)
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
function imageModels(env) {
|
|
35
|
-
return {
|
|
36
|
-
nvidia: pick(env.IMAGE_NVIDIA_MODEL, DEFAULT_IMAGE_MODELS.nvidia),
|
|
37
|
-
gemini: pick(env.IMAGE_GEMINI_MODEL, DEFAULT_IMAGE_MODELS.gemini),
|
|
38
|
-
vertex: pick(env.IMAGE_VERTEX_MODEL, env.IMAGEN_MODEL, DEFAULT_IMAGE_MODELS.vertex)
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
var paidAllowed = (env) => env.AI_ALLOW_PAID === true;
|
|
42
|
-
function vertexConfigured(env) {
|
|
43
|
-
return Boolean(env.GOOGLE_CLIENT_EMAIL && env.GOOGLE_PRIVATE_KEY && env.GOOGLE_CLOUD_PROJECT);
|
|
44
|
-
}
|
|
45
|
-
function thinkingConfigFor(model, maxOutputTokens) {
|
|
46
|
-
const long = maxOutputTokens >= 2e3;
|
|
47
|
-
if (/^gemini-[3-9]/.test(model)) {
|
|
48
|
-
return { thinkingLevel: long ? "low" : /flash/.test(model) ? "minimal" : "low" };
|
|
49
|
-
}
|
|
50
|
-
return { thinkingBudget: long ? Math.min(800, Math.floor(maxOutputTokens / 5)) : 0 };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// src/providers.ts
|
|
54
|
-
import "server-only";
|
|
55
|
-
import { createOpenAI } from "@ai-sdk/openai";
|
|
56
|
-
import { generateText } from "ai";
|
|
57
|
-
|
|
58
|
-
// src/vertex.ts
|
|
59
|
-
import "server-only";
|
|
60
|
-
var cachedToken = null;
|
|
61
|
-
function base64UrlEncode(input) {
|
|
62
|
-
return Buffer.from(input).toString("base64url");
|
|
63
|
-
}
|
|
64
|
-
async function getVertexAccessToken(account, fetchImpl = fetch) {
|
|
65
|
-
const now = Date.now();
|
|
66
|
-
if (cachedToken && cachedToken.expiresAt - 6e4 > now) return cachedToken.accessToken;
|
|
67
|
-
const { createSign } = await import("crypto");
|
|
68
|
-
const issuedAt = Math.floor(now / 1e3);
|
|
69
|
-
const expiresIn = 3600;
|
|
70
|
-
const header = base64UrlEncode(JSON.stringify({ alg: "RS256", typ: "JWT" }));
|
|
71
|
-
const claims = base64UrlEncode(
|
|
72
|
-
JSON.stringify({
|
|
73
|
-
iss: account.clientEmail,
|
|
74
|
-
scope: "https://www.googleapis.com/auth/cloud-platform",
|
|
75
|
-
aud: account.tokenUri,
|
|
76
|
-
iat: issuedAt,
|
|
77
|
-
exp: issuedAt + expiresIn
|
|
78
|
-
})
|
|
79
|
-
);
|
|
80
|
-
const signingInput = `${header}.${claims}`;
|
|
81
|
-
const signature = createSign("RSA-SHA256").update(signingInput).sign(account.privateKey);
|
|
82
|
-
const jwt = `${signingInput}.${base64UrlEncode(signature)}`;
|
|
83
|
-
const response = await fetchImpl(account.tokenUri, {
|
|
84
|
-
method: "POST",
|
|
85
|
-
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
86
|
-
body: new URLSearchParams({
|
|
87
|
-
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
88
|
-
assertion: jwt
|
|
89
|
-
})
|
|
90
|
-
});
|
|
91
|
-
if (!response.ok) {
|
|
92
|
-
throw new Error(`Vertex token exchange failed: HTTP ${response.status}`);
|
|
93
|
-
}
|
|
94
|
-
const data = await response.json();
|
|
95
|
-
cachedToken = { accessToken: data.access_token, expiresAt: now + data.expires_in * 1e3 };
|
|
96
|
-
return data.access_token;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// src/providers.ts
|
|
100
|
-
function jsonConfig(jsonMode) {
|
|
101
|
-
if (!jsonMode) return {};
|
|
102
|
-
return typeof jsonMode === "object" ? { responseMimeType: "application/json", responseSchema: jsonMode.schema } : { responseMimeType: "application/json" };
|
|
103
|
-
}
|
|
104
|
-
var createAiHealth = () => ({ cooldownUntil: /* @__PURE__ */ new Map(), latencyMs: /* @__PURE__ */ new Map() });
|
|
105
|
-
var sharedAiHealth = globalThis.sahanAiHealth ??= createAiHealth();
|
|
106
|
-
function httpOutcome(status) {
|
|
107
|
-
return { ok: false, errorClass: `http_${status}`, retryable: true, status };
|
|
108
|
-
}
|
|
109
|
-
var FREE_SUFFIX = ":free";
|
|
110
|
-
function openRouterProvider(config) {
|
|
111
|
-
const model = config.model || DEFAULT_TEXT_MODELS.blog.openrouter;
|
|
112
|
-
const client = createOpenAI({
|
|
113
|
-
apiKey: config.apiKey,
|
|
114
|
-
baseURL: config.baseUrl || "https://openrouter.ai/api/v1",
|
|
115
|
-
fetch: config.fetch
|
|
116
|
-
});
|
|
117
|
-
return {
|
|
118
|
-
name: config.name ?? "openrouter",
|
|
119
|
-
async generate(prompt, options) {
|
|
120
|
-
if (!config.allowPaidModels && !model.endsWith(FREE_SUFFIX)) {
|
|
121
|
-
return { ok: false, errorClass: "paid_model_blocked", retryable: true };
|
|
122
|
-
}
|
|
123
|
-
return sdkGenerate(client.chat(model), prompt, options);
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
async function sdkGenerate(model, prompt, options) {
|
|
128
|
-
try {
|
|
129
|
-
const result = await generateText({
|
|
130
|
-
model,
|
|
131
|
-
system: prompt.system,
|
|
132
|
-
prompt: prompt.user,
|
|
133
|
-
maxOutputTokens: options?.maxTokens ?? 1200,
|
|
134
|
-
maxRetries: 0,
|
|
135
|
-
abortSignal: options?.signal
|
|
136
|
-
});
|
|
137
|
-
if (!result.text) return { ok: false, errorClass: "empty_response", retryable: true };
|
|
138
|
-
if (result.finishReason === "length") return { ok: false, errorClass: "truncated", retryable: true };
|
|
139
|
-
return { ok: true, text: result.text };
|
|
140
|
-
} catch (error) {
|
|
141
|
-
const status = error?.statusCode ?? error?.status;
|
|
142
|
-
if (status) return httpOutcome(status);
|
|
143
|
-
return { ok: false, errorClass: options?.signal?.aborted ? "timeout" : "provider_error", retryable: true };
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
function openAiCompatibleProvider(config) {
|
|
147
|
-
const client = createOpenAI({ apiKey: config.apiKey, baseURL: config.baseUrl, headers: config.headers, fetch: config.fetch });
|
|
148
|
-
return {
|
|
149
|
-
name: config.name,
|
|
150
|
-
generate: (prompt, options) => sdkGenerate(client.chat(config.model), prompt, options)
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
function anthropicOutcome(data) {
|
|
154
|
-
const reply = data;
|
|
155
|
-
if (reply.stop_reason === "max_tokens") return { ok: false, errorClass: "truncated", retryable: true };
|
|
156
|
-
if (reply.stop_reason === "refusal") return { ok: false, errorClass: "refused", retryable: true };
|
|
157
|
-
const text = reply.content?.filter((block) => block.type === "text").map((block) => block.text ?? "").join("") ?? "";
|
|
158
|
-
if (!text) return { ok: false, errorClass: "empty_response", retryable: true };
|
|
159
|
-
return { ok: true, text };
|
|
160
|
-
}
|
|
161
|
-
function anthropicProvider(config) {
|
|
162
|
-
const fetchImpl = config.fetchImpl ?? fetch;
|
|
163
|
-
const base = (config.baseUrl || "https://api.anthropic.com").replace(/\/+$/, "");
|
|
164
|
-
return {
|
|
165
|
-
name: "anthropic",
|
|
166
|
-
async generate(prompt, options) {
|
|
167
|
-
try {
|
|
168
|
-
const system = options?.jsonMode ? `${prompt.system}
|
|
169
|
-
|
|
170
|
-
Reply with one JSON value only, no prose and no code fences.` : prompt.system;
|
|
171
|
-
const response = await fetchImpl(`${base}/v1/messages`, {
|
|
172
|
-
method: "POST",
|
|
173
|
-
headers: { "content-type": "application/json", "x-api-key": config.apiKey, "anthropic-version": "2023-06-01" },
|
|
174
|
-
signal: options?.signal,
|
|
175
|
-
body: JSON.stringify({
|
|
176
|
-
model: config.model,
|
|
177
|
-
max_tokens: options?.maxTokens ?? 1800,
|
|
178
|
-
system,
|
|
179
|
-
messages: [{ role: "user", content: prompt.user }]
|
|
180
|
-
})
|
|
181
|
-
});
|
|
182
|
-
if (!response.ok) return httpOutcome(response.status);
|
|
183
|
-
return anthropicOutcome(await response.json());
|
|
184
|
-
} catch {
|
|
185
|
-
return { ok: false, errorClass: options?.signal?.aborted ? "timeout" : "transport", retryable: true };
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
function geminiOutcome(data) {
|
|
191
|
-
const candidate = data.candidates?.[0];
|
|
192
|
-
const text = candidate?.content?.parts?.filter((part) => !part.thought).map((part) => part.text ?? "").join("") ?? "";
|
|
193
|
-
if (candidate?.finishReason === "MAX_TOKENS") return { ok: false, errorClass: "truncated", retryable: true };
|
|
194
|
-
if (!text) return { ok: false, errorClass: "empty_response", retryable: true };
|
|
195
|
-
return { ok: true, text };
|
|
196
|
-
}
|
|
197
|
-
function geminiProvider(config) {
|
|
198
|
-
const model = config.model || DEFAULT_TEXT_MODELS.blog.gemini;
|
|
199
|
-
const fetchImpl = config.fetchImpl ?? fetch;
|
|
200
|
-
return {
|
|
201
|
-
name: "gemini",
|
|
202
|
-
async generate(prompt, options) {
|
|
203
|
-
try {
|
|
204
|
-
const maxOutputTokens = options?.maxTokens ?? 2400;
|
|
205
|
-
const generationConfig = {
|
|
206
|
-
maxOutputTokens,
|
|
207
|
-
thinkingConfig: thinkingConfigFor(model, maxOutputTokens),
|
|
208
|
-
...jsonConfig(options?.jsonMode)
|
|
209
|
-
};
|
|
210
|
-
let response = await fetchImpl(
|
|
211
|
-
`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`,
|
|
212
|
-
{
|
|
213
|
-
method: "POST",
|
|
214
|
-
// Header, not ?key=: query strings end up in proxy and access logs.
|
|
215
|
-
headers: { "content-type": "application/json", "x-goog-api-key": config.apiKey },
|
|
216
|
-
signal: options?.signal,
|
|
217
|
-
body: JSON.stringify({
|
|
218
|
-
systemInstruction: { parts: [{ text: prompt.system }] },
|
|
219
|
-
contents: [{ role: "user", parts: [{ text: prompt.user }] }],
|
|
220
|
-
generationConfig
|
|
221
|
-
})
|
|
222
|
-
}
|
|
223
|
-
);
|
|
224
|
-
if (response.status === 503 && !options?.signal?.aborted) {
|
|
225
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
226
|
-
if (!options?.signal?.aborted) {
|
|
227
|
-
response = await fetchImpl(
|
|
228
|
-
`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`,
|
|
229
|
-
{
|
|
230
|
-
method: "POST",
|
|
231
|
-
headers: { "content-type": "application/json", "x-goog-api-key": config.apiKey },
|
|
232
|
-
signal: options?.signal,
|
|
233
|
-
body: JSON.stringify({
|
|
234
|
-
systemInstruction: { parts: [{ text: prompt.system }] },
|
|
235
|
-
contents: [{ role: "user", parts: [{ text: prompt.user }] }],
|
|
236
|
-
generationConfig
|
|
237
|
-
})
|
|
238
|
-
}
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
if (!response.ok) return httpOutcome(response.status);
|
|
243
|
-
return geminiOutcome(await response.json());
|
|
244
|
-
} catch {
|
|
245
|
-
return { ok: false, errorClass: options?.signal?.aborted ? "timeout" : "transport", retryable: true };
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
function vertexProvider(config) {
|
|
251
|
-
const model = config.model || DEFAULT_TEXT_MODELS.blog.vertex;
|
|
252
|
-
const location = config.location ?? (/^gemini-[3-9]/.test(model) ? "global" : "us-central1");
|
|
253
|
-
const host = location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com`;
|
|
254
|
-
const fetchImpl = config.fetchImpl ?? fetch;
|
|
255
|
-
return {
|
|
256
|
-
name: "vertex",
|
|
257
|
-
lastResort: true,
|
|
258
|
-
async generate(prompt, options) {
|
|
259
|
-
try {
|
|
260
|
-
const accessToken = await getVertexAccessToken(
|
|
261
|
-
{ clientEmail: config.clientEmail, privateKey: config.privateKey, tokenUri: config.tokenUri },
|
|
262
|
-
fetchImpl
|
|
263
|
-
);
|
|
264
|
-
const response = await fetchImpl(
|
|
265
|
-
`https://${host}/v1/projects/${config.project}/locations/${location}/publishers/google/models/${model}:generateContent`,
|
|
266
|
-
{
|
|
267
|
-
method: "POST",
|
|
268
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${accessToken}` },
|
|
269
|
-
signal: options?.signal,
|
|
270
|
-
body: JSON.stringify({
|
|
271
|
-
systemInstruction: { parts: [{ text: prompt.system }] },
|
|
272
|
-
contents: [{ role: "user", parts: [{ text: prompt.user }] }],
|
|
273
|
-
generationConfig: {
|
|
274
|
-
maxOutputTokens: options?.maxTokens ?? 1800,
|
|
275
|
-
thinkingConfig: thinkingConfigFor(model, options?.maxTokens ?? 1800),
|
|
276
|
-
...jsonConfig(options?.jsonMode)
|
|
277
|
-
}
|
|
278
|
-
})
|
|
279
|
-
}
|
|
280
|
-
);
|
|
281
|
-
if (!response.ok) return httpOutcome(response.status);
|
|
282
|
-
return geminiOutcome(await response.json());
|
|
283
|
-
} catch {
|
|
284
|
-
return { ok: false, errorClass: "transport", retryable: true };
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// src/adapters.ts
|
|
291
|
-
function modelFor(env, provider, purpose, fallback) {
|
|
292
|
-
const record = env;
|
|
293
|
-
return record[`${purpose.toUpperCase()}_${provider}_MODEL`] || record[`${provider}_MODEL`] || fallback[purpose];
|
|
294
|
-
}
|
|
295
|
-
var need = (env, ...names) => names.filter((name) => !env[name]);
|
|
296
|
-
var PAID_TEXT_MODELS = {
|
|
297
|
-
openai: { blog: "gpt-5-mini", chat: "gpt-5-nano" },
|
|
298
|
-
anthropic: { blog: "claude-sonnet-5-5", chat: "claude-haiku-4-5-20251001" },
|
|
299
|
-
deepseek: { blog: "deepseek-chat", chat: "deepseek-chat" },
|
|
300
|
-
xai: { blog: "grok-3-mini", chat: "grok-3-mini" },
|
|
301
|
-
perplexity: { blog: "sonar", chat: "sonar" }
|
|
302
|
-
};
|
|
303
|
-
function compatibleAdapter(spec) {
|
|
304
|
-
return {
|
|
305
|
-
id: spec.id,
|
|
306
|
-
label: spec.label,
|
|
307
|
-
paid: () => true,
|
|
308
|
-
missing: (env) => need(env, spec.keyVar),
|
|
309
|
-
create: ({ env, purpose, fetch: fetch2 }) => openAiCompatibleProvider({
|
|
310
|
-
name: spec.id,
|
|
311
|
-
apiKey: env[spec.keyVar],
|
|
312
|
-
baseUrl: spec.baseUrlVar && env[spec.baseUrlVar] || spec.baseUrl,
|
|
313
|
-
model: modelFor(env, spec.envPrefix, purpose, PAID_TEXT_MODELS[spec.id]),
|
|
314
|
-
fetch: fetch2
|
|
315
|
-
})
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
var openRouterAdapter = (id, keyVar) => ({
|
|
319
|
-
id,
|
|
320
|
-
label: id === "openrouter" ? "OpenRouter" : "OpenRouter (second key)",
|
|
321
|
-
// Free models only, unless OPENROUTER_ALLOW_PAID_MODELS: the adapter itself refuses a paid model id.
|
|
322
|
-
paid: () => false,
|
|
323
|
-
missing: (env) => need(env, keyVar),
|
|
324
|
-
create: ({ env, purpose, fetch: fetch2 }) => openRouterProvider({
|
|
325
|
-
apiKey: env[keyVar],
|
|
326
|
-
model: textModels(env, purpose).openrouter,
|
|
327
|
-
baseUrl: env.OPENROUTER_BASE_URL,
|
|
328
|
-
allowPaidModels: env.OPENROUTER_ALLOW_PAID_MODELS,
|
|
329
|
-
fetch: fetch2,
|
|
330
|
-
name: id
|
|
331
|
-
})
|
|
332
|
-
});
|
|
333
|
-
var BUILTIN_ADAPTERS = [
|
|
334
|
-
{
|
|
335
|
-
id: "gemini",
|
|
336
|
-
label: "Gemini",
|
|
337
|
-
paid: () => false,
|
|
338
|
-
missing: (env) => need(env, "GEMINI_API_KEY"),
|
|
339
|
-
create: ({ env, purpose, fetch: fetch2 }) => geminiProvider({ apiKey: env.GEMINI_API_KEY, model: textModels(env, purpose).gemini, fetchImpl: fetch2 })
|
|
340
|
-
},
|
|
341
|
-
openRouterAdapter("openrouter", "OPENROUTER_API_KEY"),
|
|
342
|
-
openRouterAdapter("openrouter-2", "OPENROUTER_API_KEY_2"),
|
|
343
|
-
{
|
|
344
|
-
id: "nvidia",
|
|
345
|
-
label: "NVIDIA",
|
|
346
|
-
paid: () => false,
|
|
347
|
-
missing: (env) => need(env, "NVIDIA_API_KEY"),
|
|
348
|
-
create: ({ env, purpose, fetch: fetch2 }) => openAiCompatibleProvider({
|
|
349
|
-
name: "nvidia",
|
|
350
|
-
apiKey: env.NVIDIA_API_KEY,
|
|
351
|
-
baseUrl: "https://integrate.api.nvidia.com/v1",
|
|
352
|
-
model: textModels(env, purpose).nvidia,
|
|
353
|
-
fetch: fetch2
|
|
354
|
-
})
|
|
355
|
-
},
|
|
356
|
-
compatibleAdapter({ id: "openai", label: "OpenAI", envPrefix: "OPENAI", keyVar: "OPENAI_API_KEY", baseUrl: "https://api.openai.com/v1", baseUrlVar: "OPENAI_BASE_URL" }),
|
|
357
|
-
{
|
|
358
|
-
id: "anthropic",
|
|
359
|
-
label: "Anthropic (Claude)",
|
|
360
|
-
paid: () => true,
|
|
361
|
-
missing: (env) => need(env, "ANTHROPIC_API_KEY"),
|
|
362
|
-
create: ({ env, purpose, fetch: fetch2 }) => anthropicProvider({
|
|
363
|
-
apiKey: env.ANTHROPIC_API_KEY,
|
|
364
|
-
baseUrl: env.ANTHROPIC_BASE_URL,
|
|
365
|
-
model: modelFor(env, "ANTHROPIC", purpose, PAID_TEXT_MODELS.anthropic),
|
|
366
|
-
fetchImpl: fetch2
|
|
367
|
-
})
|
|
368
|
-
},
|
|
369
|
-
compatibleAdapter({ id: "deepseek", label: "DeepSeek", envPrefix: "DEEPSEEK", keyVar: "DEEPSEEK_API_KEY", baseUrl: "https://api.deepseek.com/v1" }),
|
|
370
|
-
compatibleAdapter({ id: "xai", label: "xAI (Grok)", envPrefix: "XAI", keyVar: "XAI_API_KEY", baseUrl: "https://api.x.ai/v1" }),
|
|
371
|
-
compatibleAdapter({ id: "perplexity", label: "Perplexity", envPrefix: "PERPLEXITY", keyVar: "PERPLEXITY_API_KEY", baseUrl: "https://api.perplexity.ai" }),
|
|
372
|
-
{
|
|
373
|
-
id: "custom",
|
|
374
|
-
label: "Custom OpenAI-compatible",
|
|
375
|
-
paid: (env) => !env.AI_CUSTOM_FREE,
|
|
376
|
-
missing: (env) => need(env, "AI_CUSTOM_BASE_URL", "AI_CUSTOM_MODEL"),
|
|
377
|
-
create: ({ env, fetch: fetch2 }) => openAiCompatibleProvider({
|
|
378
|
-
name: env.AI_CUSTOM_NAME ? `custom:${env.AI_CUSTOM_NAME}` : "custom",
|
|
379
|
-
// A local server may take no key; the header must still be well formed.
|
|
380
|
-
apiKey: env.AI_CUSTOM_API_KEY ?? "none",
|
|
381
|
-
baseUrl: env.AI_CUSTOM_BASE_URL,
|
|
382
|
-
model: env.AI_CUSTOM_MODEL,
|
|
383
|
-
fetch: fetch2
|
|
384
|
-
})
|
|
385
|
-
},
|
|
386
|
-
{
|
|
387
|
-
id: "vertex",
|
|
388
|
-
label: "Vertex AI",
|
|
389
|
-
paid: () => true,
|
|
390
|
-
missing: (env) => vertexConfigured(env) ? [] : need(env, "GOOGLE_CLIENT_EMAIL", "GOOGLE_PRIVATE_KEY", "GOOGLE_CLOUD_PROJECT"),
|
|
391
|
-
create: ({ env, purpose, fetch: fetch2 }) => vertexProvider({
|
|
392
|
-
clientEmail: env.GOOGLE_CLIENT_EMAIL,
|
|
393
|
-
privateKey: env.GOOGLE_PRIVATE_KEY,
|
|
394
|
-
tokenUri: env.GOOGLE_TOKEN_URI,
|
|
395
|
-
project: env.GOOGLE_CLOUD_PROJECT,
|
|
396
|
-
model: textModels(env, purpose).vertex,
|
|
397
|
-
fetchImpl: fetch2
|
|
398
|
-
})
|
|
399
|
-
}
|
|
400
|
-
];
|
|
401
|
-
var DEFAULT_ORDER = BUILTIN_ADAPTERS.map((adapter) => adapter.id);
|
|
402
|
-
function configuredOrder(env, purpose) {
|
|
403
|
-
const raw = (purpose === "blog" ? env.AI_PROVIDER_ORDER_BLOG : env.AI_PROVIDER_ORDER_CHAT) ?? env.AI_PROVIDER_ORDER;
|
|
404
|
-
if (!raw) return null;
|
|
405
|
-
const ids = raw.split(",").map((id) => id.trim().toLowerCase()).filter(Boolean);
|
|
406
|
-
return ids.length ? [...new Set(ids)] : null;
|
|
407
|
-
}
|
|
408
|
-
function providerStatuses(env, purpose, adapters = BUILTIN_ADAPTERS) {
|
|
409
|
-
const order = configuredOrder(env, purpose);
|
|
410
|
-
const ids = order ?? adapters.map((adapter) => adapter.id);
|
|
411
|
-
const byId = new Map(adapters.map((adapter) => [adapter.id, adapter]));
|
|
412
|
-
let position = 0;
|
|
413
|
-
const statuses = [];
|
|
414
|
-
for (const id of ids) {
|
|
415
|
-
const adapter = byId.get(id);
|
|
416
|
-
if (!adapter) {
|
|
417
|
-
statuses.push({ id, label: id, paid: false, state: "unknown", missing: [] });
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
const missing = adapter.missing(env);
|
|
421
|
-
const paid = adapter.paid(env);
|
|
422
|
-
const state = missing.length ? "not_configured" : paid && !paidAllowed(env) ? "needs_paid" : "active";
|
|
423
|
-
statuses.push({ id, label: adapter.label, paid, state, missing, ...state === "active" ? { position: position++ } : {} });
|
|
424
|
-
}
|
|
425
|
-
for (const adapter of adapters) {
|
|
426
|
-
if (!ids.includes(adapter.id)) {
|
|
427
|
-
statuses.push({ id: adapter.id, label: adapter.label, paid: adapter.paid(env), state: "not_in_order", missing: adapter.missing(env) });
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
return statuses;
|
|
431
|
-
}
|
|
432
|
-
function chainPlan(env, purpose, adapters = BUILTIN_ADAPTERS) {
|
|
433
|
-
const byId = new Map(adapters.map((adapter) => [adapter.id, adapter]));
|
|
434
|
-
return providerStatuses(env, purpose, adapters).filter((status) => status.state === "active").map((status) => byId.get(status.id));
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// src/image.ts
|
|
438
|
-
import "server-only";
|
|
439
|
-
function vertexImageAvailable(env) {
|
|
440
|
-
return vertexConfigured(env);
|
|
441
|
-
}
|
|
442
|
-
function imageConfigFromEnv(env) {
|
|
443
|
-
const models = imageModels(env);
|
|
444
|
-
const paid = paidAllowed(env);
|
|
445
|
-
const config = {};
|
|
446
|
-
if (env.NVIDIA_API_KEY) config.nvidia = { apiKey: env.NVIDIA_API_KEY, model: models.nvidia };
|
|
447
|
-
if (paid && env.GEMINI_API_KEY) config.gemini = { apiKey: env.GEMINI_API_KEY, model: models.gemini };
|
|
448
|
-
if (paid && vertexImageAvailable(env)) {
|
|
449
|
-
config.vertex = {
|
|
450
|
-
clientEmail: env.GOOGLE_CLIENT_EMAIL,
|
|
451
|
-
privateKey: env.GOOGLE_PRIVATE_KEY,
|
|
452
|
-
tokenUri: env.GOOGLE_TOKEN_URI,
|
|
453
|
-
project: env.GOOGLE_CLOUD_PROJECT,
|
|
454
|
-
model: models.vertex
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
return config.nvidia || config.gemini || config.vertex ? config : null;
|
|
458
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
chainPlan
|
|
3
|
+
} from "./chunk-BK4B76LD.js";
|
|
4
|
+
import {
|
|
5
|
+
imageConfigFromEnv
|
|
6
|
+
} from "./chunk-MRNQPHWI.js";
|
|
7
|
+
import "./chunk-BL4J2JNX.js";
|
|
8
|
+
import "./chunk-PMC3UWQ2.js";
|
|
9
|
+
import "./chunk-RBSLNDFW.js";
|
|
459
10
|
|
|
460
11
|
// src/availability.ts
|
|
461
12
|
function textAiConfigured(env, purpose) {
|