223 lines
7.1 KiB
TypeScript
223 lines
7.1 KiB
TypeScript
|
|
import { Router } from 'express'
|
||
|
|
import { createOpencodeClient } from '@opencode-ai/sdk'
|
||
|
|
|
||
|
|
const OPENCODE_URL = 'http://localhost:4096'
|
||
|
|
|
||
|
|
async function getClient() {
|
||
|
|
console.log('[OpenCode] Creating client for URL:', OPENCODE_URL)
|
||
|
|
return createOpencodeClient({
|
||
|
|
baseUrl: OPENCODE_URL,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
export function createOpencodeRoutes(): Router {
|
||
|
|
const router = Router()
|
||
|
|
|
||
|
|
router.get('/', async (req, res) => {
|
||
|
|
const action = req.query.action as string | undefined
|
||
|
|
|
||
|
|
try {
|
||
|
|
const client = await getClient()
|
||
|
|
|
||
|
|
if (action === 'list-sessions') {
|
||
|
|
const response = await client.session.list()
|
||
|
|
const sessions = (response.data || []).map((session: { id: string; title?: string; createdAt?: string }) => ({
|
||
|
|
id: session.id,
|
||
|
|
title: session.title || 'New Chat',
|
||
|
|
createdAt: session.createdAt ? new Date(session.createdAt).getTime() : Date.now(),
|
||
|
|
}))
|
||
|
|
res.json({ sessions })
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
res.status(400).json({ error: 'Unknown action' })
|
||
|
|
} catch (error: unknown) {
|
||
|
|
console.error('API error:', error)
|
||
|
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
||
|
|
res.status(500).json({ error: errorMessage })
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
router.post('/', async (req, res) => {
|
||
|
|
try {
|
||
|
|
const body = req.body
|
||
|
|
const { action, sessionId, title, text, attachments } = body
|
||
|
|
console.log('[OpenCode] Received action:', action, 'sessionId:', sessionId)
|
||
|
|
|
||
|
|
const client = await getClient()
|
||
|
|
console.log('[OpenCode] Client created successfully')
|
||
|
|
|
||
|
|
if (action === 'create-session') {
|
||
|
|
try {
|
||
|
|
const response = await client.session.create({
|
||
|
|
body: { title: title || 'New Chat' },
|
||
|
|
})
|
||
|
|
|
||
|
|
if (response.error) {
|
||
|
|
res.status(500).json({ error: 'OpenCode error', details: response.error })
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
const sessionData = response.data as { id: string; title?: string; createdAt?: string } | undefined
|
||
|
|
if (sessionData) {
|
||
|
|
res.json({
|
||
|
|
session: {
|
||
|
|
id: sessionData.id,
|
||
|
|
title: sessionData.title,
|
||
|
|
createdAt: sessionData.createdAt ? new Date(sessionData.createdAt).getTime() : Date.now(),
|
||
|
|
},
|
||
|
|
})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
res.status(500).json({ error: 'Failed to create session - no data' })
|
||
|
|
return
|
||
|
|
} catch (e: unknown) {
|
||
|
|
console.error('Create session exception:', e)
|
||
|
|
const errorMessage = e instanceof Error ? e.message : 'Unknown error'
|
||
|
|
res.status(500).json({ error: errorMessage, exception: true })
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (action === 'delete-session') {
|
||
|
|
const response = await client.session.delete({
|
||
|
|
path: { id: sessionId },
|
||
|
|
})
|
||
|
|
res.json({ success: response.data })
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if (action === 'get-session') {
|
||
|
|
const response = await client.session.get({
|
||
|
|
path: { id: sessionId },
|
||
|
|
})
|
||
|
|
|
||
|
|
if (response.data) {
|
||
|
|
res.json({
|
||
|
|
title: response.data.title || 'New Chat',
|
||
|
|
})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
res.status(404).json({ error: 'Session not found' })
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if (action === 'get-messages') {
|
||
|
|
const limit = body.limit || 20
|
||
|
|
|
||
|
|
const response = await client.session.messages({
|
||
|
|
path: { id: sessionId },
|
||
|
|
query: { limit }
|
||
|
|
})
|
||
|
|
|
||
|
|
const messages = (response.data || []).map((item: { info?: { id?: string; role?: string; content?: string; createdAt?: string }; content?: string; parts?: unknown[]; id?: string; role?: string }) => {
|
||
|
|
let content = item.info?.content || item.content || ''
|
||
|
|
|
||
|
|
const parts = item.parts as Array<{ type: string; text?: string }> | undefined
|
||
|
|
if (parts && parts.length > 0) {
|
||
|
|
const textParts = parts
|
||
|
|
.filter((p) => p.type === 'text')
|
||
|
|
.map((p) => p.text)
|
||
|
|
.join('')
|
||
|
|
if (textParts) {
|
||
|
|
content = textParts
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
id: item.info?.id || item.id,
|
||
|
|
role: item.info?.role || item.role,
|
||
|
|
content: content,
|
||
|
|
parts: item.parts || [],
|
||
|
|
createdAt: item.info?.createdAt,
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
const hasMore = messages.length >= limit
|
||
|
|
|
||
|
|
res.json({ messages, hasMore })
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if (action === 'prompt') {
|
||
|
|
const parts: Array<{ type: 'text'; text: string } | { type: 'file'; url: string; name: string; mime: string }> = []
|
||
|
|
|
||
|
|
if (attachments && attachments.length > 0) {
|
||
|
|
for (const att of attachments as Array<{ url: string; name: string; mediaType: string }>) {
|
||
|
|
parts.push({
|
||
|
|
type: 'file',
|
||
|
|
url: att.url,
|
||
|
|
name: att.name,
|
||
|
|
mime: att.mediaType,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
parts.push({ type: 'text', text })
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await client.session.prompt({
|
||
|
|
path: { id: sessionId },
|
||
|
|
body: { parts },
|
||
|
|
})
|
||
|
|
|
||
|
|
if (response.data) {
|
||
|
|
res.json({
|
||
|
|
message: {
|
||
|
|
id: (response.data.info as { id?: string })?.id || crypto.randomUUID(),
|
||
|
|
role: (response.data.info as { role?: string })?.role || 'assistant',
|
||
|
|
content: '',
|
||
|
|
parts: response.data.parts || [],
|
||
|
|
createdAt: new Date().toISOString(),
|
||
|
|
},
|
||
|
|
})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
res.status(500).json({ error: 'Failed to get response', details: response.error })
|
||
|
|
return
|
||
|
|
} catch (e: unknown) {
|
||
|
|
console.error('Prompt exception:', e)
|
||
|
|
const errorMessage = e instanceof Error ? e.message : 'Unknown error'
|
||
|
|
res.status(500).json({ error: errorMessage })
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (action === 'summarize-session') {
|
||
|
|
try {
|
||
|
|
await client.session.summarize({
|
||
|
|
path: { id: sessionId },
|
||
|
|
})
|
||
|
|
|
||
|
|
const response = await client.session.get({
|
||
|
|
path: { id: sessionId },
|
||
|
|
})
|
||
|
|
|
||
|
|
if (response.data) {
|
||
|
|
res.json({
|
||
|
|
title: response.data.title || 'New Chat',
|
||
|
|
})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
res.status(500).json({ error: 'Failed to get session' })
|
||
|
|
return
|
||
|
|
} catch (e: unknown) {
|
||
|
|
console.error('Summarize error:', e)
|
||
|
|
const errorMessage = e instanceof Error ? e.message : 'Unknown error'
|
||
|
|
res.status(500).json({ error: errorMessage })
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
res.status(400).json({ error: 'Unknown action' })
|
||
|
|
} catch (error: unknown) {
|
||
|
|
console.error('API error:', error)
|
||
|
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
||
|
|
res.status(500).json({ error: errorMessage })
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
return router
|
||
|
|
}
|