@swarmclawai/swarmclaw 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +577 -0
- package/bin/server-cmd.js +359 -0
- package/bin/swarmclaw.js +29 -0
- package/bin/swarmclaw.mjs +1504 -0
- package/next.config.ts +33 -0
- package/package.json +112 -0
- package/postcss.config.mjs +7 -0
- package/public/branding/swarmclaw-org-avatar.png +0 -0
- package/public/branding/swarmclaw-org-avatar.svg +58 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/connectors.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/new-session-openclaw.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/schedules.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/agents/[id]/route.ts +30 -0
- package/src/app/api/agents/[id]/thread/route.ts +66 -0
- package/src/app/api/agents/generate/route.ts +42 -0
- package/src/app/api/agents/route.ts +33 -0
- package/src/app/api/auth/route.ts +25 -0
- package/src/app/api/claude-skills/route.ts +42 -0
- package/src/app/api/clawhub/install/route.ts +39 -0
- package/src/app/api/clawhub/search/route.ts +11 -0
- package/src/app/api/connectors/[id]/route.ts +79 -0
- package/src/app/api/connectors/route.ts +60 -0
- package/src/app/api/credentials/[id]/route.ts +14 -0
- package/src/app/api/credentials/route.ts +31 -0
- package/src/app/api/daemon/health-check/route.ts +11 -0
- package/src/app/api/daemon/route.ts +22 -0
- package/src/app/api/dirs/pick/route.ts +60 -0
- package/src/app/api/dirs/route.ts +29 -0
- package/src/app/api/documents/[id]/route.ts +47 -0
- package/src/app/api/documents/route.ts +93 -0
- package/src/app/api/files/serve/route.ts +69 -0
- package/src/app/api/generate/info/route.ts +12 -0
- package/src/app/api/generate/route.ts +106 -0
- package/src/app/api/ip/route.ts +6 -0
- package/src/app/api/knowledge/[id]/route.ts +61 -0
- package/src/app/api/knowledge/route.ts +48 -0
- package/src/app/api/knowledge/upload/route.ts +86 -0
- package/src/app/api/logs/route.ts +65 -0
- package/src/app/api/mcp-servers/[id]/route.ts +32 -0
- package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
- package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
- package/src/app/api/mcp-servers/route.ts +27 -0
- package/src/app/api/memory/[id]/route.ts +126 -0
- package/src/app/api/memory/maintenance/route.ts +63 -0
- package/src/app/api/memory/route.ts +111 -0
- package/src/app/api/memory-images/[filename]/route.ts +36 -0
- package/src/app/api/orchestrator/run/route.ts +43 -0
- package/src/app/api/plugins/install/route.ts +58 -0
- package/src/app/api/plugins/marketplace/route.ts +33 -0
- package/src/app/api/plugins/route.ts +21 -0
- package/src/app/api/preview-server/route.ts +339 -0
- package/src/app/api/providers/[id]/models/route.ts +29 -0
- package/src/app/api/providers/[id]/route.ts +34 -0
- package/src/app/api/providers/configs/route.ts +7 -0
- package/src/app/api/providers/ollama/route.ts +30 -0
- package/src/app/api/providers/openclaw/health/route.ts +23 -0
- package/src/app/api/providers/route.ts +28 -0
- package/src/app/api/runs/[id]/route.ts +9 -0
- package/src/app/api/runs/route.ts +13 -0
- package/src/app/api/schedules/[id]/route.ts +28 -0
- package/src/app/api/schedules/[id]/run/route.ts +104 -0
- package/src/app/api/schedules/route.ts +78 -0
- package/src/app/api/secrets/[id]/route.ts +29 -0
- package/src/app/api/secrets/route.ts +42 -0
- package/src/app/api/sessions/[id]/browser/route.ts +13 -0
- package/src/app/api/sessions/[id]/chat/route.ts +96 -0
- package/src/app/api/sessions/[id]/clear/route.ts +19 -0
- package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
- package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
- package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
- package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
- package/src/app/api/sessions/[id]/messages/route.ts +9 -0
- package/src/app/api/sessions/[id]/retry/route.ts +28 -0
- package/src/app/api/sessions/[id]/route.ts +103 -0
- package/src/app/api/sessions/[id]/stop/route.ts +13 -0
- package/src/app/api/sessions/heartbeat/route.ts +26 -0
- package/src/app/api/sessions/route.ts +85 -0
- package/src/app/api/settings/route.ts +58 -0
- package/src/app/api/setup/check-provider/route.ts +326 -0
- package/src/app/api/setup/doctor/route.ts +250 -0
- package/src/app/api/skills/[id]/route.ts +40 -0
- package/src/app/api/skills/import/route.ts +69 -0
- package/src/app/api/skills/route.ts +28 -0
- package/src/app/api/tasks/[id]/route.ts +102 -0
- package/src/app/api/tasks/route.ts +115 -0
- package/src/app/api/tts/route.ts +40 -0
- package/src/app/api/upload/route.ts +18 -0
- package/src/app/api/uploads/[filename]/route.ts +59 -0
- package/src/app/api/usage/route.ts +35 -0
- package/src/app/api/version/route.ts +81 -0
- package/src/app/api/version/update/route.ts +95 -0
- package/src/app/api/webhooks/[id]/history/route.ts +13 -0
- package/src/app/api/webhooks/[id]/route.ts +204 -0
- package/src/app/api/webhooks/route.ts +37 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +370 -0
- package/src/app/layout.tsx +52 -0
- package/src/app/page.tsx +172 -0
- package/src/cli/index.js +1232 -0
- package/src/cli/index.test.js +281 -0
- package/src/cli/index.ts +1158 -0
- package/src/cli/spec.js +284 -0
- package/src/components/agents/agent-card.tsx +219 -0
- package/src/components/agents/agent-chat-list.tsx +165 -0
- package/src/components/agents/agent-list.tsx +110 -0
- package/src/components/agents/agent-sheet.tsx +1220 -0
- package/src/components/auth/access-key-gate.tsx +248 -0
- package/src/components/auth/setup-wizard.tsx +940 -0
- package/src/components/auth/user-picker.tsx +88 -0
- package/src/components/chat/chat-area.tsx +406 -0
- package/src/components/chat/chat-header.tsx +491 -0
- package/src/components/chat/chat-tool-toggles.tsx +161 -0
- package/src/components/chat/code-block.tsx +146 -0
- package/src/components/chat/dev-server-bar.tsx +39 -0
- package/src/components/chat/message-bubble.tsx +486 -0
- package/src/components/chat/message-list.tsx +299 -0
- package/src/components/chat/session-debug-panel.tsx +196 -0
- package/src/components/chat/streaming-bubble.tsx +85 -0
- package/src/components/chat/thinking-indicator.tsx +26 -0
- package/src/components/chat/tool-call-bubble.tsx +438 -0
- package/src/components/chat/tool-request-banner.tsx +103 -0
- package/src/components/connectors/connector-list.tsx +196 -0
- package/src/components/connectors/connector-sheet.tsx +804 -0
- package/src/components/input/chat-input.tsx +235 -0
- package/src/components/knowledge/knowledge-list.tsx +206 -0
- package/src/components/knowledge/knowledge-sheet.tsx +316 -0
- package/src/components/layout/app-layout.tsx +1016 -0
- package/src/components/layout/daemon-indicator.tsx +56 -0
- package/src/components/layout/mobile-header.tsx +31 -0
- package/src/components/layout/network-banner.tsx +17 -0
- package/src/components/layout/update-banner.tsx +130 -0
- package/src/components/logs/log-list.tsx +358 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
- package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
- package/src/components/memory/memory-card.tsx +63 -0
- package/src/components/memory/memory-detail.tsx +339 -0
- package/src/components/memory/memory-list.tsx +198 -0
- package/src/components/memory/memory-sheet.tsx +70 -0
- package/src/components/plugins/plugin-list.tsx +60 -0
- package/src/components/plugins/plugin-sheet.tsx +311 -0
- package/src/components/providers/provider-list.tsx +96 -0
- package/src/components/providers/provider-sheet.tsx +542 -0
- package/src/components/runs/run-list.tsx +231 -0
- package/src/components/schedules/schedule-card.tsx +63 -0
- package/src/components/schedules/schedule-list.tsx +76 -0
- package/src/components/schedules/schedule-sheet.tsx +336 -0
- package/src/components/secrets/secret-sheet.tsx +180 -0
- package/src/components/secrets/secrets-list.tsx +91 -0
- package/src/components/sessions/new-session-sheet.tsx +478 -0
- package/src/components/sessions/session-card.tsx +144 -0
- package/src/components/sessions/session-list.tsx +202 -0
- package/src/components/shared/ai-gen-block.tsx +77 -0
- package/src/components/shared/avatar.tsx +48 -0
- package/src/components/shared/bottom-sheet.tsx +30 -0
- package/src/components/shared/confirm-dialog.tsx +47 -0
- package/src/components/shared/connector-platform-icon.tsx +113 -0
- package/src/components/shared/dir-browser.tsx +285 -0
- package/src/components/shared/dropdown.tsx +55 -0
- package/src/components/shared/icon-button.tsx +25 -0
- package/src/components/shared/settings/plugin-manager.tsx +207 -0
- package/src/components/shared/settings/section-capability-policy.tsx +93 -0
- package/src/components/shared/settings/section-embedding.tsx +99 -0
- package/src/components/shared/settings/section-heartbeat.tsx +168 -0
- package/src/components/shared/settings/section-memory.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +108 -0
- package/src/components/shared/settings/section-providers.tsx +181 -0
- package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
- package/src/components/shared/settings/section-secrets.tsx +132 -0
- package/src/components/shared/settings/section-user-preferences.tsx +24 -0
- package/src/components/shared/settings/section-voice.tsx +53 -0
- package/src/components/shared/settings/settings-sheet.tsx +88 -0
- package/src/components/shared/settings/types.ts +7 -0
- package/src/components/shared/settings/utils.ts +13 -0
- package/src/components/shared/settings-sheet.tsx +1 -0
- package/src/components/shared/skeleton.tsx +19 -0
- package/src/components/shared/usage-badge.tsx +28 -0
- package/src/components/skills/clawhub-browser.tsx +225 -0
- package/src/components/skills/skill-list.tsx +70 -0
- package/src/components/skills/skill-sheet.tsx +254 -0
- package/src/components/tasks/task-board.tsx +96 -0
- package/src/components/tasks/task-card.tsx +179 -0
- package/src/components/tasks/task-column.tsx +73 -0
- package/src/components/tasks/task-list.tsx +118 -0
- package/src/components/tasks/task-sheet.tsx +415 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sonner.tsx +22 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/components/usage/usage-list.tsx +105 -0
- package/src/components/webhooks/webhook-list.tsx +166 -0
- package/src/components/webhooks/webhook-sheet.tsx +402 -0
- package/src/hooks/use-auto-resize.ts +20 -0
- package/src/hooks/use-media-query.ts +21 -0
- package/src/hooks/use-speech-recognition.ts +83 -0
- package/src/instrumentation.ts +8 -0
- package/src/lib/agents.ts +13 -0
- package/src/lib/api-client.ts +100 -0
- package/src/lib/chat.ts +60 -0
- package/src/lib/memory.ts +42 -0
- package/src/lib/openclaw-endpoint.test.ts +48 -0
- package/src/lib/openclaw-endpoint.ts +67 -0
- package/src/lib/provider-config.ts +13 -0
- package/src/lib/providers/anthropic.ts +135 -0
- package/src/lib/providers/claude-cli.ts +202 -0
- package/src/lib/providers/codex-cli.ts +260 -0
- package/src/lib/providers/index.ts +351 -0
- package/src/lib/providers/ollama.ts +131 -0
- package/src/lib/providers/openai.ts +164 -0
- package/src/lib/providers/openclaw.ts +330 -0
- package/src/lib/providers/opencode-cli.ts +164 -0
- package/src/lib/runtime-loop.ts +15 -0
- package/src/lib/schedule-dedupe.test.ts +84 -0
- package/src/lib/schedule-dedupe.ts +174 -0
- package/src/lib/schedule-name.ts +62 -0
- package/src/lib/schedules.ts +16 -0
- package/src/lib/server/agent-registry.ts +70 -0
- package/src/lib/server/api-routes.test.ts +362 -0
- package/src/lib/server/autonomy-contract.ts +200 -0
- package/src/lib/server/build-llm.ts +155 -0
- package/src/lib/server/capability-router.test.ts +21 -0
- package/src/lib/server/capability-router.ts +172 -0
- package/src/lib/server/chat-execution.ts +894 -0
- package/src/lib/server/clawhub-client.test.ts +161 -0
- package/src/lib/server/clawhub-client.ts +26 -0
- package/src/lib/server/connectors/connector-routing.test.ts +243 -0
- package/src/lib/server/connectors/discord.ts +116 -0
- package/src/lib/server/connectors/googlechat.ts +66 -0
- package/src/lib/server/connectors/manager.ts +559 -0
- package/src/lib/server/connectors/matrix.ts +78 -0
- package/src/lib/server/connectors/media.ts +149 -0
- package/src/lib/server/connectors/openclaw.test.ts +375 -0
- package/src/lib/server/connectors/openclaw.ts +1132 -0
- package/src/lib/server/connectors/signal.ts +183 -0
- package/src/lib/server/connectors/slack.ts +258 -0
- package/src/lib/server/connectors/teams.ts +94 -0
- package/src/lib/server/connectors/telegram.ts +221 -0
- package/src/lib/server/connectors/types.ts +62 -0
- package/src/lib/server/connectors/whatsapp.ts +349 -0
- package/src/lib/server/context-manager.ts +232 -0
- package/src/lib/server/cost.ts +31 -0
- package/src/lib/server/daemon-state.ts +354 -0
- package/src/lib/server/data-dir.ts +3 -0
- package/src/lib/server/embeddings.ts +111 -0
- package/src/lib/server/execution-log.ts +257 -0
- package/src/lib/server/gateway/protocol.test.ts +54 -0
- package/src/lib/server/gateway/protocol.ts +114 -0
- package/src/lib/server/heartbeat-service.ts +366 -0
- package/src/lib/server/knowledge-db.test.ts +441 -0
- package/src/lib/server/logger.ts +47 -0
- package/src/lib/server/main-agent-loop.ts +1017 -0
- package/src/lib/server/mcp-client.test.ts +342 -0
- package/src/lib/server/mcp-client.ts +130 -0
- package/src/lib/server/memory-db.ts +1078 -0
- package/src/lib/server/memory-graph.test.ts +153 -0
- package/src/lib/server/memory-graph.ts +138 -0
- package/src/lib/server/openclaw-health.ts +245 -0
- package/src/lib/server/orchestrator-lg.ts +431 -0
- package/src/lib/server/orchestrator.ts +364 -0
- package/src/lib/server/playwright-proxy.mjs +70 -0
- package/src/lib/server/plugins.ts +229 -0
- package/src/lib/server/process-manager.ts +327 -0
- package/src/lib/server/provider-health.ts +113 -0
- package/src/lib/server/queue.ts +859 -0
- package/src/lib/server/runtime-settings.ts +119 -0
- package/src/lib/server/scheduler.ts +196 -0
- package/src/lib/server/session-mailbox.ts +129 -0
- package/src/lib/server/session-run-manager.ts +512 -0
- package/src/lib/server/session-tools/connector.ts +124 -0
- package/src/lib/server/session-tools/context-mgmt.ts +103 -0
- package/src/lib/server/session-tools/context.ts +114 -0
- package/src/lib/server/session-tools/crud.ts +673 -0
- package/src/lib/server/session-tools/delegate.ts +708 -0
- package/src/lib/server/session-tools/file.ts +264 -0
- package/src/lib/server/session-tools/index.ts +164 -0
- package/src/lib/server/session-tools/memory.ts +230 -0
- package/src/lib/server/session-tools/session-info.ts +422 -0
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
- package/src/lib/server/session-tools/shell.ts +171 -0
- package/src/lib/server/session-tools/web.ts +408 -0
- package/src/lib/server/session-tools.ts +9 -0
- package/src/lib/server/skills-normalize.ts +130 -0
- package/src/lib/server/storage-mcp.test.ts +161 -0
- package/src/lib/server/storage.ts +670 -0
- package/src/lib/server/stream-agent-chat.ts +571 -0
- package/src/lib/server/task-reports.ts +122 -0
- package/src/lib/server/task-result.ts +161 -0
- package/src/lib/server/task-validation.test.ts +27 -0
- package/src/lib/server/task-validation.ts +90 -0
- package/src/lib/server/tool-capability-policy.test.ts +58 -0
- package/src/lib/server/tool-capability-policy.ts +262 -0
- package/src/lib/sessions.ts +68 -0
- package/src/lib/tasks.ts +20 -0
- package/src/lib/tts.ts +42 -0
- package/src/lib/upload.ts +10 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +43 -0
- package/src/stores/use-app-store.ts +468 -0
- package/src/stores/use-chat-store.ts +323 -0
- package/src/types/index.ts +621 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { addKnowledge, searchKnowledge, listKnowledge } from '@/lib/server/memory-db'
|
|
3
|
+
|
|
4
|
+
export async function GET(req: Request) {
|
|
5
|
+
const { searchParams } = new URL(req.url)
|
|
6
|
+
const q = searchParams.get('q')
|
|
7
|
+
const tagsParam = searchParams.get('tags')
|
|
8
|
+
const limitParam = searchParams.get('limit')
|
|
9
|
+
|
|
10
|
+
const tags = tagsParam ? tagsParam.split(',').map((t) => t.trim()).filter(Boolean) : undefined
|
|
11
|
+
const limit = limitParam ? Math.max(1, Math.min(500, Number.parseInt(limitParam, 10) || 50)) : undefined
|
|
12
|
+
|
|
13
|
+
if (q) {
|
|
14
|
+
const results = searchKnowledge(q, tags, limit)
|
|
15
|
+
return NextResponse.json(results)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const entries = listKnowledge(tags, limit)
|
|
19
|
+
return NextResponse.json(entries)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function POST(req: Request) {
|
|
23
|
+
const body = await req.json().catch(() => null)
|
|
24
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
25
|
+
return NextResponse.json({ error: 'Invalid JSON body.' }, { status: 400 })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { title, content, tags } = body as Record<string, unknown>
|
|
29
|
+
|
|
30
|
+
if (typeof title !== 'string' || !title.trim()) {
|
|
31
|
+
return NextResponse.json({ error: 'title is required.' }, { status: 400 })
|
|
32
|
+
}
|
|
33
|
+
if (typeof content !== 'string') {
|
|
34
|
+
return NextResponse.json({ error: 'content is required.' }, { status: 400 })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const normalizedTags = Array.isArray(tags)
|
|
38
|
+
? (tags as unknown[]).filter((t): t is string => typeof t === 'string' && t.trim().length > 0)
|
|
39
|
+
: undefined
|
|
40
|
+
|
|
41
|
+
const entry = addKnowledge({
|
|
42
|
+
title: title.trim(),
|
|
43
|
+
content,
|
|
44
|
+
tags: normalizedTags,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return NextResponse.json(entry)
|
|
48
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import crypto from 'crypto'
|
|
5
|
+
import { UPLOAD_DIR } from '@/lib/server/storage'
|
|
6
|
+
|
|
7
|
+
const TEXT_EXTS = new Set([
|
|
8
|
+
'.txt', '.md', '.markdown', '.csv', '.tsv', '.json', '.jsonl',
|
|
9
|
+
'.html', '.htm', '.xml', '.yaml', '.yml', '.toml', '.ini', '.cfg',
|
|
10
|
+
'.js', '.ts', '.tsx', '.jsx', '.py', '.go', '.rs', '.java', '.c', '.cpp', '.h',
|
|
11
|
+
'.rb', '.php', '.sh', '.bash', '.zsh', '.sql', '.r', '.swift', '.kt',
|
|
12
|
+
'.env', '.log', '.conf', '.properties', '.gitignore', '.dockerignore',
|
|
13
|
+
])
|
|
14
|
+
|
|
15
|
+
function isTextFile(filename: string): boolean {
|
|
16
|
+
const ext = path.extname(filename).toLowerCase()
|
|
17
|
+
return TEXT_EXTS.has(ext) || ext === ''
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function deriveTitle(filename: string): string {
|
|
21
|
+
const name = path.basename(filename, path.extname(filename))
|
|
22
|
+
return name
|
|
23
|
+
.replace(/[-_]+/g, ' ')
|
|
24
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
25
|
+
.replace(/\b\w/g, (c) => c.toUpperCase())
|
|
26
|
+
.trim() || 'Uploaded Document'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function POST(req: Request) {
|
|
30
|
+
const filename = req.headers.get('x-filename') || 'document.txt'
|
|
31
|
+
const buf = Buffer.from(await req.arrayBuffer())
|
|
32
|
+
|
|
33
|
+
if (buf.length === 0) {
|
|
34
|
+
return NextResponse.json({ error: 'Empty file.' }, { status: 400 })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (buf.length > 10 * 1024 * 1024) {
|
|
38
|
+
return NextResponse.json({ error: 'File too large. Maximum 10MB.' }, { status: 400 })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Save file to uploads
|
|
42
|
+
if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true })
|
|
43
|
+
const safeName = crypto.randomBytes(4).toString('hex') + '-' + filename.replace(/[^a-zA-Z0-9._-]/g, '_')
|
|
44
|
+
const filePath = path.join(UPLOAD_DIR, safeName)
|
|
45
|
+
fs.writeFileSync(filePath, buf)
|
|
46
|
+
|
|
47
|
+
// Extract text content
|
|
48
|
+
let content = ''
|
|
49
|
+
const ext = path.extname(filename).toLowerCase()
|
|
50
|
+
|
|
51
|
+
if (ext === '.pdf') {
|
|
52
|
+
// Try dynamic import of pdf-parse if available
|
|
53
|
+
try {
|
|
54
|
+
// @ts-ignore — pdf-parse is an optional dependency
|
|
55
|
+
const pdfParse = (await import(/* webpackIgnore: true */ 'pdf-parse')).default
|
|
56
|
+
const result = await pdfParse(buf)
|
|
57
|
+
content = result.text || ''
|
|
58
|
+
} catch {
|
|
59
|
+
// pdf-parse not installed — read as raw text fallback
|
|
60
|
+
content = '[PDF document — install pdf-parse for text extraction]\n\nFile saved at: ' + filePath
|
|
61
|
+
}
|
|
62
|
+
} else if (isTextFile(filename)) {
|
|
63
|
+
content = buf.toString('utf-8')
|
|
64
|
+
} else {
|
|
65
|
+
// Binary file — can't extract text
|
|
66
|
+
content = `[Binary file: ${filename}]\n\nFile saved at: ${filePath}`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Truncate very long content to prevent memory issues
|
|
70
|
+
const MAX_CONTENT = 500_000
|
|
71
|
+
if (content.length > MAX_CONTENT) {
|
|
72
|
+
content = content.slice(0, MAX_CONTENT) + '\n\n[... truncated at 500k characters]'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const title = deriveTitle(filename)
|
|
76
|
+
const url = `/api/uploads/${safeName}`
|
|
77
|
+
|
|
78
|
+
return NextResponse.json({
|
|
79
|
+
title,
|
|
80
|
+
content,
|
|
81
|
+
filePath,
|
|
82
|
+
url,
|
|
83
|
+
filename,
|
|
84
|
+
size: buf.length,
|
|
85
|
+
})
|
|
86
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
const LOG_FILE = path.join(process.cwd(), 'data', 'app.log')
|
|
6
|
+
|
|
7
|
+
export async function GET(req: Request) {
|
|
8
|
+
const { searchParams } = new URL(req.url)
|
|
9
|
+
const lines = parseInt(searchParams.get('lines') || '200', 10)
|
|
10
|
+
const level = searchParams.get('level') || '' // INFO, WARN, ERROR, DEBUG
|
|
11
|
+
const search = searchParams.get('search') || ''
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
if (!fs.existsSync(LOG_FILE)) {
|
|
15
|
+
return NextResponse.json({ entries: [], total: 0 })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const content = fs.readFileSync(LOG_FILE, 'utf8')
|
|
19
|
+
let allLines = content.split('\n').filter(Boolean)
|
|
20
|
+
|
|
21
|
+
// Filter by level
|
|
22
|
+
if (level) {
|
|
23
|
+
const levels = level.split(',')
|
|
24
|
+
allLines = allLines.filter((l) => levels.some((lv) => l.includes(`[${lv}]`)))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Filter by search term
|
|
28
|
+
if (search) {
|
|
29
|
+
const lower = search.toLowerCase()
|
|
30
|
+
allLines = allLines.filter((l) => l.toLowerCase().includes(lower))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const total = allLines.length
|
|
34
|
+
// Return most recent lines
|
|
35
|
+
const entries = allLines.slice(-lines).reverse().map(parseLine)
|
|
36
|
+
|
|
37
|
+
return NextResponse.json({ entries, total })
|
|
38
|
+
} catch (err: any) {
|
|
39
|
+
return NextResponse.json({ error: err.message }, { status: 500 })
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function DELETE() {
|
|
44
|
+
try {
|
|
45
|
+
if (fs.existsSync(LOG_FILE)) {
|
|
46
|
+
fs.writeFileSync(LOG_FILE, '')
|
|
47
|
+
}
|
|
48
|
+
return NextResponse.json({ ok: true })
|
|
49
|
+
} catch (err: any) {
|
|
50
|
+
return NextResponse.json({ error: err.message }, { status: 500 })
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parseLine(line: string) {
|
|
55
|
+
// Format: [2026-02-19T17:06:00.000Z] [INFO] [tag] message | data
|
|
56
|
+
const match = line.match(/^\[([^\]]+)\]\s+\[(\w+)\]\s+\[([^\]]+)\]\s+(.*)$/)
|
|
57
|
+
if (!match) return { time: '', level: 'INFO', tag: '', message: line }
|
|
58
|
+
|
|
59
|
+
const [, time, level, tag, rest] = match
|
|
60
|
+
const pipeIdx = rest.indexOf(' | ')
|
|
61
|
+
const message = pipeIdx >= 0 ? rest.slice(0, pipeIdx) : rest
|
|
62
|
+
const data = pipeIdx >= 0 ? rest.slice(pipeIdx + 3) : undefined
|
|
63
|
+
|
|
64
|
+
return { time, level, tag, message, data }
|
|
65
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadMcpServers, saveMcpServers, deleteMcpServer } from '@/lib/server/storage'
|
|
3
|
+
|
|
4
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
5
|
+
const { id } = await params
|
|
6
|
+
const servers = loadMcpServers()
|
|
7
|
+
if (!servers[id]) return new NextResponse(null, { status: 404 })
|
|
8
|
+
return NextResponse.json(servers[id])
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
12
|
+
const { id } = await params
|
|
13
|
+
const body = await req.json()
|
|
14
|
+
const servers = loadMcpServers()
|
|
15
|
+
if (!servers[id]) return new NextResponse(null, { status: 404 })
|
|
16
|
+
servers[id] = {
|
|
17
|
+
...servers[id],
|
|
18
|
+
...body,
|
|
19
|
+
id,
|
|
20
|
+
updatedAt: Date.now(),
|
|
21
|
+
}
|
|
22
|
+
saveMcpServers(servers)
|
|
23
|
+
return NextResponse.json(servers[id])
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
27
|
+
const { id } = await params
|
|
28
|
+
const servers = loadMcpServers()
|
|
29
|
+
if (!servers[id]) return new NextResponse(null, { status: 404 })
|
|
30
|
+
deleteMcpServer(id)
|
|
31
|
+
return NextResponse.json({ deleted: id })
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadMcpServers } from '@/lib/server/storage'
|
|
3
|
+
import { connectMcpServer, mcpToolsToLangChain, disconnectMcpServer } from '@/lib/server/mcp-client'
|
|
4
|
+
|
|
5
|
+
export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
|
+
const { id } = await params
|
|
7
|
+
const servers = loadMcpServers()
|
|
8
|
+
const server = servers[id]
|
|
9
|
+
if (!server) return new NextResponse(null, { status: 404 })
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const { client, transport } = await connectMcpServer(server)
|
|
13
|
+
const tools = await mcpToolsToLangChain(client, server.name)
|
|
14
|
+
const toolNames = tools.map((t: any) => t.name)
|
|
15
|
+
await disconnectMcpServer(client, transport)
|
|
16
|
+
return NextResponse.json({ ok: true, tools: toolNames })
|
|
17
|
+
} catch (err: any) {
|
|
18
|
+
return NextResponse.json(
|
|
19
|
+
{ ok: false, error: err.message || 'Connection failed' },
|
|
20
|
+
{ status: 500 }
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadMcpServers } from '@/lib/server/storage'
|
|
3
|
+
import { connectMcpServer, disconnectMcpServer } from '@/lib/server/mcp-client'
|
|
4
|
+
|
|
5
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
|
+
const { id } = await params
|
|
7
|
+
const servers = loadMcpServers()
|
|
8
|
+
const config = servers[id]
|
|
9
|
+
if (!config) return new NextResponse(null, { status: 404 })
|
|
10
|
+
|
|
11
|
+
let client: any
|
|
12
|
+
let transport: any
|
|
13
|
+
try {
|
|
14
|
+
const conn = await connectMcpServer(config)
|
|
15
|
+
client = conn.client
|
|
16
|
+
transport = conn.transport
|
|
17
|
+
const { tools } = await client.listTools()
|
|
18
|
+
return NextResponse.json(
|
|
19
|
+
tools.map((t: any) => ({
|
|
20
|
+
name: t.name,
|
|
21
|
+
description: t.description ?? '',
|
|
22
|
+
inputSchema: t.inputSchema ?? {},
|
|
23
|
+
}))
|
|
24
|
+
)
|
|
25
|
+
} catch (err: any) {
|
|
26
|
+
return NextResponse.json({ error: err.message }, { status: 502 })
|
|
27
|
+
} finally {
|
|
28
|
+
if (client && transport) {
|
|
29
|
+
await disconnectMcpServer(client, transport)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import crypto from 'crypto'
|
|
3
|
+
import { loadMcpServers, saveMcpServers } from '@/lib/server/storage'
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
return NextResponse.json(loadMcpServers())
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function POST(req: Request) {
|
|
10
|
+
const body = await req.json()
|
|
11
|
+
const servers = loadMcpServers()
|
|
12
|
+
const id = crypto.randomBytes(4).toString('hex')
|
|
13
|
+
servers[id] = {
|
|
14
|
+
id,
|
|
15
|
+
name: body.name,
|
|
16
|
+
transport: body.transport,
|
|
17
|
+
command: body.command,
|
|
18
|
+
args: body.args,
|
|
19
|
+
url: body.url,
|
|
20
|
+
env: body.env,
|
|
21
|
+
headers: body.headers,
|
|
22
|
+
createdAt: Date.now(),
|
|
23
|
+
updatedAt: Date.now(),
|
|
24
|
+
}
|
|
25
|
+
saveMcpServers(servers)
|
|
26
|
+
return NextResponse.json(servers[id])
|
|
27
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import { NextResponse } from 'next/server'
|
|
4
|
+
import { getMemoryDb, getMemoryLookupLimits, storeMemoryImageAsset, storeMemoryImageFromDataUrl } from '@/lib/server/memory-db'
|
|
5
|
+
import { resolveLookupRequest } from '@/lib/server/memory-graph'
|
|
6
|
+
import type { MemoryImage } from '@/types'
|
|
7
|
+
|
|
8
|
+
function parseOptionalInt(raw: string | null): number | undefined {
|
|
9
|
+
if (!raw) return undefined
|
|
10
|
+
const parsed = Number.parseInt(raw, 10)
|
|
11
|
+
return Number.isFinite(parsed) ? parsed : undefined
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function parseJsonBody(req: Request): Promise<Record<string, unknown> | null> {
|
|
15
|
+
const body = await req.json().catch(() => null)
|
|
16
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) return null
|
|
17
|
+
return body as Record<string, unknown>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseTargetIds(value: unknown): string[] {
|
|
21
|
+
if (!Array.isArray(value)) return []
|
|
22
|
+
return value
|
|
23
|
+
.filter((item): item is string => typeof item === 'string')
|
|
24
|
+
.map((item) => item.trim())
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
29
|
+
const { id } = await params
|
|
30
|
+
const { searchParams } = new URL(req.url)
|
|
31
|
+
const envelope = searchParams.get('envelope') === 'true'
|
|
32
|
+
const requestedDepth = parseOptionalInt(searchParams.get('depth'))
|
|
33
|
+
const requestedLimit = parseOptionalInt(searchParams.get('limit'))
|
|
34
|
+
const requestedLinkedLimit = parseOptionalInt(searchParams.get('linkedLimit'))
|
|
35
|
+
const db = getMemoryDb()
|
|
36
|
+
const defaults = getMemoryLookupLimits()
|
|
37
|
+
const limits = resolveLookupRequest(defaults, {
|
|
38
|
+
depth: requestedDepth,
|
|
39
|
+
limit: requestedLimit,
|
|
40
|
+
linkedLimit: requestedLinkedLimit,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
if (limits.maxDepth <= 0) {
|
|
44
|
+
const entry = db.get(id)
|
|
45
|
+
if (!entry) return new NextResponse(null, { status: 404 })
|
|
46
|
+
if (envelope) {
|
|
47
|
+
return NextResponse.json({
|
|
48
|
+
entries: [entry],
|
|
49
|
+
truncated: false,
|
|
50
|
+
expandedLinkedCount: 0,
|
|
51
|
+
limits,
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
return NextResponse.json(entry)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const result = db.getWithLinked(id, limits.maxDepth, limits.maxPerLookup, limits.maxLinkedExpansion)
|
|
58
|
+
if (!result) return new NextResponse(null, { status: 404 })
|
|
59
|
+
if (envelope) return NextResponse.json(result)
|
|
60
|
+
return NextResponse.json(result.entries)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
64
|
+
const { id } = await params
|
|
65
|
+
const body = await parseJsonBody(req)
|
|
66
|
+
if (!body) {
|
|
67
|
+
return NextResponse.json({ error: 'Invalid JSON body.' }, { status: 400 })
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const db = getMemoryDb()
|
|
71
|
+
const linkAction = typeof body.linkAction === 'string' ? body.linkAction.trim().toLowerCase() : ''
|
|
72
|
+
|
|
73
|
+
if (linkAction === 'link' || linkAction === 'unlink') {
|
|
74
|
+
const targetIds = parseTargetIds(body.targetIds)
|
|
75
|
+
if (!targetIds.length) {
|
|
76
|
+
return NextResponse.json({ error: 'targetIds is required for linkAction.' }, { status: 400 })
|
|
77
|
+
}
|
|
78
|
+
const updated = linkAction === 'link'
|
|
79
|
+
? db.link(id, targetIds, true)
|
|
80
|
+
: db.unlink(id, targetIds, true)
|
|
81
|
+
if (!updated) return new NextResponse(null, { status: 404 })
|
|
82
|
+
return NextResponse.json(updated)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let image = body.image
|
|
86
|
+
const inputImagePath = typeof body.imagePath === 'string' ? body.imagePath.trim() : ''
|
|
87
|
+
const inputImageDataUrl = typeof body.imageDataUrl === 'string' ? body.imageDataUrl.trim() : ''
|
|
88
|
+
const clearImage = body.clearImage === true || body.image === null
|
|
89
|
+
if (clearImage) {
|
|
90
|
+
image = null
|
|
91
|
+
} else if (inputImageDataUrl) {
|
|
92
|
+
try {
|
|
93
|
+
image = await storeMemoryImageFromDataUrl(inputImageDataUrl, `${id}-${crypto.randomBytes(2).toString('hex')}`)
|
|
94
|
+
} catch (err) {
|
|
95
|
+
return NextResponse.json({ error: err instanceof Error ? err.message : 'Invalid image data URL' }, { status: 400 })
|
|
96
|
+
}
|
|
97
|
+
} else if (inputImagePath) {
|
|
98
|
+
if (!fs.existsSync(inputImagePath)) {
|
|
99
|
+
return NextResponse.json({ error: `Image file not found: ${inputImagePath}` }, { status: 400 })
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
image = await storeMemoryImageAsset(inputImagePath, `${id}-${crypto.randomBytes(2).toString('hex')}`)
|
|
103
|
+
} catch (err) {
|
|
104
|
+
return NextResponse.json({ error: err instanceof Error ? err.message : 'Failed to store memory image' }, { status: 400 })
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const entry = db.update(id, {
|
|
109
|
+
...body,
|
|
110
|
+
image: image as MemoryImage | null | undefined,
|
|
111
|
+
imagePath: clearImage
|
|
112
|
+
? null
|
|
113
|
+
: image && typeof image === 'object' && 'path' in image
|
|
114
|
+
? String((image as { path: string }).path)
|
|
115
|
+
: (typeof body.imagePath === 'string' ? body.imagePath : undefined),
|
|
116
|
+
})
|
|
117
|
+
if (!entry) return new NextResponse(null, { status: 404 })
|
|
118
|
+
return NextResponse.json(entry)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
122
|
+
const { id } = await params
|
|
123
|
+
const db = getMemoryDb()
|
|
124
|
+
db.delete(id)
|
|
125
|
+
return NextResponse.json('ok')
|
|
126
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getMemoryDb } from '@/lib/server/memory-db'
|
|
3
|
+
import { loadSettings } from '@/lib/server/storage'
|
|
4
|
+
|
|
5
|
+
function parseBool(value: unknown, fallback: boolean): boolean {
|
|
6
|
+
if (typeof value === 'boolean') return value
|
|
7
|
+
if (typeof value === 'string') {
|
|
8
|
+
const v = value.trim().toLowerCase()
|
|
9
|
+
if (v === 'true' || v === '1' || v === 'yes') return true
|
|
10
|
+
if (v === 'false' || v === '0' || v === 'no') return false
|
|
11
|
+
}
|
|
12
|
+
return fallback
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseIntBounded(value: unknown, fallback: number, min: number, max: number): number {
|
|
16
|
+
const parsed = typeof value === 'number'
|
|
17
|
+
? value
|
|
18
|
+
: typeof value === 'string'
|
|
19
|
+
? Number.parseInt(value, 10)
|
|
20
|
+
: Number.NaN
|
|
21
|
+
if (!Number.isFinite(parsed)) return fallback
|
|
22
|
+
return Math.max(min, Math.min(max, Math.trunc(parsed)))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function GET(req: Request) {
|
|
26
|
+
const db = getMemoryDb()
|
|
27
|
+
const settings = loadSettings()
|
|
28
|
+
const { searchParams } = new URL(req.url)
|
|
29
|
+
const ttlHours = parseIntBounded(
|
|
30
|
+
searchParams.get('ttlHours') ?? settings.memoryWorkingTtlHours,
|
|
31
|
+
24,
|
|
32
|
+
1,
|
|
33
|
+
24 * 365,
|
|
34
|
+
)
|
|
35
|
+
const analyzed = db.analyzeMaintenance(ttlHours)
|
|
36
|
+
return NextResponse.json({
|
|
37
|
+
ok: true,
|
|
38
|
+
ttlHours,
|
|
39
|
+
analyzed,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function POST(req: Request) {
|
|
44
|
+
const body = await req.json().catch(() => ({}))
|
|
45
|
+
const settings = loadSettings()
|
|
46
|
+
const db = getMemoryDb()
|
|
47
|
+
const ttlHours = parseIntBounded(body?.ttlHours ?? settings.memoryWorkingTtlHours, 24, 1, 24 * 365)
|
|
48
|
+
const maxDeletes = parseIntBounded(body?.maxDeletes, 500, 1, 20_000)
|
|
49
|
+
const result = db.maintain({
|
|
50
|
+
ttlHours,
|
|
51
|
+
maxDeletes,
|
|
52
|
+
dedupe: parseBool(body?.dedupe, true),
|
|
53
|
+
canonicalDedupe: parseBool(body?.canonicalDedupe, false),
|
|
54
|
+
pruneWorking: parseBool(body?.pruneWorking, true),
|
|
55
|
+
})
|
|
56
|
+
return NextResponse.json({
|
|
57
|
+
ok: true,
|
|
58
|
+
ttlHours,
|
|
59
|
+
maxDeletes,
|
|
60
|
+
...result,
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import { NextResponse } from 'next/server'
|
|
4
|
+
import { getMemoryDb, getMemoryLookupLimits, storeMemoryImageAsset, storeMemoryImageFromDataUrl } from '@/lib/server/memory-db'
|
|
5
|
+
import { resolveLookupRequest } from '@/lib/server/memory-graph'
|
|
6
|
+
import type { MemoryReference, FileReference, MemoryImage } from '@/types'
|
|
7
|
+
|
|
8
|
+
function parseOptionalInt(raw: string | null): number | undefined {
|
|
9
|
+
if (!raw) return undefined
|
|
10
|
+
const parsed = Number.parseInt(raw, 10)
|
|
11
|
+
return Number.isFinite(parsed) ? parsed : undefined
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function parseJsonBody(req: Request): Promise<Record<string, unknown> | null> {
|
|
15
|
+
const body = await req.json().catch(() => null)
|
|
16
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) return null
|
|
17
|
+
return body as Record<string, unknown>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function GET(req: Request) {
|
|
21
|
+
const { searchParams } = new URL(req.url)
|
|
22
|
+
const q = searchParams.get('q')
|
|
23
|
+
const agentId = searchParams.get('agentId')
|
|
24
|
+
const envelope = searchParams.get('envelope') === 'true'
|
|
25
|
+
const requestedDepth = parseOptionalInt(searchParams.get('depth'))
|
|
26
|
+
const requestedLimit = parseOptionalInt(searchParams.get('limit'))
|
|
27
|
+
const requestedLinkedLimit = parseOptionalInt(searchParams.get('linkedLimit'))
|
|
28
|
+
|
|
29
|
+
const db = getMemoryDb()
|
|
30
|
+
const defaults = getMemoryLookupLimits()
|
|
31
|
+
const limits = resolveLookupRequest(defaults, {
|
|
32
|
+
depth: requestedDepth,
|
|
33
|
+
limit: requestedLimit,
|
|
34
|
+
linkedLimit: requestedLinkedLimit,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
if (q) {
|
|
38
|
+
if (limits.maxDepth > 0) {
|
|
39
|
+
const result = db.searchWithLinked(q, agentId || undefined, limits.maxDepth, limits.maxPerLookup, limits.maxLinkedExpansion)
|
|
40
|
+
if (envelope) return NextResponse.json(result)
|
|
41
|
+
return NextResponse.json(result.entries)
|
|
42
|
+
}
|
|
43
|
+
const base = db.search(q, agentId || undefined)
|
|
44
|
+
const entries = base.slice(0, limits.maxPerLookup)
|
|
45
|
+
if (envelope) {
|
|
46
|
+
return NextResponse.json({
|
|
47
|
+
entries,
|
|
48
|
+
truncated: base.length > entries.length,
|
|
49
|
+
expandedLinkedCount: 0,
|
|
50
|
+
limits,
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
return NextResponse.json(entries)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const entries = db.list(agentId || undefined, limits.maxPerLookup)
|
|
57
|
+
if (envelope) {
|
|
58
|
+
return NextResponse.json({
|
|
59
|
+
entries,
|
|
60
|
+
truncated: false,
|
|
61
|
+
expandedLinkedCount: 0,
|
|
62
|
+
limits,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
return NextResponse.json(entries)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function POST(req: Request) {
|
|
69
|
+
const body = await parseJsonBody(req)
|
|
70
|
+
if (!body) {
|
|
71
|
+
return NextResponse.json({ error: 'Invalid JSON body.' }, { status: 400 })
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const db = getMemoryDb()
|
|
75
|
+
const draftId = crypto.randomBytes(6).toString('hex')
|
|
76
|
+
|
|
77
|
+
let image = body.image
|
|
78
|
+
const inputImagePath = typeof body.imagePath === 'string' ? body.imagePath.trim() : ''
|
|
79
|
+
const inputImageDataUrl = typeof body.imageDataUrl === 'string' ? body.imageDataUrl.trim() : ''
|
|
80
|
+
if (inputImageDataUrl) {
|
|
81
|
+
try {
|
|
82
|
+
image = await storeMemoryImageFromDataUrl(inputImageDataUrl, draftId)
|
|
83
|
+
} catch (err) {
|
|
84
|
+
return NextResponse.json({ error: err instanceof Error ? err.message : 'Invalid image data URL' }, { status: 400 })
|
|
85
|
+
}
|
|
86
|
+
} else if (inputImagePath) {
|
|
87
|
+
if (!fs.existsSync(inputImagePath)) {
|
|
88
|
+
return NextResponse.json({ error: `Image file not found: ${inputImagePath}` }, { status: 400 })
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
image = await storeMemoryImageAsset(inputImagePath, draftId)
|
|
92
|
+
} catch (err) {
|
|
93
|
+
return NextResponse.json({ error: err instanceof Error ? err.message : 'Failed to store memory image' }, { status: 400 })
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const entry = db.add({
|
|
98
|
+
agentId: typeof body.agentId === 'string' ? body.agentId : null,
|
|
99
|
+
sessionId: typeof body.sessionId === 'string' ? body.sessionId : null,
|
|
100
|
+
category: typeof body.category === 'string' && body.category.trim() ? body.category : 'note',
|
|
101
|
+
title: typeof body.title === 'string' && body.title.trim() ? body.title : 'Untitled',
|
|
102
|
+
content: typeof body.content === 'string' ? body.content : '',
|
|
103
|
+
metadata: body.metadata as Record<string, unknown> | undefined,
|
|
104
|
+
references: body.references as MemoryReference[] | undefined,
|
|
105
|
+
filePaths: body.filePaths as FileReference[] | undefined,
|
|
106
|
+
image: image as MemoryImage | null | undefined,
|
|
107
|
+
imagePath: image && typeof image === 'object' && 'path' in image ? String((image as { path: string }).path) : null,
|
|
108
|
+
linkedMemoryIds: body.linkedMemoryIds as string[] | undefined,
|
|
109
|
+
})
|
|
110
|
+
return NextResponse.json(entry)
|
|
111
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
const IMAGES_DIR = path.join(process.cwd(), 'data', 'memory-images')
|
|
6
|
+
|
|
7
|
+
const MIME_TYPES: Record<string, string> = {
|
|
8
|
+
'.png': 'image/png',
|
|
9
|
+
'.jpg': 'image/jpeg',
|
|
10
|
+
'.jpeg': 'image/jpeg',
|
|
11
|
+
'.gif': 'image/gif',
|
|
12
|
+
'.webp': 'image/webp',
|
|
13
|
+
'.bmp': 'image/bmp',
|
|
14
|
+
'.tiff': 'image/tiff',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ filename: string }> }) {
|
|
18
|
+
const { filename } = await params
|
|
19
|
+
const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, '')
|
|
20
|
+
const filePath = path.join(IMAGES_DIR, safeName)
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(filePath)) {
|
|
23
|
+
return new NextResponse(null, { status: 404 })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ext = path.extname(safeName).toLowerCase()
|
|
27
|
+
const contentType = MIME_TYPES[ext] || 'application/octet-stream'
|
|
28
|
+
const data = fs.readFileSync(filePath)
|
|
29
|
+
|
|
30
|
+
return new NextResponse(data, {
|
|
31
|
+
headers: {
|
|
32
|
+
'Content-Type': contentType,
|
|
33
|
+
'Cache-Control': 'public, max-age=86400',
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
}
|