66 lines
2.7 KiB
TypeScript
66 lines
2.7 KiB
TypeScript
/**
|
|
* Services: Business logic layer.
|
|
*
|
|
* This module contains the business logic for the application.
|
|
* Services coordinate between controllers and repositories.
|
|
*/
|
|
|
|
import type { AgentRequest, AgentResponse, AgentConnection } from '../models'
|
|
|
|
/**
|
|
* Build OpenAI client and full prompt from request body.
|
|
* Returns { openai, modelId, fullPrompt } or { error }.
|
|
*/
|
|
export function buildAgentRequest(body: AgentRequest): { openai: unknown; modelId: string; fullPrompt: string } | { error: string } {
|
|
const { prompt, context, contextNodes, connection: conn, reasoning } = body ?? {}
|
|
const reasoningEnabled = Boolean(reasoning)
|
|
let baseURL = process.env.AI_BASE_URL?.trim() || null
|
|
let apiKey = process.env.OPENAI_API_KEY?.trim() || null
|
|
let modelId = process.env.AI_MODEL?.trim() || (baseURL ? 'local-model' : 'gpt-4o-mini')
|
|
|
|
if (conn && typeof conn === 'object') {
|
|
const c = conn as AgentConnection
|
|
const provider = c.provider === 'openai' ? 'openai' : 'local'
|
|
if (provider === 'local') {
|
|
baseURL = (typeof c.baseURL === 'string' && c.baseURL.trim()) ? c.baseURL.trim() : baseURL
|
|
apiKey = (typeof c.apiKey === 'string' && c.apiKey.trim()) ? c.apiKey.trim() : (apiKey || 'lm-studio')
|
|
} else {
|
|
baseURL = null
|
|
apiKey = (typeof c.apiKey === 'string' && c.apiKey.trim()) ? c.apiKey.trim() : apiKey
|
|
}
|
|
if (typeof c.model === 'string' && c.model.trim()) modelId = c.model.trim()
|
|
}
|
|
|
|
if (!baseURL && !apiKey) {
|
|
return { error: 'No AI configured. Set connection in Settings (AI) or env: OPENAI_API_KEY or AI_BASE_URL.' }
|
|
}
|
|
|
|
const basePrompt = [
|
|
typeof prompt === 'string' ? prompt : 'No prompt provided.',
|
|
context && typeof context === 'string' ? `\n\nAdditional context:\n${context}` : '',
|
|
Array.isArray(contextNodes) && contextNodes.length > 0
|
|
? `\n\nContext from connected nodes:\n${contextNodes.map((n) => (n.content != null ? n.content : `${n.id}: (no content)`)).join('\n\n')}`
|
|
: '',
|
|
].join('')
|
|
|
|
const fullPrompt = reasoningEnabled
|
|
? basePrompt + '\n\nRespond in exactly two markdown sections. First: "## Reasoning" with your step-by-step reasoning. Then: "## Output" with only the final answer. No preamble.'
|
|
: basePrompt + '\n\nRespond with structured markdown only. No preamble.'
|
|
|
|
const { createOpenAI } = require('@ai-sdk/openai')
|
|
const openai = createOpenAI({
|
|
apiKey: apiKey || 'lm-studio',
|
|
...(baseURL && { baseURL, compatibility: 'compatible' }),
|
|
})
|
|
|
|
return { openai, modelId, fullPrompt }
|
|
}
|
|
|
|
/**
|
|
* Process agent response
|
|
*/
|
|
export function processAgentResponse(result: { text?: string }): AgentResponse {
|
|
const markdown = result?.text ?? ''
|
|
return { markdown }
|
|
}
|