@bonginkan/maria 4.3.46 → 4.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/README.md +8 -3
- package/dist/READY.manifest.json +1 -1
- package/dist/bin/maria.cjs +1331 -756
- package/dist/bin/maria.cjs.map +1 -1
- package/dist/cli.cjs +1330 -755
- package/dist/cli.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/server/express-server.cjs +298 -74
- package/dist/server/express-server.js +298 -74
- package/dist/server-express.cjs +298 -74
- package/dist/server-express.cjs.map +1 -1
- package/package.json +3 -3
- package/src/slash-commands/READY.manifest.json +1 -1
|
@@ -6854,6 +6854,65 @@ var init_image_post = __esm({
|
|
|
6854
6854
|
"src/services/media-orchestrator/image-post.ts"() {
|
|
6855
6855
|
}
|
|
6856
6856
|
});
|
|
6857
|
+
|
|
6858
|
+
// src/services/routing/model-routing.ts
|
|
6859
|
+
var model_routing_exports = {};
|
|
6860
|
+
__export(model_routing_exports, {
|
|
6861
|
+
determineRouting: () => determineRouting,
|
|
6862
|
+
providerFromModel: () => providerFromModel
|
|
6863
|
+
});
|
|
6864
|
+
function providerFromModel(model) {
|
|
6865
|
+
const m2 = (model || "").toLowerCase();
|
|
6866
|
+
if (!m2) return void 0;
|
|
6867
|
+
if (m2.startsWith("gemini") || m2.startsWith("veo")) return "google";
|
|
6868
|
+
if (m2.startsWith("gpt") || m2.startsWith("sora")) return "openai";
|
|
6869
|
+
return void 0;
|
|
6870
|
+
}
|
|
6871
|
+
function determineRouting(input) {
|
|
6872
|
+
const requestedProvider = normalizeProvider(input.requestedProvider);
|
|
6873
|
+
const requestedModel = input.requestedModel?.trim();
|
|
6874
|
+
const isPro = !!input.isProOrAbove;
|
|
6875
|
+
if (requestedModel && requestedModel.length > 0) {
|
|
6876
|
+
const p = providerFromModel(requestedModel);
|
|
6877
|
+
if (!p) return { error: "unknown_model", message: `Unknown model family for '${requestedModel}'` };
|
|
6878
|
+
if (p === "google" && !input.hasGeminiKey) return { error: "missing_key", message: `Requested model '${requestedModel}' requires Google API key` };
|
|
6879
|
+
if (p === "openai" && !input.hasOpenAIKey) return { error: "missing_key", message: `Requested model '${requestedModel}' requires OpenAI API key` };
|
|
6880
|
+
return { provider: p, model: requestedModel };
|
|
6881
|
+
}
|
|
6882
|
+
if (requestedProvider) {
|
|
6883
|
+
if (requestedProvider === "google") {
|
|
6884
|
+
if (!input.hasGeminiKey) return { error: "missing_key", message: "Google API key is not configured" };
|
|
6885
|
+
return { provider: "google", model: "gemini-2.5-flash" };
|
|
6886
|
+
}
|
|
6887
|
+
if (requestedProvider === "openai") {
|
|
6888
|
+
if (!input.hasOpenAIKey) return { error: "missing_key", message: "OpenAI API key is not configured" };
|
|
6889
|
+
return { provider: "openai", model: isPro ? "gpt-5" : "gpt-5-mini" };
|
|
6890
|
+
}
|
|
6891
|
+
}
|
|
6892
|
+
if ((input.taskType || "").toLowerCase() === "research") {
|
|
6893
|
+
if (!input.hasGeminiKey) return { error: "missing_key", message: "Google API key is required for research task" };
|
|
6894
|
+
return { provider: "google", model: "gemini-2.5-flash" };
|
|
6895
|
+
}
|
|
6896
|
+
if (isPro && input.hasOpenAIKey) {
|
|
6897
|
+
return { provider: "openai", model: "gpt-5" };
|
|
6898
|
+
}
|
|
6899
|
+
if (input.hasGeminiKey) {
|
|
6900
|
+
return { provider: "google", model: "gemini-2.5-flash" };
|
|
6901
|
+
}
|
|
6902
|
+
if (input.hasOpenAIKey) {
|
|
6903
|
+
return { provider: "openai", model: "gpt-5-mini" };
|
|
6904
|
+
}
|
|
6905
|
+
return { error: "no_provider_available", message: "No valid provider API key configured" };
|
|
6906
|
+
}
|
|
6907
|
+
function normalizeProvider(p) {
|
|
6908
|
+
const s2 = (p || "").toLowerCase().trim();
|
|
6909
|
+
if (s2 === "google" || s2 === "openai") return s2;
|
|
6910
|
+
return void 0;
|
|
6911
|
+
}
|
|
6912
|
+
var init_model_routing = __esm({
|
|
6913
|
+
"src/services/routing/model-routing.ts"() {
|
|
6914
|
+
}
|
|
6915
|
+
});
|
|
6857
6916
|
var rateLimitStore = /* @__PURE__ */ new Map();
|
|
6858
6917
|
var RATE_LIMITS = {
|
|
6859
6918
|
"/image:FREE": { windowMs: 3e3, requests: 1 },
|
|
@@ -7219,7 +7278,7 @@ var GeminiMediaProvider = class {
|
|
|
7219
7278
|
const fps = typeof req?.fps === "number" ? req.fps : "n/a";
|
|
7220
7279
|
const duration = typeof req?.duration === "number" ? req.duration : "n/a";
|
|
7221
7280
|
throw new Error(
|
|
7222
|
-
`GeminiMediaProvider.generateVideoFrames is not supported locally. Use server /api/v1/video (veo-3.
|
|
7281
|
+
`GeminiMediaProvider.generateVideoFrames is not supported locally. Use server /api/v1/video (veo-3.1-generate-preview). requested_fps=${fps}; requested_duration=${duration}`
|
|
7223
7282
|
);
|
|
7224
7283
|
}
|
|
7225
7284
|
};
|
|
@@ -9447,7 +9506,7 @@ app.get("/api/status", (req, res) => {
|
|
|
9447
9506
|
app.get("/", (req, res) => {
|
|
9448
9507
|
res.json({
|
|
9449
9508
|
name: "MARIA CODE API",
|
|
9450
|
-
version: "4.
|
|
9509
|
+
version: "4.4.0",
|
|
9451
9510
|
status: "running",
|
|
9452
9511
|
environment: process.env.NODE_ENV || "development",
|
|
9453
9512
|
endpoints: {
|
|
@@ -9931,7 +9990,8 @@ app.post("/api/v1/image", rateLimitMiddleware, async (req, res) => {
|
|
|
9931
9990
|
mime: format === "jpg" ? "image/jpeg" : `image/${format}`,
|
|
9932
9991
|
bytesBase64: b.toString("base64")
|
|
9933
9992
|
}));
|
|
9934
|
-
|
|
9993
|
+
const publicUrl = saved.manifestPath.startsWith("/") ? saved.manifestPath : `/${saved.manifestPath}`;
|
|
9994
|
+
return res.json({ success: true, data: { url: publicUrl, files: saved.files, filesInline, jobId: manifest.trace } });
|
|
9935
9995
|
} catch (error) {
|
|
9936
9996
|
console.error("[Image API] Error:", error);
|
|
9937
9997
|
const mapped = classifyMediaError(error);
|
|
@@ -9943,7 +10003,7 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
9943
10003
|
await loadProviderKeys();
|
|
9944
10004
|
const auth = req.headers.authorization;
|
|
9945
10005
|
if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized", message: "Login required", hint: "Sign in and retry" });
|
|
9946
|
-
const { prompt, duration = 8, fps = 24, res: resStr = "1280x720", aspect: aspectStr, format = "mp4", model, seed } = req.body || {};
|
|
10006
|
+
const { prompt, duration = 8, fps = 24, res: resStr = "1280x720", aspect: aspectStr, format = "mp4", model, seed, provider: reqProvider } = req.body || {};
|
|
9947
10007
|
if (!prompt) return res.status(400).json({ error: "bad_request", message: "prompt required" });
|
|
9948
10008
|
let w, h2;
|
|
9949
10009
|
const aspect = typeof aspectStr === "string" && (aspectStr === "16:9" || aspectStr === "9:16") ? aspectStr : void 0;
|
|
@@ -9971,15 +10031,102 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
9971
10031
|
} else {
|
|
9972
10032
|
return res.status(400).json({ error: "bad_request", message: "res must be WxH or 720|1080" });
|
|
9973
10033
|
}
|
|
10034
|
+
const vidToken = auth.substring("Bearer ".length).trim();
|
|
10035
|
+
const vidDecoded = await decodeFirebaseToken(vidToken).catch(() => null);
|
|
10036
|
+
const vidUid = vidDecoded?.uid || vidDecoded?.sub || "current";
|
|
10037
|
+
let isProOrAbove = false;
|
|
10038
|
+
try {
|
|
10039
|
+
const { planId: _pid } = await getUserPlanAndLimits(vidUid);
|
|
10040
|
+
const p = String(_pid || "free").toLowerCase();
|
|
10041
|
+
isProOrAbove = ["pro", "pro-annual", "ultra", "ultra-annual", "enterprise"].includes(p);
|
|
10042
|
+
} catch {
|
|
10043
|
+
}
|
|
10044
|
+
const requestedProvider = typeof reqProvider === "string" ? reqProvider.toLowerCase() : void 0;
|
|
10045
|
+
const requestedModel = typeof model === "string" ? String(model).trim().toLowerCase() : void 0;
|
|
10046
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
10047
|
+
const preferOpenAI = requestedProvider === "openai" || requestedModel?.startsWith("sora") || isProOrAbove && !!openaiKey;
|
|
10048
|
+
if (preferOpenAI && openaiKey) {
|
|
10049
|
+
const OpenAI2 = (await import('openai')).default;
|
|
10050
|
+
const client = new OpenAI2({ apiKey: openaiKey });
|
|
10051
|
+
const soraModel = requestedModel && requestedModel.length > 0 ? requestedModel : "sora-2";
|
|
10052
|
+
const secondsStr = (() => {
|
|
10053
|
+
const d = Number(duration) || 8;
|
|
10054
|
+
if (d <= 4) return "4";
|
|
10055
|
+
if (d <= 8) return "8";
|
|
10056
|
+
return "12";
|
|
10057
|
+
})();
|
|
10058
|
+
const size = `${w}x${h2}`;
|
|
10059
|
+
const startedMs2 = Date.now();
|
|
10060
|
+
let job;
|
|
10061
|
+
try {
|
|
10062
|
+
job = await client.videos.create({ model: soraModel, prompt: String(prompt), seconds: secondsStr, size });
|
|
10063
|
+
} catch (e2) {
|
|
10064
|
+
try {
|
|
10065
|
+
console.warn("[Video API][Sora] videos.create failed:", e2?.message || String(e2));
|
|
10066
|
+
} catch {
|
|
10067
|
+
}
|
|
10068
|
+
throw e2;
|
|
10069
|
+
}
|
|
10070
|
+
let safetyDeadline = Date.now() + 72e4;
|
|
10071
|
+
while (job && (job.status === "queued" || job.status === "in_progress")) {
|
|
10072
|
+
if (Date.now() > safetyDeadline) return res.status(504).json({ error: "timeout", message: "video generation timed out" });
|
|
10073
|
+
await new Promise((r2) => setTimeout(r2, 8e3));
|
|
10074
|
+
job = await client.videos.retrieve(job.id);
|
|
10075
|
+
}
|
|
10076
|
+
if (!job || job.status !== "completed") {
|
|
10077
|
+
return res.status(500).json({ error: "internal_error", message: `video generation failed: ${job?.status || "unknown"}` });
|
|
10078
|
+
}
|
|
10079
|
+
const downloadWithBackoff = async () => {
|
|
10080
|
+
let delay = 2e3;
|
|
10081
|
+
for (let i2 = 0; i2 < 6; i2++) {
|
|
10082
|
+
try {
|
|
10083
|
+
const content = await client.videos.downloadContent(job.id);
|
|
10084
|
+
const ab = await content.arrayBuffer();
|
|
10085
|
+
return Buffer.from(ab);
|
|
10086
|
+
} catch (e2) {
|
|
10087
|
+
if (i2 === 5) throw e2;
|
|
10088
|
+
await new Promise((r2) => setTimeout(r2, delay));
|
|
10089
|
+
delay = Math.min(15e3, Math.floor(delay * 1.8));
|
|
10090
|
+
}
|
|
10091
|
+
}
|
|
10092
|
+
throw new Error("download failed after retries");
|
|
10093
|
+
};
|
|
10094
|
+
const videoBytes2 = await downloadWithBackoff();
|
|
10095
|
+
const outExt2 = ".mp4";
|
|
10096
|
+
const traceId2 = Math.random().toString(36).slice(2, 8).toUpperCase();
|
|
10097
|
+
const promptHash2 = hashPrompt(prompt);
|
|
10098
|
+
const manifest2 = {
|
|
10099
|
+
kind: "video",
|
|
10100
|
+
request: { promptHash: promptHash2, seed, params: { size: [w, h2], fps: Number(fps) || 24, duration: Number(secondsStr), format: "mp4" }, model: soraModel, provider: "openai" },
|
|
10101
|
+
artifacts: [],
|
|
10102
|
+
metrics: { durationMs: Date.now() - startedMs2, retries: 0, fallbacks: 0 },
|
|
10103
|
+
trace: traceId2
|
|
10104
|
+
};
|
|
10105
|
+
const baseDir2 = path__namespace.default.join("artifacts", "media", "videos", traceId2);
|
|
10106
|
+
const saved2 = await saveArtifacts({ root: process.cwd(), kind: "video", baseDir: baseDir2, flat: true, trace: traceId2 }, [{ bytes: videoBytes2, ext: outExt2, logicalName: "video" }], manifest2);
|
|
10107
|
+
const idemKey2 = req.headers["idempotency-key"] || void 0;
|
|
10108
|
+
await applyConsumption(vidUid, { requests: 1, video: 1 }, idemKey2);
|
|
10109
|
+
jobIndex.set(String(manifest2.trace), { id: String(manifest2.trace), status: "completed", kind: "video", manifestPath: saved2.manifestPath, createdAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString(), uid: vidUid });
|
|
10110
|
+
const publicUrl = saved2.manifestPath.startsWith("/") ? saved2.manifestPath : `/${saved2.manifestPath}`;
|
|
10111
|
+
return res.json({ success: true, data: { url: publicUrl || (saved2.files[0] ? `/${saved2.files[0]}` : void 0), files: saved2.files, jobId: manifest2.trace, applied: { durationSeconds: Number(secondsStr), frameRate: Number(fps) || 24 } } });
|
|
10112
|
+
}
|
|
9974
10113
|
const { GoogleGenAI } = __require("@google/genai");
|
|
9975
10114
|
const apiKey = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY;
|
|
9976
10115
|
if (!apiKey) return res.status(503).json({ error: "provider_unavailable", message: "Provider API key is not configured", hint: "Set GOOGLE_API_KEY or GEMINI_API_KEY on the server" });
|
|
9977
10116
|
const ai = new GoogleGenAI({ apiKey });
|
|
9978
|
-
const veoModel = model && String(model).trim() || process.env.MARIA_VIDEO_MODEL || "veo-3.
|
|
9979
|
-
const aspectRatio = "16:9";
|
|
10117
|
+
const veoModel = model && String(model).trim() || process.env.MARIA_VIDEO_MODEL || "veo-3.1-generate-preview";
|
|
10118
|
+
const aspectRatio = w >= h2 ? "16:9" : "9:16";
|
|
9980
10119
|
const startedMs = Date.now();
|
|
9981
10120
|
const requestedDuration = Number(duration);
|
|
9982
|
-
const
|
|
10121
|
+
const maxEdge = Math.max(w, h2);
|
|
10122
|
+
const is1080 = maxEdge >= 1920;
|
|
10123
|
+
const effectiveDuration = (() => {
|
|
10124
|
+
if (is1080) return 8;
|
|
10125
|
+
const d = Number.isFinite(requestedDuration) ? Math.floor(requestedDuration) : 8;
|
|
10126
|
+
if (d <= 4) return 4;
|
|
10127
|
+
if (d <= 6) return 6;
|
|
10128
|
+
return 8;
|
|
10129
|
+
})();
|
|
9983
10130
|
const requestedFps = Number(fps);
|
|
9984
10131
|
const effectiveFps = Number.isFinite(requestedFps) ? Math.min(60, Math.max(1, Math.floor(requestedFps))) : 24;
|
|
9985
10132
|
const resolution = Math.max(w, h2) >= 1920 ? "1080p" : "720p";
|
|
@@ -9994,12 +10141,12 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
9994
10141
|
frameRate: effectiveFps
|
|
9995
10142
|
}
|
|
9996
10143
|
});
|
|
9997
|
-
const deadline = Date.now() +
|
|
10144
|
+
const deadline = Date.now() + 9e5;
|
|
9998
10145
|
while (!operation?.done) {
|
|
9999
10146
|
if (Date.now() > deadline) {
|
|
10000
10147
|
return res.status(504).json({ error: "timeout", message: "video generation timed out" });
|
|
10001
10148
|
}
|
|
10002
|
-
await new Promise((r2) => setTimeout(r2,
|
|
10149
|
+
await new Promise((r2) => setTimeout(r2, 12e3));
|
|
10003
10150
|
operation = await ai.operations.getVideosOperation({ operation });
|
|
10004
10151
|
}
|
|
10005
10152
|
await new Promise((r2) => setTimeout(r2, 2500));
|
|
@@ -10145,7 +10292,7 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
10145
10292
|
return null;
|
|
10146
10293
|
};
|
|
10147
10294
|
let videoBytes = null;
|
|
10148
|
-
const maxAttempts =
|
|
10295
|
+
const maxAttempts = 15;
|
|
10149
10296
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
10150
10297
|
let bytes = await trySdkDownloadOnce();
|
|
10151
10298
|
if (!bytes) bytes = await tryManualDownload();
|
|
@@ -10156,7 +10303,7 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
10156
10303
|
break;
|
|
10157
10304
|
}
|
|
10158
10305
|
}
|
|
10159
|
-
await new Promise((r2) => setTimeout(r2, Math.min(
|
|
10306
|
+
await new Promise((r2) => setTimeout(r2, Math.min(12e3, 1800 * attempt)));
|
|
10160
10307
|
}
|
|
10161
10308
|
if (!videoBytes) throw new Error("failed to obtain fully materialized video");
|
|
10162
10309
|
const header = videoBytes.subarray(0, 256);
|
|
@@ -10258,6 +10405,13 @@ app.post("/api/v1/code", rateLimitMiddleware, async (req, res) => {
|
|
|
10258
10405
|
const decoded = await decodeFirebaseToken(idToken).catch(() => null);
|
|
10259
10406
|
if (!decoded) return res.status(401).json({ error: "unauthorized" });
|
|
10260
10407
|
const uid = decoded?.uid || decoded?.sub;
|
|
10408
|
+
let isProOrAbove = false;
|
|
10409
|
+
try {
|
|
10410
|
+
const { planId: _pid } = await getUserPlanAndLimits(uid);
|
|
10411
|
+
const p = String(_pid || "free").toLowerCase();
|
|
10412
|
+
isProOrAbove = ["pro", "pro-annual", "ultra", "ultra-annual", "enterprise"].includes(p);
|
|
10413
|
+
} catch {
|
|
10414
|
+
}
|
|
10261
10415
|
const { prompt, language = "typescript", model = "gpt-4" } = req.body;
|
|
10262
10416
|
if (!prompt) {
|
|
10263
10417
|
return res.status(400).json({
|
|
@@ -10351,7 +10505,8 @@ app.post("/v1/ai-proxy", rateLimitMiddleware, async (req, res) => {
|
|
|
10351
10505
|
const idemKey = req.headers["idempotency-key"] || void 0;
|
|
10352
10506
|
if (process.env.MARIA_TELEMETRY === "1") {
|
|
10353
10507
|
try {
|
|
10354
|
-
|
|
10508
|
+
const attCount = Array.isArray((req.body?.metadata || {}).attachments) ? (req.body.metadata.attachments || []).filter(Boolean).length : 0;
|
|
10509
|
+
console.log(JSON.stringify({ ev: "ai_proxy_request", taskType: taskType || "unknown", promptLen: String(effectivePrompt || research?.query || "").length, requestedProvider: reqProvider || null, requestedModel: reqModel || null, attachments: attCount }));
|
|
10355
10510
|
} catch {
|
|
10356
10511
|
}
|
|
10357
10512
|
}
|
|
@@ -10383,18 +10538,40 @@ app.post("/v1/ai-proxy", rateLimitMiddleware, async (req, res) => {
|
|
|
10383
10538
|
const gemKey = sanitizeKey2(keys?.googleApiKey || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY);
|
|
10384
10539
|
const requestedProvider = typeof reqProvider === "string" ? reqProvider.toLowerCase() : void 0;
|
|
10385
10540
|
const requestedModel = typeof reqModel === "string" ? String(reqModel).trim() : void 0;
|
|
10386
|
-
|
|
10387
|
-
|
|
10541
|
+
let isProOrAbove = false;
|
|
10542
|
+
try {
|
|
10543
|
+
const { planId: _pid } = await getUserPlanAndLimits(uid);
|
|
10544
|
+
const p = String(_pid || "free").toLowerCase();
|
|
10545
|
+
isProOrAbove = ["pro", "pro-annual", "ultra", "ultra-annual", "enterprise"].includes(p);
|
|
10546
|
+
} catch {
|
|
10547
|
+
}
|
|
10548
|
+
const { determineRouting: determineRouting2 } = await Promise.resolve().then(() => (init_model_routing(), model_routing_exports));
|
|
10549
|
+
const decision = determineRouting2({
|
|
10550
|
+
requestedModel,
|
|
10551
|
+
requestedProvider,
|
|
10552
|
+
taskType,
|
|
10553
|
+
isProOrAbove,
|
|
10554
|
+
hasGeminiKey: !!gemKey,
|
|
10555
|
+
hasOpenAIKey: !!(keys?.openaiApiKey || process.env.OPENAI_API_KEY)
|
|
10556
|
+
});
|
|
10557
|
+
if (decision.error) {
|
|
10558
|
+
const map = { missing_key: 503, unknown_model: 400, no_provider_available: 503 };
|
|
10559
|
+
const status = map[decision.error] || 400;
|
|
10560
|
+
return res.status(status).json({ error: decision.error, message: decision.message });
|
|
10561
|
+
}
|
|
10562
|
+
const effectiveProvider = decision.provider;
|
|
10563
|
+
const effectiveModel = decision.model;
|
|
10564
|
+
if (effectiveProvider === "google") {
|
|
10388
10565
|
try {
|
|
10389
10566
|
const { GoogleGenerativeAI: GoogleGenerativeAI2 } = await import('@google/generative-ai');
|
|
10390
10567
|
const ai = new GoogleGenerativeAI2(gemKey);
|
|
10391
|
-
const modelName =
|
|
10392
|
-
const
|
|
10393
|
-
let
|
|
10568
|
+
const modelName = effectiveModel || "gemini-2.5-flash";
|
|
10569
|
+
const model = ai.getGenerativeModel({ model: modelName });
|
|
10570
|
+
let content = "";
|
|
10394
10571
|
if (taskType === "research" && research?.query) {
|
|
10395
10572
|
const tool = { googleSearch: {} };
|
|
10396
10573
|
const r2 = await ai.models.generateContent({ model: modelName, contents: String(research.query), config: { tools: [tool] } });
|
|
10397
|
-
|
|
10574
|
+
content = String(r2?.text?.() || r2?.text || r2?.response?.candidates?.[0]?.content?.parts?.[0]?.text || "");
|
|
10398
10575
|
} else {
|
|
10399
10576
|
const attachments = Array.isArray((req.body?.metadata || {}).attachments) ? (req.body.metadata.attachments || []).filter(Boolean) : [];
|
|
10400
10577
|
const parts = [{ text: String(effectivePrompt || "") }];
|
|
@@ -10403,93 +10580,140 @@ app.post("/v1/ai-proxy", rateLimitMiddleware, async (req, res) => {
|
|
|
10403
10580
|
const b64 = String(a.data_base64 || "");
|
|
10404
10581
|
const mime = String(a.mime || "application/octet-stream");
|
|
10405
10582
|
if (!b64) continue;
|
|
10583
|
+
if (Buffer.byteLength(b64, "base64") > 10 * 1024 * 1024) continue;
|
|
10406
10584
|
parts.push({ inlineData: { data: b64, mimeType: mime } });
|
|
10407
10585
|
} catch {
|
|
10408
10586
|
}
|
|
10409
10587
|
}
|
|
10410
|
-
const resp = await
|
|
10411
|
-
|
|
10588
|
+
const resp = await model.generateContent({ contents: [{ role: "user", parts }] });
|
|
10589
|
+
content = resp?.response?.text?.() || resp?.response?.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
10412
10590
|
}
|
|
10413
10591
|
if (process.env.MARIA_TELEMETRY === "1") {
|
|
10414
10592
|
try {
|
|
10415
|
-
console.log(JSON.stringify({ ev: "ai_proxy_route", vendor: "google", model: modelName, empty: !
|
|
10593
|
+
console.log(JSON.stringify({ ev: "ai_proxy_route", vendor: "google", model: modelName, empty: !content }));
|
|
10416
10594
|
} catch {
|
|
10417
10595
|
}
|
|
10418
10596
|
}
|
|
10419
|
-
const
|
|
10420
|
-
if (taskType === "code" || taskType === "evaluation")
|
|
10597
|
+
const consumption = { requests: 1 };
|
|
10598
|
+
if (taskType === "code" || taskType === "evaluation") consumption.code = 1;
|
|
10421
10599
|
try {
|
|
10422
|
-
await applyConsumption(uid,
|
|
10600
|
+
await applyConsumption(uid, consumption, idemKey);
|
|
10423
10601
|
} catch {
|
|
10424
10602
|
}
|
|
10425
|
-
return res.json({ data: { content
|
|
10603
|
+
return res.json({ data: { content, routedModel: { vendor: "google", family: "gemini", name: modelName, reason: taskType || "code" } } });
|
|
10426
10604
|
} catch (e2) {
|
|
10427
|
-
|
|
10605
|
+
const msg = e2?.message || "Google provider request failed";
|
|
10428
10606
|
if (process.env.MARIA_TELEMETRY === "1") {
|
|
10429
10607
|
try {
|
|
10430
10608
|
console.log(JSON.stringify({ ev: "ai_proxy_google_error", message: e2?.message || String(e2) }));
|
|
10431
10609
|
} catch {
|
|
10432
10610
|
}
|
|
10433
10611
|
}
|
|
10612
|
+
return res.status(502).json({ error: "provider_error", message: String(msg) });
|
|
10434
10613
|
}
|
|
10435
10614
|
}
|
|
10436
|
-
|
|
10437
|
-
|
|
10615
|
+
if (effectiveProvider === "openai") {
|
|
10616
|
+
const openaiKey = sanitizeKey2(keys?.openaiApiKey || process.env.OPENAI_API_KEY);
|
|
10617
|
+
if (!openaiKey) {
|
|
10618
|
+
if (process.env.MARIA_TELEMETRY === "1") {
|
|
10619
|
+
try {
|
|
10620
|
+
console.log(JSON.stringify({ ev: "ai_proxy_no_keys" }));
|
|
10621
|
+
} catch {
|
|
10622
|
+
}
|
|
10623
|
+
}
|
|
10624
|
+
return res.status(503).json({ error: "provider_unavailable", message: "No valid OpenAI API key" });
|
|
10625
|
+
}
|
|
10626
|
+
const OpenAI2 = (await import('openai')).default;
|
|
10627
|
+
const client = new OpenAI2({ apiKey: openaiKey });
|
|
10628
|
+
let model = effectiveModel || (isProOrAbove ? "gpt-5" : "gpt-5-mini");
|
|
10629
|
+
let content = "";
|
|
10630
|
+
let totalTokens = 0;
|
|
10631
|
+
const attachments = Array.isArray((req.body?.metadata || {}).attachments) ? (req.body.metadata.attachments || []).filter(Boolean) : [];
|
|
10632
|
+
try {
|
|
10633
|
+
if (attachments.length > 0) {
|
|
10634
|
+
const vs = await client.vectorStores.create({ name: `ai-proxy-${uid}-${Date.now()}` });
|
|
10635
|
+
for (const a of attachments) {
|
|
10636
|
+
try {
|
|
10637
|
+
const b64 = String(a.data_base64 || "");
|
|
10638
|
+
if (!b64) continue;
|
|
10639
|
+
const buffer = Buffer.from(b64, "base64");
|
|
10640
|
+
const filename = a.name || "attachment.txt";
|
|
10641
|
+
const toFile = OpenAI2.toFile;
|
|
10642
|
+
const fileInput = toFile ? await toFile(buffer, filename) : buffer;
|
|
10643
|
+
const file = await client.files.create({ file: fileInput, purpose: "assistants" });
|
|
10644
|
+
await client.vectorStores.files.create(vs.id, { file_id: file.id });
|
|
10645
|
+
} catch {
|
|
10646
|
+
}
|
|
10647
|
+
}
|
|
10648
|
+
const r2 = await client.responses.create({
|
|
10649
|
+
model,
|
|
10650
|
+
input: String(effectivePrompt || ""),
|
|
10651
|
+
tools: [{ type: "file_search", vector_store_ids: [vs.id] }]
|
|
10652
|
+
});
|
|
10653
|
+
content = r2?.output_text || r2?.content?.[0]?.text || "";
|
|
10654
|
+
const u = r2?.usage;
|
|
10655
|
+
if (u) {
|
|
10656
|
+
const inTok = Number(u.input_tokens || u.inputTokens || 0);
|
|
10657
|
+
const outTok = Number(u.output_tokens || u.outputTokens || 0);
|
|
10658
|
+
totalTokens = Number(u.total_tokens || u.totalTokens || inTok + outTok || 0);
|
|
10659
|
+
}
|
|
10660
|
+
} else {
|
|
10661
|
+
const r2 = await client.responses.create({
|
|
10662
|
+
model,
|
|
10663
|
+
input: [
|
|
10664
|
+
{ role: "system", content: "You output only code blocks when asked for code." },
|
|
10665
|
+
{ role: "user", content: effectivePrompt || "" }
|
|
10666
|
+
]
|
|
10667
|
+
});
|
|
10668
|
+
content = r2?.output_text || r2?.content?.[0]?.text || "";
|
|
10669
|
+
const u = r2?.usage;
|
|
10670
|
+
if (u) {
|
|
10671
|
+
const inTok = Number(u.input_tokens || u.inputTokens || 0);
|
|
10672
|
+
const outTok = Number(u.output_tokens || u.outputTokens || 0);
|
|
10673
|
+
totalTokens = Number(u.total_tokens || u.totalTokens || inTok + outTok || 0);
|
|
10674
|
+
}
|
|
10675
|
+
}
|
|
10676
|
+
} catch (_e) {
|
|
10677
|
+
try {
|
|
10678
|
+
console.warn("[AI Proxy][OpenAI] responses.create failed:", _e?.message || String(_e));
|
|
10679
|
+
} catch {
|
|
10680
|
+
}
|
|
10681
|
+
model = "gpt-5-mini";
|
|
10682
|
+
try {
|
|
10683
|
+
const r2 = await client.chat.completions.create({
|
|
10684
|
+
model,
|
|
10685
|
+
messages: [
|
|
10686
|
+
{ role: "system", content: "You output only code blocks when asked for code." },
|
|
10687
|
+
{ role: "user", content: effectivePrompt || "" }
|
|
10688
|
+
]
|
|
10689
|
+
});
|
|
10690
|
+
content = r2.choices?.[0]?.message?.content || "";
|
|
10691
|
+
const u2 = r2?.usage;
|
|
10692
|
+
if (u2) totalTokens = Number(u2.total_tokens || 0);
|
|
10693
|
+
} catch (e2) {
|
|
10694
|
+
try {
|
|
10695
|
+
console.error("[AI Proxy][OpenAI] chat.completions fallback failed:", e2?.message || String(e2));
|
|
10696
|
+
} catch {
|
|
10697
|
+
}
|
|
10698
|
+
throw e2;
|
|
10699
|
+
}
|
|
10700
|
+
}
|
|
10438
10701
|
if (process.env.MARIA_TELEMETRY === "1") {
|
|
10439
10702
|
try {
|
|
10440
|
-
console.log(JSON.stringify({ ev: "
|
|
10703
|
+
console.log(JSON.stringify({ ev: "ai_proxy_route", vendor: "openai", model, empty: !content }));
|
|
10441
10704
|
} catch {
|
|
10442
10705
|
}
|
|
10443
10706
|
}
|
|
10444
|
-
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
const client = new OpenAI2({ apiKey: openaiKey });
|
|
10448
|
-
let model = requestedProvider === "openai" && requestedModel ? requestedModel : process.env.MARIA_CODE_MODEL || "gpt-5-mini";
|
|
10449
|
-
let content = "";
|
|
10450
|
-
let totalTokens = 0;
|
|
10451
|
-
try {
|
|
10452
|
-
const r2 = await client.responses.create({
|
|
10453
|
-
model,
|
|
10454
|
-
input: [
|
|
10455
|
-
{ role: "system", content: "You output only code blocks when asked for code." },
|
|
10456
|
-
{ role: "user", content: effectivePrompt || "" }
|
|
10457
|
-
]
|
|
10458
|
-
});
|
|
10459
|
-
content = r2?.output_text || r2?.content?.[0]?.text || "";
|
|
10460
|
-
const u = r2?.usage;
|
|
10461
|
-
if (u) {
|
|
10462
|
-
const inTok = Number(u.input_tokens || u.inputTokens || 0);
|
|
10463
|
-
const outTok = Number(u.output_tokens || u.outputTokens || 0);
|
|
10464
|
-
totalTokens = Number(u.total_tokens || u.totalTokens || inTok + outTok || 0);
|
|
10465
|
-
}
|
|
10466
|
-
} catch (_e) {
|
|
10467
|
-
model = "gpt-4o-mini";
|
|
10468
|
-
const r2 = await client.chat.completions.create({
|
|
10469
|
-
model,
|
|
10470
|
-
messages: [
|
|
10471
|
-
{ role: "system", content: "You output only code blocks when asked for code." },
|
|
10472
|
-
{ role: "user", content: effectivePrompt || "" }
|
|
10473
|
-
]
|
|
10474
|
-
});
|
|
10475
|
-
content = r2.choices?.[0]?.message?.content || "";
|
|
10476
|
-
const u2 = r2?.usage;
|
|
10477
|
-
if (u2) totalTokens = Number(u2.total_tokens || 0);
|
|
10478
|
-
}
|
|
10479
|
-
if (process.env.MARIA_TELEMETRY === "1") {
|
|
10707
|
+
const consumption = { requests: 1 };
|
|
10708
|
+
if (totalTokens > 0) consumption.tokens = totalTokens;
|
|
10709
|
+
if (taskType === "code" || taskType === "evaluation") consumption.code = 1;
|
|
10480
10710
|
try {
|
|
10481
|
-
|
|
10711
|
+
await applyConsumption(uid, consumption, idemKey);
|
|
10482
10712
|
} catch {
|
|
10483
10713
|
}
|
|
10714
|
+
return res.json({ data: { content, routedModel: { vendor: "openai", family: "gpt", name: model, reason: taskType || "code" } } });
|
|
10484
10715
|
}
|
|
10485
|
-
|
|
10486
|
-
if (totalTokens > 0) consumption.tokens = totalTokens;
|
|
10487
|
-
if (taskType === "code" || taskType === "evaluation") consumption.code = 1;
|
|
10488
|
-
try {
|
|
10489
|
-
await applyConsumption(uid, consumption, idemKey);
|
|
10490
|
-
} catch {
|
|
10491
|
-
}
|
|
10492
|
-
return res.json({ data: { content, routedModel: { vendor: "openai", family: "gpt", name: model, reason: taskType || "code" } } });
|
|
10716
|
+
return res.status(500).json({ error: "routing_error", message: "No provider selected for AI proxy" });
|
|
10493
10717
|
} catch (error) {
|
|
10494
10718
|
console.error("[AI Proxy] Error:", error);
|
|
10495
10719
|
if (process.env.MARIA_TELEMETRY === "1") {
|