feat: agent node

This commit is contained in:
2026-03-11 14:39:38 +01:00
parent 94da42f9fc
commit f201fe92f4
15 changed files with 772 additions and 24 deletions

View File

@@ -80,6 +80,63 @@ app.delete('/api/todos/:id', (req, res) => {
}
})
/** POST /api/agent — run AI agent; body: { prompt, context?, contextNodes?, connection? }; returns { markdown }.
* connection (from Settings): { provider, baseURL?, model?, apiKey? }. If omitted, uses env (OPENAI_API_KEY / AI_BASE_URL / AI_MODEL).
*/
app.post('/api/agent', async (req, res) => {
try {
const body = req.body ?? {}
const { prompt, context, contextNodes, connection: conn } = body
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
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 res.status(503).json({
error: 'No AI configured. Set connection in Settings (AI) or env: OPENAI_API_KEY or AI_BASE_URL.',
})
}
const fullPrompt = [
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 { generateText } = await import('ai')
const { createOpenAI } = await import('@ai-sdk/openai')
const openai = createOpenAI({
apiKey: apiKey || 'lm-studio',
...(baseURL && { baseURL, compatibility: 'compatible' }),
})
const result = await generateText({
model: openai(modelId),
prompt: fullPrompt + '\n\nRespond with structured markdown only. No preamble.',
})
const markdown = result?.text ?? ''
res.json({ markdown })
} catch (err) {
console.error('Agent error:', err)
res.status(500).json({ error: err?.message ?? 'Agent request failed' })
}
})
/** Health check for Docker / orchestration */
app.get('/health', (req, res) => {
res.status(200).json({ ok: true })