@lovelybunch/api 1.0.35 → 1.0.38

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.
@@ -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 (!message) {
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: message },
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 aiResponse = data.choices?.[0]?.message?.content;
90
- if (!aiResponse) {
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
- return c.json({ response: aiResponse });
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,5 +1,6 @@
1
1
  import { Hono } from 'hono';
2
- import { GET } from './route.js';
2
+ import { GET, PUT } from './route.js';
3
3
  const config = new Hono();
4
4
  config.get('/', GET);
5
+ config.put('/', PUT);
5
6
  export default config;
@@ -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
- try {
12
- const configPath = await getConfigPath();
13
- if (!configPath) {
14
- return c.json({ error: 'GAIT directory not found' }, 404);
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
- catch (error) {
24
- if (error.code === 'ENOENT') {
25
- return c.json({ error: 'Config file not found' }, 404);
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
- return c.json({ error: 'Failed to read config' }, 500);
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
  }