@dubeyvishal/orbital-cli 1.6.13 → 1.6.14

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