@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.
@@ -0,0 +1,194 @@
1
+ import {
2
+ getVertexAccessToken
3
+ } from "./chunk-BL4J2JNX.js";
4
+ import {
5
+ imageModels,
6
+ paidAllowed,
7
+ vertexConfigured
8
+ } from "./chunk-RBSLNDFW.js";
9
+
10
+ // src/image.ts
11
+ import "server-only";
12
+ var DEFAULT_IMAGE_MODELS = [
13
+ { model: "gemini-3.1-flash-image", location: "global" },
14
+ { model: "gemini-2.5-flash-image", location: "us-central1" }
15
+ ];
16
+ function defaultLocation(model) {
17
+ return /^gemini-3/.test(model) ? "global" : "us-central1";
18
+ }
19
+ function imageModelChain(config) {
20
+ const chain = config.model ? [{ model: config.model, location: config.location ?? defaultLocation(config.model) }] : [];
21
+ for (const candidate of DEFAULT_IMAGE_MODELS) {
22
+ if (!chain.some((entry) => entry.model === candidate.model)) chain.push(candidate);
23
+ }
24
+ return chain;
25
+ }
26
+ function endpoint(project, target, method) {
27
+ const host = target.location === "global" ? "aiplatform.googleapis.com" : `${target.location}-aiplatform.googleapis.com`;
28
+ return `https://${host}/v1/projects/${project}/locations/${target.location}/publishers/google/models/${target.model}:${method}`;
29
+ }
30
+ function requestFor(target, prompt, aspectRatio) {
31
+ if (target.model.startsWith("imagen-")) {
32
+ return {
33
+ method: "predict",
34
+ body: {
35
+ instances: [{ prompt }],
36
+ parameters: { sampleCount: 1, aspectRatio, safetySetting: "block_medium_and_above", personGeneration: "allow_adult" }
37
+ }
38
+ };
39
+ }
40
+ return {
41
+ method: "generateContent",
42
+ body: {
43
+ contents: [{ role: "user", parts: [{ text: prompt }] }],
44
+ generationConfig: { responseModalities: ["IMAGE"], imageConfig: { aspectRatio } }
45
+ }
46
+ };
47
+ }
48
+ function extractImage(data) {
49
+ const prediction = data?.predictions?.[0];
50
+ if (prediction?.bytesBase64Encoded) return { base64: prediction.bytesBase64Encoded, mimeType: prediction.mimeType || "image/png" };
51
+ const parts = data?.candidates?.[0]?.content?.parts;
52
+ const inline = parts?.find((part) => part.inlineData?.data)?.inlineData;
53
+ return inline?.data ? { base64: inline.data, mimeType: inline.mimeType || "image/png" } : null;
54
+ }
55
+ async function generateImageVertex(prompt, config, options = {}) {
56
+ const fetchImpl = options.fetchImpl ?? fetch;
57
+ const aspectRatio = options.aspectRatio ?? "16:9";
58
+ let accessToken;
59
+ try {
60
+ accessToken = await getVertexAccessToken(
61
+ { clientEmail: config.clientEmail, privateKey: config.privateKey, tokenUri: config.tokenUri },
62
+ fetchImpl
63
+ );
64
+ } catch (error) {
65
+ return { ok: false, error: `Image generation failed: ${error instanceof Error ? error.message : "authentication error"}` };
66
+ }
67
+ const failures = [];
68
+ for (const target of imageModelChain(config)) {
69
+ if (options.signal?.aborted) return { ok: false, error: "Image generation timed out." };
70
+ const { method, body } = requestFor(target, prompt, aspectRatio);
71
+ try {
72
+ const response = await fetchImpl(endpoint(config.project, target, method), {
73
+ method: "POST",
74
+ headers: { "content-type": "application/json", authorization: `Bearer ${accessToken}` },
75
+ signal: options.signal,
76
+ body: JSON.stringify(body)
77
+ });
78
+ if (!response.ok) {
79
+ failures.push(`${target.model}: HTTP ${response.status}`);
80
+ continue;
81
+ }
82
+ const image = extractImage(await response.json());
83
+ if (image) return { ok: true, ...image };
84
+ failures.push(`${target.model}: no image data`);
85
+ } catch (error) {
86
+ if (options.signal?.aborted) return { ok: false, error: "Image generation timed out." };
87
+ failures.push(`${target.model}: ${error instanceof Error ? error.message : "transport error"}`);
88
+ }
89
+ }
90
+ return { ok: false, error: `Image generation failed (${failures.join("; ")}).` };
91
+ }
92
+ function vertexImageAvailable(env) {
93
+ return vertexConfigured(env);
94
+ }
95
+ var FLUX_SIZE = {
96
+ "16:9": { width: 1344, height: 768 },
97
+ "4:3": { width: 1024, height: 768 },
98
+ "1:1": { width: 1024, height: 1024 }
99
+ };
100
+ function nvidiaImageRequest(model, prompt, aspectRatio, seed) {
101
+ const size = FLUX_SIZE[aspectRatio];
102
+ if (/schnell/.test(model)) return { prompt, ...size, steps: 4, seed };
103
+ return { prompt, ...size, steps: 30, cfg_scale: 3.5, mode: "base", seed };
104
+ }
105
+ function extractNvidiaImage(data) {
106
+ const artifact = data?.artifacts?.[0];
107
+ if (!artifact?.base64 || artifact.finishReason && artifact.finishReason !== "SUCCESS") return null;
108
+ const mimeType = artifact.base64.startsWith("/9j/") ? "image/jpeg" : artifact.base64.startsWith("UklG") ? "image/webp" : "image/png";
109
+ return { base64: artifact.base64, mimeType };
110
+ }
111
+ async function postForImage(model, url, init, extract, options) {
112
+ try {
113
+ const response = await (options.fetchImpl ?? fetch)(url, {
114
+ method: "POST",
115
+ headers: { "content-type": "application/json", ...init.headers },
116
+ signal: options.signal,
117
+ body: JSON.stringify(init.body)
118
+ });
119
+ if (!response.ok) return { ok: false, error: `${model}: HTTP ${response.status}` };
120
+ const image = extract(await response.json());
121
+ return image ? { ok: true, ...image } : { ok: false, error: `${model}: no image (filtered or empty)` };
122
+ } catch (error) {
123
+ if (options.signal?.aborted) return { ok: false, error: "Image generation timed out." };
124
+ return { ok: false, error: `${model}: ${error instanceof Error ? error.message : "transport error"}` };
125
+ }
126
+ }
127
+ function generateImageNvidia(prompt, config, aspectRatio, options) {
128
+ return postForImage(
129
+ config.model,
130
+ `https://ai.api.nvidia.com/v1/genai/${config.model}`,
131
+ {
132
+ headers: { authorization: `Bearer ${config.apiKey}`, accept: "application/json" },
133
+ body: nvidiaImageRequest(config.model, prompt, aspectRatio, Math.floor(Math.random() * 2 ** 31))
134
+ },
135
+ extractNvidiaImage,
136
+ options
137
+ );
138
+ }
139
+ function generateImageGemini(prompt, config, aspectRatio, options) {
140
+ return postForImage(
141
+ config.model,
142
+ `https://generativelanguage.googleapis.com/v1beta/models/${config.model}:generateContent`,
143
+ {
144
+ headers: { "x-goog-api-key": config.apiKey },
145
+ body: { contents: [{ role: "user", parts: [{ text: prompt }] }], generationConfig: { responseModalities: ["IMAGE"], imageConfig: { aspectRatio } } }
146
+ },
147
+ extractImage,
148
+ options
149
+ );
150
+ }
151
+ function imageConfigFromEnv(env) {
152
+ const models = imageModels(env);
153
+ const paid = paidAllowed(env);
154
+ const config = {};
155
+ if (env.NVIDIA_API_KEY) config.nvidia = { apiKey: env.NVIDIA_API_KEY, model: models.nvidia };
156
+ if (paid && env.GEMINI_API_KEY) config.gemini = { apiKey: env.GEMINI_API_KEY, model: models.gemini };
157
+ if (paid && vertexImageAvailable(env)) {
158
+ config.vertex = {
159
+ clientEmail: env.GOOGLE_CLIENT_EMAIL,
160
+ privateKey: env.GOOGLE_PRIVATE_KEY,
161
+ tokenUri: env.GOOGLE_TOKEN_URI,
162
+ project: env.GOOGLE_CLOUD_PROJECT,
163
+ model: models.vertex
164
+ };
165
+ }
166
+ return config.nvidia || config.gemini || config.vertex ? config : null;
167
+ }
168
+ async function generateImage(prompt, config, options = {}) {
169
+ const aspectRatio = options.aspectRatio ?? "16:9";
170
+ const attempts = [];
171
+ if (config.nvidia) attempts.push(() => generateImageNvidia(prompt, config.nvidia, aspectRatio, options));
172
+ if (config.gemini) attempts.push(() => generateImageGemini(prompt, config.gemini, aspectRatio, options));
173
+ if (config.vertex) attempts.push(() => generateImageVertex(prompt, config.vertex, options));
174
+ const failures = [];
175
+ for (const run of attempts) {
176
+ if (options.signal?.aborted) return { ok: false, error: "Image generation timed out." };
177
+ const outcome = await run();
178
+ if (outcome.ok) return outcome;
179
+ failures.push(outcome.error);
180
+ }
181
+ return { ok: false, error: failures.length ? `Image generation failed (${failures.join("; ")}).` : "No image provider is configured." };
182
+ }
183
+
184
+ export {
185
+ DEFAULT_IMAGE_MODELS,
186
+ imageModelChain,
187
+ extractImage,
188
+ generateImageVertex,
189
+ vertexImageAvailable,
190
+ nvidiaImageRequest,
191
+ extractNvidiaImage,
192
+ imageConfigFromEnv,
193
+ generateImage
194
+ };
@@ -0,0 +1,10 @@
1
+ // src/log.ts
2
+ var consoleAiLogger = {
3
+ warn(message, meta) {
4
+ console.warn(JSON.stringify({ level: "warn", message, ...meta }));
5
+ }
6
+ };
7
+
8
+ export {
9
+ consoleAiLogger
10
+ };
@@ -0,0 +1,61 @@
1
+ // src/models.ts
2
+ var DEFAULT_TEXT_MODELS = {
3
+ // Long structured JSON: a full flash model, not lite.
4
+ blog: {
5
+ gemini: "gemini-2.5-flash",
6
+ openrouter: "nvidia/nemotron-3-super-120b-a12b:free",
7
+ nvidia: "nvidia/nemotron-3-super-120b-a12b",
8
+ vertex: "gemini-2.5-flash"
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
+ export {
54
+ DEFAULT_TEXT_MODELS,
55
+ DEFAULT_IMAGE_MODELS,
56
+ textModels,
57
+ imageModels,
58
+ paidAllowed,
59
+ vertexConfigured,
60
+ thinkingConfigFor
61
+ };
@@ -0,0 +1,126 @@
1
+ // src/env.ts
2
+ import { z } from "zod";
3
+ var text = z.string().optional().transform((value) => {
4
+ const trimmed = value?.trim();
5
+ return trimmed ? trimmed : void 0;
6
+ });
7
+ var ON = ["1", "true", "yes", "on"];
8
+ var flag = z.string().optional().transform((value) => ON.includes((value ?? "").trim().toLowerCase()));
9
+ var flagDefaultOn = z.string().optional().transform((value) => {
10
+ const normalized = (value ?? "").trim().toLowerCase();
11
+ return normalized === "" || ON.includes(normalized);
12
+ });
13
+ var privateKey = text.transform((value) => value?.replace(/\\n/g, "\n"));
14
+ var DEFAULT_GOOGLE_TOKEN_URI = "https://oauth2.googleapis.com/token";
15
+ var aiEnvSchema = z.object({
16
+ // Paid providers (Vertex text and images, Gemini images) join the chains
17
+ // only with AI_ALLOW_PAID; off by default, so AI costs $0.
18
+ AI_ALLOW_PAID: flag,
19
+ // Feature switches. The blog assistant is off until turned on; the chatbot
20
+ // is on unless turned off. Each also needs a text provider key.
21
+ ENABLE_BLOG_AI: flag,
22
+ ENABLE_CHATBOT: flagDefaultOn,
23
+ OPENROUTER_BASE_URL: text,
24
+ OPENROUTER_API_KEY: text,
25
+ OPENROUTER_API_KEY_2: text,
26
+ OPENROUTER_ALLOW_PAID_MODELS: flag,
27
+ GEMINI_API_KEY: text,
28
+ NVIDIA_API_KEY: text,
29
+ // More free tiers (./adapters). Groq: free plan, no card. Mistral: free
30
+ // Experiment plan, which may train on prompts, so it joins the chat chain
31
+ // only when AI_PROVIDER_ORDER_CHAT lists it. Cloudflare Workers AI: 10,000
32
+ // free neurons a day; needs the account id and a Workers AI token.
33
+ GROQ_API_KEY: text,
34
+ GROQ_MODEL: text,
35
+ BLOG_GROQ_MODEL: text,
36
+ CHAT_GROQ_MODEL: text,
37
+ MISTRAL_API_KEY: text,
38
+ MISTRAL_MODEL: text,
39
+ BLOG_MISTRAL_MODEL: text,
40
+ CHAT_MISTRAL_MODEL: text,
41
+ CLOUDFLARE_ACCOUNT_ID: text,
42
+ CLOUDFLARE_AI_API_TOKEN: text,
43
+ CLOUDFLARE_AI_MODEL: text,
44
+ BLOG_CLOUDFLARE_AI_MODEL: text,
45
+ CHAT_CLOUDFLARE_AI_MODEL: text,
46
+ // Model overrides, all optional (models.ts): purpose variable, then the
47
+ // provider-wide *_MODEL, then a verified free default.
48
+ OPENROUTER_MODEL: text,
49
+ GEMINI_MODEL: text,
50
+ NVIDIA_MODEL: text,
51
+ VERTEX_MODEL: text,
52
+ IMAGEN_MODEL: text,
53
+ BLOG_GEMINI_MODEL: text,
54
+ BLOG_OPENROUTER_MODEL: text,
55
+ BLOG_NVIDIA_MODEL: text,
56
+ BLOG_VERTEX_MODEL: text,
57
+ CHAT_GEMINI_MODEL: text,
58
+ CHAT_OPENROUTER_MODEL: text,
59
+ CHAT_NVIDIA_MODEL: text,
60
+ CHAT_VERTEX_MODEL: text,
61
+ IMAGE_NVIDIA_MODEL: text,
62
+ IMAGE_GEMINI_MODEL: text,
63
+ IMAGE_VERTEX_MODEL: text,
64
+ // Provider order (./adapters). Comma-separated adapter ids; only the listed
65
+ // ones run, in that order. Per purpose first, then the global one; unset =
66
+ // the built-in order (free tiers first, paid last).
67
+ AI_PROVIDER_ORDER: text,
68
+ AI_PROVIDER_ORDER_BLOG: text,
69
+ AI_PROVIDER_ORDER_CHAT: text,
70
+ // Paid OpenAI-compatible and Anthropic providers: each joins a chain only
71
+ // with its key set and AI_ALLOW_PAID on. Models: purpose variable, then the
72
+ // provider-wide one, then the default in ./adapters.
73
+ OPENAI_API_KEY: text,
74
+ OPENAI_BASE_URL: text,
75
+ OPENAI_MODEL: text,
76
+ BLOG_OPENAI_MODEL: text,
77
+ CHAT_OPENAI_MODEL: text,
78
+ ANTHROPIC_API_KEY: text,
79
+ ANTHROPIC_BASE_URL: text,
80
+ ANTHROPIC_MODEL: text,
81
+ BLOG_ANTHROPIC_MODEL: text,
82
+ CHAT_ANTHROPIC_MODEL: text,
83
+ DEEPSEEK_API_KEY: text,
84
+ DEEPSEEK_MODEL: text,
85
+ BLOG_DEEPSEEK_MODEL: text,
86
+ CHAT_DEEPSEEK_MODEL: text,
87
+ XAI_API_KEY: text,
88
+ XAI_MODEL: text,
89
+ BLOG_XAI_MODEL: text,
90
+ CHAT_XAI_MODEL: text,
91
+ PERPLEXITY_API_KEY: text,
92
+ PERPLEXITY_MODEL: text,
93
+ BLOG_PERPLEXITY_MODEL: text,
94
+ CHAT_PERPLEXITY_MODEL: text,
95
+ // One custom OpenAI-compatible endpoint (a gateway, vLLM, Ollama, LiteLLM).
96
+ // Treated as paid unless AI_CUSTOM_FREE is on; the key is optional for a
97
+ // local server.
98
+ AI_CUSTOM_BASE_URL: text,
99
+ AI_CUSTOM_API_KEY: text,
100
+ AI_CUSTOM_MODEL: text,
101
+ AI_CUSTOM_NAME: text,
102
+ AI_CUSTOM_FREE: flag,
103
+ // Google Vertex service account (paid).
104
+ GOOGLE_CLIENT_EMAIL: text,
105
+ GOOGLE_PRIVATE_KEY: privateKey,
106
+ GOOGLE_CLOUD_PROJECT: text,
107
+ GOOGLE_TOKEN_URI: text.transform((value) => value ?? DEFAULT_GOOGLE_TOKEN_URI)
108
+ });
109
+ function parseAiEnv(source) {
110
+ const result = aiEnvSchema.safeParse(source);
111
+ if (!result.success) {
112
+ const names = [...new Set(result.error.issues.map((issue) => String(issue.path[0] ?? "?")))];
113
+ throw new Error(`Invalid AI environment variables: ${names.join(", ")}`);
114
+ }
115
+ return result.data;
116
+ }
117
+ function aiEnvFromProcess() {
118
+ return parseAiEnv(process.env);
119
+ }
120
+
121
+ export {
122
+ DEFAULT_GOOGLE_TOKEN_URI,
123
+ aiEnvSchema,
124
+ parseAiEnv,
125
+ aiEnvFromProcess
126
+ };
package/dist/env.d.ts CHANGED
@@ -11,6 +11,19 @@ declare const aiEnvSchema: z.ZodObject<{
11
11
  OPENROUTER_ALLOW_PAID_MODELS: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<boolean, string | undefined>>;
12
12
  GEMINI_API_KEY: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
13
13
  NVIDIA_API_KEY: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
14
+ GROQ_API_KEY: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
15
+ GROQ_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
16
+ BLOG_GROQ_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
17
+ CHAT_GROQ_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
18
+ MISTRAL_API_KEY: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
19
+ MISTRAL_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
20
+ BLOG_MISTRAL_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
21
+ CHAT_MISTRAL_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
22
+ CLOUDFLARE_ACCOUNT_ID: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
23
+ CLOUDFLARE_AI_API_TOKEN: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
24
+ CLOUDFLARE_AI_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
25
+ BLOG_CLOUDFLARE_AI_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
26
+ CHAT_CLOUDFLARE_AI_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
14
27
  OPENROUTER_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
15
28
  GEMINI_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
16
29
  NVIDIA_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
package/dist/env.js CHANGED
@@ -1,105 +1,9 @@
1
- // src/env.ts
2
- import { z } from "zod";
3
- var text = z.string().optional().transform((value) => {
4
- const trimmed = value?.trim();
5
- return trimmed ? trimmed : void 0;
6
- });
7
- var ON = ["1", "true", "yes", "on"];
8
- var flag = z.string().optional().transform((value) => ON.includes((value ?? "").trim().toLowerCase()));
9
- var flagDefaultOn = z.string().optional().transform((value) => {
10
- const normalized = (value ?? "").trim().toLowerCase();
11
- return normalized === "" || ON.includes(normalized);
12
- });
13
- var privateKey = text.transform((value) => value?.replace(/\\n/g, "\n"));
14
- var DEFAULT_GOOGLE_TOKEN_URI = "https://oauth2.googleapis.com/token";
15
- var aiEnvSchema = z.object({
16
- // Paid providers (Vertex text and images, Gemini images) join the chains
17
- // only with AI_ALLOW_PAID; off by default, so AI costs $0.
18
- AI_ALLOW_PAID: flag,
19
- // Feature switches. The blog assistant is off until turned on; the chatbot
20
- // is on unless turned off. Each also needs a text provider key.
21
- ENABLE_BLOG_AI: flag,
22
- ENABLE_CHATBOT: flagDefaultOn,
23
- OPENROUTER_BASE_URL: text,
24
- OPENROUTER_API_KEY: text,
25
- OPENROUTER_API_KEY_2: text,
26
- OPENROUTER_ALLOW_PAID_MODELS: flag,
27
- GEMINI_API_KEY: text,
28
- NVIDIA_API_KEY: text,
29
- // Model overrides, all optional (models.ts): purpose variable, then the
30
- // provider-wide *_MODEL, then a verified free default.
31
- OPENROUTER_MODEL: text,
32
- GEMINI_MODEL: text,
33
- NVIDIA_MODEL: text,
34
- VERTEX_MODEL: text,
35
- IMAGEN_MODEL: text,
36
- BLOG_GEMINI_MODEL: text,
37
- BLOG_OPENROUTER_MODEL: text,
38
- BLOG_NVIDIA_MODEL: text,
39
- BLOG_VERTEX_MODEL: text,
40
- CHAT_GEMINI_MODEL: text,
41
- CHAT_OPENROUTER_MODEL: text,
42
- CHAT_NVIDIA_MODEL: text,
43
- CHAT_VERTEX_MODEL: text,
44
- IMAGE_NVIDIA_MODEL: text,
45
- IMAGE_GEMINI_MODEL: text,
46
- IMAGE_VERTEX_MODEL: text,
47
- // Provider order (./adapters). Comma-separated adapter ids; only the listed
48
- // ones run, in that order. Per purpose first, then the global one; unset =
49
- // the built-in order (free tiers first, paid last).
50
- AI_PROVIDER_ORDER: text,
51
- AI_PROVIDER_ORDER_BLOG: text,
52
- AI_PROVIDER_ORDER_CHAT: text,
53
- // Paid OpenAI-compatible and Anthropic providers: each joins a chain only
54
- // with its key set and AI_ALLOW_PAID on. Models: purpose variable, then the
55
- // provider-wide one, then the default in ./adapters.
56
- OPENAI_API_KEY: text,
57
- OPENAI_BASE_URL: text,
58
- OPENAI_MODEL: text,
59
- BLOG_OPENAI_MODEL: text,
60
- CHAT_OPENAI_MODEL: text,
61
- ANTHROPIC_API_KEY: text,
62
- ANTHROPIC_BASE_URL: text,
63
- ANTHROPIC_MODEL: text,
64
- BLOG_ANTHROPIC_MODEL: text,
65
- CHAT_ANTHROPIC_MODEL: text,
66
- DEEPSEEK_API_KEY: text,
67
- DEEPSEEK_MODEL: text,
68
- BLOG_DEEPSEEK_MODEL: text,
69
- CHAT_DEEPSEEK_MODEL: text,
70
- XAI_API_KEY: text,
71
- XAI_MODEL: text,
72
- BLOG_XAI_MODEL: text,
73
- CHAT_XAI_MODEL: text,
74
- PERPLEXITY_API_KEY: text,
75
- PERPLEXITY_MODEL: text,
76
- BLOG_PERPLEXITY_MODEL: text,
77
- CHAT_PERPLEXITY_MODEL: text,
78
- // One custom OpenAI-compatible endpoint (a gateway, vLLM, Ollama, LiteLLM).
79
- // Treated as paid unless AI_CUSTOM_FREE is on; the key is optional for a
80
- // local server.
81
- AI_CUSTOM_BASE_URL: text,
82
- AI_CUSTOM_API_KEY: text,
83
- AI_CUSTOM_MODEL: text,
84
- AI_CUSTOM_NAME: text,
85
- AI_CUSTOM_FREE: flag,
86
- // Google Vertex service account (paid).
87
- GOOGLE_CLIENT_EMAIL: text,
88
- GOOGLE_PRIVATE_KEY: privateKey,
89
- GOOGLE_CLOUD_PROJECT: text,
90
- GOOGLE_TOKEN_URI: text.transform((value) => value ?? DEFAULT_GOOGLE_TOKEN_URI)
91
- });
92
- function parseAiEnv(source) {
93
- const result = aiEnvSchema.safeParse(source);
94
- if (!result.success) {
95
- const names = [...new Set(result.error.issues.map((issue) => String(issue.path[0] ?? "?")))];
96
- throw new Error(`Invalid AI environment variables: ${names.join(", ")}`);
97
- }
98
- return result.data;
99
- }
100
- function aiEnvFromProcess() {
101
- return parseAiEnv(process.env);
102
- }
1
+ import {
2
+ DEFAULT_GOOGLE_TOKEN_URI,
3
+ aiEnvFromProcess,
4
+ aiEnvSchema,
5
+ parseAiEnv
6
+ } from "./chunk-SI7JE5BG.js";
103
7
  export {
104
8
  DEFAULT_GOOGLE_TOKEN_URI,
105
9
  aiEnvFromProcess,