@sahan-sac/ai-core 0.2.0 → 0.3.1

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,109 @@
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
+ }
103
+
104
+ export {
105
+ DEFAULT_GOOGLE_TOKEN_URI,
106
+ aiEnvSchema,
107
+ parseAiEnv,
108
+ aiEnvFromProcess
109
+ };
package/dist/env.d.ts CHANGED
@@ -27,6 +27,36 @@ declare const aiEnvSchema: z.ZodObject<{
27
27
  IMAGE_NVIDIA_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
28
28
  IMAGE_GEMINI_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
29
29
  IMAGE_VERTEX_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
30
+ AI_PROVIDER_ORDER: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
31
+ AI_PROVIDER_ORDER_BLOG: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
32
+ AI_PROVIDER_ORDER_CHAT: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
33
+ OPENAI_API_KEY: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
34
+ OPENAI_BASE_URL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
35
+ OPENAI_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
36
+ BLOG_OPENAI_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
37
+ CHAT_OPENAI_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
38
+ ANTHROPIC_API_KEY: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
39
+ ANTHROPIC_BASE_URL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
40
+ ANTHROPIC_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
41
+ BLOG_ANTHROPIC_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
42
+ CHAT_ANTHROPIC_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
43
+ DEEPSEEK_API_KEY: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
44
+ DEEPSEEK_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
45
+ BLOG_DEEPSEEK_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
46
+ CHAT_DEEPSEEK_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
47
+ XAI_API_KEY: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
48
+ XAI_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
49
+ BLOG_XAI_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
50
+ CHAT_XAI_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
51
+ PERPLEXITY_API_KEY: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
52
+ PERPLEXITY_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
53
+ BLOG_PERPLEXITY_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
54
+ CHAT_PERPLEXITY_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
55
+ AI_CUSTOM_BASE_URL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
56
+ AI_CUSTOM_API_KEY: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
57
+ AI_CUSTOM_MODEL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
58
+ AI_CUSTOM_NAME: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
59
+ AI_CUSTOM_FREE: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<boolean, string | undefined>>;
30
60
  GOOGLE_CLIENT_EMAIL: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
31
61
  GOOGLE_PRIVATE_KEY: z.ZodPipe<z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>, z.ZodTransform<string | undefined, string | undefined>>;
32
62
  GOOGLE_CLOUD_PROJECT: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
package/dist/env.js CHANGED
@@ -1,66 +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
- // Google Vertex service account (paid).
48
- GOOGLE_CLIENT_EMAIL: text,
49
- GOOGLE_PRIVATE_KEY: privateKey,
50
- GOOGLE_CLOUD_PROJECT: text,
51
- GOOGLE_TOKEN_URI: text.transform((value) => value ?? DEFAULT_GOOGLE_TOKEN_URI)
52
- });
53
- function parseAiEnv(source) {
54
- const result = aiEnvSchema.safeParse(source);
55
- if (!result.success) {
56
- const names = [...new Set(result.error.issues.map((issue) => String(issue.path[0] ?? "?")))];
57
- throw new Error(`Invalid AI environment variables: ${names.join(", ")}`);
58
- }
59
- return result.data;
60
- }
61
- function aiEnvFromProcess() {
62
- return parseAiEnv(process.env);
63
- }
1
+ import {
2
+ DEFAULT_GOOGLE_TOKEN_URI,
3
+ aiEnvFromProcess,
4
+ aiEnvSchema,
5
+ parseAiEnv
6
+ } from "./chunk-ZIHRZXPE.js";
64
7
  export {
65
8
  DEFAULT_GOOGLE_TOKEN_URI,
66
9
  aiEnvFromProcess,
package/dist/guard.d.ts CHANGED
@@ -1,8 +1,20 @@
1
1
  declare const DATA_START = "<<<SAHAN_USER_DATA_START>>>";
2
2
  declare const DATA_END = "<<<SAHAN_USER_DATA_END>>>";
3
- /** Strips control characters, caps length, and neutralizes any attempt to
3
+ /** Strips control characters, caps length (4000 by default), and neutralizes any attempt to
4
4
  * forge the delimiter markers themselves before fencing the text as data. */
5
- declare function wrapUserData(text: string): string;
5
+ declare function wrapUserData(text: string, maxLength?: number): string;
6
+ /** Longest owner guidance (per scope, after merging) that reaches a prompt. */
7
+ declare const MAX_GUIDANCE_LENGTH = 6000;
8
+ /**
9
+ * Standing guidance the site owner wrote in the admin settings (tone, facts,
10
+ * do and don't), appended to a system prompt. Unlike visitor or editor data
11
+ * it is meant to steer the model, so it is not fenced; it is still capped,
12
+ * stripped of control characters and forged fence markers, and placed after
13
+ * the fixed rules with a note that those rules win. Empty input gives "".
14
+ */
15
+ declare function guidanceSection(guidance: string | null | undefined): string;
16
+ /** Joins owner guidance from broad to specific (global, then a feature's own), skipping empty parts. */
17
+ declare function mergeGuidance(...parts: Array<string | null | undefined>): string;
6
18
  interface ModelPrompt {
7
19
  system: string;
8
20
  user: string;
@@ -15,4 +27,4 @@ interface ModelPrompt {
15
27
  */
16
28
  declare function looksLikeLeak(text: string): boolean;
17
29
 
18
- export { DATA_END, DATA_START, type ModelPrompt, looksLikeLeak, wrapUserData };
30
+ export { DATA_END, DATA_START, MAX_GUIDANCE_LENGTH, type ModelPrompt, guidanceSection, looksLikeLeak, mergeGuidance, wrapUserData };
package/dist/guard.js CHANGED
Binary file
package/dist/image.js CHANGED
@@ -1,240 +1,18 @@
1
- // src/image.ts
2
- import "server-only";
3
-
4
- // src/models.ts
5
- var DEFAULT_IMAGE_MODELS = {
6
- nvidia: "black-forest-labs/flux.1-dev",
7
- gemini: "gemini-3.1-flash-image",
8
- vertex: "gemini-3.1-flash-image"
9
- };
10
- var pick = (...values) => values.find((value) => value && value.trim()).trim();
11
- function imageModels(env) {
12
- return {
13
- nvidia: pick(env.IMAGE_NVIDIA_MODEL, DEFAULT_IMAGE_MODELS.nvidia),
14
- gemini: pick(env.IMAGE_GEMINI_MODEL, DEFAULT_IMAGE_MODELS.gemini),
15
- vertex: pick(env.IMAGE_VERTEX_MODEL, env.IMAGEN_MODEL, DEFAULT_IMAGE_MODELS.vertex)
16
- };
17
- }
18
- var paidAllowed = (env) => env.AI_ALLOW_PAID === true;
19
- function vertexConfigured(env) {
20
- return Boolean(env.GOOGLE_CLIENT_EMAIL && env.GOOGLE_PRIVATE_KEY && env.GOOGLE_CLOUD_PROJECT);
21
- }
22
-
23
- // src/vertex.ts
24
- import "server-only";
25
- var cachedToken = null;
26
- function base64UrlEncode(input) {
27
- return Buffer.from(input).toString("base64url");
28
- }
29
- async function getVertexAccessToken(account, fetchImpl = fetch) {
30
- const now = Date.now();
31
- if (cachedToken && cachedToken.expiresAt - 6e4 > now) return cachedToken.accessToken;
32
- const { createSign } = await import("crypto");
33
- const issuedAt = Math.floor(now / 1e3);
34
- const expiresIn = 3600;
35
- const header = base64UrlEncode(JSON.stringify({ alg: "RS256", typ: "JWT" }));
36
- const claims = base64UrlEncode(
37
- JSON.stringify({
38
- iss: account.clientEmail,
39
- scope: "https://www.googleapis.com/auth/cloud-platform",
40
- aud: account.tokenUri,
41
- iat: issuedAt,
42
- exp: issuedAt + expiresIn
43
- })
44
- );
45
- const signingInput = `${header}.${claims}`;
46
- const signature = createSign("RSA-SHA256").update(signingInput).sign(account.privateKey);
47
- const jwt = `${signingInput}.${base64UrlEncode(signature)}`;
48
- const response = await fetchImpl(account.tokenUri, {
49
- method: "POST",
50
- headers: { "content-type": "application/x-www-form-urlencoded" },
51
- body: new URLSearchParams({
52
- grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
53
- assertion: jwt
54
- })
55
- });
56
- if (!response.ok) {
57
- throw new Error(`Vertex token exchange failed: HTTP ${response.status}`);
58
- }
59
- const data = await response.json();
60
- cachedToken = { accessToken: data.access_token, expiresAt: now + data.expires_in * 1e3 };
61
- return data.access_token;
62
- }
63
-
64
- // src/image.ts
65
- var DEFAULT_IMAGE_MODELS2 = [
66
- { model: "gemini-3.1-flash-image", location: "global" },
67
- { model: "gemini-2.5-flash-image", location: "us-central1" }
68
- ];
69
- function defaultLocation(model) {
70
- return /^gemini-3/.test(model) ? "global" : "us-central1";
71
- }
72
- function imageModelChain(config) {
73
- const chain = config.model ? [{ model: config.model, location: config.location ?? defaultLocation(config.model) }] : [];
74
- for (const candidate of DEFAULT_IMAGE_MODELS2) {
75
- if (!chain.some((entry) => entry.model === candidate.model)) chain.push(candidate);
76
- }
77
- return chain;
78
- }
79
- function endpoint(project, target, method) {
80
- const host = target.location === "global" ? "aiplatform.googleapis.com" : `${target.location}-aiplatform.googleapis.com`;
81
- return `https://${host}/v1/projects/${project}/locations/${target.location}/publishers/google/models/${target.model}:${method}`;
82
- }
83
- function requestFor(target, prompt, aspectRatio) {
84
- if (target.model.startsWith("imagen-")) {
85
- return {
86
- method: "predict",
87
- body: {
88
- instances: [{ prompt }],
89
- parameters: { sampleCount: 1, aspectRatio, safetySetting: "block_medium_and_above", personGeneration: "allow_adult" }
90
- }
91
- };
92
- }
93
- return {
94
- method: "generateContent",
95
- body: {
96
- contents: [{ role: "user", parts: [{ text: prompt }] }],
97
- generationConfig: { responseModalities: ["IMAGE"], imageConfig: { aspectRatio } }
98
- }
99
- };
100
- }
101
- function extractImage(data) {
102
- const prediction = data?.predictions?.[0];
103
- if (prediction?.bytesBase64Encoded) return { base64: prediction.bytesBase64Encoded, mimeType: prediction.mimeType || "image/png" };
104
- const parts = data?.candidates?.[0]?.content?.parts;
105
- const inline = parts?.find((part) => part.inlineData?.data)?.inlineData;
106
- return inline?.data ? { base64: inline.data, mimeType: inline.mimeType || "image/png" } : null;
107
- }
108
- async function generateImageVertex(prompt, config, options = {}) {
109
- const fetchImpl = options.fetchImpl ?? fetch;
110
- const aspectRatio = options.aspectRatio ?? "16:9";
111
- let accessToken;
112
- try {
113
- accessToken = await getVertexAccessToken(
114
- { clientEmail: config.clientEmail, privateKey: config.privateKey, tokenUri: config.tokenUri },
115
- fetchImpl
116
- );
117
- } catch (error) {
118
- return { ok: false, error: `Image generation failed: ${error instanceof Error ? error.message : "authentication error"}` };
119
- }
120
- const failures = [];
121
- for (const target of imageModelChain(config)) {
122
- if (options.signal?.aborted) return { ok: false, error: "Image generation timed out." };
123
- const { method, body } = requestFor(target, prompt, aspectRatio);
124
- try {
125
- const response = await fetchImpl(endpoint(config.project, target, method), {
126
- method: "POST",
127
- headers: { "content-type": "application/json", authorization: `Bearer ${accessToken}` },
128
- signal: options.signal,
129
- body: JSON.stringify(body)
130
- });
131
- if (!response.ok) {
132
- failures.push(`${target.model}: HTTP ${response.status}`);
133
- continue;
134
- }
135
- const image = extractImage(await response.json());
136
- if (image) return { ok: true, ...image };
137
- failures.push(`${target.model}: no image data`);
138
- } catch (error) {
139
- if (options.signal?.aborted) return { ok: false, error: "Image generation timed out." };
140
- failures.push(`${target.model}: ${error instanceof Error ? error.message : "transport error"}`);
141
- }
142
- }
143
- return { ok: false, error: `Image generation failed (${failures.join("; ")}).` };
144
- }
145
- function vertexImageAvailable(env) {
146
- return vertexConfigured(env);
147
- }
148
- var FLUX_SIZE = {
149
- "16:9": { width: 1344, height: 768 },
150
- "4:3": { width: 1024, height: 768 },
151
- "1:1": { width: 1024, height: 1024 }
152
- };
153
- function nvidiaImageRequest(model, prompt, aspectRatio, seed) {
154
- const size = FLUX_SIZE[aspectRatio];
155
- if (/schnell/.test(model)) return { prompt, ...size, steps: 4, seed };
156
- return { prompt, ...size, steps: 30, cfg_scale: 3.5, mode: "base", seed };
157
- }
158
- function extractNvidiaImage(data) {
159
- const artifact = data?.artifacts?.[0];
160
- if (!artifact?.base64 || artifact.finishReason && artifact.finishReason !== "SUCCESS") return null;
161
- const mimeType = artifact.base64.startsWith("/9j/") ? "image/jpeg" : artifact.base64.startsWith("UklG") ? "image/webp" : "image/png";
162
- return { base64: artifact.base64, mimeType };
163
- }
164
- async function postForImage(model, url, init, extract, options) {
165
- try {
166
- const response = await (options.fetchImpl ?? fetch)(url, {
167
- method: "POST",
168
- headers: { "content-type": "application/json", ...init.headers },
169
- signal: options.signal,
170
- body: JSON.stringify(init.body)
171
- });
172
- if (!response.ok) return { ok: false, error: `${model}: HTTP ${response.status}` };
173
- const image = extract(await response.json());
174
- return image ? { ok: true, ...image } : { ok: false, error: `${model}: no image (filtered or empty)` };
175
- } catch (error) {
176
- if (options.signal?.aborted) return { ok: false, error: "Image generation timed out." };
177
- return { ok: false, error: `${model}: ${error instanceof Error ? error.message : "transport error"}` };
178
- }
179
- }
180
- function generateImageNvidia(prompt, config, aspectRatio, options) {
181
- return postForImage(
182
- config.model,
183
- `https://ai.api.nvidia.com/v1/genai/${config.model}`,
184
- {
185
- headers: { authorization: `Bearer ${config.apiKey}`, accept: "application/json" },
186
- body: nvidiaImageRequest(config.model, prompt, aspectRatio, Math.floor(Math.random() * 2 ** 31))
187
- },
188
- extractNvidiaImage,
189
- options
190
- );
191
- }
192
- function generateImageGemini(prompt, config, aspectRatio, options) {
193
- return postForImage(
194
- config.model,
195
- `https://generativelanguage.googleapis.com/v1beta/models/${config.model}:generateContent`,
196
- {
197
- headers: { "x-goog-api-key": config.apiKey },
198
- body: { contents: [{ role: "user", parts: [{ text: prompt }] }], generationConfig: { responseModalities: ["IMAGE"], imageConfig: { aspectRatio } } }
199
- },
200
- extractImage,
201
- options
202
- );
203
- }
204
- function imageConfigFromEnv(env) {
205
- const models = imageModels(env);
206
- const paid = paidAllowed(env);
207
- const config = {};
208
- if (env.NVIDIA_API_KEY) config.nvidia = { apiKey: env.NVIDIA_API_KEY, model: models.nvidia };
209
- if (paid && env.GEMINI_API_KEY) config.gemini = { apiKey: env.GEMINI_API_KEY, model: models.gemini };
210
- if (paid && vertexImageAvailable(env)) {
211
- config.vertex = {
212
- clientEmail: env.GOOGLE_CLIENT_EMAIL,
213
- privateKey: env.GOOGLE_PRIVATE_KEY,
214
- tokenUri: env.GOOGLE_TOKEN_URI,
215
- project: env.GOOGLE_CLOUD_PROJECT,
216
- model: models.vertex
217
- };
218
- }
219
- return config.nvidia || config.gemini || config.vertex ? config : null;
220
- }
221
- async function generateImage(prompt, config, options = {}) {
222
- const aspectRatio = options.aspectRatio ?? "16:9";
223
- const attempts = [];
224
- if (config.nvidia) attempts.push(() => generateImageNvidia(prompt, config.nvidia, aspectRatio, options));
225
- if (config.gemini) attempts.push(() => generateImageGemini(prompt, config.gemini, aspectRatio, options));
226
- if (config.vertex) attempts.push(() => generateImageVertex(prompt, config.vertex, options));
227
- const failures = [];
228
- for (const run of attempts) {
229
- if (options.signal?.aborted) return { ok: false, error: "Image generation timed out." };
230
- const outcome = await run();
231
- if (outcome.ok) return outcome;
232
- failures.push(outcome.error);
233
- }
234
- return { ok: false, error: failures.length ? `Image generation failed (${failures.join("; ")}).` : "No image provider is configured." };
235
- }
1
+ import {
2
+ DEFAULT_IMAGE_MODELS,
3
+ extractImage,
4
+ extractNvidiaImage,
5
+ generateImage,
6
+ generateImageVertex,
7
+ imageConfigFromEnv,
8
+ imageModelChain,
9
+ nvidiaImageRequest,
10
+ vertexImageAvailable
11
+ } from "./chunk-MRNQPHWI.js";
12
+ import "./chunk-BL4J2JNX.js";
13
+ import "./chunk-RBSLNDFW.js";
236
14
  export {
237
- DEFAULT_IMAGE_MODELS2 as DEFAULT_IMAGE_MODELS,
15
+ DEFAULT_IMAGE_MODELS,
238
16
  extractImage,
239
17
  extractNvidiaImage,
240
18
  generateImage,
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { AiEnv, aiEnvFromProcess, aiEnvSchema, parseAiEnv } from './env.js';
2
2
  export { ImageModels, TextModels, TextPurpose } from './models.js';
3
- export { AiHealth, AiProvider, AiResult } from './providers.js';
3
+ export { a as AdapterState, b as AdapterStatus, AiHealth, AiProvider, AiResult, c as ProviderAdapter } from './providers.js';
4
4
  export { ModelPrompt } from './guard.js';
5
5
  export { AiLogger } from './log.js';
6
6
  import 'zod';
package/dist/index.js CHANGED
@@ -1,66 +1,8 @@
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
- // Google Vertex service account (paid).
48
- GOOGLE_CLIENT_EMAIL: text,
49
- GOOGLE_PRIVATE_KEY: privateKey,
50
- GOOGLE_CLOUD_PROJECT: text,
51
- GOOGLE_TOKEN_URI: text.transform((value) => value ?? DEFAULT_GOOGLE_TOKEN_URI)
52
- });
53
- function parseAiEnv(source) {
54
- const result = aiEnvSchema.safeParse(source);
55
- if (!result.success) {
56
- const names = [...new Set(result.error.issues.map((issue) => String(issue.path[0] ?? "?")))];
57
- throw new Error(`Invalid AI environment variables: ${names.join(", ")}`);
58
- }
59
- return result.data;
60
- }
61
- function aiEnvFromProcess() {
62
- return parseAiEnv(process.env);
63
- }
1
+ import {
2
+ aiEnvFromProcess,
3
+ aiEnvSchema,
4
+ parseAiEnv
5
+ } from "./chunk-ZIHRZXPE.js";
64
6
  export {
65
7
  aiEnvFromProcess,
66
8
  aiEnvSchema,
package/dist/log.js CHANGED
@@ -1,9 +1,6 @@
1
- // src/log.ts
2
- var consoleAiLogger = {
3
- warn(message, meta) {
4
- console.warn(JSON.stringify({ level: "warn", message, ...meta }));
5
- }
6
- };
1
+ import {
2
+ consoleAiLogger
3
+ } from "./chunk-PMC3UWQ2.js";
7
4
  export {
8
5
  consoleAiLogger
9
6
  };
package/dist/models.js CHANGED
@@ -1,54 +1,12 @@
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
- }
1
+ import {
2
+ DEFAULT_IMAGE_MODELS,
3
+ DEFAULT_TEXT_MODELS,
4
+ imageModels,
5
+ paidAllowed,
6
+ textModels,
7
+ thinkingConfigFor,
8
+ vertexConfigured
9
+ } from "./chunk-RBSLNDFW.js";
52
10
  export {
53
11
  DEFAULT_IMAGE_MODELS,
54
12
  DEFAULT_TEXT_MODELS,