/opt/mawid/apps/api/src/agent
NameSizeModeActions
agent.e2e.spec.ts132960644editdlrm
agent.module.ts8320644editdlrm
agent.service.ts81630644editdlrm
availability.spec.ts84820644editdlrm
availability.ts51830644editdlrm
booking-tools.service.ts144400644editdlrm
openai.client.ts22470644editdlrm
system-prompt.ts18550644editdlrm
tool-definitions.ts36140644editdlrm
tz.ts28820644editdlrm
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'; }