@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.
- package/CHANGELOG.md +14 -0
- package/README.md +75 -10
- package/dist/adapters.d.ts +6 -0
- package/dist/adapters.js +27 -0
- package/dist/availability.d.ts +8 -3
- package/dist/availability.js +14 -48
- package/dist/chunk-BL4J2JNX.js +48 -0
- package/dist/chunk-MRNQPHWI.js +194 -0
- package/dist/chunk-PMC3UWQ2.js +10 -0
- package/dist/chunk-R3YY2Y7S.js +571 -0
- package/dist/chunk-RBSLNDFW.js +61 -0
- package/dist/chunk-ZIHRZXPE.js +109 -0
- package/dist/env.d.ts +30 -0
- package/dist/env.js +6 -63
- package/dist/guard.d.ts +15 -3
- package/dist/guard.js +0 -0
- package/dist/image.js +14 -236
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -63
- package/dist/log.js +3 -6
- package/dist/models.js +9 -51
- package/dist/providers.d.ts +144 -8
- package/dist/providers.js +23 -422
- package/dist/vertex.js +4 -43
- package/package.json +6 -2
|
@@ -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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
package/dist/models.js
CHANGED
|
@@ -1,54 +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
|
-
}
|
|
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,
|