/opt/mawid/apps/api/src/agent
Edit: /opt/mawid/apps/api/src/agent/openai.client.ts (2247B)
import OpenAI from 'openai';
export const LLM_CLIENT = Symbol('LLM_CLIENT');
/**
* The slice of the OpenAI SDK the agent uses — narrow so tests can inject a
* scripted fake.
*/
export interface AgentLlmClient {
chat: {
completions: {
create(
params: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming,
): Promise
;
};
};
}
export function createOpenAiClient(apiKey: string): AgentLlmClient {
const client = new OpenAI({ apiKey });
return {
chat: {
completions: {
// Fresh project keys intermittently 401 ("insufficient permissions")
// from stale edge auth caches while the same request succeeds on
// retry. The SDK only auto-retries 429/5xx, so retry 401s here.
create: async (params) => {
let lastError: unknown;
for (let attempt = 0; attempt < 3; attempt++) {
try {
return await client.chat.completions.create(params);
} catch (err) {
lastError = err;
const status = (err as { status?: number }).status;
if (status !== 401) throw err;
await new Promise((resolve) => setTimeout(resolve, 1000 * (attempt + 1)));
}
}
throw lastError;
},
},
},
};
}
/**
* Model routing: cheap intent gate, capable booking agent.
* Defaults are the previous generation (gpt-5-mini / gpt-5): fully supported
* and 100% reliable in testing, while GPT-5.6-tier access intermittently
* 401s on new/unverified OpenAI orgs (20-50% of calls, observed 2026-07-19).
* Override via OPENAI_INTENT_MODEL / OPENAI_AGENT_MODEL once the org is
* verified — e.g. gpt-5.6-luna / gpt-5.6-terra.
*/
export const INTENT_MODEL = process.env.OPENAI_INTENT_MODEL || 'gpt-5-mini';
export const AGENT_MODEL = process.env.OPENAI_AGENT_MODEL || 'gpt-5';
/**
* Effort vocabularies differ by generation: gpt-5.x uses 'minimal' as its
* floor; gpt-5.6 replaced it with 'none' (and rejects tools + any other
* value in /v1/chat/completions).
*/
export function minReasoningEffort(model: string): 'none' | 'minimal' {
return model.startsWith('gpt-5.6') ? 'none' : 'minimal';
}