@swarmclawai/swarmclaw 0.3.0 → 0.4.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 +20 -11
- package/bin/server-cmd.js +14 -7
- package/bin/swarmclaw.js +3 -1
- package/bin/update-cmd.js +120 -0
- package/next.config.ts +2 -0
- package/package.json +3 -1
- package/src/app/api/agents/[id]/route.ts +3 -0
- package/src/app/api/agents/[id]/thread/route.ts +2 -1
- package/src/app/api/agents/route.ts +5 -1
- package/src/app/api/auth/route.ts +3 -1
- package/src/app/api/claude-skills/route.ts +3 -1
- package/src/app/api/connectors/[id]/route.ts +4 -0
- package/src/app/api/connectors/route.ts +6 -1
- package/src/app/api/credentials/route.ts +3 -1
- package/src/app/api/daemon/route.ts +6 -1
- package/src/app/api/ip/route.ts +3 -1
- package/src/app/api/mcp-servers/route.ts +3 -1
- package/src/app/api/orchestrator/graph/route.ts +25 -0
- package/src/app/api/plugins/marketplace/route.ts +3 -1
- package/src/app/api/plugins/route.ts +3 -1
- package/src/app/api/providers/[id]/route.ts +3 -0
- package/src/app/api/providers/configs/route.ts +3 -1
- package/src/app/api/providers/route.ts +5 -1
- package/src/app/api/schedules/[id]/route.ts +3 -0
- package/src/app/api/schedules/route.ts +6 -1
- package/src/app/api/secrets/route.ts +3 -1
- package/src/app/api/sessions/[id]/chat/route.ts +5 -2
- package/src/app/api/sessions/route.ts +9 -2
- package/src/app/api/settings/route.ts +3 -1
- package/src/app/api/setup/doctor/route.ts +1 -0
- package/src/app/api/setup/openclaw-device/route.ts +3 -1
- package/src/app/api/skills/route.ts +3 -1
- package/src/app/api/tasks/[id]/approve/route.ts +73 -0
- package/src/app/api/tasks/[id]/route.ts +3 -0
- package/src/app/api/tasks/route.ts +3 -0
- package/src/app/api/usage/route.ts +3 -1
- package/src/app/api/version/route.ts +3 -1
- package/src/app/api/webhooks/[id]/route.ts +2 -1
- package/src/app/api/webhooks/route.ts +3 -1
- package/src/app/icon.svg +58 -0
- package/src/app/page.tsx +8 -2
- package/src/cli/index.js +1 -9
- package/src/cli/index.ts +51 -1
- package/src/cli/spec.js +0 -8
- package/src/components/agents/agent-card.tsx +1 -1
- package/src/components/agents/agent-sheet.tsx +63 -80
- package/src/components/chat/chat-area.tsx +44 -30
- package/src/components/chat/chat-tool-toggles.tsx +12 -53
- package/src/components/chat/message-bubble.tsx +110 -42
- package/src/components/chat/tool-call-bubble.tsx +41 -3
- package/src/components/chat/tool-request-banner.tsx +1 -9
- package/src/components/connectors/connector-list.tsx +3 -8
- package/src/components/connectors/connector-sheet.tsx +24 -29
- package/src/components/input/chat-input.tsx +72 -56
- package/src/components/knowledge/knowledge-list.tsx +27 -31
- package/src/components/layout/app-layout.tsx +92 -71
- package/src/components/layout/daemon-indicator.tsx +3 -5
- package/src/components/logs/log-list.tsx +5 -9
- package/src/components/mcp-servers/mcp-server-list.tsx +24 -2
- package/src/components/memory/memory-detail.tsx +1 -1
- package/src/components/plugins/plugin-list.tsx +227 -27
- package/src/components/providers/provider-list.tsx +46 -13
- package/src/components/providers/provider-sheet.tsx +0 -45
- package/src/components/runs/run-list.tsx +6 -15
- package/src/components/schedules/schedule-card.tsx +54 -4
- package/src/components/schedules/schedule-list.tsx +6 -3
- package/src/components/schedules/schedule-sheet.tsx +0 -47
- package/src/components/secrets/secrets-list.tsx +20 -2
- package/src/components/sessions/new-session-sheet.tsx +8 -9
- package/src/components/shared/connector-platform-icon.tsx +22 -20
- package/src/components/shared/model-combobox.tsx +148 -0
- package/src/components/shared/settings/section-heartbeat.tsx +7 -39
- package/src/components/shared/settings/section-orchestrator.tsx +8 -9
- package/src/components/skills/skill-list.tsx +260 -34
- package/src/components/skills/skill-sheet.tsx +0 -45
- package/src/components/tasks/task-board.tsx +3 -6
- package/src/components/tasks/task-card.tsx +43 -1
- package/src/components/tasks/task-list.tsx +3 -5
- package/src/components/tasks/task-sheet.tsx +0 -44
- package/src/components/usage/usage-list.tsx +12 -4
- package/src/hooks/use-ws.ts +66 -0
- package/src/instrumentation.ts +2 -0
- package/src/lib/chat.ts +14 -2
- package/src/lib/providers/anthropic.ts +1 -1
- package/src/lib/providers/index.ts +2 -0
- package/src/lib/providers/ollama.ts +1 -1
- package/src/lib/providers/openai.ts +33 -12
- package/src/lib/server/chat-execution.ts +19 -4
- package/src/lib/server/connectors/manager.ts +9 -3
- package/src/lib/server/context-manager.ts +1 -1
- package/src/lib/server/daemon-state.ts +3 -0
- package/src/lib/server/data-dir.ts +1 -0
- package/src/lib/server/heartbeat-service.ts +67 -3
- package/src/lib/server/langgraph-checkpoint.ts +274 -0
- package/src/lib/server/main-agent-loop.ts +61 -2
- package/src/lib/server/orchestrator-lg.ts +394 -13
- package/src/lib/server/orchestrator.ts +25 -5
- package/src/lib/server/queue.ts +17 -3
- package/src/lib/server/session-run-manager.ts +6 -1
- package/src/lib/server/session-tools/delegate.ts +2 -2
- package/src/lib/server/session-tools/index.ts +2 -0
- package/src/lib/server/session-tools/sandbox.ts +164 -0
- package/src/lib/server/storage-mcp.test.ts +25 -2
- package/src/lib/server/storage.ts +24 -7
- package/src/lib/server/stream-agent-chat.ts +77 -22
- package/src/lib/server/task-validation.test.ts +23 -0
- package/src/lib/server/task-validation.ts +5 -3
- package/src/lib/server/ws-hub.ts +85 -0
- package/src/lib/tool-definitions.ts +42 -0
- package/src/lib/upload.ts +7 -1
- package/src/lib/ws-client.ts +124 -0
- package/src/stores/use-chat-store.ts +33 -13
- package/src/types/index.ts +8 -1
- package/src/app/api/agents/generate/route.ts +0 -42
- package/src/app/api/generate/info/route.ts +0 -12
- package/src/app/api/generate/route.ts +0 -106
- package/src/app/favicon.ico +0 -0
- package/src/components/shared/ai-gen-block.tsx +0 -77
package/src/lib/upload.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { UploadResult } from '../types'
|
|
2
|
+
import { getStoredAccessKey } from './api-client'
|
|
2
3
|
|
|
3
4
|
export async function uploadImage(file: File): Promise<UploadResult> {
|
|
5
|
+
const key = getStoredAccessKey()
|
|
4
6
|
const res = await fetch('/api/upload', {
|
|
5
7
|
method: 'POST',
|
|
6
|
-
headers: {
|
|
8
|
+
headers: {
|
|
9
|
+
'X-Filename': file.name,
|
|
10
|
+
...(key ? { 'X-Access-Key': key } : {}),
|
|
11
|
+
},
|
|
7
12
|
body: file,
|
|
8
13
|
})
|
|
14
|
+
if (!res.ok) throw new Error(`Upload failed (${res.status})`)
|
|
9
15
|
return res.json()
|
|
10
16
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
type WsCallback = () => void
|
|
2
|
+
|
|
3
|
+
let ws: WebSocket | null = null
|
|
4
|
+
let accessKey = ''
|
|
5
|
+
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
|
6
|
+
let reconnectDelay = 1000
|
|
7
|
+
const MAX_RECONNECT_DELAY = 30_000
|
|
8
|
+
const listeners = new Map<string, Set<WsCallback>>()
|
|
9
|
+
let connected = false
|
|
10
|
+
|
|
11
|
+
function getWsUrl(key: string): string {
|
|
12
|
+
const host = typeof window !== 'undefined' ? window.location.hostname : 'localhost'
|
|
13
|
+
const port = process.env.NEXT_PUBLIC_WS_PORT || '3457'
|
|
14
|
+
const protocol = typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'wss' : 'ws'
|
|
15
|
+
return `${protocol}://${host}:${port}/ws?key=${encodeURIComponent(key)}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function handleMessage(event: MessageEvent) {
|
|
19
|
+
try {
|
|
20
|
+
const msg = JSON.parse(event.data)
|
|
21
|
+
const topic = msg.topic as string
|
|
22
|
+
if (!topic) return
|
|
23
|
+
const cbs = listeners.get(topic)
|
|
24
|
+
if (cbs) {
|
|
25
|
+
for (const cb of cbs) cb()
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
// ignore malformed
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function scheduleReconnect() {
|
|
33
|
+
if (reconnectTimer) return
|
|
34
|
+
reconnectTimer = setTimeout(() => {
|
|
35
|
+
reconnectTimer = null
|
|
36
|
+
if (!accessKey) return
|
|
37
|
+
connect(accessKey)
|
|
38
|
+
}, reconnectDelay)
|
|
39
|
+
reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function connect(key: string) {
|
|
43
|
+
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
ws = new WebSocket(getWsUrl(key))
|
|
47
|
+
} catch {
|
|
48
|
+
scheduleReconnect()
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
ws.onopen = () => {
|
|
53
|
+
connected = true
|
|
54
|
+
reconnectDelay = 1000
|
|
55
|
+
// Subscribe to all currently registered topics
|
|
56
|
+
const topics = Array.from(listeners.keys())
|
|
57
|
+
if (topics.length > 0) {
|
|
58
|
+
ws?.send(JSON.stringify({ type: 'subscribe', topics }))
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
ws.onmessage = handleMessage
|
|
63
|
+
|
|
64
|
+
ws.onclose = () => {
|
|
65
|
+
connected = false
|
|
66
|
+
ws = null
|
|
67
|
+
if (accessKey) scheduleReconnect()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
ws.onerror = () => {
|
|
71
|
+
// onclose will fire after this
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function connectWs(key: string) {
|
|
76
|
+
accessKey = key
|
|
77
|
+
reconnectDelay = 1000
|
|
78
|
+
connect(key)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function disconnectWs() {
|
|
82
|
+
accessKey = ''
|
|
83
|
+
if (reconnectTimer) {
|
|
84
|
+
clearTimeout(reconnectTimer)
|
|
85
|
+
reconnectTimer = null
|
|
86
|
+
}
|
|
87
|
+
if (ws) {
|
|
88
|
+
ws.onclose = null
|
|
89
|
+
ws.close()
|
|
90
|
+
ws = null
|
|
91
|
+
}
|
|
92
|
+
connected = false
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function subscribeWs(topic: string, callback: WsCallback) {
|
|
96
|
+
let set = listeners.get(topic)
|
|
97
|
+
const isNew = !set
|
|
98
|
+
if (!set) {
|
|
99
|
+
set = new Set()
|
|
100
|
+
listeners.set(topic, set)
|
|
101
|
+
}
|
|
102
|
+
set.add(callback)
|
|
103
|
+
|
|
104
|
+
// Tell server about new topic subscription
|
|
105
|
+
if (isNew && ws?.readyState === WebSocket.OPEN) {
|
|
106
|
+
ws.send(JSON.stringify({ type: 'subscribe', topics: [topic] }))
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function unsubscribeWs(topic: string, callback: WsCallback) {
|
|
111
|
+
const set = listeners.get(topic)
|
|
112
|
+
if (!set) return
|
|
113
|
+
set.delete(callback)
|
|
114
|
+
if (set.size === 0) {
|
|
115
|
+
listeners.delete(topic)
|
|
116
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
117
|
+
ws.send(JSON.stringify({ type: 'unsubscribe', topics: [topic] }))
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function isWsConnected(): boolean {
|
|
123
|
+
return connected
|
|
124
|
+
}
|
|
@@ -7,7 +7,7 @@ import { speak } from '../lib/tts'
|
|
|
7
7
|
import { getStoredAccessKey } from '../lib/api-client'
|
|
8
8
|
import { useAppStore } from './use-app-store'
|
|
9
9
|
|
|
10
|
-
interface
|
|
10
|
+
export interface PendingFile {
|
|
11
11
|
file: File
|
|
12
12
|
path: string
|
|
13
13
|
url: string
|
|
@@ -44,8 +44,15 @@ interface ChatState {
|
|
|
44
44
|
ttsEnabled: boolean
|
|
45
45
|
toggleTts: () => void
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
// Multi-file attachment support
|
|
48
|
+
pendingFiles: PendingFile[]
|
|
49
|
+
addPendingFile: (f: PendingFile) => void
|
|
50
|
+
removePendingFile: (index: number) => void
|
|
51
|
+
clearPendingFiles: () => void
|
|
52
|
+
|
|
53
|
+
// Legacy single-image compat (reads first pendingFile)
|
|
54
|
+
pendingImage: PendingFile | null
|
|
55
|
+
setPendingImage: (img: PendingFile | null) => void
|
|
49
56
|
|
|
50
57
|
devServer: DevServerStatus | null
|
|
51
58
|
setDevServer: (ds: DevServerStatus | null) => void
|
|
@@ -70,21 +77,34 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
70
77
|
lastUsage: null,
|
|
71
78
|
ttsEnabled: false,
|
|
72
79
|
toggleTts: () => set((s) => ({ ttsEnabled: !s.ttsEnabled })),
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
|
|
81
|
+
pendingFiles: [],
|
|
82
|
+
addPendingFile: (f) => set((s) => ({ pendingFiles: [...s.pendingFiles, f] })),
|
|
83
|
+
removePendingFile: (index) => set((s) => ({ pendingFiles: s.pendingFiles.filter((_, i) => i !== index) })),
|
|
84
|
+
clearPendingFiles: () => set({ pendingFiles: [] }),
|
|
85
|
+
|
|
86
|
+
// Legacy compat: pendingImage reads/writes the first pending file
|
|
87
|
+
get pendingImage() { const files = get().pendingFiles; return files.length ? files[0] : null },
|
|
88
|
+
setPendingImage: (img) => set({ pendingFiles: img ? [img] : [] }),
|
|
89
|
+
|
|
75
90
|
devServer: null,
|
|
76
91
|
setDevServer: (ds) => set({ devServer: ds }),
|
|
77
92
|
debugOpen: false,
|
|
78
93
|
setDebugOpen: (open) => set({ debugOpen: open }),
|
|
79
94
|
|
|
80
95
|
sendMessage: async (text: string) => {
|
|
81
|
-
|
|
96
|
+
const { pendingFiles } = get()
|
|
97
|
+
if ((!text.trim() && !pendingFiles.length) || get().streaming) return
|
|
82
98
|
const sessionId = useAppStore.getState().currentSessionId
|
|
83
99
|
if (!sessionId) return
|
|
84
100
|
|
|
85
|
-
|
|
86
|
-
const imagePath =
|
|
87
|
-
const imageUrl =
|
|
101
|
+
// Primary image (backward compat)
|
|
102
|
+
const imagePath = pendingFiles[0]?.path
|
|
103
|
+
const imageUrl = pendingFiles[0]?.url
|
|
104
|
+
// All attached file paths
|
|
105
|
+
const attachedFiles = pendingFiles.length > 1
|
|
106
|
+
? pendingFiles.map((f) => f.path)
|
|
107
|
+
: undefined
|
|
88
108
|
|
|
89
109
|
const userMsg: Message = {
|
|
90
110
|
role: 'user',
|
|
@@ -92,13 +112,14 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
92
112
|
time: Date.now(),
|
|
93
113
|
imagePath,
|
|
94
114
|
imageUrl,
|
|
115
|
+
attachedFiles,
|
|
95
116
|
}
|
|
96
117
|
set((s) => ({
|
|
97
118
|
streaming: true,
|
|
98
119
|
streamingSessionId: sessionId,
|
|
99
120
|
streamText: '',
|
|
100
121
|
messages: [...s.messages, userMsg],
|
|
101
|
-
|
|
122
|
+
pendingFiles: [],
|
|
102
123
|
toolEvents: [],
|
|
103
124
|
lastUsage: null,
|
|
104
125
|
}))
|
|
@@ -166,7 +187,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
166
187
|
} else if (event.t === 'done') {
|
|
167
188
|
// done
|
|
168
189
|
}
|
|
169
|
-
})
|
|
190
|
+
}, attachedFiles)
|
|
170
191
|
|
|
171
192
|
if (fullText.trim()) {
|
|
172
193
|
const currentToolEvents = get().toolEvents
|
|
@@ -218,9 +239,8 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
218
239
|
set({ messages: msgs })
|
|
219
240
|
}
|
|
220
241
|
// Re-send the last user message through the normal SSE flow
|
|
221
|
-
// Temporarily set pendingImage if there was one
|
|
222
242
|
if (imagePath) {
|
|
223
|
-
set({
|
|
243
|
+
set({ pendingFiles: [{ file: new File([], ''), path: imagePath, url: '' }] })
|
|
224
244
|
}
|
|
225
245
|
await get().sendMessage(message)
|
|
226
246
|
} catch {
|
package/src/types/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface Message {
|
|
|
11
11
|
time: number
|
|
12
12
|
imagePath?: string
|
|
13
13
|
imageUrl?: string
|
|
14
|
+
attachedFiles?: string[]
|
|
14
15
|
toolEvents?: MessageToolEvent[]
|
|
15
16
|
kind?: 'chat' | 'heartbeat' | 'system'
|
|
16
17
|
suppressed?: boolean
|
|
@@ -22,6 +23,7 @@ export interface ProviderInfo {
|
|
|
22
23
|
id: ProviderType
|
|
23
24
|
name: string
|
|
24
25
|
models: string[]
|
|
26
|
+
defaultModels?: string[]
|
|
25
27
|
requiresApiKey: boolean
|
|
26
28
|
optionalApiKey?: boolean
|
|
27
29
|
requiresEndpoint: boolean
|
|
@@ -321,7 +323,7 @@ export interface MemoryEntry {
|
|
|
321
323
|
}
|
|
322
324
|
|
|
323
325
|
export type SessionType = 'human' | 'orchestrated'
|
|
324
|
-
export type AppView = '
|
|
326
|
+
export type AppView = 'agents' | 'schedules' | 'memory' | 'tasks' | 'secrets' | 'providers' | 'skills' | 'connectors' | 'webhooks' | 'mcp_servers' | 'knowledge' | 'plugins' | 'usage' | 'runs' | 'logs'
|
|
325
327
|
|
|
326
328
|
// --- Session Runs ---
|
|
327
329
|
|
|
@@ -588,6 +590,11 @@ export interface BoardTask {
|
|
|
588
590
|
reasons: string[]
|
|
589
591
|
checkedAt: number
|
|
590
592
|
} | null
|
|
593
|
+
pendingApproval?: {
|
|
594
|
+
toolName: string
|
|
595
|
+
args: Record<string, unknown>
|
|
596
|
+
threadId: string
|
|
597
|
+
} | null
|
|
591
598
|
}
|
|
592
599
|
|
|
593
600
|
// --- MCP Servers ---
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server'
|
|
2
|
-
import { z } from 'zod'
|
|
3
|
-
import { buildLLM } from '@/lib/server/build-llm'
|
|
4
|
-
|
|
5
|
-
const agentSchema = z.object({
|
|
6
|
-
name: z.string().describe('Short name for the agent'),
|
|
7
|
-
description: z.string().describe('One sentence describing what it does'),
|
|
8
|
-
systemPrompt: z.string().describe('Full system prompt — thorough and specific, at least 3-4 paragraphs'),
|
|
9
|
-
isOrchestrator: z.boolean().describe('True only if it needs to coordinate multiple sub-agents'),
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
const GENERATE_PROMPT = `You are a agent generator. The user will describe an AI agent they want to create. Generate a complete agent definition.
|
|
13
|
-
|
|
14
|
-
Set isOrchestrator to true ONLY if the user describes something that needs to coordinate multiple sub-agents.
|
|
15
|
-
Make the systemPrompt detailed and actionable — at least 3-4 paragraphs. Include specific instructions about how the agent should behave, what it should focus on, and how it should format its responses.`
|
|
16
|
-
|
|
17
|
-
export async function POST(req: Request) {
|
|
18
|
-
const { prompt } = await req.json()
|
|
19
|
-
if (!prompt?.trim()) {
|
|
20
|
-
return NextResponse.json({ error: 'prompt is required' }, { status: 400 })
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const { llm, provider } = await buildLLM()
|
|
25
|
-
const structured = provider === 'anthropic'
|
|
26
|
-
? llm.withStructuredOutput(agentSchema)
|
|
27
|
-
: (llm as any).withStructuredOutput(agentSchema, {
|
|
28
|
-
name: 'agent_definition',
|
|
29
|
-
method: 'functionCalling',
|
|
30
|
-
})
|
|
31
|
-
const result = await structured.invoke([
|
|
32
|
-
{ role: 'system' as const, content: GENERATE_PROMPT },
|
|
33
|
-
{ role: 'user' as const, content: prompt },
|
|
34
|
-
], { signal: AbortSignal.timeout(60_000) })
|
|
35
|
-
|
|
36
|
-
return NextResponse.json(result)
|
|
37
|
-
} catch (err: unknown) {
|
|
38
|
-
const message = err instanceof Error ? err.message : 'Generation failed'
|
|
39
|
-
console.error('[agent-generate] Error:', message)
|
|
40
|
-
return NextResponse.json({ error: message }, { status: 500 })
|
|
41
|
-
}
|
|
42
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server'
|
|
2
|
-
import { buildLLM } from '@/lib/server/build-llm'
|
|
3
|
-
|
|
4
|
-
/** Returns which provider/model the generate endpoints will use */
|
|
5
|
-
export async function GET() {
|
|
6
|
-
try {
|
|
7
|
-
const { provider, model } = await buildLLM()
|
|
8
|
-
return NextResponse.json({ provider, model })
|
|
9
|
-
} catch (err: any) {
|
|
10
|
-
return NextResponse.json({ provider: 'none', model: '', error: err.message })
|
|
11
|
-
}
|
|
12
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server'
|
|
2
|
-
import { z } from 'zod'
|
|
3
|
-
import { buildLLM } from '@/lib/server/build-llm'
|
|
4
|
-
|
|
5
|
-
const scheduleSchema = z.object({
|
|
6
|
-
name: z.string().describe('Short descriptive name for the schedule'),
|
|
7
|
-
taskPrompt: z.string().describe('The prompt/instructions the agent should execute when the schedule fires'),
|
|
8
|
-
scheduleType: z.enum(['cron', 'interval', 'once']).describe('Type of schedule'),
|
|
9
|
-
cron: z.string().optional().describe('Cron expression if scheduleType is cron, e.g. "0 9 * * 1" for every Monday at 9am'),
|
|
10
|
-
intervalMs: z.number().optional().describe('Interval in milliseconds if scheduleType is interval'),
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
const taskSchema = z.object({
|
|
14
|
-
title: z.string().describe('Short descriptive title for the task'),
|
|
15
|
-
description: z.string().describe('Detailed description of what needs to be done'),
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
const skillSchema = z.object({
|
|
19
|
-
name: z.string().describe('Short name for the skill'),
|
|
20
|
-
description: z.string().describe('One sentence describing what this skill does'),
|
|
21
|
-
content: z.string().describe('Full markdown content of the skill — detailed instructions, at least 3-4 paragraphs'),
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
const providerSchema = z.object({
|
|
25
|
-
name: z.string().describe('Display name for the provider, e.g. "Together AI", "Groq", "z.ai"'),
|
|
26
|
-
baseUrl: z.string().describe('The OpenAI-compatible API base URL, e.g. "https://api.together.xyz/v1" or "https://api.groq.com/openai/v1"'),
|
|
27
|
-
models: z.string().describe('Comma-separated list of available model IDs, e.g. "llama-3-70b,mixtral-8x7b"'),
|
|
28
|
-
requiresApiKey: z.boolean().describe('Whether this provider requires an API key'),
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
const SCHEMAS: Record<string, { schema: z.ZodObject<any>; prompt: string }> = {
|
|
32
|
-
schedule: {
|
|
33
|
-
schema: scheduleSchema,
|
|
34
|
-
prompt: `You are a schedule generator for SwarmClaw, an AI agent orchestration platform. The user will describe what they want scheduled. Generate a complete schedule definition.
|
|
35
|
-
|
|
36
|
-
Choose the appropriate scheduleType:
|
|
37
|
-
- "cron" for recurring schedules (provide a cron expression)
|
|
38
|
-
- "interval" for periodic execution (provide intervalMs in milliseconds)
|
|
39
|
-
- "once" for one-time execution
|
|
40
|
-
|
|
41
|
-
Make the taskPrompt detailed and specific — it should contain everything the agent needs to know to complete the task.`,
|
|
42
|
-
},
|
|
43
|
-
task: {
|
|
44
|
-
schema: taskSchema,
|
|
45
|
-
prompt: `You are a task generator for SwarmClaw, an AI agent orchestration platform. The user will describe a task they want to create. Generate a clear task definition.
|
|
46
|
-
|
|
47
|
-
Make the description thorough and actionable — include specific goals, acceptance criteria, and any relevant context. The description should give an AI agent enough information to complete the task autonomously.`,
|
|
48
|
-
},
|
|
49
|
-
skill: {
|
|
50
|
-
schema: skillSchema,
|
|
51
|
-
prompt: `You are a skill generator for SwarmClaw, an AI agent orchestration platform. Skills are reusable markdown instruction sets that get injected into agent system prompts.
|
|
52
|
-
|
|
53
|
-
The user will describe what skill they want. Generate a comprehensive skill definition with detailed markdown content. The content should be:
|
|
54
|
-
- Written as instructions/guidelines for an AI agent
|
|
55
|
-
- Thorough and specific (at least 3-4 paragraphs)
|
|
56
|
-
- Structured with markdown headings, lists, and examples
|
|
57
|
-
- Focused on actionable guidance the agent can follow`,
|
|
58
|
-
},
|
|
59
|
-
provider: {
|
|
60
|
-
schema: providerSchema,
|
|
61
|
-
prompt: `You are a provider configuration generator for SwarmClaw, an AI agent orchestration platform. The user will name an LLM provider they want to add.
|
|
62
|
-
|
|
63
|
-
Generate the correct OpenAI-compatible API configuration. You should know the base URLs and model names for popular providers:
|
|
64
|
-
- Together AI: https://api.together.xyz/v1
|
|
65
|
-
- Groq: https://api.groq.com/openai/v1
|
|
66
|
-
- Fireworks: https://api.fireworks.ai/inference/v1
|
|
67
|
-
- Perplexity: https://api.perplexity.ai
|
|
68
|
-
- Mistral: https://api.mistral.ai/v1
|
|
69
|
-
- DeepSeek: https://api.deepseek.com/v1
|
|
70
|
-
- OpenRouter: https://openrouter.ai/api/v1
|
|
71
|
-
- xAI/Grok: https://api.x.ai/v1
|
|
72
|
-
- z.ai: https://api.z.ai/v1
|
|
73
|
-
|
|
74
|
-
List the most popular/recommended models for the provider as comma-separated values. Most providers require an API key.
|
|
75
|
-
If you don't know the exact URL, make your best guess based on common patterns (provider domain + /v1).`,
|
|
76
|
-
},
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export async function POST(req: Request) {
|
|
80
|
-
const { type, prompt } = await req.json()
|
|
81
|
-
if (!prompt?.trim()) {
|
|
82
|
-
return NextResponse.json({ error: 'prompt is required' }, { status: 400 })
|
|
83
|
-
}
|
|
84
|
-
const config = SCHEMAS[type]
|
|
85
|
-
if (!config) {
|
|
86
|
-
return NextResponse.json({ error: `Invalid type: ${type}. Must be one of: ${Object.keys(SCHEMAS).join(', ')}` }, { status: 400 })
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const { llm, provider } = await buildLLM()
|
|
91
|
-
const structured = provider === 'anthropic'
|
|
92
|
-
? llm.withStructuredOutput(config.schema)
|
|
93
|
-
: (llm as any).withStructuredOutput(config.schema, {
|
|
94
|
-
name: `${type}_definition`,
|
|
95
|
-
method: 'functionCalling',
|
|
96
|
-
})
|
|
97
|
-
const result = await structured.invoke([
|
|
98
|
-
{ role: 'system' as const, content: config.prompt },
|
|
99
|
-
{ role: 'user' as const, content: prompt },
|
|
100
|
-
], { signal: AbortSignal.timeout(60_000) })
|
|
101
|
-
return NextResponse.json(result)
|
|
102
|
-
} catch (err: unknown) {
|
|
103
|
-
const message = err instanceof Error ? err.message : 'Generation failed'
|
|
104
|
-
return NextResponse.json({ error: message }, { status: 500 })
|
|
105
|
-
}
|
|
106
|
-
}
|
package/src/app/favicon.ico
DELETED
|
Binary file
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect } from 'react'
|
|
4
|
-
import { api } from '@/lib/api-client'
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
aiPrompt: string
|
|
8
|
-
setAiPrompt: (v: string) => void
|
|
9
|
-
generating: boolean
|
|
10
|
-
generated: boolean
|
|
11
|
-
genError: string
|
|
12
|
-
onGenerate: () => void
|
|
13
|
-
appSettings?: Record<string, any>
|
|
14
|
-
placeholder: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function AiGenBlock({ aiPrompt, setAiPrompt, generating, generated, genError, onGenerate, placeholder }: Props) {
|
|
18
|
-
const [expanded, setExpanded] = useState(false)
|
|
19
|
-
const [genInfo, setGenInfo] = useState<{ provider: string; model: string } | null>(null)
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
if (expanded && !genInfo) {
|
|
23
|
-
api<{ provider: string; model: string }>('GET', '/generate/info')
|
|
24
|
-
.then(setGenInfo)
|
|
25
|
-
.catch((err) => console.error('AI gen info fetch failed:', err))
|
|
26
|
-
}
|
|
27
|
-
}, [expanded])
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<div className="mb-10">
|
|
31
|
-
<button
|
|
32
|
-
onClick={() => setExpanded(!expanded)}
|
|
33
|
-
className="flex items-center gap-2.5 px-4 py-3 rounded-[14px] border border-[#6366F1]/15 bg-[#6366F1]/[0.03] hover:bg-[#6366F1]/[0.06] transition-all cursor-pointer w-full text-left"
|
|
34
|
-
style={{ fontFamily: 'inherit' }}
|
|
35
|
-
>
|
|
36
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" className="text-accent-bright shrink-0">
|
|
37
|
-
<path d="M12 2L14.5 9.5L22 12L14.5 14.5L12 22L9.5 14.5L2 12L9.5 9.5L12 2Z" fill="currentColor" />
|
|
38
|
-
</svg>
|
|
39
|
-
<span className="font-display text-[13px] font-600 text-accent-bright flex-1">Generate with AI</span>
|
|
40
|
-
<svg
|
|
41
|
-
width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"
|
|
42
|
-
className={`text-accent-bright/50 transition-transform duration-200 ${expanded ? 'rotate-180' : ''}`}
|
|
43
|
-
>
|
|
44
|
-
<polyline points="6 9 12 15 18 9" />
|
|
45
|
-
</svg>
|
|
46
|
-
</button>
|
|
47
|
-
|
|
48
|
-
{expanded && (
|
|
49
|
-
<div className="mt-3 p-5 rounded-[18px] border border-[#6366F1]/15 bg-[#6366F1]/[0.03]"
|
|
50
|
-
style={{ animation: 'fade-in 0.2s ease' }}>
|
|
51
|
-
<textarea
|
|
52
|
-
value={aiPrompt}
|
|
53
|
-
onChange={(e) => setAiPrompt(e.target.value)}
|
|
54
|
-
placeholder={placeholder}
|
|
55
|
-
rows={2}
|
|
56
|
-
className="w-full px-4 py-3 rounded-[12px] border border-[#6366F1]/10 bg-[#6366F1]/[0.02] text-text text-[14px] outline-none transition-all duration-200 placeholder:text-text-3/70 focus:border-[#6366F1]/30 resize-none"
|
|
57
|
-
style={{ fontFamily: 'inherit' }}
|
|
58
|
-
autoFocus
|
|
59
|
-
/>
|
|
60
|
-
<button
|
|
61
|
-
onClick={onGenerate}
|
|
62
|
-
disabled={generating || !aiPrompt.trim()}
|
|
63
|
-
className="mt-3 px-5 py-2.5 rounded-[12px] border-none bg-[#6366F1] text-white text-[13px] font-600 cursor-pointer disabled:opacity-30 transition-all hover:brightness-110 active:scale-[0.97] shadow-[0_2px_12px_rgba(99,102,241,0.2)]"
|
|
64
|
-
style={{ fontFamily: 'inherit' }}
|
|
65
|
-
>
|
|
66
|
-
{generating ? 'Generating...' : generated ? 'Regenerate' : 'Generate'}
|
|
67
|
-
</button>
|
|
68
|
-
{generated && <span className="ml-3 text-[12px] text-emerald-400/70">Fields populated — edit below</span>}
|
|
69
|
-
{genError && <p className="mt-2 text-[12px] text-red-400/80">{genError}</p>}
|
|
70
|
-
<p className="mt-3 text-[11px] text-text-3/50">
|
|
71
|
-
Using {genInfo ? `${genInfo.model} via ${genInfo.provider}` : 'auto-detected provider'}
|
|
72
|
-
</p>
|
|
73
|
-
</div>
|
|
74
|
-
)}
|
|
75
|
-
</div>
|
|
76
|
-
)
|
|
77
|
-
}
|