← back to AbramsOS
lib/chat-providers/openai.js
63 lines
// OpenAI adapter — EXTERNAL lane. Mirrors the proven `ask-openai` call path
// (Responses API, gpt-5.2). Prompt is already redacted + NER-scrubbed by the
// route before it reaches here (AGENTS.md: no PII to a model provider without
// redaction). AGENTS.md bans only the ANTHROPIC key — OpenAI is permitted.
const BASE = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';
const CHAT_TIMEOUT_MS = 60_000;
function key() {
const k = process.env.OPENAI_API_KEY;
if (!k) throw new Error('OPENAI_API_KEY not set');
return k;
}
// Pull the text out of a Responses-API payload (output_text convenience field,
// else the output[].content[] blocks of type output_text).
function extractText(j) {
if (typeof j.output_text === 'string' && j.output_text.trim()) return j.output_text.trim();
const parts = [];
for (const item of j.output || []) {
for (const c of item.content || []) {
if (c.type === 'output_text' && typeof c.text === 'string') parts.push(c.text);
}
}
return parts.join('').trim();
}
async function chat({ system, messages, providerModel }) {
const model = providerModel || 'gpt-5.2';
// Responses API accepts an array of {role, content} as `input`; `instructions`
// carries the system prompt.
const input = (messages || []).map((m) => ({
role: m.role === 'assistant' ? 'assistant' : 'user',
content: String(m.content ?? ''),
}));
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), CHAT_TIMEOUT_MS);
try {
const r = await fetch(`${BASE}/responses`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${key()}`,
},
body: JSON.stringify({ model, instructions: system || undefined, input }),
signal: ctrl.signal,
});
if (!r.ok) {
const body = (await r.text().catch(() => '')).slice(0, 300);
throw new Error(`openai ${r.status}: ${body}`);
}
const j = await r.json();
const text = extractText(j);
if (!text) throw new Error('openai returned empty response');
return { text };
} finally {
clearTimeout(t);
}
}
module.exports = { chat, extractText, BASE };