@bonginkan/maria 4.3.33 → 4.3.34
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 +4 -4
- package/dist/READY.manifest.json +1 -1
- package/dist/bin/maria.cjs +148 -36
- package/dist/bin/maria.cjs.map +1 -1
- package/dist/cli.cjs +148 -36
- 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 +259 -250
- package/dist/server/express-server.js +259 -250
- package/dist/server-express.cjs +259 -250
- package/dist/server-express.cjs.map +1 -1
- package/package.json +3 -3
- package/src/slash-commands/READY.manifest.json +1 -1
package/dist/server-express.cjs
CHANGED
|
@@ -6815,6 +6815,45 @@ var init_src = __esm({
|
|
|
6815
6815
|
supportedSchemas = /* @__PURE__ */ new Set(["data:", "http:", "https:"]);
|
|
6816
6816
|
}
|
|
6817
6817
|
});
|
|
6818
|
+
|
|
6819
|
+
// src/services/media-orchestrator/image-post.ts
|
|
6820
|
+
var image_post_exports = {};
|
|
6821
|
+
__export(image_post_exports, {
|
|
6822
|
+
processImageOptional: () => processImageOptional
|
|
6823
|
+
});
|
|
6824
|
+
async function processImageOptional(bytes, format, keepExif, targetSize) {
|
|
6825
|
+
try {
|
|
6826
|
+
const sharp = (await import('sharp')).default;
|
|
6827
|
+
let img = sharp(bytes).toColourspace("srgb");
|
|
6828
|
+
if (targetSize && Number.isFinite(targetSize.width) && Number.isFinite(targetSize.height)) {
|
|
6829
|
+
const width = Math.max(1, Math.floor(targetSize.width));
|
|
6830
|
+
const height = Math.max(1, Math.floor(targetSize.height));
|
|
6831
|
+
img = img.resize(width, height, { fit: "cover" });
|
|
6832
|
+
}
|
|
6833
|
+
if (keepExif) img = img.withMetadata();
|
|
6834
|
+
const qEnv = Number(process.env.MARIA_SHARP_QUALITY || "80");
|
|
6835
|
+
const quality = Number.isFinite(qEnv) ? Math.max(1, Math.min(100, Math.floor(qEnv))) : 80;
|
|
6836
|
+
const alphaMode = String(process.env.MARIA_SHARP_ALPHA || "").toLowerCase();
|
|
6837
|
+
if (format === "jpg" && alphaMode !== "preserve") {
|
|
6838
|
+
img = img.flatten({ background: { r: 255, g: 255, b: 255 } });
|
|
6839
|
+
}
|
|
6840
|
+
switch (format) {
|
|
6841
|
+
case "png":
|
|
6842
|
+
return await img.png().toBuffer();
|
|
6843
|
+
case "webp":
|
|
6844
|
+
return await img.webp({ quality }).toBuffer();
|
|
6845
|
+
case "jpg":
|
|
6846
|
+
return await img.jpeg({ mozjpeg: true, quality }).toBuffer();
|
|
6847
|
+
}
|
|
6848
|
+
return bytes;
|
|
6849
|
+
} catch {
|
|
6850
|
+
return bytes;
|
|
6851
|
+
}
|
|
6852
|
+
}
|
|
6853
|
+
var init_image_post = __esm({
|
|
6854
|
+
"src/services/media-orchestrator/image-post.ts"() {
|
|
6855
|
+
}
|
|
6856
|
+
});
|
|
6818
6857
|
var rateLimitStore = /* @__PURE__ */ new Map();
|
|
6819
6858
|
var RATE_LIMITS = {
|
|
6820
6859
|
"/image:FREE": { windowMs: 3e3, requests: 1 },
|
|
@@ -7105,6 +7144,14 @@ var GeminiMediaProvider = class {
|
|
|
7105
7144
|
async generateImage(req) {
|
|
7106
7145
|
const modelName = this.primaryModel;
|
|
7107
7146
|
const promptPreview = String(req.prompt ?? "").replace(/\s+/g, " ").slice(0, 200);
|
|
7147
|
+
const targetMime = (() => {
|
|
7148
|
+
const fmt = (req.format || "png").toLowerCase();
|
|
7149
|
+
if (fmt === "jpg") return "image/jpeg";
|
|
7150
|
+
if (fmt === "jpeg") return "image/jpeg";
|
|
7151
|
+
if (fmt === "png") return "image/png";
|
|
7152
|
+
if (fmt === "webp") return "image/webp";
|
|
7153
|
+
return "image/png";
|
|
7154
|
+
})();
|
|
7108
7155
|
let resp;
|
|
7109
7156
|
try {
|
|
7110
7157
|
resp = await this.ai.models.generateContent({
|
|
@@ -7112,7 +7159,7 @@ var GeminiMediaProvider = class {
|
|
|
7112
7159
|
contents: [{ role: "user", parts: [{ text: String(req.prompt) }] }],
|
|
7113
7160
|
generationConfig: {
|
|
7114
7161
|
responseModalities: ["IMAGE"],
|
|
7115
|
-
responseMimeType:
|
|
7162
|
+
responseMimeType: targetMime
|
|
7116
7163
|
}
|
|
7117
7164
|
});
|
|
7118
7165
|
} catch (err) {
|
|
@@ -7134,7 +7181,7 @@ var GeminiMediaProvider = class {
|
|
|
7134
7181
|
const mime = p?.inlineData?.mimeType || p?.inline_data?.mime_type || p?.inline_data?.mimeType || p?.inlineData?.mime_type;
|
|
7135
7182
|
if (data) {
|
|
7136
7183
|
const buf = Buffer.from(String(data), "base64");
|
|
7137
|
-
if (buf.length > 0) return { bytes: buf, mime: typeof mime === "string" ? mime :
|
|
7184
|
+
if (buf.length > 0) return { bytes: buf, mime: typeof mime === "string" ? mime : targetMime };
|
|
7138
7185
|
}
|
|
7139
7186
|
}
|
|
7140
7187
|
try {
|
|
@@ -7147,7 +7194,7 @@ var GeminiMediaProvider = class {
|
|
|
7147
7194
|
const bytesB64 = img0?.imageBytes || img0?.bytesBase64Encoded;
|
|
7148
7195
|
if (bytesB64) {
|
|
7149
7196
|
const buf = Buffer.from(String(bytesB64), "base64");
|
|
7150
|
-
if (buf.length > 0) return { bytes: buf, mime:
|
|
7197
|
+
if (buf.length > 0) return { bytes: buf, mime: targetMime };
|
|
7151
7198
|
}
|
|
7152
7199
|
} catch {
|
|
7153
7200
|
}
|
|
@@ -7167,148 +7214,6 @@ var GeminiMediaProvider = class {
|
|
|
7167
7214
|
}
|
|
7168
7215
|
};
|
|
7169
7216
|
|
|
7170
|
-
// src/config/plans/free-plan.json
|
|
7171
|
-
var free_plan_default = {
|
|
7172
|
-
id: "free",
|
|
7173
|
-
name: "Free",
|
|
7174
|
-
displayName: "Free Plan",
|
|
7175
|
-
priceUsd: 0,
|
|
7176
|
-
priceJpy: 0,
|
|
7177
|
-
status: "active",
|
|
7178
|
-
features: {
|
|
7179
|
-
priority: "community",
|
|
7180
|
-
support: "community",
|
|
7181
|
-
accessLevel: "basic"
|
|
7182
|
-
},
|
|
7183
|
-
buckets: {
|
|
7184
|
-
req: 100,
|
|
7185
|
-
tokens: 15e4,
|
|
7186
|
-
code: 20,
|
|
7187
|
-
attachment: 5,
|
|
7188
|
-
image: 25,
|
|
7189
|
-
video: 5
|
|
7190
|
-
},
|
|
7191
|
-
models: [
|
|
7192
|
-
"google:gemini-2.5-flash",
|
|
7193
|
-
"google:gemini-2.0-flash"
|
|
7194
|
-
],
|
|
7195
|
-
imageModels: [
|
|
7196
|
-
"google:imagen-4-fast",
|
|
7197
|
-
"google:gemini-2.5-image"
|
|
7198
|
-
],
|
|
7199
|
-
videoModels: [
|
|
7200
|
-
"google:veo-3-fast",
|
|
7201
|
-
"google:veo-2.0-generate-001"
|
|
7202
|
-
],
|
|
7203
|
-
costRules: {
|
|
7204
|
-
longContextTokens: {
|
|
7205
|
-
threshold: 8e3,
|
|
7206
|
-
multiplier: 2,
|
|
7207
|
-
description: "Long context (>8k tokens) consumes 2x"
|
|
7208
|
-
},
|
|
7209
|
-
imageGen: {
|
|
7210
|
-
multiplier: 1,
|
|
7211
|
-
description: "1 image = 1 image bucket consumption"
|
|
7212
|
-
},
|
|
7213
|
-
videoGen: {
|
|
7214
|
-
multiplier: 1,
|
|
7215
|
-
description: "1 video = 1 video bucket consumption"
|
|
7216
|
-
}
|
|
7217
|
-
},
|
|
7218
|
-
limits: {
|
|
7219
|
-
image: {
|
|
7220
|
-
maxSize: "1024x1024",
|
|
7221
|
-
maxWidth: 1024,
|
|
7222
|
-
maxHeight: 1024,
|
|
7223
|
-
maxCountPerCall: 1,
|
|
7224
|
-
formats: ["png", "jpg", "jpeg", "webp", "svg"],
|
|
7225
|
-
monthlyLimit: 25
|
|
7226
|
-
},
|
|
7227
|
-
video: {
|
|
7228
|
-
maxDurationSec: 8,
|
|
7229
|
-
minDurationSec: 6,
|
|
7230
|
-
maxCountPerCall: 1,
|
|
7231
|
-
aspectWhitelist: ["16:9", "9:16", "1:1"],
|
|
7232
|
-
personGeneration: "DONT_ALLOW",
|
|
7233
|
-
monthlyLimit: 5
|
|
7234
|
-
},
|
|
7235
|
-
code: {
|
|
7236
|
-
maxTokensPerRequest: 8e3,
|
|
7237
|
-
maxOutputTokens: 2048,
|
|
7238
|
-
temperature: {
|
|
7239
|
-
min: 0.1,
|
|
7240
|
-
max: 0.2,
|
|
7241
|
-
default: 0.15
|
|
7242
|
-
}
|
|
7243
|
-
},
|
|
7244
|
-
rateLimit: {
|
|
7245
|
-
requestsPerSecond: 0.33,
|
|
7246
|
-
description: "1 request per 3 seconds"
|
|
7247
|
-
}
|
|
7248
|
-
},
|
|
7249
|
-
fileSave: {
|
|
7250
|
-
allowExtensions: [
|
|
7251
|
-
".ts",
|
|
7252
|
-
".tsx",
|
|
7253
|
-
".js",
|
|
7254
|
-
".jsx",
|
|
7255
|
-
".py",
|
|
7256
|
-
".java",
|
|
7257
|
-
".go",
|
|
7258
|
-
".rs",
|
|
7259
|
-
".html",
|
|
7260
|
-
".css",
|
|
7261
|
-
".scss",
|
|
7262
|
-
".json",
|
|
7263
|
-
".yml",
|
|
7264
|
-
".yaml",
|
|
7265
|
-
".sql",
|
|
7266
|
-
".csv",
|
|
7267
|
-
".md",
|
|
7268
|
-
".sh",
|
|
7269
|
-
".bash",
|
|
7270
|
-
".png",
|
|
7271
|
-
".jpg",
|
|
7272
|
-
".jpeg",
|
|
7273
|
-
".webp",
|
|
7274
|
-
".svg"
|
|
7275
|
-
],
|
|
7276
|
-
maxFileSizeMB: 10,
|
|
7277
|
-
naming: {
|
|
7278
|
-
convention: "kebab-case",
|
|
7279
|
-
pattern: "^[a-z0-9-_]+\\.[a-z]+$"
|
|
7280
|
-
},
|
|
7281
|
-
dirs: {
|
|
7282
|
-
default: "outputs",
|
|
7283
|
-
images: "outputs/images",
|
|
7284
|
-
videos: "outputs/videos",
|
|
7285
|
-
code: "outputs/code"
|
|
7286
|
-
}
|
|
7287
|
-
},
|
|
7288
|
-
ui: {
|
|
7289
|
-
badge: "FREE",
|
|
7290
|
-
badgeColor: "#4CAF50",
|
|
7291
|
-
description: "Perfect for getting started with AI development",
|
|
7292
|
-
highlights: [
|
|
7293
|
-
"100 requests/month",
|
|
7294
|
-
"Gemini 2.5 Flash for code",
|
|
7295
|
-
"25 images/month (1024px)",
|
|
7296
|
-
"5 videos/month (8 sec)",
|
|
7297
|
-
"Community support"
|
|
7298
|
-
]
|
|
7299
|
-
},
|
|
7300
|
-
telemetry: {
|
|
7301
|
-
trackingEnabled: true,
|
|
7302
|
-
anonymousId: true,
|
|
7303
|
-
events: [
|
|
7304
|
-
"model_usage",
|
|
7305
|
-
"command_execution",
|
|
7306
|
-
"error_rate",
|
|
7307
|
-
"latency"
|
|
7308
|
-
]
|
|
7309
|
-
}
|
|
7310
|
-
};
|
|
7311
|
-
|
|
7312
7217
|
// src/services/intelligent-model-selector/IMSFacade.ts
|
|
7313
7218
|
init_SecretManagerIntegration();
|
|
7314
7219
|
|
|
@@ -9532,7 +9437,7 @@ app.get("/api/status", (req, res) => {
|
|
|
9532
9437
|
app.get("/", (req, res) => {
|
|
9533
9438
|
res.json({
|
|
9534
9439
|
name: "MARIA CODE API",
|
|
9535
|
-
version: "4.3.
|
|
9440
|
+
version: "4.3.34",
|
|
9536
9441
|
status: "running",
|
|
9537
9442
|
environment: process.env.NODE_ENV || "development",
|
|
9538
9443
|
endpoints: {
|
|
@@ -9585,7 +9490,7 @@ function classifyMediaError(err) {
|
|
|
9585
9490
|
return { status: 422, code: "policy_violation", message: "Request was blocked by provider policy", hint: "Modify the prompt to comply with safety policies" };
|
|
9586
9491
|
}
|
|
9587
9492
|
if (lower2.includes("no inline image returned") || lower2.includes("no video returned") || lower2.includes("refus")) {
|
|
9588
|
-
return { status: 422, code: "content_refused", message: "Model refused or returned no content", hint: "Try rephrasing the prompt
|
|
9493
|
+
return { status: 422, code: "content_refused", message: "Model refused or returned no content", hint: "Try rephrasing the prompt" };
|
|
9589
9494
|
}
|
|
9590
9495
|
if (lower2.includes("timeout")) {
|
|
9591
9496
|
return { status: 504, code: "timeout", message: "Generation timed out", hint: "Please retry later" };
|
|
@@ -9619,6 +9524,106 @@ async function decodeFirebaseToken(token) {
|
|
|
9619
9524
|
return null;
|
|
9620
9525
|
}
|
|
9621
9526
|
}
|
|
9527
|
+
function getCurrentPeriodId() {
|
|
9528
|
+
const now = /* @__PURE__ */ new Date();
|
|
9529
|
+
return `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}`;
|
|
9530
|
+
}
|
|
9531
|
+
function nextMonthResetISO() {
|
|
9532
|
+
const d = /* @__PURE__ */ new Date();
|
|
9533
|
+
d.setUTCMonth(d.getUTCMonth() + 1, 1);
|
|
9534
|
+
d.setUTCHours(0, 0, 0, 0);
|
|
9535
|
+
return d.toISOString();
|
|
9536
|
+
}
|
|
9537
|
+
var PLAN_LIMITS = {
|
|
9538
|
+
free: { req: 300, tokens: 5e4, code: 40, attachment: 5 },
|
|
9539
|
+
starter: { req: 1400, tokens: 1e6, code: 300, attachment: 50 },
|
|
9540
|
+
"starter-annual": { req: 1400, tokens: 1e6, code: 300, attachment: 50 },
|
|
9541
|
+
pro: { req: 5e3, tokens: 35e5, code: 1200, attachment: 200 },
|
|
9542
|
+
"pro-annual": { req: 5e3, tokens: 35e5, code: 1200, attachment: 200 },
|
|
9543
|
+
ultra: { req: 1e4, tokens: 5e6, code: 5e3, attachment: 500 },
|
|
9544
|
+
"ultra-annual": { req: 1e4, tokens: 5e6, code: 5e3, attachment: 500 },
|
|
9545
|
+
enterprise: { req: -1, tokens: -1, code: -1, attachment: -1 }
|
|
9546
|
+
};
|
|
9547
|
+
function planName(planId) {
|
|
9548
|
+
const names = {
|
|
9549
|
+
free: "Free",
|
|
9550
|
+
starter: "Starter",
|
|
9551
|
+
"starter-annual": "Starter (\u5E74\u984D)",
|
|
9552
|
+
pro: "Pro",
|
|
9553
|
+
"pro-annual": "Pro (\u5E74\u984D)",
|
|
9554
|
+
ultra: "Ultra",
|
|
9555
|
+
"ultra-annual": "Ultra (\u5E74\u984D)",
|
|
9556
|
+
enterprise: "Enterprise"
|
|
9557
|
+
};
|
|
9558
|
+
return names[planId] || "Free";
|
|
9559
|
+
}
|
|
9560
|
+
async function getUserPlanAndLimits(uid) {
|
|
9561
|
+
const db = await getFirestoreSafe();
|
|
9562
|
+
const fallback = { planId: "free", limits: PLAN_LIMITS.free };
|
|
9563
|
+
if (!db) return fallback;
|
|
9564
|
+
try {
|
|
9565
|
+
const snap = await db.collection("user_subscriptions").doc(uid).get();
|
|
9566
|
+
const pid = snap.exists && snap.data()?.planId ? String(snap.data().planId) : "free";
|
|
9567
|
+
return { planId: pid, limits: PLAN_LIMITS[pid] || PLAN_LIMITS.free };
|
|
9568
|
+
} catch {
|
|
9569
|
+
return fallback;
|
|
9570
|
+
}
|
|
9571
|
+
}
|
|
9572
|
+
async function ensureUsageDoc(uid) {
|
|
9573
|
+
const db = await getFirestoreSafe();
|
|
9574
|
+
if (!db) return { ref: null, data: null, planId: "free", limits: PLAN_LIMITS.free };
|
|
9575
|
+
const periodId = getCurrentPeriodId();
|
|
9576
|
+
const { planId: pid, limits } = await getUserPlanAndLimits(uid);
|
|
9577
|
+
const ref = db.collection("users").doc(uid).collection("usage").doc(periodId);
|
|
9578
|
+
const snap = await ref.get();
|
|
9579
|
+
if (!snap.exists) {
|
|
9580
|
+
const nowISO = (/* @__PURE__ */ new Date()).toISOString();
|
|
9581
|
+
const init = {
|
|
9582
|
+
periodId,
|
|
9583
|
+
createdAt: nowISO,
|
|
9584
|
+
updatedAt: nowISO,
|
|
9585
|
+
resetAt: nextMonthResetISO(),
|
|
9586
|
+
limits,
|
|
9587
|
+
remain: limits,
|
|
9588
|
+
used: { req: 0, tokens: 0, code: 0, attachment: 0 }
|
|
9589
|
+
};
|
|
9590
|
+
await ref.set(init, { merge: true });
|
|
9591
|
+
return { ref, data: init, planId: pid, limits };
|
|
9592
|
+
}
|
|
9593
|
+
return { ref, data: snap.data(), planId: pid, limits };
|
|
9594
|
+
}
|
|
9595
|
+
async function applyConsumption(uid, consumption, idemKey) {
|
|
9596
|
+
const db = await getFirestoreSafe();
|
|
9597
|
+
if (!db) return null;
|
|
9598
|
+
const { ref, data, planId: pid, limits } = await ensureUsageDoc(uid);
|
|
9599
|
+
const nowISO = (/* @__PURE__ */ new Date()).toISOString();
|
|
9600
|
+
if (idemKey) {
|
|
9601
|
+
const idemRef = ref.collection("consumptions").doc(idemKey);
|
|
9602
|
+
const idemSnap = await idemRef.get();
|
|
9603
|
+
if (!idemSnap.exists) await idemRef.set({ createdAt: nowISO, consumption });
|
|
9604
|
+
else return data;
|
|
9605
|
+
}
|
|
9606
|
+
const toNum = (v) => typeof v === "number" && isFinite(v) ? v : 0;
|
|
9607
|
+
const incReq = toNum(consumption.requests) + toNum(consumption.req);
|
|
9608
|
+
const incTokens = toNum(consumption.tokens);
|
|
9609
|
+
const incCode = toNum(consumption.code) + toNum(consumption.image) + toNum(consumption.video);
|
|
9610
|
+
const incAttachment = toNum(consumption.attachment);
|
|
9611
|
+
const used = data?.used || { req: 0, tokens: 0, code: 0, attachment: 0 };
|
|
9612
|
+
const newUsed = {
|
|
9613
|
+
req: Math.max(0, used.req + incReq),
|
|
9614
|
+
tokens: Math.max(0, used.tokens + incTokens),
|
|
9615
|
+
code: Math.max(0, used.code + incCode),
|
|
9616
|
+
attachment: Math.max(0, used.attachment + incAttachment)
|
|
9617
|
+
};
|
|
9618
|
+
const remain = {
|
|
9619
|
+
req: limits.req < 0 ? -1 : Math.max(0, limits.req - newUsed.req),
|
|
9620
|
+
tokens: limits.tokens < 0 ? -1 : Math.max(0, limits.tokens - newUsed.tokens),
|
|
9621
|
+
code: limits.code < 0 ? -1 : Math.max(0, limits.code - newUsed.code),
|
|
9622
|
+
attachment: limits.attachment < 0 ? -1 : Math.max(0, limits.attachment - newUsed.attachment)
|
|
9623
|
+
};
|
|
9624
|
+
await ref.set({ used: newUsed, remain, updatedAt: nowISO }, { merge: true });
|
|
9625
|
+
return { ...data || {}, used: newUsed, remain, limits, periodId: getCurrentPeriodId(), updatedAt: nowISO };
|
|
9626
|
+
}
|
|
9622
9627
|
app.get("/api/user/profile", async (req, res) => {
|
|
9623
9628
|
try {
|
|
9624
9629
|
const authHeader = req.headers.authorization || "";
|
|
@@ -9639,19 +9644,21 @@ app.get("/api/user/profile", async (req, res) => {
|
|
|
9639
9644
|
if (Array.isArray(ids["github.com"]) && ids["github.com"].length > 0) provider = "github";
|
|
9640
9645
|
else if (Array.isArray(ids["google.com"]) && ids["google.com"].length > 0) provider = "google";
|
|
9641
9646
|
}
|
|
9642
|
-
const
|
|
9647
|
+
const { planId: pid, limits } = await getUserPlanAndLimits(uid);
|
|
9648
|
+
const periodRef = await ensureUsageDoc(uid);
|
|
9649
|
+
const currentUsedReq = Number(periodRef?.data?.used?.req || 0);
|
|
9643
9650
|
const response2 = {
|
|
9644
9651
|
id: uid,
|
|
9645
9652
|
email,
|
|
9646
9653
|
name: displayName,
|
|
9647
9654
|
provider: provider || "unknown",
|
|
9648
|
-
plan,
|
|
9655
|
+
plan: String(pid).toUpperCase(),
|
|
9649
9656
|
usage: {
|
|
9650
|
-
requests:
|
|
9651
|
-
requestLimit:
|
|
9652
|
-
tokens: 0,
|
|
9653
|
-
tokenLimit:
|
|
9654
|
-
resetDate:
|
|
9657
|
+
requests: currentUsedReq,
|
|
9658
|
+
requestLimit: limits.req < 0 ? Number.MAX_SAFE_INTEGER : limits.req,
|
|
9659
|
+
tokens: Number(periodRef?.data?.used?.tokens || 0),
|
|
9660
|
+
tokenLimit: limits.tokens < 0 ? Number.MAX_SAFE_INTEGER : limits.tokens,
|
|
9661
|
+
resetDate: periodRef?.data?.resetAt || nextMonthResetISO()
|
|
9655
9662
|
},
|
|
9656
9663
|
models: ["gpt-5", "gemini-2.5-pro"]
|
|
9657
9664
|
};
|
|
@@ -9762,7 +9769,9 @@ app.post("/api/v1/image", rateLimitMiddleware, async (req, res) => {
|
|
|
9762
9769
|
const decoded = await decodeFirebaseToken(idToken).catch(() => null);
|
|
9763
9770
|
if (!decoded) return res.status(401).json({ error: "unauthorized", message: "Invalid login session", hint: "Re-login to continue" });
|
|
9764
9771
|
const uid = decoded?.uid || decoded?.sub || "current";
|
|
9765
|
-
const { prompt, model, size = "1024x1024", format = "png", count = 1, seed } = req.body || {};
|
|
9772
|
+
const { prompt, model, size = "1024x1024", format: formatRaw = "png", count = 1, seed } = req.body || {};
|
|
9773
|
+
const fmt0 = String(formatRaw || "png").toLowerCase();
|
|
9774
|
+
const format = fmt0 === "jpeg" ? "jpg" : ["png", "jpg", "webp"].includes(fmt0) ? fmt0 : "png";
|
|
9766
9775
|
if (!prompt) return res.status(400).json({ error: "bad_request", message: "prompt required" });
|
|
9767
9776
|
const m2 = /^(\d{2,4})x(\d{2,4})$/.exec(String(size));
|
|
9768
9777
|
if (!m2) return res.status(400).json({ error: "bad_request", message: "size must be WxH" });
|
|
@@ -9772,7 +9781,13 @@ app.post("/api/v1/image", rateLimitMiddleware, async (req, res) => {
|
|
|
9772
9781
|
const buffers = [];
|
|
9773
9782
|
for (let i2 = 0; i2 < Math.max(1, Math.min(8, Number(count || 1))); i2++) {
|
|
9774
9783
|
const r2 = await provider.generateImage({ prompt, width: w, height: h2, format, seed: (seed ?? 0) + i2 });
|
|
9775
|
-
|
|
9784
|
+
const processed = await (await Promise.resolve().then(() => (init_image_post(), image_post_exports))).processImageOptional(
|
|
9785
|
+
r2.bytes,
|
|
9786
|
+
String(format),
|
|
9787
|
+
false,
|
|
9788
|
+
{ width: w, height: h2 }
|
|
9789
|
+
);
|
|
9790
|
+
buffers.push(processed);
|
|
9776
9791
|
}
|
|
9777
9792
|
const promptHash = hashPrompt(prompt);
|
|
9778
9793
|
const manifest = {
|
|
@@ -9793,10 +9808,10 @@ app.post("/api/v1/image", rateLimitMiddleware, async (req, res) => {
|
|
|
9793
9808
|
uid
|
|
9794
9809
|
});
|
|
9795
9810
|
const idemKey = req.headers["idempotency-key"] || void 0;
|
|
9796
|
-
await
|
|
9811
|
+
await applyConsumption(uid, { requests: 1, image: Math.max(1, buffers.length) }, idemKey);
|
|
9797
9812
|
const filesInline = buffers.map((b, idx) => ({
|
|
9798
9813
|
file: saved.files[idx] || "",
|
|
9799
|
-
mime: `image/${format}`,
|
|
9814
|
+
mime: format === "jpg" ? "image/jpeg" : `image/${format}`,
|
|
9800
9815
|
bytesBase64: b.toString("base64")
|
|
9801
9816
|
}));
|
|
9802
9817
|
return res.json({ success: true, data: { url: saved.manifestPath, files: saved.files, filesInline, jobId: manifest.trace } });
|
|
@@ -10038,7 +10053,7 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
10038
10053
|
const idToken = auth.substring("Bearer ".length).trim();
|
|
10039
10054
|
const decoded = await decodeFirebaseToken(idToken).catch(() => null);
|
|
10040
10055
|
const uid = decoded?.uid || decoded?.sub || "current";
|
|
10041
|
-
await
|
|
10056
|
+
await applyConsumption(uid, { requests: 1, video: 1 }, idemKey);
|
|
10042
10057
|
jobIndex.set(String(manifest.trace), {
|
|
10043
10058
|
id: String(manifest.trace),
|
|
10044
10059
|
status: "completed",
|
|
@@ -10091,6 +10106,12 @@ app.get("/api/v1/jobs/:id", async (req, res) => {
|
|
|
10091
10106
|
});
|
|
10092
10107
|
app.post("/api/v1/code", rateLimitMiddleware, async (req, res) => {
|
|
10093
10108
|
try {
|
|
10109
|
+
const auth = req.headers.authorization;
|
|
10110
|
+
if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
|
|
10111
|
+
const idToken = auth.substring("Bearer ".length).trim();
|
|
10112
|
+
const decoded = await decodeFirebaseToken(idToken).catch(() => null);
|
|
10113
|
+
if (!decoded) return res.status(401).json({ error: "unauthorized" });
|
|
10114
|
+
const uid = decoded?.uid || decoded?.sub;
|
|
10094
10115
|
const { prompt, language = "typescript", model = "gpt-4" } = req.body;
|
|
10095
10116
|
if (!prompt) {
|
|
10096
10117
|
return res.status(400).json({
|
|
@@ -10098,6 +10119,8 @@ app.post("/api/v1/code", rateLimitMiddleware, async (req, res) => {
|
|
|
10098
10119
|
message: "Prompt is required"
|
|
10099
10120
|
});
|
|
10100
10121
|
}
|
|
10122
|
+
const idemKey = req.headers["idempotency-key"] || void 0;
|
|
10123
|
+
await applyConsumption(uid, { requests: 1, code: 1 }, idemKey);
|
|
10101
10124
|
return res.json({
|
|
10102
10125
|
success: true,
|
|
10103
10126
|
data: {
|
|
@@ -10121,6 +10144,12 @@ function example() {
|
|
|
10121
10144
|
});
|
|
10122
10145
|
app.post("/api/v1/chat", rateLimitMiddleware, async (req, res) => {
|
|
10123
10146
|
try {
|
|
10147
|
+
const auth = req.headers.authorization;
|
|
10148
|
+
if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
|
|
10149
|
+
const idToken = auth.substring("Bearer ".length).trim();
|
|
10150
|
+
const decoded = await decodeFirebaseToken(idToken).catch(() => null);
|
|
10151
|
+
if (!decoded) return res.status(401).json({ error: "unauthorized" });
|
|
10152
|
+
const uid = decoded?.uid || decoded?.sub;
|
|
10124
10153
|
const { message, model = "gpt-4" } = req.body;
|
|
10125
10154
|
if (!message) {
|
|
10126
10155
|
return res.status(400).json({
|
|
@@ -10128,6 +10157,8 @@ app.post("/api/v1/chat", rateLimitMiddleware, async (req, res) => {
|
|
|
10128
10157
|
message: "Message is required"
|
|
10129
10158
|
});
|
|
10130
10159
|
}
|
|
10160
|
+
const idemKey = req.headers["idempotency-key"] || void 0;
|
|
10161
|
+
await applyConsumption(uid, { requests: 1 }, idemKey);
|
|
10131
10162
|
return res.json({
|
|
10132
10163
|
success: true,
|
|
10133
10164
|
data: {
|
|
@@ -10150,6 +10181,11 @@ app.post("/v1/ai-proxy", rateLimitMiddleware, async (req, res) => {
|
|
|
10150
10181
|
if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
|
|
10151
10182
|
const { prompt, taskType } = req.body || {};
|
|
10152
10183
|
if (!prompt) return res.status(400).json({ error: "bad_request", message: "prompt required" });
|
|
10184
|
+
const idToken = auth.substring("Bearer ".length).trim();
|
|
10185
|
+
const decoded = await decodeFirebaseToken(idToken).catch(() => null);
|
|
10186
|
+
if (!decoded) return res.status(401).json({ error: "unauthorized" });
|
|
10187
|
+
const uid = decoded?.uid || decoded?.sub;
|
|
10188
|
+
const idemKey = req.headers["idempotency-key"] || void 0;
|
|
10153
10189
|
if (process.env.MARIA_TELEMETRY === "1") {
|
|
10154
10190
|
try {
|
|
10155
10191
|
console.log(JSON.stringify({ ev: "ai_proxy_request", taskType: taskType || "unknown", promptLen: String(prompt).length }));
|
|
@@ -10196,6 +10232,12 @@ app.post("/v1/ai-proxy", rateLimitMiddleware, async (req, res) => {
|
|
|
10196
10232
|
} catch {
|
|
10197
10233
|
}
|
|
10198
10234
|
}
|
|
10235
|
+
const consumption2 = { requests: 1 };
|
|
10236
|
+
if (taskType === "code" || taskType === "evaluation") consumption2.code = 1;
|
|
10237
|
+
try {
|
|
10238
|
+
await applyConsumption(uid, consumption2, idemKey);
|
|
10239
|
+
} catch {
|
|
10240
|
+
}
|
|
10199
10241
|
return res.json({ data: { content: content2, routedModel: { vendor: "google", family: "gemini", name: modelName, reason: taskType || "code" } } });
|
|
10200
10242
|
} catch (e2) {
|
|
10201
10243
|
console.warn("[AI Proxy] Gemini path failed, falling back to OpenAI:", e2?.message || e2);
|
|
@@ -10221,6 +10263,7 @@ app.post("/v1/ai-proxy", rateLimitMiddleware, async (req, res) => {
|
|
|
10221
10263
|
const client = new OpenAI2({ apiKey: openaiKey });
|
|
10222
10264
|
let model = process.env.MARIA_CODE_MODEL || "gpt-5-mini";
|
|
10223
10265
|
let content = "";
|
|
10266
|
+
let totalTokens = 0;
|
|
10224
10267
|
try {
|
|
10225
10268
|
const r2 = await client.responses.create({
|
|
10226
10269
|
model,
|
|
@@ -10230,6 +10273,12 @@ app.post("/v1/ai-proxy", rateLimitMiddleware, async (req, res) => {
|
|
|
10230
10273
|
]
|
|
10231
10274
|
});
|
|
10232
10275
|
content = r2?.output_text || r2?.content?.[0]?.text || "";
|
|
10276
|
+
const u = r2?.usage;
|
|
10277
|
+
if (u) {
|
|
10278
|
+
const inTok = Number(u.input_tokens || u.inputTokens || 0);
|
|
10279
|
+
const outTok = Number(u.output_tokens || u.outputTokens || 0);
|
|
10280
|
+
totalTokens = Number(u.total_tokens || u.totalTokens || inTok + outTok || 0);
|
|
10281
|
+
}
|
|
10233
10282
|
} catch (_e) {
|
|
10234
10283
|
model = "gpt-4o-mini";
|
|
10235
10284
|
const r2 = await client.chat.completions.create({
|
|
@@ -10240,6 +10289,8 @@ app.post("/v1/ai-proxy", rateLimitMiddleware, async (req, res) => {
|
|
|
10240
10289
|
]
|
|
10241
10290
|
});
|
|
10242
10291
|
content = r2.choices?.[0]?.message?.content || "";
|
|
10292
|
+
const u2 = r2?.usage;
|
|
10293
|
+
if (u2) totalTokens = Number(u2.total_tokens || 0);
|
|
10243
10294
|
}
|
|
10244
10295
|
if (process.env.MARIA_TELEMETRY === "1") {
|
|
10245
10296
|
try {
|
|
@@ -10247,6 +10298,13 @@ app.post("/v1/ai-proxy", rateLimitMiddleware, async (req, res) => {
|
|
|
10247
10298
|
} catch {
|
|
10248
10299
|
}
|
|
10249
10300
|
}
|
|
10301
|
+
const consumption = { requests: 1 };
|
|
10302
|
+
if (totalTokens > 0) consumption.tokens = totalTokens;
|
|
10303
|
+
if (taskType === "code" || taskType === "evaluation") consumption.code = 1;
|
|
10304
|
+
try {
|
|
10305
|
+
await applyConsumption(uid, consumption, idemKey);
|
|
10306
|
+
} catch {
|
|
10307
|
+
}
|
|
10250
10308
|
return res.json({ data: { content, routedModel: { vendor: "openai", family: "gpt", name: model, reason: taskType || "code" } } });
|
|
10251
10309
|
} catch (error) {
|
|
10252
10310
|
console.error("[AI Proxy] Error:", error);
|
|
@@ -10274,89 +10332,35 @@ async function getFirestoreSafe() {
|
|
|
10274
10332
|
return null;
|
|
10275
10333
|
}
|
|
10276
10334
|
}
|
|
10277
|
-
function calcNextReset() {
|
|
10278
|
-
const d = /* @__PURE__ */ new Date();
|
|
10279
|
-
d.setUTCMonth(d.getUTCMonth() + 1, 1);
|
|
10280
|
-
d.setUTCHours(0, 0, 0, 0);
|
|
10281
|
-
return d.toISOString();
|
|
10282
|
-
}
|
|
10283
|
-
async function recordConsumption(uid, consumption, idempotencyKey) {
|
|
10284
|
-
const db = await getFirestoreSafe();
|
|
10285
|
-
if (!db) return;
|
|
10286
|
-
const docPath = `projects/default/usage/${uid}`;
|
|
10287
|
-
const usageRef = db.doc(docPath);
|
|
10288
|
-
const nowISO = (/* @__PURE__ */ new Date()).toISOString();
|
|
10289
|
-
if (idempotencyKey) {
|
|
10290
|
-
const idemRef = db.doc(`projects/default/usage/${uid}/consumptions/${idempotencyKey}`);
|
|
10291
|
-
const idemSnap = await idemRef.get();
|
|
10292
|
-
if (idemSnap.exists) return;
|
|
10293
|
-
await idemRef.set({ createdAt: nowISO, consumption });
|
|
10294
|
-
}
|
|
10295
|
-
const snap = await usageRef.get();
|
|
10296
|
-
let data = snap.exists ? snap.data() : null;
|
|
10297
|
-
if (!data) {
|
|
10298
|
-
data = {
|
|
10299
|
-
plan: { name: "FREE", limits: { requests: free_plan_default.buckets.req } },
|
|
10300
|
-
monthly: {
|
|
10301
|
-
requests: { used: 0, limit: free_plan_default.buckets.req },
|
|
10302
|
-
image: { used: 0, limit: free_plan_default.buckets.image },
|
|
10303
|
-
video: { used: 0, limit: free_plan_default.buckets.video },
|
|
10304
|
-
code: { used: 0, limit: free_plan_default.buckets.code },
|
|
10305
|
-
resetAt: calcNextReset()
|
|
10306
|
-
},
|
|
10307
|
-
updatedAt: nowISO
|
|
10308
|
-
};
|
|
10309
|
-
}
|
|
10310
|
-
const m2 = data.monthly || {};
|
|
10311
|
-
if (consumption?.requests) m2.requests.used = Math.max(0, (m2.requests.used || 0) + Number(consumption.requests));
|
|
10312
|
-
if (consumption?.image) m2.image.used = Math.max(0, (m2.image.used || 0) + Number(consumption.image));
|
|
10313
|
-
if (consumption?.video) m2.video.used = Math.max(0, (m2.video.used || 0) + Number(consumption.video));
|
|
10314
|
-
if (consumption?.code) m2.code.used = Math.max(0, (m2.code.used || 0) + Number(consumption.code));
|
|
10315
|
-
data.monthly = m2;
|
|
10316
|
-
data.updatedAt = nowISO;
|
|
10317
|
-
await usageRef.set(data, { merge: true });
|
|
10318
|
-
}
|
|
10319
10335
|
app.get("/api/v1/usage", rateLimitMiddleware, async (req, res) => {
|
|
10320
10336
|
try {
|
|
10321
10337
|
const auth = req.headers.authorization;
|
|
10322
10338
|
if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
|
|
10323
|
-
const
|
|
10324
|
-
const
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
data
|
|
10347
|
-
}
|
|
10348
|
-
data = {
|
|
10349
|
-
plan: { name: "FREE", limits: { requests: free_plan_default.buckets.req } },
|
|
10350
|
-
monthly: {
|
|
10351
|
-
requests: { used: 0, limit: free_plan_default.buckets.req },
|
|
10352
|
-
image: { used: 0, limit: free_plan_default.buckets.image },
|
|
10353
|
-
video: { used: 0, limit: free_plan_default.buckets.video },
|
|
10354
|
-
code: { used: 0, limit: free_plan_default.buckets.code },
|
|
10355
|
-
resetAt: calcNextReset()
|
|
10356
|
-
}
|
|
10357
|
-
};
|
|
10358
|
-
}
|
|
10359
|
-
return res.json(data);
|
|
10339
|
+
const idToken = auth.substring("Bearer ".length).trim();
|
|
10340
|
+
const decoded = await decodeFirebaseToken(idToken).catch(() => null);
|
|
10341
|
+
if (!decoded) return res.status(401).json({ error: "unauthorized" });
|
|
10342
|
+
const uid = decoded?.uid || decoded?.sub;
|
|
10343
|
+
const { planId: pid, limits } = await getUserPlanAndLimits(uid);
|
|
10344
|
+
const { data } = await ensureUsageDoc(uid);
|
|
10345
|
+
const periodId = getCurrentPeriodId();
|
|
10346
|
+
const used = data?.used || { req: 0, tokens: 0, code: 0, attachment: 0 };
|
|
10347
|
+
const remain = data?.remain || limits;
|
|
10348
|
+
const percentage = {
|
|
10349
|
+
req: limits.req > 0 ? Math.round(used.req / limits.req * 100) : 0,
|
|
10350
|
+
tokens: limits.tokens > 0 ? Math.round(used.tokens / limits.tokens * 100) : 0,
|
|
10351
|
+
code: limits.code > 0 ? Math.round(used.code / limits.code * 100) : 0,
|
|
10352
|
+
attachment: limits.attachment > 0 ? Math.round(used.attachment / limits.attachment * 100) : 0
|
|
10353
|
+
};
|
|
10354
|
+
return res.json({
|
|
10355
|
+
periodId,
|
|
10356
|
+
planCode: pid,
|
|
10357
|
+
planName: planName(pid),
|
|
10358
|
+
used,
|
|
10359
|
+
remain,
|
|
10360
|
+
limits,
|
|
10361
|
+
percentage,
|
|
10362
|
+
resetAt: data?.resetAt || nextMonthResetISO()
|
|
10363
|
+
});
|
|
10360
10364
|
} catch (e2) {
|
|
10361
10365
|
console.error("[Usage] GET error", e2);
|
|
10362
10366
|
return res.status(500).json({ error: "internal_error" });
|
|
@@ -10368,18 +10372,23 @@ app.post("/api/v1/usage", rateLimitMiddleware, async (req, res) => {
|
|
|
10368
10372
|
if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
|
|
10369
10373
|
const { consumption } = req.body || {};
|
|
10370
10374
|
const idemKey = req.headers["idempotency-key"] || void 0;
|
|
10371
|
-
const
|
|
10372
|
-
const
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10375
|
+
const idToken = auth.substring("Bearer ".length).trim();
|
|
10376
|
+
const decoded = await decodeFirebaseToken(idToken).catch(() => null);
|
|
10377
|
+
if (!decoded) return res.status(401).json({ error: "unauthorized" });
|
|
10378
|
+
const uid = decoded?.uid || decoded?.sub;
|
|
10379
|
+
await applyConsumption(uid, consumption || {}, idemKey);
|
|
10380
|
+
const { data } = await ensureUsageDoc(uid);
|
|
10381
|
+
const { planId: pid, limits } = await getUserPlanAndLimits(uid);
|
|
10382
|
+
const periodId = getCurrentPeriodId();
|
|
10383
|
+
const used = data?.used || { req: 0, tokens: 0, code: 0, attachment: 0 };
|
|
10384
|
+
const remain = data?.remain || limits;
|
|
10385
|
+
const percentage = {
|
|
10386
|
+
req: limits.req > 0 ? Math.round(used.req / limits.req * 100) : 0,
|
|
10387
|
+
tokens: limits.tokens > 0 ? Math.round(used.tokens / limits.tokens * 100) : 0,
|
|
10388
|
+
code: limits.code > 0 ? Math.round(used.code / limits.code * 100) : 0,
|
|
10389
|
+
attachment: limits.attachment > 0 ? Math.round(used.attachment / limits.attachment * 100) : 0
|
|
10390
|
+
};
|
|
10391
|
+
return res.json({ periodId, planCode: pid, planName: planName(pid), used, remain, limits, percentage, resetAt: data?.resetAt || nextMonthResetISO() });
|
|
10383
10392
|
} catch (e2) {
|
|
10384
10393
|
console.error("[Usage] POST error", e2);
|
|
10385
10394
|
return res.status(500).json({ error: "internal_error" });
|