import process from 'node:process' import { createOpencode, createOpencodeClient } from '@opencode-ai/sdk' // import Anthropic from '@anthropic-ai/sdk' import OpenAI from 'openai' const log = createLogger('llm') // export const claude = new Anthropic({ // apiKey: process.env.ANTHROPIC_API_KEY, // }) const OPENCODE_URL = 'http://127.0.0.1:4096' let _client: ReturnType | null = null async function getClient() { if (_client) return _client // Если сервер уже запущен (напр. после хот-релоада) — просто подключаемся try { const existing = createOpencodeClient({ baseUrl: OPENCODE_URL }) await existing.session.list() _client = existing return _client } catch {} // Иначе стартуем новый сервер const oc = await createOpencode({ hostname: '127.0.0.1', port: 4096, config: { model: 'openrouter/deepseek/deepseek-v4-flash', provider: { openrouter: { options: { apiKey: process.env.OPENROUTER_API_KEY }, }, }, agent: { build: { tools: { '*': false } }, }, }, }) _client = oc.client return _client } export const groq = new OpenAI({ apiKey: process.env.GROQ_API_KEY, baseURL: 'https://api.groq.com/openai/v1', }) export async function test() { const client = createOpencodeClient({ baseUrl: 'http://localhost:4096', }) const test = await client.config.providers() log.debug('providers', test.data) } export async function askLLM(userMessage: string) { const client = await getClient() log.info('askLLM called') // Создаём сессию const session = await client.session.create({ body: { title: 'My request' }, }) // console.log('session: ', session.data) const result = await client.session.prompt({ path: { id: session.data!.id }, body: { parts: [{ type: 'text', text: userMessage }], }, }) // console.log('result: ', result.data) const textPart = result.data?.parts?.find((p: { type: string }) => p.type === 'text') as { type: 'text', text: string } | undefined // console.log(textPart?.text ?? '') return textPart?.text ?? '' } export async function createMessage(resume: string, message: string, prompt?: string) { const client = await getClient() log.debug('client.instance:', !!client.instance) const session = await client.session.create({ body: { title: 'Cover letter' } }) const sessionId = session.data!.id log.debug('sessionId:', sessionId) const finalPromt = prompt || 'Ты — помощник по написанию сопроводительных писем. Отвечай только текстом самого письма, без вступлений, ремарок и пояснений. Опирайся на резюме и ничего не выдумывай, чего недостаточно в резюме лучше умолчать. Пиши по короче и простыми словами. В конце письма оставляй все контакты для связи.' const resumePreview = resume.slice(0, 200).replace(/\n/g, ' ') log.divider('Prompt 1 — system + resume (noReply)') log.llm(`system: ${finalPromt.slice(0, 80)}…`) log.llm(`resume: ${resumePreview}…`) await client.session.prompt({ path: { id: sessionId }, body: { noReply: true, parts: [{ type: 'text', text: `${finalPromt}\n Резюме:\n${resume}` }], }, }) const vacancyPreview = message.slice(0, 300).replace(/\n/g, ' ') log.divider('Prompt 2 — vacancy') log.llm(`📝 vacancy → ожидаю ответ…`) log.llm(`vacancy: ${vacancyPreview}…`) // ${prompt}\n\n const result = await client.session.prompt({ path: { id: sessionId }, body: { parts: [{ type: 'text', text: `Вакансия:\n${message}` }], }, }) const parts = (result.data?.parts ?? []) as { type: string, text?: string }[] const textPart = parts.find(p => p.type === 'text') log.divider('Ответ получен') log.llm(`✅ ${textPart?.text?.length ?? 0} символов`) log.llm(`${textPart?.text?.slice(0, 150).replace(/\n/g, ' ') ?? 'null'}…`) try { await client.session.delete({ path: { id: sessionId } }) } catch (e) { log.error('Session cleanup error:', (e as Error).message) } return textPart?.text ?? null } export async function askGPT(resume: string, message: string, prompt: string) { // return 'test' const res = await groq.chat.completions.create({ model: 'llama-3.3-70b-versatile', messages: [ { role: 'system', content: `${prompt} ${resume}` }, { role: 'user', content: message }, ], }) return res.choices[0].message.content }