@projectservan8n/cnapse 0.8.2 → 0.10.0

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.
@@ -0,0 +1,7 @@
1
+ import {
2
+ ProviderSelector
3
+ } from "./chunk-7SDY7OPA.js";
4
+ import "./chunk-COKO6V5J.js";
5
+ export {
6
+ ProviderSelector
7
+ };
@@ -0,0 +1,419 @@
1
+ import {
2
+ getLearner
3
+ } from "./chunk-WSBJFRQH.js";
4
+ import {
5
+ chat
6
+ } from "./chunk-GP73OJCZ.js";
7
+ import {
8
+ clickMouse,
9
+ getMousePosition,
10
+ keyCombo,
11
+ moveMouse,
12
+ pressKey,
13
+ scrollMouse,
14
+ typeText
15
+ } from "./chunk-TFHK5CYF.js";
16
+ import {
17
+ captureScreenshot,
18
+ describeScreen
19
+ } from "./chunk-OIVTPXE4.js";
20
+ import "./chunk-COKO6V5J.js";
21
+
22
+ // src/agents/autonomous.ts
23
+ import { EventEmitter } from "events";
24
+ var DEFAULT_CONFIG = {
25
+ maxAttempts: 25,
26
+ actionDelayMs: 1500,
27
+ stuckThreshold: 3,
28
+ verifyActions: true,
29
+ humanLikeTiming: true,
30
+ learnFromSuccess: true,
31
+ askForHelpWhenStuck: true
32
+ };
33
+ var AutonomousAgent = class extends EventEmitter {
34
+ state;
35
+ config;
36
+ learner;
37
+ abortController = null;
38
+ constructor(config = {}) {
39
+ super();
40
+ this.config = { ...DEFAULT_CONFIG, ...config };
41
+ this.learner = getLearner();
42
+ this.state = this.createInitialState("");
43
+ }
44
+ createInitialState(goal) {
45
+ return {
46
+ goal,
47
+ isActive: false,
48
+ isPaused: false,
49
+ currentAction: null,
50
+ actionHistory: [],
51
+ stuckCount: 0,
52
+ attemptCount: 0,
53
+ lastScreenHash: "",
54
+ startTime: Date.now(),
55
+ confidence: 100
56
+ };
57
+ }
58
+ /**
59
+ * Start pursuing a goal autonomously
60
+ */
61
+ async start(goal) {
62
+ if (this.state.isActive) {
63
+ return { success: false, message: "Agent is already running" };
64
+ }
65
+ this.state = this.createInitialState(goal);
66
+ this.state.isActive = true;
67
+ this.abortController = new AbortController();
68
+ this.emit("started", { goal });
69
+ try {
70
+ await this.learner.load();
71
+ while (this.state.isActive && this.state.attemptCount < this.config.maxAttempts) {
72
+ if (this.state.isPaused) {
73
+ await this.sleep(500);
74
+ continue;
75
+ }
76
+ this.state.attemptCount++;
77
+ this.emit("attempt", { count: this.state.attemptCount, max: this.config.maxAttempts });
78
+ const observation = await this.observe();
79
+ if (!observation) continue;
80
+ const remembered = await this.learner.recall(goal, observation.description);
81
+ if (remembered) {
82
+ this.emit("recalled", remembered);
83
+ const result2 = await this.executeAction(remembered.actionType, remembered.actionValue);
84
+ if (result2.success) {
85
+ await this.learner.learn(
86
+ observation.description,
87
+ goal,
88
+ remembered.actionType,
89
+ remembered.actionValue,
90
+ "memory"
91
+ );
92
+ }
93
+ continue;
94
+ }
95
+ const decision = await this.think(observation.description);
96
+ if (decision.action === "done") {
97
+ this.state.isActive = false;
98
+ this.emit("completed", { success: true, attempts: this.state.attemptCount });
99
+ return { success: true, message: "Goal accomplished!" };
100
+ }
101
+ if (decision.action === "stuck") {
102
+ this.state.stuckCount++;
103
+ if (this.state.stuckCount >= this.config.stuckThreshold && this.config.askForHelpWhenStuck) {
104
+ this.emit("asking_help", { stuckCount: this.state.stuckCount });
105
+ const suggestions = await this.learner.getHelp(
106
+ goal,
107
+ observation.description,
108
+ this.state.actionHistory.slice(-5).map((a) => `${a.action}: ${a.value}`)
109
+ );
110
+ if (suggestions.length > 0) {
111
+ const suggestion = suggestions[0];
112
+ this.emit("trying_suggestion", suggestion);
113
+ const result2 = await this.executeAction(suggestion.action, suggestion.value);
114
+ if (result2.success && this.config.learnFromSuccess) {
115
+ await this.learner.learn(
116
+ observation.description,
117
+ goal,
118
+ suggestion.action,
119
+ suggestion.value,
120
+ suggestion.source
121
+ );
122
+ this.state.stuckCount = 0;
123
+ }
124
+ }
125
+ }
126
+ continue;
127
+ }
128
+ const result = await this.executeAction(decision.action, decision.value);
129
+ if (this.config.verifyActions) {
130
+ const afterScreen = await captureScreenshot();
131
+ const screenChanged = afterScreen !== observation.screenshot;
132
+ if (screenChanged) {
133
+ this.state.stuckCount = 0;
134
+ this.state.confidence = Math.min(100, this.state.confidence + 5);
135
+ if (result.success && this.config.learnFromSuccess) {
136
+ await this.learner.learn(
137
+ observation.description,
138
+ goal,
139
+ decision.action,
140
+ decision.value,
141
+ "self"
142
+ );
143
+ }
144
+ } else {
145
+ this.state.stuckCount++;
146
+ this.state.confidence = Math.max(0, this.state.confidence - 10);
147
+ }
148
+ }
149
+ if (this.config.humanLikeTiming) {
150
+ const delay = this.config.actionDelayMs + Math.random() * 500;
151
+ await this.sleep(delay);
152
+ } else {
153
+ await this.sleep(this.config.actionDelayMs);
154
+ }
155
+ }
156
+ this.state.isActive = false;
157
+ this.emit("completed", { success: false, attempts: this.state.attemptCount });
158
+ return {
159
+ success: false,
160
+ message: `Reached max attempts (${this.config.maxAttempts}). Goal may be partially complete.`
161
+ };
162
+ } catch (error) {
163
+ this.state.isActive = false;
164
+ const message = error instanceof Error ? error.message : "Unknown error";
165
+ this.emit("error", { error: message });
166
+ return { success: false, message };
167
+ }
168
+ }
169
+ /**
170
+ * Stop the agent
171
+ */
172
+ stop() {
173
+ this.state.isActive = false;
174
+ this.abortController?.abort();
175
+ this.emit("stopped", { attempts: this.state.attemptCount });
176
+ }
177
+ /**
178
+ * Pause the agent
179
+ */
180
+ pause() {
181
+ this.state.isPaused = true;
182
+ this.emit("paused");
183
+ }
184
+ /**
185
+ * Resume the agent
186
+ */
187
+ resume() {
188
+ this.state.isPaused = false;
189
+ this.emit("resumed");
190
+ }
191
+ /**
192
+ * Observe current screen state
193
+ */
194
+ async observe() {
195
+ try {
196
+ this.emit("observing");
197
+ const result = await describeScreen();
198
+ this.emit("observed", { description: result.description.slice(0, 200) });
199
+ return result;
200
+ } catch (error) {
201
+ this.emit("observe_error", { error });
202
+ return null;
203
+ }
204
+ }
205
+ /**
206
+ * Think about what action to take next
207
+ */
208
+ async think(screenDescription) {
209
+ this.emit("thinking");
210
+ const prompt = this.buildThinkingPrompt(screenDescription);
211
+ try {
212
+ const response = await chat([{ role: "user", content: prompt }]);
213
+ const decision = this.parseDecision(response.content);
214
+ this.emit("decided", decision);
215
+ return decision;
216
+ } catch (error) {
217
+ return { action: "stuck", value: "", reasoning: "Failed to get AI decision" };
218
+ }
219
+ }
220
+ buildThinkingPrompt(screenDescription) {
221
+ const recentActions = this.state.actionHistory.slice(-5).map((a) => `- ${a.action}: ${a.value} (${a.result})`).join("\n");
222
+ return `GOAL: ${this.state.goal}
223
+
224
+ CURRENT SCREEN: ${screenDescription}
225
+
226
+ PREVIOUS ACTIONS:
227
+ ${recentActions || "None yet"}
228
+
229
+ ATTEMPT: ${this.state.attemptCount}/${this.config.maxAttempts}
230
+ STUCK COUNT: ${this.state.stuckCount}
231
+
232
+ Based on what you see, what's the SINGLE next action to take?
233
+
234
+ Available actions:
235
+ - click: Click at current mouse position
236
+ - clickAt: Click at specific coordinates (VALUE: x,y)
237
+ - type: Type text (VALUE: text to type)
238
+ - press: Press a key (VALUE: Enter, Tab, Escape, etc.)
239
+ - keyCombo: Press key combination (VALUE: command+s, control+c, etc.)
240
+ - scroll: Scroll (VALUE: up or down)
241
+ - navigate: Open URL (VALUE: full URL)
242
+ - moveTo: Move mouse (VALUE: x,y coordinates)
243
+ - wait: Wait for something (VALUE: seconds)
244
+ - done: Goal is accomplished
245
+ - stuck: Can't figure out what to do
246
+
247
+ Respond EXACTLY in this format:
248
+ ACTION: <action_type>
249
+ VALUE: <parameter>
250
+ REASONING: <brief why>`;
251
+ }
252
+ parseDecision(content) {
253
+ const actionMatch = content.match(/ACTION:\s*(\w+)/i);
254
+ const valueMatch = content.match(/VALUE:\s*(.+?)(?:\n|$)/i);
255
+ const reasoningMatch = content.match(/REASONING:\s*(.+?)(?:\n|$)/i);
256
+ return {
257
+ action: actionMatch?.[1]?.toLowerCase() || "stuck",
258
+ value: valueMatch?.[1]?.trim() || "",
259
+ reasoning: reasoningMatch?.[1]?.trim() || "No reasoning provided"
260
+ };
261
+ }
262
+ /**
263
+ * Execute an action
264
+ */
265
+ async executeAction(action, value) {
266
+ const record = {
267
+ timestamp: Date.now(),
268
+ action,
269
+ value,
270
+ result: "pending",
271
+ screenBefore: this.state.lastScreenHash,
272
+ reasoning: ""
273
+ };
274
+ this.state.currentAction = `${action}: ${value}`;
275
+ this.emit("executing", { action, value });
276
+ try {
277
+ switch (action) {
278
+ case "click":
279
+ await clickMouse("left");
280
+ break;
281
+ case "clickat":
282
+ case "clickAt": {
283
+ const [x, y] = value.split(",").map((n) => parseInt(n.trim()));
284
+ if (!isNaN(x) && !isNaN(y)) {
285
+ await moveMouse(x, y);
286
+ await this.sleep(100);
287
+ await clickMouse("left");
288
+ }
289
+ break;
290
+ }
291
+ case "type":
292
+ if (this.config.humanLikeTiming) {
293
+ await this.typeHumanLike(value);
294
+ } else {
295
+ await typeText(value);
296
+ }
297
+ break;
298
+ case "press":
299
+ await pressKey(value || "Return");
300
+ break;
301
+ case "keycombo":
302
+ case "keyCombo": {
303
+ const keys = value.split("+").map((k) => k.trim().toLowerCase());
304
+ await keyCombo(keys);
305
+ break;
306
+ }
307
+ case "scroll":
308
+ const amount = value.toLowerCase().includes("up") ? 3 : -3;
309
+ await scrollMouse(amount);
310
+ break;
311
+ case "navigate": {
312
+ const browser = await import("./browser-YLFWQXIY.js");
313
+ const url = value.startsWith("http") ? value : `https://${value}`;
314
+ await browser.openUrl(url);
315
+ break;
316
+ }
317
+ case "moveto":
318
+ case "moveTo": {
319
+ const [mx, my] = value.split(",").map((n) => parseInt(n.trim()));
320
+ if (!isNaN(mx) && !isNaN(my)) {
321
+ if (this.config.humanLikeTiming) {
322
+ await this.moveMouseSmooth(mx, my);
323
+ } else {
324
+ await moveMouse(mx, my);
325
+ }
326
+ }
327
+ break;
328
+ }
329
+ case "wait": {
330
+ const seconds = parseFloat(value) || 2;
331
+ await this.sleep(seconds * 1e3);
332
+ break;
333
+ }
334
+ case "done":
335
+ case "stuck":
336
+ break;
337
+ default:
338
+ record.result = "failure";
339
+ this.state.actionHistory.push(record);
340
+ return { success: false, error: `Unknown action: ${action}` };
341
+ }
342
+ record.result = "success";
343
+ this.state.actionHistory.push(record);
344
+ this.emit("executed", { action, value, success: true });
345
+ return { success: true };
346
+ } catch (error) {
347
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
348
+ record.result = "failure";
349
+ this.state.actionHistory.push(record);
350
+ this.emit("executed", { action, value, success: false, error: errorMsg });
351
+ return { success: false, error: errorMsg };
352
+ } finally {
353
+ this.state.currentAction = null;
354
+ }
355
+ }
356
+ /**
357
+ * Type text with human-like timing
358
+ */
359
+ async typeHumanLike(text) {
360
+ const baseDelay = 50;
361
+ for (const char of text) {
362
+ await typeText(char);
363
+ const delay = baseDelay + Math.random() * 30;
364
+ await this.sleep(delay);
365
+ if (Math.random() < 0.05) {
366
+ await this.sleep(200 + Math.random() * 300);
367
+ }
368
+ }
369
+ }
370
+ /**
371
+ * Move mouse smoothly (basic linear interpolation)
372
+ */
373
+ async moveMouseSmooth(targetX, targetY) {
374
+ const currentPos = await getMousePosition();
375
+ const match = currentPos.output.match(/(\d+),\s*(\d+)/);
376
+ if (!match) {
377
+ await moveMouse(targetX, targetY);
378
+ return;
379
+ }
380
+ const startX = parseInt(match[1]);
381
+ const startY = parseInt(match[2]);
382
+ const steps = 10;
383
+ for (let i = 1; i <= steps; i++) {
384
+ const t = i / steps;
385
+ const x = Math.round(startX + (targetX - startX) * t);
386
+ const y = Math.round(startY + (targetY - startY) * t);
387
+ await moveMouse(x, y);
388
+ await this.sleep(20);
389
+ }
390
+ }
391
+ sleep(ms) {
392
+ return new Promise((resolve) => setTimeout(resolve, ms));
393
+ }
394
+ /**
395
+ * Get current state
396
+ */
397
+ getState() {
398
+ return { ...this.state };
399
+ }
400
+ /**
401
+ * Get action history
402
+ */
403
+ getHistory() {
404
+ return [...this.state.actionHistory];
405
+ }
406
+ };
407
+ var agentInstance = null;
408
+ function getAutonomousAgent(config) {
409
+ if (!agentInstance) {
410
+ agentInstance = new AutonomousAgent(config);
411
+ }
412
+ return agentInstance;
413
+ }
414
+ var autonomous_default = AutonomousAgent;
415
+ export {
416
+ AutonomousAgent,
417
+ autonomous_default as default,
418
+ getAutonomousAgent
419
+ };
@@ -0,0 +1,87 @@
1
+ import {
2
+ askAI,
3
+ browser_default,
4
+ click,
5
+ clickElement,
6
+ closeBrowser,
7
+ closeTab,
8
+ elementExists,
9
+ focusAddressBar,
10
+ getFullAIResponse,
11
+ getPage,
12
+ getPageText,
13
+ getTextContent,
14
+ goBack,
15
+ goForward,
16
+ googleDocsType,
17
+ googleSheetsType,
18
+ initBrowser,
19
+ navigateTo,
20
+ newTab,
21
+ nextTab,
22
+ openGmailCompose,
23
+ openGoogleDoc,
24
+ openGoogleSheet,
25
+ openOutlookCompose,
26
+ openUrl,
27
+ pressKey,
28
+ refresh,
29
+ research,
30
+ scroll,
31
+ searchGoogle,
32
+ sendGmail,
33
+ sendOutlook,
34
+ takeScreenshot,
35
+ typeInBrowser,
36
+ typeInElement,
37
+ typeSlowly,
38
+ typeUrl,
39
+ waitForNavigation,
40
+ waitForText,
41
+ webSearch
42
+ } from "./chunk-MOKGR7WE.js";
43
+ import "./chunk-TFHK5CYF.js";
44
+ import "./chunk-OIVTPXE4.js";
45
+ import "./chunk-COKO6V5J.js";
46
+ export {
47
+ askAI,
48
+ click,
49
+ clickElement,
50
+ closeBrowser,
51
+ closeTab,
52
+ browser_default as default,
53
+ elementExists,
54
+ focusAddressBar,
55
+ getFullAIResponse,
56
+ getPage,
57
+ getPageText,
58
+ getTextContent,
59
+ goBack,
60
+ goForward,
61
+ googleDocsType,
62
+ googleSheetsType,
63
+ initBrowser,
64
+ navigateTo,
65
+ newTab,
66
+ nextTab,
67
+ openGmailCompose,
68
+ openGoogleDoc,
69
+ openGoogleSheet,
70
+ openOutlookCompose,
71
+ openUrl,
72
+ pressKey,
73
+ refresh,
74
+ research,
75
+ scroll,
76
+ searchGoogle,
77
+ sendGmail,
78
+ sendOutlook,
79
+ takeScreenshot,
80
+ typeInBrowser,
81
+ typeInElement,
82
+ typeSlowly,
83
+ typeUrl,
84
+ waitForNavigation,
85
+ waitForText,
86
+ webSearch
87
+ };
@@ -1,52 +1,16 @@
1
+ import {
2
+ getConfig,
3
+ setApiKey,
4
+ setModel,
5
+ setProvider
6
+ } from "./chunk-COKO6V5J.js";
7
+
1
8
  // src/components/ProviderSelector.tsx
2
9
  import { useState, useEffect } from "react";
3
10
  import { Box, Text, useInput } from "ink";
4
11
  import TextInput from "ink-text-input";
5
12
  import Spinner from "ink-spinner";
6
13
 
7
- // src/lib/config.ts
8
- import Conf from "conf";
9
- var config = new Conf({
10
- projectName: "cnapse",
11
- defaults: {
12
- provider: "ollama",
13
- model: "qwen2.5:0.5b",
14
- apiKeys: {},
15
- ollamaHost: "http://localhost:11434",
16
- openrouter: {
17
- siteUrl: "https://github.com/projectservan8n/C-napse",
18
- appName: "C-napse"
19
- },
20
- telegram: {
21
- enabled: false
22
- }
23
- }
24
- });
25
- function getConfig() {
26
- return {
27
- provider: config.get("provider"),
28
- model: config.get("model"),
29
- apiKeys: config.get("apiKeys"),
30
- ollamaHost: config.get("ollamaHost"),
31
- openrouter: config.get("openrouter"),
32
- telegram: config.get("telegram")
33
- };
34
- }
35
- function setProvider(provider) {
36
- config.set("provider", provider);
37
- }
38
- function setModel(model) {
39
- config.set("model", model);
40
- }
41
- function setApiKey(provider, key) {
42
- const keys = config.get("apiKeys");
43
- keys[provider] = key;
44
- config.set("apiKeys", keys);
45
- }
46
- function getApiKey(provider) {
47
- return config.get("apiKeys")[provider];
48
- }
49
-
50
14
  // src/lib/ollama.ts
51
15
  import { exec } from "child_process";
52
16
  import { promisify } from "util";
@@ -159,10 +123,10 @@ var PROVIDERS = [
159
123
  }
160
124
  ];
161
125
  function ProviderSelector({ onClose, onSelect }) {
162
- const config2 = getConfig();
126
+ const config = getConfig();
163
127
  const [step, setStep] = useState("provider");
164
128
  const [providerIndex, setProviderIndex] = useState(() => {
165
- const idx = PROVIDERS.findIndex((p) => p.id === config2.provider);
129
+ const idx = PROVIDERS.findIndex((p) => p.id === config.provider);
166
130
  return idx >= 0 ? idx : 0;
167
131
  });
168
132
  const [modelIndex, setModelIndex] = useState(0);
@@ -195,12 +159,12 @@ function ProviderSelector({ onClose, onSelect }) {
195
159
  } else if (key.return) {
196
160
  const provider = PROVIDERS[providerIndex];
197
161
  setSelectedProvider(provider);
198
- const currentIdx = provider.models.findIndex((m) => m.id === config2.model);
162
+ const currentIdx = provider.models.findIndex((m) => m.id === config.model);
199
163
  const recommendedIdx = provider.models.findIndex((m) => m.recommended);
200
164
  setModelIndex(currentIdx >= 0 ? currentIdx : recommendedIdx >= 0 ? recommendedIdx : 0);
201
165
  if (provider.needsApiKey) {
202
166
  const apiKeyProvider = provider.id;
203
- if (!config2.apiKeys[apiKeyProvider]) {
167
+ if (!config.apiKeys[apiKeyProvider]) {
204
168
  setStep("apiKey");
205
169
  } else {
206
170
  setStep("model");
@@ -246,8 +210,8 @@ function ProviderSelector({ onClose, onSelect }) {
246
210
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: "Arrows to navigate, Enter to select" }) }),
247
211
  PROVIDERS.map((provider, index) => {
248
212
  const isSelected = index === providerIndex;
249
- const isCurrent = provider.id === config2.provider;
250
- const hasKey = provider.needsApiKey && provider.id !== "ollama" ? !!config2.apiKeys[provider.id] : true;
213
+ const isCurrent = provider.id === config.provider;
214
+ const hasKey = provider.needsApiKey && provider.id !== "ollama" ? !!config.apiKeys[provider.id] : true;
251
215
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
252
216
  /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "white", children: [
253
217
  isSelected ? "\u276F " : " ",
@@ -335,7 +299,7 @@ function ProviderSelector({ onClose, onSelect }) {
335
299
  /* @__PURE__ */ jsx(Box, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: "Arrows to navigate, Enter to select, B to go back" }) }),
336
300
  selectedProvider.models.map((model, index) => {
337
301
  const isSelected = index === modelIndex;
338
- const isCurrent = model.id === config2.model && selectedProvider.id === config2.provider;
302
+ const isCurrent = model.id === config.model && selectedProvider.id === config.provider;
339
303
  let modelStatus = "";
340
304
  if (isOllama && ollamaStatus) {
341
305
  const available = hasModel(ollamaStatus, model.id);
@@ -382,10 +346,5 @@ function ProviderSelector({ onClose, onSelect }) {
382
346
  }
383
347
 
384
348
  export {
385
- getConfig,
386
- setProvider,
387
- setModel,
388
- setApiKey,
389
- getApiKey,
390
349
  ProviderSelector
391
350
  };
@@ -0,0 +1,50 @@
1
+ // src/lib/config.ts
2
+ import Conf from "conf";
3
+ var config = new Conf({
4
+ projectName: "cnapse",
5
+ defaults: {
6
+ provider: "ollama",
7
+ model: "qwen2.5:0.5b",
8
+ apiKeys: {},
9
+ ollamaHost: "http://localhost:11434",
10
+ openrouter: {
11
+ siteUrl: "https://github.com/projectservan8n/C-napse",
12
+ appName: "C-napse"
13
+ },
14
+ telegram: {
15
+ enabled: false
16
+ }
17
+ }
18
+ });
19
+ function getConfig() {
20
+ return {
21
+ provider: config.get("provider"),
22
+ model: config.get("model"),
23
+ apiKeys: config.get("apiKeys"),
24
+ ollamaHost: config.get("ollamaHost"),
25
+ openrouter: config.get("openrouter"),
26
+ telegram: config.get("telegram")
27
+ };
28
+ }
29
+ function setProvider(provider) {
30
+ config.set("provider", provider);
31
+ }
32
+ function setModel(model) {
33
+ config.set("model", model);
34
+ }
35
+ function setApiKey(provider, key) {
36
+ const keys = config.get("apiKeys");
37
+ keys[provider] = key;
38
+ config.set("apiKeys", keys);
39
+ }
40
+ function getApiKey(provider) {
41
+ return config.get("apiKeys")[provider];
42
+ }
43
+
44
+ export {
45
+ getConfig,
46
+ setProvider,
47
+ setModel,
48
+ setApiKey,
49
+ getApiKey
50
+ };