← back to Dw Activation Debug TK11314

lib/vision-http.js

54 lines

'use strict';
const https = require('node:https');

function redact(value, secrets = []) {
  let text = String(value || '');
  for (const secret of secrets.filter(Boolean)) text = text.split(secret).join('[REDACTED]');
  return text.replace(/AIza[\w-]{20,}/g, '[REDACTED]')
    .replace(/Help Token:\s*\S+/gi, 'Help Token: [REDACTED]')
    .replace(/([?&]key=)[^&\s"']+/gi, '$1[REDACTED]').slice(0, 800);
}
function errorDetails(response, secrets = []) {
  let error = {};
  try { error = JSON.parse(response.body || '{}').error || {}; } catch {}
  const details = Array.isArray(error.details) ? error.details : [];
  const retry = details.find(d => /RetryInfo$/.test(d['@type'] || ''))?.retryDelay;
  const retrySeconds = typeof retry === 'string' && /^\d+(?:\.\d+)?s$/.test(retry)
    ? Number(retry.slice(0, -1)) : 0;
  const header = response.headers?.['retry-after'];
  const headerMs = /^\d+(?:\.\d+)?$/.test(String(header || '')) ? Number(header) * 1000 : 0;
  const quota = details.filter(d => /QuotaFailure$/.test(d['@type'] || ''))
    .flatMap(d => Array.isArray(d.violations) ? d.violations : [])
    .map(v => ({ metric: redact(v.quotaMetric, secrets), id: redact(v.quotaId, secrets),
      value: redact(v.quotaValue, secrets) }));
  return { httpStatus: response.status, status: redact(error.status, secrets),
    message: redact(error.message || response.err || '', secrets), quota,
    retryAfterMs: Math.min(86400000, Math.max(0, retrySeconds * 1000, headerMs)) };
}
function cooldownMs(error) {
  const dailyOrSpend = /perday|per.day|monthly|spend|billing|budget/i.test(
    `${error.message || ''} ${JSON.stringify(error.quota || [])}`);
  return Math.max(dailyOrSpend ? 3600000 : 60000, error.retryAfterMs || 0);
}
function requestVision(body, key, { timeoutMs = 30000, request = https.request } = {}) {
  const data = JSON.stringify(body);
  return new Promise(resolve => {
    let finished = false;
    const finish = result => { if (!finished) { finished = true; resolve(result); } };
    const req = request({ host: 'generativelanguage.googleapis.com',
      path: '/v1beta/models/gemini-2.5-flash:generateContent', method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data),
        'x-goog-api-key': key },
    }, response => {
      let body = '';
      response.on('data', chunk => { body += chunk; });
      response.on('error', error => finish({ status: 0, err: error.message }));
      response.on('end', () => finish({ status: response.statusCode, body, headers: response.headers }));
    });
    req.setTimeout(timeoutMs, () => req.destroy(new Error('vision-request-timeout')));
    req.on('error', error => finish({ status: 0, err: error.message }));
    req.end(data);
  });
}
module.exports = { redact, errorDetails, cooldownMs, requestVision };