@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.
@@ -7,7 +7,7 @@ const chatService = new ChatService();
7
7
 
8
8
  export const respond = async (req, res, next) => {
9
9
  try {
10
- const { conversationId, mode, toolIds } = req.body || {};
10
+ const { conversationId, mode, toolIds, provider, model } = req.body || {};
11
11
 
12
12
  if (!conversationId) {
13
13
  return res.status(400).json({ error: "conversationId is required" });
@@ -16,16 +16,18 @@ export const respond = async (req, res, next) => {
16
16
  const messages = await chatService.getMessages(conversationId);
17
17
  const aiMessages = chatService.formatMessageForAI(messages);
18
18
 
19
+ const modelConfig = provider || model ? { provider, model } : null;
20
+ const aiService = getAIService(modelConfig);
21
+
19
22
  let tools;
20
23
  if (mode === "tool") {
21
24
  resetTools();
22
25
  if (Array.isArray(toolIds)) {
23
26
  enableTools(toolIds);
24
27
  }
25
- tools = getEnabledTools();
28
+ tools = getEnabledTools(aiService.provider);
26
29
  }
27
30
 
28
- const aiService = getAIService();
29
31
  const result = await aiService.sendMessage(aiMessages, null, tools);
30
32
 
31
33
  await chatService.addMessage(conversationId, "assistant", result.content);
@@ -34,6 +36,7 @@ export const respond = async (req, res, next) => {
34
36
  content: result.content,
35
37
  toolCalls: result.toolCalls || [],
36
38
  toolResults: result.toolResults || [],
39
+ model: aiService.getDisplayName(),
37
40
  });
38
41
  } catch (error) {
39
42
  return next(error);
@@ -42,16 +45,17 @@ export const respond = async (req, res, next) => {
42
45
 
43
46
  export const generateAgentPlan = async (req, res, next) => {
44
47
  try {
45
- const { description } = req.body || {};
48
+ const { description, provider, model } = req.body || {};
46
49
 
47
50
  if (!description || typeof description !== "string") {
48
51
  return res.status(400).json({ error: "description is required" });
49
52
  }
50
53
 
51
- const aiService = getAIService();
54
+ const modelConfig = provider || model ? { provider, model } : null;
55
+ const aiService = getAIService(modelConfig);
52
56
  const application = await generateApplicationPlan(description, aiService);
53
57
 
54
- return res.json({ application });
58
+ return res.json({ application, model: aiService.getDisplayName() });
55
59
  } catch (error) {
56
60
  return next(error);
57
61
  }
@@ -16,32 +16,65 @@ const loadKeytar = async () => {
16
16
  };
17
17
 
18
18
  export const getCredentialServiceName = () => ORBITAL_KEYTAR_SERVICE;
19
- export const getApiKeyAccountName = () => ORBITAL_API_KEY_ACCOUNT;
19
+ export const getApiKeyAccountName = (provider = "gemini") => {
20
+ const p = (provider || "gemini").toLowerCase().trim();
21
+ if (p === "google" || p === "gemini") return "api-key-gemini";
22
+ if (p === "openai") return "api-key-openai";
23
+ if (p === "xai" || p === "grok") return "api-key-grok";
24
+ return `api-key-${p}`;
25
+ };
20
26
 
21
- export const getStoredApiKey = async () => {
27
+ export const getStoredApiKey = async (provider = "gemini") => {
22
28
  const keytar = await loadKeytar();
23
- const value = await keytar.getPassword(
24
- ORBITAL_KEYTAR_SERVICE,
25
- ORBITAL_API_KEY_ACCOUNT
26
- );
29
+ const account = getApiKeyAccountName(provider);
30
+ let value = await keytar.getPassword(ORBITAL_KEYTAR_SERVICE, account);
31
+
32
+ // Backward-compatibility for Gemini: fall back to legacy ORBITAL_API_KEY_ACCOUNT
33
+ const p = (provider || "gemini").toLowerCase().trim();
34
+ if (!value && (p === "gemini" || p === "google")) {
35
+ value = await keytar.getPassword(
36
+ ORBITAL_KEYTAR_SERVICE,
37
+ ORBITAL_API_KEY_ACCOUNT
38
+ );
39
+ }
40
+
27
41
  return typeof value === "string" ? value.trim() : "";
28
42
  };
29
43
 
30
- export const storeApiKey = async (apiKey) => {
44
+ export const storeApiKey = async (apiKey, provider = "gemini") => {
31
45
  const trimmed = typeof apiKey === "string" ? apiKey.trim() : "";
32
46
  if (!trimmed) throw new Error("API key is required");
33
47
 
34
48
  const keytar = await loadKeytar();
35
- await keytar.setPassword(
36
- ORBITAL_KEYTAR_SERVICE,
37
- ORBITAL_API_KEY_ACCOUNT,
38
- trimmed
39
- );
49
+ const account = getApiKeyAccountName(provider);
50
+ await keytar.setPassword(ORBITAL_KEYTAR_SERVICE, account, trimmed);
51
+
52
+ // For Gemini, also keep legacy account updated for backward compatibility
53
+ const p = (provider || "gemini").toLowerCase().trim();
54
+ if (p === "gemini" || p === "google") {
55
+ await keytar.setPassword(
56
+ ORBITAL_KEYTAR_SERVICE,
57
+ ORBITAL_API_KEY_ACCOUNT,
58
+ trimmed
59
+ );
60
+ }
61
+
40
62
  return true;
41
63
  };
42
64
 
43
- export const deleteStoredApiKey = async () => {
65
+ export const deleteStoredApiKey = async (provider = "gemini") => {
44
66
  const keytar = await loadKeytar();
45
- await keytar.deletePassword(ORBITAL_KEYTAR_SERVICE, ORBITAL_API_KEY_ACCOUNT);
67
+ const account = getApiKeyAccountName(provider);
68
+ await keytar.deletePassword(ORBITAL_KEYTAR_SERVICE, account);
69
+
70
+ const p = (provider || "gemini").toLowerCase().trim();
71
+ if (p === "gemini" || p === "google") {
72
+ await keytar.deletePassword(
73
+ ORBITAL_KEYTAR_SERVICE,
74
+ ORBITAL_API_KEY_ACCOUNT
75
+ );
76
+ }
77
+
46
78
  return true;
47
79
  };
80
+
@@ -86,10 +86,73 @@ export const updateOrbitalConfig = async (patch = {}) => {
86
86
  return nextConfig;
87
87
  };
88
88
 
89
- const getGeminiApiKeyFromEnvSync = () => {
90
- return typeof process.env.GOOGLE_GENERATIVE_AI_API_KEY === "string"
91
- ? process.env.GOOGLE_GENERATIVE_AI_API_KEY.trim()
92
- : "";
89
+ // --- Model Preference Persistence ---
90
+
91
+ export const getSelectedModel = async () => {
92
+ const cfg = await readOrbitalConfig();
93
+ let model = cfg.selectedModel || "gemini-2.5-flash";
94
+ if (model.includes("gemini-2.0")) model = "gemini-2.5-flash";
95
+ return {
96
+ provider: cfg.selectedProvider || "gemini",
97
+ model,
98
+ };
99
+ };
100
+
101
+ export const getSelectedModelSync = () => {
102
+ const cfg = readOrbitalConfigSync();
103
+ let model = cfg.selectedModel || "gemini-2.5-flash";
104
+ if (model.includes("gemini-2.0")) model = "gemini-2.5-flash";
105
+ return {
106
+ provider: cfg.selectedProvider || "gemini",
107
+ model,
108
+ };
109
+ };
110
+
111
+ export const saveSelectedModel = async ({ provider, model }) => {
112
+ return await updateOrbitalConfig({
113
+ selectedProvider: provider,
114
+ selectedModel: model,
115
+ });
116
+ };
117
+
118
+ // --- Multi-Provider API Key Management ---
119
+
120
+ export const normalizeProviderName = (provider = "gemini") => {
121
+ const p = (provider || "gemini").toLowerCase().trim();
122
+ if (p === "google" || p === "gemini") return "gemini";
123
+ if (p === "openai") return "openai";
124
+ if (p === "xai" || p === "grok") return "grok";
125
+ return p;
126
+ };
127
+
128
+ export const getApiKeyFromEnvSync = (provider = "gemini") => {
129
+ const norm = normalizeProviderName(provider);
130
+ if (norm === "gemini") {
131
+ return (
132
+ (typeof process.env.GOOGLE_GENERATIVE_AI_API_KEY === "string" &&
133
+ process.env.GOOGLE_GENERATIVE_AI_API_KEY.trim()) ||
134
+ (typeof process.env.GEMINI_API_KEY === "string" &&
135
+ process.env.GEMINI_API_KEY.trim()) ||
136
+ ""
137
+ );
138
+ }
139
+ if (norm === "openai") {
140
+ return (
141
+ (typeof process.env.OPENAI_API_KEY === "string" &&
142
+ process.env.OPENAI_API_KEY.trim()) ||
143
+ ""
144
+ );
145
+ }
146
+ if (norm === "grok") {
147
+ return (
148
+ (typeof process.env.XAI_API_KEY === "string" &&
149
+ process.env.XAI_API_KEY.trim()) ||
150
+ (typeof process.env.GROK_API_KEY === "string" &&
151
+ process.env.GROK_API_KEY.trim()) ||
152
+ ""
153
+ );
154
+ }
155
+ return "";
93
156
  };
94
157
 
95
158
  const getLegacyGeminiApiKeyFromConfigSync = () => {
@@ -113,80 +176,123 @@ const removeLegacyGeminiApiKeyFromConfig = async () => {
113
176
  return true;
114
177
  };
115
178
 
116
- export const hydrateGeminiApiKeyEnv = async () => {
117
- const already = getGeminiApiKeyFromEnvSync();
179
+ export const hydrateApiKeyEnv = async (provider = "gemini") => {
180
+ const norm = normalizeProviderName(provider);
181
+ const already = getApiKeyFromEnvSync(norm);
118
182
  if (already) return already;
119
183
 
120
- // Primary: OS credential manager via keytar.
184
+ // OS credential manager via keytar
121
185
  try {
122
- const fromKeytar = await getStoredApiKey();
186
+ const fromKeytar = await getStoredApiKey(norm);
123
187
  if (fromKeytar) {
124
- process.env.GOOGLE_GENERATIVE_AI_API_KEY = fromKeytar;
188
+ if (norm === "gemini") {
189
+ process.env.GOOGLE_GENERATIVE_AI_API_KEY = fromKeytar;
190
+ } else if (norm === "openai") {
191
+ process.env.OPENAI_API_KEY = fromKeytar;
192
+ } else if (norm === "grok") {
193
+ process.env.XAI_API_KEY = fromKeytar;
194
+ }
125
195
  return fromKeytar;
126
196
  }
127
197
  } catch {
128
- // ignore here; requireGeminiApiKey will surface a helpful error
198
+ // Ignore keytar error; will be handled in requireApiKey
129
199
  }
130
200
 
131
- // One-time migration: if the key exists in legacy ~/.orbital/config.json,
132
- // move it into keytar and remove it from disk.
133
- const legacy = getLegacyGeminiApiKeyFromConfigSync();
134
- if (legacy) {
135
- await storeApiKey(legacy);
136
- await removeLegacyGeminiApiKeyFromConfig().catch(() => {});
137
- process.env.GOOGLE_GENERATIVE_AI_API_KEY = legacy;
138
- return legacy;
201
+ // One-time migration for legacy Gemini key on disk
202
+ if (norm === "gemini") {
203
+ const legacy = getLegacyGeminiApiKeyFromConfigSync();
204
+ if (legacy) {
205
+ await storeApiKey(legacy, "gemini");
206
+ await removeLegacyGeminiApiKeyFromConfig().catch(() => {});
207
+ process.env.GOOGLE_GENERATIVE_AI_API_KEY = legacy;
208
+ return legacy;
209
+ }
139
210
  }
140
211
 
141
212
  return "";
142
213
  };
143
214
 
144
- export const getGeminiApiKeySync = () => getGeminiApiKeyFromEnvSync();
215
+ export const hydrateAllApiKeysEnv = async () => {
216
+ await Promise.all([
217
+ hydrateApiKeyEnv("gemini"),
218
+ hydrateApiKeyEnv("openai"),
219
+ hydrateApiKeyEnv("grok"),
220
+ ]);
221
+ };
222
+
223
+ export const getApiKeySync = (provider = "gemini") => {
224
+ return getApiKeyFromEnvSync(provider);
225
+ };
145
226
 
146
- export const getGeminiApiKey = async () => {
147
- const fromEnv = getGeminiApiKeyFromEnvSync();
227
+ export const getApiKey = async (provider = "gemini") => {
228
+ const norm = normalizeProviderName(provider);
229
+ const fromEnv = getApiKeyFromEnvSync(norm);
148
230
  if (fromEnv) return fromEnv;
149
- return await hydrateGeminiApiKeyEnv();
231
+ return await hydrateApiKeyEnv(norm);
150
232
  };
151
233
 
152
- export const hasGeminiApiKeySync = () => Boolean(getGeminiApiKeyFromEnvSync());
234
+ export const hasApiKeySync = (provider = "gemini") => {
235
+ return Boolean(getApiKeyFromEnvSync(provider));
236
+ };
153
237
 
154
- export const requireGeminiApiKeySync = () => {
155
- const apiKey = getGeminiApiKeyFromEnvSync();
238
+ export const requireApiKeySync = (provider = "gemini") => {
239
+ const norm = normalizeProviderName(provider);
240
+ const apiKey = getApiKeyFromEnvSync(norm);
156
241
  if (!apiKey) {
242
+ const displayName =
243
+ norm === "gemini" ? "Gemini" : norm === "openai" ? "OpenAI" : "Grok (xAI)";
157
244
  const err = new Error(
158
- "Gemini API key not set. Run: orbital set-key <API_KEY>"
245
+ `${displayName} API key not set. Run: orbital set-key --provider ${norm} <API_KEY>`
159
246
  );
160
- err.code = "ORBITAL_GEMINI_API_KEY_NOT_SET";
247
+ err.code = `ORBITAL_${norm.toUpperCase()}_API_KEY_NOT_SET`;
161
248
  throw err;
162
249
  }
163
250
  return apiKey;
164
251
  };
165
252
 
166
- export const requireGeminiApiKey = async () => {
167
- const apiKey = await getGeminiApiKey();
253
+ export const requireApiKey = async (provider = "gemini") => {
254
+ const norm = normalizeProviderName(provider);
255
+ const apiKey = await getApiKey(norm);
168
256
  if (!apiKey) {
257
+ const displayName =
258
+ norm === "gemini" ? "Gemini" : norm === "openai" ? "OpenAI" : "Grok (xAI)";
169
259
  const err = new Error(
170
- "Gemini API key not set. Run: orbital set-key <API_KEY>"
260
+ `${displayName} API key not set. Run: orbital set-key --provider ${norm} <API_KEY>`
171
261
  );
172
- err.code = "ORBITAL_GEMINI_API_KEY_NOT_SET";
262
+ err.code = `ORBITAL_${norm.toUpperCase()}_API_KEY_NOT_SET`;
173
263
  throw err;
174
264
  }
175
265
  return apiKey;
176
266
  };
177
267
 
178
- export const setGeminiApiKey = async (apiKey) => {
268
+ export const setApiKey = async (provider = "gemini", apiKey) => {
269
+ const norm = normalizeProviderName(provider);
179
270
  const trimmed = typeof apiKey === "string" ? apiKey.trim() : "";
180
271
  if (!trimmed) throw new Error("API key is required");
181
272
 
182
- await storeApiKey(trimmed);
183
- // Ensure any legacy on-disk key is removed.
184
- await removeLegacyGeminiApiKeyFromConfig().catch(() => {});
273
+ await storeApiKey(trimmed, norm);
274
+
275
+ if (norm === "gemini") {
276
+ await removeLegacyGeminiApiKeyFromConfig().catch(() => {});
277
+ process.env.GOOGLE_GENERATIVE_AI_API_KEY = trimmed;
278
+ process.env.GEMINI_API_KEY = trimmed;
279
+ } else if (norm === "openai") {
280
+ process.env.OPENAI_API_KEY = trimmed;
281
+ } else if (norm === "grok") {
282
+ process.env.XAI_API_KEY = trimmed;
283
+ process.env.GROK_API_KEY = trimmed;
284
+ }
185
285
 
186
- process.env.GOOGLE_GENERATIVE_AI_API_KEY = trimmed;
187
286
  return true;
188
287
  };
189
288
 
190
- // Back-compat exports (no longer reads from config specifically).
191
- export const requireGeminiApiKeyFromConfigSync = requireGeminiApiKeySync;
289
+ // --- Back-compatibility exports for Gemini ---
192
290
 
291
+ export const hydrateGeminiApiKeyEnv = () => hydrateApiKeyEnv("gemini");
292
+ export const getGeminiApiKeySync = () => getApiKeySync("gemini");
293
+ export const getGeminiApiKey = () => getApiKey("gemini");
294
+ export const hasGeminiApiKeySync = () => hasApiKeySync("gemini");
295
+ export const requireGeminiApiKeySync = () => requireApiKeySync("gemini");
296
+ export const requireGeminiApiKey = () => requireApiKey("gemini");
297
+ export const setGeminiApiKey = (apiKey) => setApiKey("gemini", apiKey);
298
+ export const requireGeminiApiKeyFromConfigSync = requireGeminiApiKeySync;
@@ -1,5 +1,5 @@
1
- import { AIService } from "../cli/ai/googleService.js";
2
-
3
- export const getAIService = () => {
4
- return new AIService();
5
- };
1
+ import { AIService } from "../cli/ai/googleService.js";
2
+
3
+ export const getAIService = (modelConfig = null) => {
4
+ return new AIService(modelConfig);
5
+ };