@dubeyvishal/orbital-cli 1.6.14 → 1.6.16
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 +3 -3
- package/package.json +2 -4
- package/server/src/cli/ai/googleService.js +30 -80
- package/server/src/cli/chat/chat-with-ai-agent.js +20 -40
- package/server/src/cli/chat/chat-with-ai-tools.js +46 -108
- package/server/src/cli/chat/chat-with-ai.js +197 -232
- package/server/src/cli/commands/ai/wakeUp.js +78 -139
- package/server/src/cli/commands/auth/login.js +7 -1
- package/server/src/cli/commands/config/setkey.js +6 -57
- package/server/src/cli/main.js +1 -1
- package/server/src/config/googleConfig.js +1 -1
- package/server/src/config/toolConfig.js +44 -25
- package/server/src/controllers/aiController.js +6 -10
- package/server/src/lib/credentialStore.js +14 -47
- package/server/src/lib/orbitalConfig.js +38 -144
- package/server/src/service/aiService.js +2 -2
- package/server/src/config/aiConfig.js +0 -159
package/README.md
CHANGED
|
@@ -25,13 +25,13 @@ Start Orbital:
|
|
|
25
25
|
## 📸 Project Screenshots
|
|
26
26
|
|
|
27
27
|
### 🔐 Orbital Login Screen
|
|
28
|
-

|
|
29
29
|
|
|
30
30
|
### 🛠️ Orbital Wakeup – Tools Loaded
|
|
31
|
-

|
|
32
32
|
|
|
33
33
|
### 🤖 Agentic Mode
|
|
34
|
-

|
|
35
35
|
|
|
36
36
|
------------------------------------------------------------------------
|
|
37
37
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dubeyvishal/orbital-cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.16",
|
|
4
4
|
"description": "A fullstack CLI-based AI platform with chat mode, multi-tool agents, and agentic AI workflows. Includes GitHub login with device authorization, secure authentication, and modular client–server architecture for building intelligent automation tools.",
|
|
5
5
|
"author": "Vishal Dubey",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,8 +21,6 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@ai-sdk/google": "^3.0.6",
|
|
24
|
-
"@ai-sdk/openai": "^4.0.57",
|
|
25
|
-
"@ai-sdk/xai": "^4.0.54",
|
|
26
24
|
"@clack/prompts": "^0.11.0",
|
|
27
25
|
"ai": "^6.0.29",
|
|
28
26
|
"boxen": "^8.0.1",
|
|
@@ -51,4 +49,4 @@
|
|
|
51
49
|
"client-server",
|
|
52
50
|
"javascript"
|
|
53
51
|
]
|
|
54
|
-
}
|
|
52
|
+
}
|
|
@@ -1,19 +1,11 @@
|
|
|
1
|
+
import { google } from "@ai-sdk/google";
|
|
1
2
|
import { streamText, generateObject } from "ai";
|
|
2
3
|
import { config } from "../../config/googleConfig.js";
|
|
3
4
|
import chalk from "chalk";
|
|
4
|
-
import {
|
|
5
|
-
normalizeProviderName,
|
|
6
|
-
getSelectedModelSync,
|
|
7
|
-
} from "../../lib/orbitalConfig.js";
|
|
8
|
-
import {
|
|
9
|
-
AI_PROVIDERS,
|
|
10
|
-
createModelInstance,
|
|
11
|
-
getModelDisplayName,
|
|
12
|
-
parseModelChoice,
|
|
13
|
-
} from "../../config/aiConfig.js";
|
|
5
|
+
import { requireGeminiApiKeySync } from "../../lib/orbitalConfig.js";
|
|
14
6
|
|
|
15
7
|
const MAX_RETRIES = 3;
|
|
16
|
-
const BASE_DELAY_MS =
|
|
8
|
+
const BASE_DELAY_MS = 5000; // 5 seconds
|
|
17
9
|
|
|
18
10
|
const isRateLimitError = (error) => {
|
|
19
11
|
if (!error) return false;
|
|
@@ -31,30 +23,12 @@ const isRateLimitError = (error) => {
|
|
|
31
23
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
32
24
|
|
|
33
25
|
export class AIService {
|
|
34
|
-
constructor(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (modelConfig) {
|
|
38
|
-
resolved = parseModelChoice(modelConfig);
|
|
39
|
-
} else {
|
|
40
|
-
const saved = getSelectedModelSync();
|
|
41
|
-
const provider = process.env.ORBITAL_PROVIDER || saved?.provider || "gemini";
|
|
42
|
-
const model =
|
|
43
|
-
process.env.ORBITAL_MODEL ||
|
|
44
|
-
saved?.model ||
|
|
45
|
-
AI_PROVIDERS[normalizeProviderName(provider)]?.defaultModel ||
|
|
46
|
-
"gemini-2.5-flash";
|
|
47
|
-
resolved = { provider: normalizeProviderName(provider), model };
|
|
26
|
+
constructor() {
|
|
27
|
+
const apiKey = requireGeminiApiKeySync();
|
|
48
28
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.modelName = resolved.model;
|
|
53
|
-
this.model = createModelInstance(this.provider, this.modelName);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
getDisplayName() {
|
|
57
|
-
return getModelDisplayName(this.provider, this.modelName);
|
|
29
|
+
this.model = google(config.model, {
|
|
30
|
+
apiKey,
|
|
31
|
+
});
|
|
58
32
|
}
|
|
59
33
|
|
|
60
34
|
async sendMessage(messages, onChunk, tools = undefined, onToolCall = null) {
|
|
@@ -71,28 +45,22 @@ export class AIService {
|
|
|
71
45
|
if (tools && Object.keys(tools).length > 0) {
|
|
72
46
|
streamConfig.tools = tools;
|
|
73
47
|
streamConfig.maxSteps = 5;
|
|
48
|
+
if (attempt === 1) {
|
|
49
|
+
console.log(
|
|
50
|
+
chalk.gray(
|
|
51
|
+
`[DEBUG] Tools enabled: ${Object.keys(tools).join(", ")}`
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
74
55
|
}
|
|
75
56
|
|
|
76
57
|
const result = await streamText(streamConfig);
|
|
77
58
|
|
|
78
59
|
let fullResponse = "";
|
|
79
60
|
|
|
80
|
-
for await (const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
fullResponse += chunk;
|
|
84
|
-
if (onChunk) onChunk(chunk);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (!fullResponse) {
|
|
91
|
-
try {
|
|
92
|
-
fullResponse = (await result.text) || "";
|
|
93
|
-
} catch {
|
|
94
|
-
// ignore if result.text is not available
|
|
95
|
-
}
|
|
61
|
+
for await (const chunk of result.textStream) {
|
|
62
|
+
fullResponse += chunk;
|
|
63
|
+
if (onChunk) onChunk(chunk);
|
|
96
64
|
}
|
|
97
65
|
|
|
98
66
|
const toolCalls = [];
|
|
@@ -123,20 +91,6 @@ export class AIService {
|
|
|
123
91
|
}
|
|
124
92
|
}
|
|
125
93
|
|
|
126
|
-
if (!fullResponse && toolResults.length > 0) {
|
|
127
|
-
fullResponse = toolResults
|
|
128
|
-
.map((tr) => {
|
|
129
|
-
const output = tr.output ?? tr.result;
|
|
130
|
-
const resStr =
|
|
131
|
-
typeof output === "object"
|
|
132
|
-
? JSON.stringify(output)
|
|
133
|
-
: String(output);
|
|
134
|
-
return `Tool ${tr.toolName} output: ${resStr}`;
|
|
135
|
-
})
|
|
136
|
-
.join("\n");
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
94
|
return {
|
|
141
95
|
content: fullResponse,
|
|
142
96
|
finishReason: result.finishReason,
|
|
@@ -153,41 +107,36 @@ export class AIService {
|
|
|
153
107
|
const delaySec = Math.round(delayMs / 1000);
|
|
154
108
|
console.log(
|
|
155
109
|
chalk.yellow(
|
|
156
|
-
`\n⚠ Rate limit hit (429)
|
|
110
|
+
`\n⚠ Rate limit hit (429). Retrying in ${delaySec}s... (attempt ${attempt}/${MAX_RETRIES})`
|
|
157
111
|
)
|
|
158
112
|
);
|
|
159
113
|
await sleep(delayMs);
|
|
160
114
|
continue;
|
|
161
115
|
}
|
|
162
116
|
|
|
163
|
-
// Provide actionable
|
|
117
|
+
// Provide an actionable message for rate-limit errors.
|
|
164
118
|
if (isRateLimitError(error)) {
|
|
165
|
-
const provInfo = AI_PROVIDERS[this.provider];
|
|
166
119
|
console.error(
|
|
167
120
|
chalk.red(
|
|
168
|
-
|
|
121
|
+
"\n✖ Gemini API quota exhausted. All retry attempts failed."
|
|
169
122
|
)
|
|
170
123
|
);
|
|
171
124
|
console.error(
|
|
172
125
|
chalk.yellow(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
126
|
+
" Possible fixes:\n" +
|
|
127
|
+
" 1. Wait a few minutes and try again (free-tier resets per minute)\n" +
|
|
128
|
+
" 2. Check your quota: https://ai.google.dev/gemini-api/docs/rate-limits\n" +
|
|
129
|
+
" 3. Upgrade your Gemini API plan for higher limits\n" +
|
|
130
|
+
" 4. Use a different API key with available quota"
|
|
177
131
|
)
|
|
178
132
|
);
|
|
179
133
|
} else {
|
|
180
|
-
const detailedMsg =
|
|
181
|
-
error?.data?.error?.message ||
|
|
182
|
-
error?.message ||
|
|
183
|
-
error;
|
|
184
134
|
console.error(
|
|
185
|
-
chalk.red(
|
|
186
|
-
|
|
135
|
+
chalk.red("AI Service Error:"),
|
|
136
|
+
error?.message || error
|
|
187
137
|
);
|
|
188
138
|
}
|
|
189
139
|
|
|
190
|
-
|
|
191
140
|
throw error;
|
|
192
141
|
}
|
|
193
142
|
}
|
|
@@ -209,10 +158,11 @@ export class AIService {
|
|
|
209
158
|
return result.object;
|
|
210
159
|
} catch (error) {
|
|
211
160
|
console.log(
|
|
212
|
-
chalk.red(
|
|
161
|
+
chalk.red("AI Structured Generation Error:"),
|
|
213
162
|
error?.message || error
|
|
214
163
|
);
|
|
215
164
|
throw error;
|
|
216
165
|
}
|
|
217
166
|
}
|
|
218
167
|
}
|
|
168
|
+
|
|
@@ -12,12 +12,14 @@ import {
|
|
|
12
12
|
} from "../../config/agentConfig.js";
|
|
13
13
|
import { apiRequestSafe } from "../utils/apiClient.js";
|
|
14
14
|
import { AIService } from "../ai/googleService.js";
|
|
15
|
-
import {
|
|
16
|
-
import { parseModelChoice } from "../../config/aiConfig.js";
|
|
17
|
-
|
|
15
|
+
import { requireGeminiApiKey } from "../../lib/orbitalConfig.js";
|
|
18
16
|
|
|
19
17
|
marked.use(markedTerminal());
|
|
20
18
|
|
|
19
|
+
const getEnabledToolNames = () => {
|
|
20
|
+
return [];
|
|
21
|
+
};
|
|
22
|
+
|
|
21
23
|
const getUserFromToken = async () => {
|
|
22
24
|
const token = await getStoredToken();
|
|
23
25
|
|
|
@@ -42,12 +44,7 @@ const getUserFromToken = async () => {
|
|
|
42
44
|
}
|
|
43
45
|
};
|
|
44
46
|
|
|
45
|
-
const initConversation = async (
|
|
46
|
-
userId,
|
|
47
|
-
conversationId = null,
|
|
48
|
-
mode = "agent",
|
|
49
|
-
modelDisplayName = null
|
|
50
|
-
) => {
|
|
47
|
+
const initConversation = async (userId, conversationId = null, mode = "tool") => {
|
|
51
48
|
const spinner = yoctoSpinner({ text: "Loading conversation..." }).start();
|
|
52
49
|
|
|
53
50
|
const result = await apiRequestSafe("/api/cli/conversations/init", {
|
|
@@ -58,14 +55,16 @@ const initConversation = async (
|
|
|
58
55
|
|
|
59
56
|
spinner.success("Conversation Loaded");
|
|
60
57
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
const enabledToolNames = getEnabledToolNames();
|
|
59
|
+
const toolsDisplay =
|
|
60
|
+
enabledToolNames.length > 0
|
|
61
|
+
? `\n${chalk.gray("Active Tools:")} ${enabledToolNames.join(", ")}`
|
|
62
|
+
: `\n${chalk.gray("No tools enabled")}`;
|
|
64
63
|
|
|
65
64
|
const conversationInfo = boxen(
|
|
66
65
|
`${chalk.bold("Conversation")}: ${conversation.title}\n${chalk.gray(
|
|
67
66
|
"ID: " + conversation.id
|
|
68
|
-
)}\n${chalk.gray("Mode: " + conversation.mode)}${
|
|
67
|
+
)}\n${chalk.gray("Mode: " + conversation.mode)}${toolsDisplay}\n${chalk.cyan(
|
|
69
68
|
"Working Directory: "
|
|
70
69
|
)}${process.cwd()}`,
|
|
71
70
|
{
|
|
@@ -89,7 +88,7 @@ const saveMessage = async (conversationId, role, content) => {
|
|
|
89
88
|
});
|
|
90
89
|
};
|
|
91
90
|
|
|
92
|
-
const agentLoop = async (conversation
|
|
91
|
+
const agentLoop = async (conversation) => {
|
|
93
92
|
const helpBox = boxen(
|
|
94
93
|
`${chalk.cyan.bold("What can the agent do?")}\n\n` +
|
|
95
94
|
`${chalk.gray("• Generate complete applications from descriptions")}\n` +
|
|
@@ -114,8 +113,8 @@ const agentLoop = async (conversation, modelConfig = null) => {
|
|
|
114
113
|
|
|
115
114
|
while (true) {
|
|
116
115
|
const userInput = await text({
|
|
117
|
-
message: chalk.magenta("
|
|
118
|
-
placeholder: "
|
|
116
|
+
message: chalk.magenta("What would you like to build?"),
|
|
117
|
+
placeholder: "Describe your application...",
|
|
119
118
|
validate(value) {
|
|
120
119
|
if (!value || value.trim().length === 0) {
|
|
121
120
|
return "Description cannot be empty";
|
|
@@ -150,8 +149,8 @@ const agentLoop = async (conversation, modelConfig = null) => {
|
|
|
150
149
|
await saveMessage(conversation.id, "user", userInput);
|
|
151
150
|
|
|
152
151
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
await requireGeminiApiKey();
|
|
153
|
+
const aiService = new AIService();
|
|
155
154
|
const application = await generateApplicationPlan(userInput, aiService);
|
|
156
155
|
|
|
157
156
|
if (!application || !Array.isArray(application.files) || application.files.length === 0) {
|
|
@@ -210,17 +209,7 @@ const agentLoop = async (conversation, modelConfig = null) => {
|
|
|
210
209
|
}
|
|
211
210
|
};
|
|
212
211
|
|
|
213
|
-
export const startAgentChat = async (
|
|
214
|
-
let modelConfig = null;
|
|
215
|
-
let conversationId = null;
|
|
216
|
-
|
|
217
|
-
if (typeof modelConfigOrConvId === "string") {
|
|
218
|
-
conversationId = modelConfigOrConvId;
|
|
219
|
-
} else if (modelConfigOrConvId && typeof modelConfigOrConvId === "object") {
|
|
220
|
-
modelConfig = modelConfigOrConvId;
|
|
221
|
-
conversationId = convId;
|
|
222
|
-
}
|
|
223
|
-
|
|
212
|
+
export const startAgentChat = async (conversationId = null) => {
|
|
224
213
|
try {
|
|
225
214
|
intro(
|
|
226
215
|
boxen(
|
|
@@ -234,10 +223,6 @@ export const startAgentChat = async (modelConfigOrConvId = null, convId = null)
|
|
|
234
223
|
)
|
|
235
224
|
);
|
|
236
225
|
|
|
237
|
-
const { provider } = parseModelChoice(modelConfig);
|
|
238
|
-
await hydrateApiKeyEnv(provider);
|
|
239
|
-
const aiService = new AIService(modelConfig);
|
|
240
|
-
|
|
241
226
|
const user = await getUserFromToken();
|
|
242
227
|
|
|
243
228
|
const shouldContinue = await confirm({
|
|
@@ -252,13 +237,8 @@ export const startAgentChat = async (modelConfigOrConvId = null, convId = null)
|
|
|
252
237
|
process.exit(0);
|
|
253
238
|
}
|
|
254
239
|
|
|
255
|
-
const conversation = await initConversation(
|
|
256
|
-
|
|
257
|
-
conversationId,
|
|
258
|
-
"agent",
|
|
259
|
-
aiService.getDisplayName()
|
|
260
|
-
);
|
|
261
|
-
await agentLoop(conversation, modelConfig);
|
|
240
|
+
const conversation = await initConversation(user.id, conversationId);
|
|
241
|
+
await agentLoop(conversation);
|
|
262
242
|
|
|
263
243
|
outro(chalk.green.bold("\nThanks for using Agent Mode!"));
|
|
264
244
|
} catch (error) {
|
|
@@ -17,13 +17,11 @@ import {
|
|
|
17
17
|
enableTools,
|
|
18
18
|
getEnabledTools,
|
|
19
19
|
getEnabledToolNames,
|
|
20
|
-
getToolsForProvider,
|
|
21
20
|
resetTools,
|
|
22
21
|
} from "../../config/toolConfig.js";
|
|
23
22
|
import { apiRequestSafe } from "../utils/apiClient.js";
|
|
24
23
|
import { AIService } from "../ai/googleService.js";
|
|
25
|
-
import {
|
|
26
|
-
import { parseModelChoice } from "../../config/aiConfig.js";
|
|
24
|
+
import { requireGeminiApiKey } from "../../lib/orbitalConfig.js";
|
|
27
25
|
|
|
28
26
|
marked.use(
|
|
29
27
|
markedTerminal({
|
|
@@ -69,7 +67,7 @@ const getUserFromToken = async () => {
|
|
|
69
67
|
}
|
|
70
68
|
};
|
|
71
69
|
|
|
72
|
-
const selectTools = async (
|
|
70
|
+
const selectTools = async () => {
|
|
73
71
|
const truncateHint = (text, maxLen = 70) => {
|
|
74
72
|
if (!text) return "";
|
|
75
73
|
const singleLine = String(text).replace(/\s+/g, " ").trim();
|
|
@@ -77,19 +75,10 @@ const selectTools = async (provider = "gemini") => {
|
|
|
77
75
|
return singleLine.slice(0, Math.max(0, maxLen - 3)) + "...";
|
|
78
76
|
};
|
|
79
77
|
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
if (providerTools.length === 0) {
|
|
83
|
-
console.log(
|
|
84
|
-
chalk.yellow(`\nNo tools available for ${provider}. AI will work without tools.\n`)
|
|
85
|
-
);
|
|
86
|
-
enableTools([]);
|
|
87
|
-
return [];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const toolOptions = providerTools.map((tool) => ({
|
|
78
|
+
const toolOptions = availableTools.map((tool) => ({
|
|
91
79
|
value: tool.id,
|
|
92
80
|
label: tool.name,
|
|
81
|
+
|
|
93
82
|
hint: truncateHint(tool.description),
|
|
94
83
|
}));
|
|
95
84
|
|
|
@@ -115,7 +104,7 @@ const selectTools = async (provider = "gemini") => {
|
|
|
115
104
|
chalk.green(
|
|
116
105
|
`Enabled tools:\n${selectedTools
|
|
117
106
|
.map((id) => {
|
|
118
|
-
const tool =
|
|
107
|
+
const tool = availableTools.find((t) => t.id === id);
|
|
119
108
|
return tool ? ` • ${tool.name}` : ` • ${id}`;
|
|
120
109
|
})
|
|
121
110
|
.join("\n")}`
|
|
@@ -135,12 +124,7 @@ const selectTools = async (provider = "gemini") => {
|
|
|
135
124
|
return selectedTools;
|
|
136
125
|
};
|
|
137
126
|
|
|
138
|
-
const initConversation = async (
|
|
139
|
-
userId,
|
|
140
|
-
conversationId = null,
|
|
141
|
-
mode = "tool",
|
|
142
|
-
modelDisplayName = null
|
|
143
|
-
) => {
|
|
127
|
+
const initConversation = async (userId, conversationId = null, mode = "tool") => {
|
|
144
128
|
const spinner = yoctoSpinner({ text: "Loading conversation..." }).start();
|
|
145
129
|
const result = await apiRequestSafe("/api/cli/conversations/init", {
|
|
146
130
|
method: "POST",
|
|
@@ -158,14 +142,10 @@ const initConversation = async (
|
|
|
158
142
|
? `\n${chalk.gray("Active Tools:")} ${enabledToolNames.join(", ")}`
|
|
159
143
|
: `\n${chalk.gray("No tools enabled")}`;
|
|
160
144
|
|
|
161
|
-
const modelLine = modelDisplayName
|
|
162
|
-
? `\n${chalk.cyan("Model: " + modelDisplayName)}`
|
|
163
|
-
: "";
|
|
164
|
-
|
|
165
145
|
const conversationInfo = boxen(
|
|
166
146
|
`${chalk.bold("Conversation")}: ${conversation.title}\n${chalk.gray(
|
|
167
147
|
"ID: " + conversation.id
|
|
168
|
-
)}\n${chalk.gray("Mode: " + conversation.mode)}${
|
|
148
|
+
)}\n${chalk.gray("Mode: " + conversation.mode)}${toolsDisplay}`,
|
|
169
149
|
{
|
|
170
150
|
padding: 1,
|
|
171
151
|
margin: { top: 1, bottom: 1 },
|
|
@@ -230,22 +210,8 @@ const saveMessage = async (conversationId, role, content) => {
|
|
|
230
210
|
});
|
|
231
211
|
};
|
|
232
212
|
|
|
233
|
-
const getAIResponse = async (conversationId, toolIds = []
|
|
234
|
-
const
|
|
235
|
-
await requireApiKey(aiService.provider);
|
|
236
|
-
|
|
237
|
-
// Ensure tool state matches what the user selected
|
|
238
|
-
resetTools();
|
|
239
|
-
if (Array.isArray(toolIds)) enableTools(toolIds);
|
|
240
|
-
|
|
241
|
-
const tools = getEnabledTools(aiService.provider);
|
|
242
|
-
const enabledNames = getEnabledToolNames();
|
|
243
|
-
const toolBadge =
|
|
244
|
-
enabledNames.length > 0 ? chalk.dim(` [Tools: ${enabledNames.join(", ")}]`) : "";
|
|
245
|
-
|
|
246
|
-
const spinner = yoctoSpinner({
|
|
247
|
-
text: `${aiService.getDisplayName()} is thinking...${toolBadge}`,
|
|
248
|
-
}).start();
|
|
213
|
+
const getAIResponse = async (conversationId, toolIds = []) => {
|
|
214
|
+
const spinner = yoctoSpinner({ text: "AI is thinking..." }).start();
|
|
249
215
|
let fullResponse = "";
|
|
250
216
|
const toolCallsDetected = [];
|
|
251
217
|
|
|
@@ -258,16 +224,20 @@ const getAIResponse = async (conversationId, toolIds = [], modelConfig = null) =
|
|
|
258
224
|
const messages = Array.isArray(messageResult?.messages) ? messageResult.messages : [];
|
|
259
225
|
const aiMessages = messages.map((m) => ({ role: m.role, content: m.content }));
|
|
260
226
|
|
|
227
|
+
// Ensure the API key is present in env before any tools are initialized.
|
|
228
|
+
await requireGeminiApiKey();
|
|
229
|
+
|
|
230
|
+
// Ensure tool state matches what the user selected.
|
|
231
|
+
resetTools();
|
|
232
|
+
if (Array.isArray(toolIds)) enableTools(toolIds);
|
|
233
|
+
|
|
234
|
+
const tools = getEnabledTools();
|
|
235
|
+
const aiService = new AIService();
|
|
261
236
|
const result = await aiService.sendMessage(aiMessages, null, tools);
|
|
262
237
|
|
|
263
238
|
spinner.stop();
|
|
264
239
|
console.log("\n");
|
|
265
|
-
|
|
266
|
-
enabledNames.length > 0 ? chalk.dim(` • Tools: ${enabledNames.join(", ")}`) : "";
|
|
267
|
-
console.log(
|
|
268
|
-
chalk.green.bold(`Assistant (${aiService.getDisplayName()}):`) +
|
|
269
|
-
(toolsHeader ? ` ${toolsHeader}` : "")
|
|
270
|
-
);
|
|
240
|
+
console.log(chalk.green.bold("Assistant: "));
|
|
271
241
|
console.log(chalk.gray("-".repeat(60)));
|
|
272
242
|
|
|
273
243
|
fullResponse = result?.content || "";
|
|
@@ -300,20 +270,12 @@ const getAIResponse = async (conversationId, toolIds = [], modelConfig = null) =
|
|
|
300
270
|
if (result?.toolResults && result.toolResults.length > 0) {
|
|
301
271
|
const toolResultBox = boxen(
|
|
302
272
|
result.toolResults
|
|
303
|
-
.map(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
? JSON.stringify(output, null, 2)
|
|
310
|
-
: String(output);
|
|
311
|
-
const truncated =
|
|
312
|
-
resStr.length > 200 ? `${resStr.slice(0, 200)}...` : resStr;
|
|
313
|
-
return `${chalk.green("✓ Tool:")} ${tr.toolName}\n${chalk.gray(
|
|
314
|
-
"Result:"
|
|
315
|
-
)} ${truncated}`;
|
|
316
|
-
})
|
|
273
|
+
.map(
|
|
274
|
+
(tr) =>
|
|
275
|
+
`${chalk.green("✓ Tool:")} ${tr.toolName}\n${chalk.gray(
|
|
276
|
+
"Result:"
|
|
277
|
+
)} ${String(JSON.stringify(tr.result, null, 2)).slice(0, 200)}...`
|
|
278
|
+
)
|
|
317
279
|
.join("\n\n"),
|
|
318
280
|
{
|
|
319
281
|
padding: 1,
|
|
@@ -338,7 +300,7 @@ const getAIResponse = async (conversationId, toolIds = [], modelConfig = null) =
|
|
|
338
300
|
}
|
|
339
301
|
};
|
|
340
302
|
|
|
341
|
-
const chatLoop = async (conversation, selectedToolIds = []
|
|
303
|
+
const chatLoop = async (conversation, selectedToolIds = []) => {
|
|
342
304
|
const enabledToolNames = getEnabledToolNames();
|
|
343
305
|
|
|
344
306
|
const helpText = [
|
|
@@ -346,29 +308,28 @@ const chatLoop = async (conversation, selectedToolIds = [], modelConfig = null)
|
|
|
346
308
|
`• AI has access to: ${
|
|
347
309
|
enabledToolNames.length > 0 ? enabledToolNames.join(", ") : "No tools"
|
|
348
310
|
}`,
|
|
349
|
-
"• Tools are invoked automatically by AI when needed",
|
|
350
|
-
"• Markdown formatting is supported",
|
|
351
311
|
'• Type "exit" to end conversation',
|
|
352
312
|
"• Press Ctrl+C to quit anytime",
|
|
353
|
-
]
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
313
|
+
]
|
|
314
|
+
.map((line) => chalk.gray(line))
|
|
315
|
+
.join("\n");
|
|
316
|
+
|
|
317
|
+
console.log(
|
|
318
|
+
boxen(helpText, {
|
|
319
|
+
padding: 1,
|
|
320
|
+
margin: { bottom: 1 },
|
|
321
|
+
borderStyle: "round",
|
|
322
|
+
borderColor: "gray",
|
|
323
|
+
dimBorder: true,
|
|
324
|
+
})
|
|
325
|
+
);
|
|
363
326
|
|
|
364
327
|
while (true) {
|
|
365
328
|
const userInput = await text({
|
|
366
329
|
message: chalk.blue("Your message"),
|
|
367
|
-
placeholder: "
|
|
330
|
+
placeholder: "Type your message...",
|
|
368
331
|
validate(value) {
|
|
369
|
-
if (!value || value.trim().length === 0)
|
|
370
|
-
return "Message cannot be empty";
|
|
371
|
-
}
|
|
332
|
+
if (!value || value.trim().length === 0) return "Message cannot be empty";
|
|
372
333
|
},
|
|
373
334
|
});
|
|
374
335
|
|
|
@@ -413,11 +374,7 @@ const chatLoop = async (conversation, selectedToolIds = [], modelConfig = null)
|
|
|
413
374
|
{ method: "GET" }
|
|
414
375
|
);
|
|
415
376
|
|
|
416
|
-
const aiResponse = await getAIResponse(
|
|
417
|
-
conversation.id,
|
|
418
|
-
selectedToolIds,
|
|
419
|
-
modelConfig
|
|
420
|
-
);
|
|
377
|
+
const aiResponse = await getAIResponse(conversation.id, selectedToolIds);
|
|
421
378
|
await saveMessage(conversation.id, "assistant", aiResponse);
|
|
422
379
|
|
|
423
380
|
await updateConversationTitle(
|
|
@@ -428,17 +385,7 @@ const chatLoop = async (conversation, selectedToolIds = [], modelConfig = null)
|
|
|
428
385
|
}
|
|
429
386
|
};
|
|
430
387
|
|
|
431
|
-
export const startToolChat = async (
|
|
432
|
-
let modelConfig = null;
|
|
433
|
-
let conversationId = null;
|
|
434
|
-
|
|
435
|
-
if (typeof modelConfigOrConvId === "string") {
|
|
436
|
-
conversationId = modelConfigOrConvId;
|
|
437
|
-
} else if (modelConfigOrConvId && typeof modelConfigOrConvId === "object") {
|
|
438
|
-
modelConfig = modelConfigOrConvId;
|
|
439
|
-
conversationId = convId;
|
|
440
|
-
}
|
|
441
|
-
|
|
388
|
+
export const startToolChat = async (conversationId) => {
|
|
442
389
|
try {
|
|
443
390
|
intro(
|
|
444
391
|
boxen(chalk.bold.cyan("Orbital AI - Tool Calling Mode"), {
|
|
@@ -448,22 +395,13 @@ export const startToolChat = async (modelConfigOrConvId = null, convId = null) =
|
|
|
448
395
|
})
|
|
449
396
|
);
|
|
450
397
|
|
|
451
|
-
const { provider } = parseModelChoice(modelConfig);
|
|
452
|
-
await hydrateApiKeyEnv(provider);
|
|
453
|
-
const aiService = new AIService(modelConfig);
|
|
454
|
-
|
|
455
398
|
const user = await getUserFromToken();
|
|
456
399
|
|
|
457
|
-
const selectedToolIds = await selectTools(
|
|
400
|
+
const selectedToolIds = await selectTools();
|
|
458
401
|
|
|
459
|
-
const conversation = await initConversation(
|
|
460
|
-
user.id,
|
|
461
|
-
conversationId,
|
|
462
|
-
"tool",
|
|
463
|
-
aiService.getDisplayName()
|
|
464
|
-
);
|
|
402
|
+
const conversation = await initConversation(user.id, conversationId, "tool");
|
|
465
403
|
|
|
466
|
-
await chatLoop(conversation, selectedToolIds
|
|
404
|
+
await chatLoop(conversation, selectedToolIds);
|
|
467
405
|
|
|
468
406
|
resetTools();
|
|
469
407
|
outro(chalk.green("Thanks for using tools"));
|