@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.
@@ -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.0-generate-001). requested_fps=${fps}; requested_duration=${duration}`
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.3.46",
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
- return res.json({ success: true, data: { url: saved.manifestPath, files: saved.files, filesInline, jobId: manifest.trace } });
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.0-generate-001";
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 effectiveDuration = Number.isFinite(requestedDuration) && requestedDuration === 8 ? 8 : 8;
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() + 72e4;
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, 1e4));
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 = 10;
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(8e3, 1500 * attempt)));
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
- console.log(JSON.stringify({ ev: "ai_proxy_request", taskType: taskType || "unknown", promptLen: String(effectivePrompt || research?.query || "").length }));
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
- const wantsGoogle = requestedProvider === "google" || !!requestedModel && requestedModel.toLowerCase().startsWith("gemini") || !requestedProvider && !requestedModel;
10387
- if (gemKey && wantsGoogle) {
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 = requestedModel && requestedModel.length > 0 ? requestedModel : process.env.MARIA_CODE_MODEL || "gemini-2.5-flash";
10392
- const model2 = ai.getGenerativeModel({ model: modelName });
10393
- let content2 = "";
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
- content2 = String(r2?.text?.() || r2?.text || r2?.response?.candidates?.[0]?.content?.parts?.[0]?.text || "");
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 model2.generateContent({ contents: [{ role: "user", parts }] });
10411
- content2 = resp?.response?.text?.() || resp?.response?.candidates?.[0]?.content?.parts?.[0]?.text || "";
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: !content2 }));
10593
+ console.log(JSON.stringify({ ev: "ai_proxy_route", vendor: "google", model: modelName, empty: !content }));
10416
10594
  } catch {
10417
10595
  }
10418
10596
  }
10419
- const consumption2 = { requests: 1 };
10420
- if (taskType === "code" || taskType === "evaluation") consumption2.code = 1;
10597
+ const consumption = { requests: 1 };
10598
+ if (taskType === "code" || taskType === "evaluation") consumption.code = 1;
10421
10599
  try {
10422
- await applyConsumption(uid, consumption2, idemKey);
10600
+ await applyConsumption(uid, consumption, idemKey);
10423
10601
  } catch {
10424
10602
  }
10425
- return res.json({ data: { content: content2, routedModel: { vendor: "google", family: "gemini", name: modelName, reason: taskType || "code" } } });
10603
+ return res.json({ data: { content, routedModel: { vendor: "google", family: "gemini", name: modelName, reason: taskType || "code" } } });
10426
10604
  } catch (e2) {
10427
- console.warn("[AI Proxy] Gemini path failed, falling back to OpenAI:", e2?.message || e2);
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
- const openaiKey = sanitizeKey2(keys?.openaiApiKey || process.env.OPENAI_API_KEY);
10437
- if (!openaiKey) {
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: "ai_proxy_no_keys" }));
10703
+ console.log(JSON.stringify({ ev: "ai_proxy_route", vendor: "openai", model, empty: !content }));
10441
10704
  } catch {
10442
10705
  }
10443
10706
  }
10444
- return res.status(503).json({ error: "provider_unavailable", message: "No valid provider key (set GEMINI_API_KEY/GOOGLE_API_KEY or OPENAI_API_KEY)" });
10445
- }
10446
- const OpenAI2 = (await import('openai')).default;
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
- console.log(JSON.stringify({ ev: "ai_proxy_route", vendor: "openai", model, empty: !content }));
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
- const consumption = { requests: 1 };
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") {