← back to Dw Daily Catchup 20260909
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 };