@lovelybunch/api 1.0.34 → 1.0.37
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/dist/routes/api/v1/ai/route.js +121 -12
- package/dist/routes/api/v1/config/index.js +2 -1
- package/dist/routes/api/v1/config/route.d.ts +13 -3
- package/dist/routes/api/v1/config/route.js +114 -14
- package/dist/routes/api/v1/context/architecture/route.js +0 -3
- package/dist/routes/api/v1/context/project/route.js +0 -3
- package/dist/routes/api/v1/mcp/index.js +307 -11
- package/dist/routes/api/v1/mcp/route.d.ts +3 -0
- package/dist/routes/api/v1/mcp/route.js +5 -0
- package/dist/routes/api/v1/proposals/[id]/route.d.ts +2 -0
- package/dist/routes/api/v1/proposals/route.d.ts +2 -0
- package/package.json +4 -3
- package/static/assets/index-Bqsw-cOu.js +640 -0
- package/static/assets/index-D4cXZATc.js +719 -0
- package/static/assets/index-DJ49lCi9.css +33 -0
- package/static/index.html +2 -2
|
@@ -2,6 +2,7 @@ import { homedir } from 'os';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync, readFileSync } from 'fs';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
|
+
import { proposalsTool, listProposalsTool } from '@lovelybunch/mcp';
|
|
5
6
|
// Function to get global config API key as fallback
|
|
6
7
|
function getGlobalApiKey(provider) {
|
|
7
8
|
try {
|
|
@@ -30,8 +31,8 @@ function getGlobalApiKey(provider) {
|
|
|
30
31
|
}
|
|
31
32
|
export async function POST(c) {
|
|
32
33
|
try {
|
|
33
|
-
const { message, history, model, context, contextContent, agentPersona, systemPrompt: systemOverride, maxTokens } = await c.req.json();
|
|
34
|
-
if (!
|
|
34
|
+
const { message: userMessage, history, model, context, contextContent, agentPersona, systemPrompt: systemOverride, maxTokens, enableTools } = await c.req.json();
|
|
35
|
+
if (!userMessage) {
|
|
35
36
|
return c.json({ error: "Message is required" }, 400);
|
|
36
37
|
}
|
|
37
38
|
// Try environment variable first, then global config
|
|
@@ -49,6 +50,17 @@ export async function POST(c) {
|
|
|
49
50
|
const systemPrompt = agentPersona
|
|
50
51
|
? `${baseSystem}\n\nThe following persona is authoritative and overrides general guidance above. You must strictly follow it.\n\n${agentPersona}`
|
|
51
52
|
: baseSystem;
|
|
53
|
+
// Prepare tools for function calling
|
|
54
|
+
const tools = enableTools ? [
|
|
55
|
+
{
|
|
56
|
+
type: "function",
|
|
57
|
+
function: listProposalsTool
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: "function",
|
|
61
|
+
function: proposalsTool
|
|
62
|
+
}
|
|
63
|
+
] : null;
|
|
52
64
|
// Compose the message list: system + history (preferred) or single message
|
|
53
65
|
let messagesPayload;
|
|
54
66
|
if (Array.isArray(history) && history.length > 0) {
|
|
@@ -62,9 +74,27 @@ export async function POST(c) {
|
|
|
62
74
|
else {
|
|
63
75
|
messagesPayload = [
|
|
64
76
|
{ role: 'system', content: systemPrompt },
|
|
65
|
-
{ role: 'user', content:
|
|
77
|
+
{ role: 'user', content: userMessage },
|
|
66
78
|
];
|
|
67
79
|
}
|
|
80
|
+
const requestBody = {
|
|
81
|
+
model: model || "openai/gpt-4o",
|
|
82
|
+
messages: messagesPayload,
|
|
83
|
+
temperature: 0.7,
|
|
84
|
+
max_tokens: typeof maxTokens === 'number' ? Math.max(256, Math.min(4000, Math.floor(maxTokens))) : 2000,
|
|
85
|
+
};
|
|
86
|
+
// Add tools if enabled
|
|
87
|
+
if (tools) {
|
|
88
|
+
requestBody.tools = tools;
|
|
89
|
+
requestBody.tool_choice = "auto";
|
|
90
|
+
}
|
|
91
|
+
// Debug logging
|
|
92
|
+
console.log('AI Request Debug:', {
|
|
93
|
+
enableTools,
|
|
94
|
+
tools: tools ? tools.length : 'null',
|
|
95
|
+
hasToolChoice: !!requestBody.tool_choice,
|
|
96
|
+
model: requestBody.model
|
|
97
|
+
});
|
|
68
98
|
const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
69
99
|
method: "POST",
|
|
70
100
|
headers: {
|
|
@@ -73,12 +103,7 @@ export async function POST(c) {
|
|
|
73
103
|
"HTTP-Referer": process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3001",
|
|
74
104
|
"X-Title": "Coconut AI Assistant",
|
|
75
105
|
},
|
|
76
|
-
body: JSON.stringify(
|
|
77
|
-
model: model || "anthropic/claude-3-sonnet",
|
|
78
|
-
messages: messagesPayload,
|
|
79
|
-
temperature: 0.7,
|
|
80
|
-
max_tokens: typeof maxTokens === 'number' ? Math.max(256, Math.min(4000, Math.floor(maxTokens))) : 2000,
|
|
81
|
-
}),
|
|
106
|
+
body: JSON.stringify(requestBody),
|
|
82
107
|
});
|
|
83
108
|
if (!response.ok) {
|
|
84
109
|
const error = await response.text();
|
|
@@ -86,17 +111,101 @@ export async function POST(c) {
|
|
|
86
111
|
return c.json({ error: "Failed to get AI response" }, 500);
|
|
87
112
|
}
|
|
88
113
|
const data = await response.json();
|
|
89
|
-
const
|
|
90
|
-
if (!
|
|
114
|
+
const aiMessage = data.choices?.[0]?.message;
|
|
115
|
+
if (!aiMessage) {
|
|
91
116
|
return c.json({ error: "No response from AI model" }, 500);
|
|
92
117
|
}
|
|
93
|
-
|
|
118
|
+
// Handle tool calls
|
|
119
|
+
if (aiMessage.tool_calls && aiMessage.tool_calls.length > 0) {
|
|
120
|
+
const toolResults = await executeToolCalls(aiMessage.tool_calls);
|
|
121
|
+
// Add tool results to the conversation and get final response
|
|
122
|
+
const messagesWithTools = [
|
|
123
|
+
...messagesPayload,
|
|
124
|
+
aiMessage,
|
|
125
|
+
...toolResults.map(result => ({
|
|
126
|
+
role: 'tool',
|
|
127
|
+
content: result.content,
|
|
128
|
+
tool_call_id: result.tool_call_id
|
|
129
|
+
}))
|
|
130
|
+
];
|
|
131
|
+
// Get final response from AI with tool results
|
|
132
|
+
const finalResponse = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: {
|
|
135
|
+
"Authorization": `Bearer ${openRouterKey}`,
|
|
136
|
+
"Content-Type": "application/json",
|
|
137
|
+
"HTTP-Referer": process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3001",
|
|
138
|
+
"X-Title": "Coconut AI Assistant",
|
|
139
|
+
},
|
|
140
|
+
body: JSON.stringify({
|
|
141
|
+
model: model || "openai/gpt-4o",
|
|
142
|
+
messages: messagesWithTools,
|
|
143
|
+
temperature: 0.7,
|
|
144
|
+
max_tokens: typeof maxTokens === 'number' ? Math.max(256, Math.min(4000, Math.floor(maxTokens))) : 2000,
|
|
145
|
+
}),
|
|
146
|
+
});
|
|
147
|
+
if (!finalResponse.ok) {
|
|
148
|
+
const error = await finalResponse.text();
|
|
149
|
+
console.error("OpenRouter API error for final response:", error);
|
|
150
|
+
return c.json({ error: "Failed to get final AI response" }, 500);
|
|
151
|
+
}
|
|
152
|
+
const finalData = await finalResponse.json();
|
|
153
|
+
const finalMessage = finalData.choices?.[0]?.message;
|
|
154
|
+
return c.json({
|
|
155
|
+
response: finalMessage?.content || "Tool execution completed",
|
|
156
|
+
toolCalls: aiMessage.tool_calls,
|
|
157
|
+
toolResults: toolResults
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return c.json({ response: aiMessage.content });
|
|
94
161
|
}
|
|
95
162
|
catch (error) {
|
|
96
163
|
console.error("AI API error:", error);
|
|
97
164
|
return c.json({ error: "Internal server error" }, 500);
|
|
98
165
|
}
|
|
99
166
|
}
|
|
167
|
+
async function executeToolCalls(toolCalls) {
|
|
168
|
+
const results = [];
|
|
169
|
+
for (const toolCall of toolCalls) {
|
|
170
|
+
try {
|
|
171
|
+
// Handle both function calling formats
|
|
172
|
+
const functionName = toolCall.function?.name || toolCall.name;
|
|
173
|
+
const functionArgs = toolCall.function?.arguments ?
|
|
174
|
+
JSON.parse(toolCall.function.arguments) :
|
|
175
|
+
toolCall.arguments;
|
|
176
|
+
const response = await fetch(`${process.env.API_BASE_URL || 'http://localhost:3001'}/api/v1/mcp/execute`, {
|
|
177
|
+
method: 'POST',
|
|
178
|
+
headers: {
|
|
179
|
+
'Content-Type': 'application/json',
|
|
180
|
+
},
|
|
181
|
+
body: JSON.stringify({
|
|
182
|
+
tool: functionName,
|
|
183
|
+
arguments: functionArgs
|
|
184
|
+
})
|
|
185
|
+
});
|
|
186
|
+
const result = await response.json();
|
|
187
|
+
results.push({
|
|
188
|
+
tool_call_id: toolCall.id,
|
|
189
|
+
content: JSON.stringify({
|
|
190
|
+
success: result.success,
|
|
191
|
+
data: result.data,
|
|
192
|
+
message: result.message,
|
|
193
|
+
error: result.error
|
|
194
|
+
})
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
results.push({
|
|
199
|
+
tool_call_id: toolCall.id,
|
|
200
|
+
content: JSON.stringify({
|
|
201
|
+
success: false,
|
|
202
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
203
|
+
})
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return results;
|
|
208
|
+
}
|
|
100
209
|
function readSharedPrompt(name) {
|
|
101
210
|
// Try multiple candidate paths depending on cwd when running via Turbo
|
|
102
211
|
const moduleDir = fileURLToPath(new URL('.', import.meta.url));
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { Context } from 'hono';
|
|
2
2
|
export declare function GET(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
3
|
-
error: string;
|
|
4
|
-
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
5
3
|
success: true;
|
|
6
4
|
data: any;
|
|
7
5
|
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
8
6
|
error: string;
|
|
9
|
-
}, 500, "json">)
|
|
7
|
+
}, 500, "json">) | (Response & import("hono").TypedResponse<{
|
|
8
|
+
error: string;
|
|
9
|
+
}, 404, "json">)>;
|
|
10
|
+
export declare function PUT(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
11
|
+
error: string;
|
|
12
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
13
|
+
success: true;
|
|
14
|
+
message: string;
|
|
15
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
16
|
+
error: string;
|
|
17
|
+
}, 500, "json">) | (Response & import("hono").TypedResponse<{
|
|
18
|
+
error: string;
|
|
19
|
+
}, 405, "json">)>;
|
|
@@ -1,29 +1,129 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { findGaitDirectory } from '../../../../lib/gait-path.js';
|
|
4
|
+
import { homedir } from 'os';
|
|
4
5
|
async function getConfigPath() {
|
|
5
6
|
const gaitDir = await findGaitDirectory();
|
|
6
7
|
if (!gaitDir)
|
|
7
8
|
return null;
|
|
8
9
|
return path.join(gaitDir, 'config.json');
|
|
9
10
|
}
|
|
11
|
+
// Get global config directory (same logic as ConfigManager)
|
|
12
|
+
function getGlobalConfigDir() {
|
|
13
|
+
const platform = process.platform;
|
|
14
|
+
if (platform === 'win32') {
|
|
15
|
+
return path.join(process.env.APPDATA || homedir(), 'coconuts');
|
|
16
|
+
}
|
|
17
|
+
else if (platform === 'darwin') {
|
|
18
|
+
return path.join(homedir(), 'Library', 'Application Support', 'coconuts');
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
// Linux/Unix
|
|
22
|
+
return path.join(process.env.XDG_CONFIG_HOME || path.join(homedir(), '.config'), 'coconuts');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function getGlobalConfigPath() {
|
|
26
|
+
const configDir = getGlobalConfigDir();
|
|
27
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
28
|
+
return path.join(configDir, 'config.json');
|
|
29
|
+
}
|
|
10
30
|
export async function GET(c) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
31
|
+
const url = new URL(c.req.url);
|
|
32
|
+
const type = url.searchParams.get('type');
|
|
33
|
+
if (type === 'global') {
|
|
34
|
+
// Return global CLI config (API keys)
|
|
35
|
+
try {
|
|
36
|
+
const configPath = await getGlobalConfigPath();
|
|
37
|
+
let config;
|
|
38
|
+
try {
|
|
39
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
40
|
+
config = JSON.parse(content);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error.code === 'ENOENT') {
|
|
44
|
+
config = { apiKeys: {}, defaults: {} };
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return c.json({
|
|
51
|
+
success: true,
|
|
52
|
+
data: config
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
return c.json({ error: 'Failed to read global config' }, 500);
|
|
15
57
|
}
|
|
16
|
-
const content = await fs.readFile(configPath, 'utf-8');
|
|
17
|
-
const config = JSON.parse(content);
|
|
18
|
-
return c.json({
|
|
19
|
-
success: true,
|
|
20
|
-
data: config
|
|
21
|
-
});
|
|
22
58
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
59
|
+
else {
|
|
60
|
+
// Original GAIT project config logic
|
|
61
|
+
try {
|
|
62
|
+
const configPath = await getConfigPath();
|
|
63
|
+
if (!configPath) {
|
|
64
|
+
return c.json({ error: 'GAIT directory not found' }, 404);
|
|
65
|
+
}
|
|
66
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
67
|
+
const config = JSON.parse(content);
|
|
68
|
+
return c.json({
|
|
69
|
+
success: true,
|
|
70
|
+
data: config
|
|
71
|
+
});
|
|
26
72
|
}
|
|
27
|
-
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error.code === 'ENOENT') {
|
|
75
|
+
return c.json({ error: 'Config file not found' }, 404);
|
|
76
|
+
}
|
|
77
|
+
return c.json({ error: 'Failed to read config' }, 500);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export async function PUT(c) {
|
|
82
|
+
const url = new URL(c.req.url);
|
|
83
|
+
const type = url.searchParams.get('type');
|
|
84
|
+
if (type === 'global') {
|
|
85
|
+
// Update global CLI config (API keys)
|
|
86
|
+
try {
|
|
87
|
+
const body = await c.req.json();
|
|
88
|
+
const { apiKeys, defaults } = body;
|
|
89
|
+
if (!apiKeys && !defaults) {
|
|
90
|
+
return c.json({ error: 'Missing apiKeys or defaults in request body' }, 400);
|
|
91
|
+
}
|
|
92
|
+
const configPath = await getGlobalConfigPath();
|
|
93
|
+
// Load existing config
|
|
94
|
+
let config;
|
|
95
|
+
try {
|
|
96
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
97
|
+
config = JSON.parse(content);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (error.code === 'ENOENT') {
|
|
101
|
+
config = { apiKeys: {}, defaults: {} };
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Update config
|
|
108
|
+
if (apiKeys) {
|
|
109
|
+
config.apiKeys = { ...config.apiKeys, ...apiKeys };
|
|
110
|
+
}
|
|
111
|
+
if (defaults) {
|
|
112
|
+
config.defaults = { ...config.defaults, ...defaults };
|
|
113
|
+
}
|
|
114
|
+
// Save config
|
|
115
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
116
|
+
return c.json({
|
|
117
|
+
success: true,
|
|
118
|
+
message: 'Global configuration updated successfully'
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
console.error('Error updating global config:', error);
|
|
123
|
+
return c.json({ error: 'Failed to update global config' }, 500);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
return c.json({ error: 'PUT not supported for project config' }, 405);
|
|
28
128
|
}
|
|
29
129
|
}
|
|
@@ -2,11 +2,8 @@ import { Hono } from 'hono';
|
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import matter from 'gray-matter';
|
|
5
|
-
import { trimTrailingSlash } from 'hono/trailing-slash';
|
|
6
5
|
import { findGaitDirectory } from '../../../../../lib/gait-path.js';
|
|
7
6
|
const app = new Hono();
|
|
8
|
-
// Handle trailing slashes consistently
|
|
9
|
-
app.use('*', trimTrailingSlash());
|
|
10
7
|
async function getArchitecturePath() {
|
|
11
8
|
const gaitDir = await findGaitDirectory();
|
|
12
9
|
if (!gaitDir)
|
|
@@ -2,11 +2,8 @@ import { Hono } from 'hono';
|
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import matter from 'gray-matter';
|
|
5
|
-
import { trimTrailingSlash } from 'hono/trailing-slash';
|
|
6
5
|
import { findGaitDirectory } from '../../../../../lib/gait-path.js';
|
|
7
6
|
const app = new Hono();
|
|
8
|
-
// Handle trailing slashes consistently
|
|
9
|
-
app.use('*', trimTrailingSlash());
|
|
10
7
|
async function getProjectPath() {
|
|
11
8
|
const gaitDir = await findGaitDirectory();
|
|
12
9
|
if (!gaitDir)
|