Files
zui/backend/src/services/agentService.ts
2026-03-20 00:04:46 +01:00

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 }
}