/ src / app / api / knowledge / route.ts
route.ts
 1  import { NextResponse } from 'next/server'
 2  import {
 3    createKnowledgeSource,
 4    listKnowledgeSourceSummaries,
 5    searchKnowledgeHits,
 6  } from '@/lib/server/knowledge-sources'
 7  import type { KnowledgeSourceKind } from '@/types'
 8  
 9  function parseTags(raw: string | null): string[] | undefined {
10    if (!raw) return undefined
11    const tags = raw.split(',').map((tag) => tag.trim()).filter(Boolean)
12    return tags.length > 0 ? tags : undefined
13  }
14  
15  function parseLimit(raw: string | null): number | undefined {
16    if (!raw) return undefined
17    return Math.max(1, Math.min(500, Number.parseInt(raw, 10) || 50))
18  }
19  
20  function parseBool(raw: string | null): boolean {
21    if (!raw) return false
22    const value = raw.trim().toLowerCase()
23    return value === '1' || value === 'true' || value === 'yes'
24  }
25  
26  function inferKind(body: Record<string, unknown>): KnowledgeSourceKind {
27    if (body.kind === 'file' || body.kind === 'url' || body.kind === 'manual') return body.kind
28    if (typeof body.sourcePath === 'string' && body.sourcePath.trim()) return 'file'
29    if (typeof body.sourceUrl === 'string' && body.sourceUrl.trim() && typeof body.content !== 'string') return 'url'
30    return 'manual'
31  }
32  
33  export async function GET(req: Request) {
34    const { searchParams } = new URL(req.url)
35    const query = searchParams.get('q')
36    const tags = parseTags(searchParams.get('tags'))
37    const limit = parseLimit(searchParams.get('limit'))
38    const includeArchived = parseBool(searchParams.get('includeArchived'))
39  
40    if (query && query.trim()) {
41      const hits = await searchKnowledgeHits({ query, tags, limit, includeArchived })
42      return NextResponse.json(hits)
43    }
44  
45    const sources = await listKnowledgeSourceSummaries({ tags, limit, includeArchived })
46    return NextResponse.json(sources)
47  }
48  
49  export async function POST(req: Request) {
50    const body = await req.json().catch(() => null)
51    if (!body || typeof body !== 'object' || Array.isArray(body)) {
52      return NextResponse.json({ error: 'Invalid JSON body.' }, { status: 400 })
53    }
54  
55    const payload = body as Record<string, unknown>
56  
57    try {
58      const detail = await createKnowledgeSource({
59        kind: inferKind(payload),
60        title: typeof payload.title === 'string' ? payload.title : undefined,
61        content: typeof payload.content === 'string' ? payload.content : undefined,
62        tags: Array.isArray(payload.tags) ? payload.tags.filter((tag): tag is string => typeof tag === 'string') : undefined,
63        scope: payload.scope === 'agent' ? 'agent' : 'global',
64        agentIds: Array.isArray(payload.agentIds) ? payload.agentIds.filter((id): id is string => typeof id === 'string') : undefined,
65        sourceLabel: typeof payload.sourceLabel === 'string'
66          ? payload.sourceLabel
67          : typeof payload.source === 'string'
68            ? payload.source
69            : undefined,
70        sourceUrl: typeof payload.sourceUrl === 'string' ? payload.sourceUrl : undefined,
71        sourcePath: typeof payload.sourcePath === 'string'
72          ? payload.sourcePath
73          : typeof payload.filePath === 'string'
74            ? payload.filePath
75            : undefined,
76        metadata: payload.metadata && typeof payload.metadata === 'object' && !Array.isArray(payload.metadata)
77          ? payload.metadata as Record<string, unknown>
78          : undefined,
79      })
80  
81      return NextResponse.json(detail)
82    } catch (error) {
83      return NextResponse.json(
84        { error: error instanceof Error ? error.message : 'Failed to create knowledge source.' },
85        { status: 400 },
86      )
87    }
88  }