protoagent 0.0.5 → 0.1.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.
Files changed (58) hide show
  1. package/README.md +99 -19
  2. package/dist/App.js +602 -0
  3. package/dist/agentic-loop.js +492 -525
  4. package/dist/cli.js +39 -0
  5. package/dist/components/CollapsibleBox.js +26 -0
  6. package/dist/components/ConfigDialog.js +40 -0
  7. package/dist/components/ConsolidatedToolMessage.js +41 -0
  8. package/dist/components/FormattedMessage.js +93 -0
  9. package/dist/components/Table.js +275 -0
  10. package/dist/config.js +171 -0
  11. package/dist/mcp.js +170 -0
  12. package/dist/providers.js +137 -0
  13. package/dist/sessions.js +161 -0
  14. package/dist/skills.js +229 -0
  15. package/dist/sub-agent.js +103 -0
  16. package/dist/system-prompt.js +131 -0
  17. package/dist/tools/bash.js +178 -0
  18. package/dist/tools/edit-file.js +65 -171
  19. package/dist/tools/index.js +79 -134
  20. package/dist/tools/list-directory.js +20 -73
  21. package/dist/tools/read-file.js +57 -101
  22. package/dist/tools/search-files.js +74 -162
  23. package/dist/tools/todo.js +57 -140
  24. package/dist/tools/webfetch.js +310 -0
  25. package/dist/tools/write-file.js +44 -135
  26. package/dist/utils/approval.js +69 -0
  27. package/dist/utils/compactor.js +87 -0
  28. package/dist/utils/cost-tracker.js +26 -81
  29. package/dist/utils/format-message.js +26 -0
  30. package/dist/utils/logger.js +101 -307
  31. package/dist/utils/path-validation.js +74 -0
  32. package/package.json +45 -51
  33. package/LICENSE +0 -21
  34. package/dist/config/client.js +0 -315
  35. package/dist/config/commands.js +0 -223
  36. package/dist/config/manager.js +0 -117
  37. package/dist/config/mcp-commands.js +0 -266
  38. package/dist/config/mcp-manager.js +0 -240
  39. package/dist/config/mcp-types.js +0 -28
  40. package/dist/config/providers.js +0 -229
  41. package/dist/config/setup.js +0 -209
  42. package/dist/config/system-prompt.js +0 -397
  43. package/dist/config/types.js +0 -4
  44. package/dist/index.js +0 -229
  45. package/dist/tools/create-directory.js +0 -76
  46. package/dist/tools/directory-operations.js +0 -195
  47. package/dist/tools/file-operations.js +0 -211
  48. package/dist/tools/run-shell-command.js +0 -746
  49. package/dist/tools/search-operations.js +0 -179
  50. package/dist/tools/shell-operations.js +0 -342
  51. package/dist/tools/task-complete.js +0 -26
  52. package/dist/tools/view-directory-tree.js +0 -125
  53. package/dist/tools.js +0 -2
  54. package/dist/utils/conversation-compactor.js +0 -140
  55. package/dist/utils/enhanced-prompt.js +0 -23
  56. package/dist/utils/file-operations-approval.js +0 -373
  57. package/dist/utils/interrupt-handler.js +0 -127
  58. package/dist/utils/user-cancellation.js +0 -34
@@ -1,117 +0,0 @@
1
- /**
2
- * Configuration manager for ProtoAgent
3
- */
4
- import fs from 'fs/promises';
5
- import path from 'path';
6
- import os from 'os';
7
- import { geminiProvider, openaiProvider, cerebrasProvider } from './providers.js';
8
- /**
9
- * Get the config directory path
10
- */
11
- function getConfigDir() {
12
- const homeDir = os.homedir();
13
- // Use XDG Base Directory specification on Unix-like systems
14
- if (process.platform === 'win32') {
15
- return path.join(homeDir, 'AppData', 'Local', 'protoagent');
16
- }
17
- else {
18
- return path.join(homeDir, '.local', 'share', 'protoagent');
19
- }
20
- }
21
- /**
22
- * Get the config file path
23
- */
24
- function getConfigPath() {
25
- return path.join(getConfigDir(), 'config.json');
26
- }
27
- /**
28
- * Ensure config directory exists
29
- */
30
- async function ensureConfigDir() {
31
- const configDir = getConfigDir();
32
- try {
33
- await fs.access(configDir);
34
- }
35
- catch {
36
- await fs.mkdir(configDir, { recursive: true });
37
- }
38
- }
39
- /**
40
- * Check if configuration exists
41
- */
42
- export async function hasConfig() {
43
- try {
44
- await fs.access(getConfigPath());
45
- return true;
46
- }
47
- catch {
48
- return false;
49
- }
50
- }
51
- /**
52
- * Load user configuration
53
- */
54
- export async function loadConfig() {
55
- try {
56
- const configPath = getConfigPath();
57
- const configData = await fs.readFile(configPath, 'utf-8');
58
- return JSON.parse(configData);
59
- }
60
- catch (error) {
61
- throw new Error(`Failed to load configuration: ${error.message}`);
62
- }
63
- }
64
- /**
65
- * Save user configuration
66
- */
67
- export async function saveConfig(config) {
68
- try {
69
- await ensureConfigDir();
70
- const configPath = getConfigPath();
71
- await fs.writeFile(configPath, JSON.stringify(config, null, 2));
72
- }
73
- catch (error) {
74
- throw new Error(`Failed to save configuration: ${error.message}`);
75
- }
76
- }
77
- /**
78
- * Validate configuration
79
- */
80
- export function validateConfig(config) {
81
- if (!config || typeof config !== 'object') {
82
- return false;
83
- }
84
- const { provider, model, credentials } = config;
85
- if (!model || !credentials || !(credentials.OPENAI_API_KEY || credentials.GEMINI_API_KEY || credentials.CEREBRAS_API_KEY)) {
86
- return false;
87
- }
88
- if (provider === 'gemini') {
89
- // Check if model is available for Gemini
90
- if (!geminiProvider.models.includes(model)) {
91
- return false;
92
- }
93
- // Additional model validation for Gemini can be added here
94
- return true;
95
- }
96
- if (provider === 'openai') {
97
- // Check if model is available for OpenAI
98
- if (!openaiProvider.models.includes(model)) {
99
- return false;
100
- }
101
- return true;
102
- }
103
- if (provider === 'cerebras') {
104
- // Check if model is available for Cerebras
105
- if (!cerebrasProvider.models.includes(model)) {
106
- return false;
107
- }
108
- return true;
109
- }
110
- return false;
111
- }
112
- /**
113
- * Get configuration directory path for display
114
- */
115
- export function getConfigDirectory() {
116
- return getConfigDir();
117
- }
@@ -1,266 +0,0 @@
1
- /**
2
- * MCP management commands for ProtoAgent CLI
3
- */
4
- import inquirer from 'inquirer';
5
- import { MCPManager } from './mcp-manager.js';
6
- const mcpManager = new MCPManager();
7
- /**
8
- * Show current MCP configuration and status
9
- */
10
- export async function showMCPStatus() {
11
- try {
12
- await mcpManager.loadConfig();
13
- console.log('\n🔌 MCP Configuration & Status:');
14
- console.log('─'.repeat(50));
15
- const servers = mcpManager.listServers();
16
- const connectionStatus = mcpManager.getConnectionStatus();
17
- if (Object.keys(servers).length === 0) {
18
- console.log('No MCP servers configured');
19
- return;
20
- }
21
- for (const [name, config] of Object.entries(servers)) {
22
- const status = connectionStatus[name];
23
- const statusIcon = status === true ? '🟢' : status === false ? '🔴' : '⚪';
24
- const typeLabel = config.isBuiltIn ? '(built-in)' : '(custom)';
25
- console.log(`${statusIcon} ${name} ${typeLabel}`);
26
- console.log(` Description: ${config.description}`);
27
- console.log(` Command: ${config.command} ${(config.args || []).join(' ')}`);
28
- console.log(` Enabled: ${config.enabled ? 'Yes' : 'No'}`);
29
- console.log('');
30
- }
31
- // Show available tools if connected
32
- const tools = await mcpManager.getAllTools();
33
- if (tools.length > 0) {
34
- console.log('🛠️ Available Tools:');
35
- for (const tool of tools) {
36
- console.log(` • ${tool.server}:${tool.name} - ${tool.description || 'No description'}`);
37
- }
38
- }
39
- console.log('─'.repeat(50));
40
- }
41
- catch (error) {
42
- console.error(`❌ Error showing MCP status: ${error.message}`);
43
- }
44
- }
45
- /**
46
- * Connect to MCP servers
47
- */
48
- export async function connectMCPServers() {
49
- try {
50
- await mcpManager.loadConfig();
51
- await mcpManager.connectToAllServers();
52
- }
53
- catch (error) {
54
- console.error(`❌ Error connecting to MCP servers: ${error.message}`);
55
- }
56
- }
57
- /**
58
- * Add a custom MCP server
59
- */
60
- export async function addCustomMCPServer() {
61
- try {
62
- console.log('\n🔧 Add Custom MCP Server');
63
- console.log('Configure a new MCP server for ProtoAgent\n');
64
- const serverConfig = await inquirer.prompt([
65
- {
66
- type: 'input',
67
- name: 'name',
68
- message: 'Server name (unique identifier):',
69
- validate: (input) => {
70
- if (!input || input.trim().length === 0) {
71
- return 'Server name is required';
72
- }
73
- if (!/^[a-zA-Z0-9\-_]+$/.test(input.trim())) {
74
- return 'Server name can only contain letters, numbers, hyphens, and underscores';
75
- }
76
- return true;
77
- }
78
- },
79
- {
80
- type: 'input',
81
- name: 'description',
82
- message: 'Description:',
83
- validate: (input) => input.trim().length > 0 ? true : 'Description is required'
84
- },
85
- {
86
- type: 'input',
87
- name: 'command',
88
- message: 'Command to run the server:',
89
- validate: (input) => input.trim().length > 0 ? true : 'Command is required'
90
- },
91
- {
92
- type: 'input',
93
- name: 'args',
94
- message: 'Arguments (space-separated, optional):',
95
- default: ''
96
- },
97
- {
98
- type: 'confirm',
99
- name: 'enabled',
100
- message: 'Enable this server?',
101
- default: true
102
- }
103
- ]);
104
- const newServer = {
105
- name: serverConfig.name.trim(),
106
- description: serverConfig.description.trim(),
107
- command: serverConfig.command.trim(),
108
- args: serverConfig.args.trim() ? serverConfig.args.trim().split(' ') : [],
109
- enabled: serverConfig.enabled,
110
- isBuiltIn: false
111
- };
112
- await mcpManager.loadConfig();
113
- await mcpManager.addCustomServer(newServer);
114
- console.log(`\n✅ Custom MCP server '${newServer.name}' added successfully!`);
115
- if (newServer.enabled) {
116
- const { connectNow } = await inquirer.prompt([
117
- {
118
- type: 'confirm',
119
- name: 'connectNow',
120
- message: 'Connect to this server now?',
121
- default: true
122
- }
123
- ]);
124
- if (connectNow) {
125
- const connection = await mcpManager.connectToServer(newServer.name);
126
- if (connection) {
127
- console.log(`🎉 Successfully connected to ${newServer.name}!`);
128
- }
129
- }
130
- }
131
- }
132
- catch (error) {
133
- console.error(`❌ Error adding custom MCP server: ${error.message}`);
134
- }
135
- }
136
- /**
137
- * Remove a custom MCP server
138
- */
139
- export async function removeCustomMCPServer() {
140
- try {
141
- await mcpManager.loadConfig();
142
- const servers = mcpManager.listServers();
143
- const customServers = Object.entries(servers).filter(([_, config]) => !config.isBuiltIn);
144
- if (customServers.length === 0) {
145
- console.log('📭 No custom MCP servers configured');
146
- return;
147
- }
148
- const { serverToRemove } = await inquirer.prompt([
149
- {
150
- type: 'list',
151
- name: 'serverToRemove',
152
- message: 'Select server to remove:',
153
- choices: customServers.map(([name, config]) => ({
154
- name: `${name} - ${config.description}`,
155
- value: name
156
- }))
157
- }
158
- ]);
159
- const { confirm } = await inquirer.prompt([
160
- {
161
- type: 'confirm',
162
- name: 'confirm',
163
- message: `Are you sure you want to remove '${serverToRemove}'?`,
164
- default: false
165
- }
166
- ]);
167
- if (confirm) {
168
- await mcpManager.removeCustomServer(serverToRemove);
169
- console.log(`✅ Custom MCP server '${serverToRemove}' removed successfully!`);
170
- }
171
- else {
172
- console.log('Operation cancelled.');
173
- }
174
- }
175
- catch (error) {
176
- console.error(`❌ Error removing custom MCP server: ${error.message}`);
177
- }
178
- }
179
- /**
180
- * Toggle MCP server enabled/disabled status
181
- */
182
- export async function toggleMCPServer() {
183
- try {
184
- await mcpManager.loadConfig();
185
- const servers = mcpManager.listServers();
186
- if (Object.keys(servers).length === 0) {
187
- console.log('📭 No MCP servers configured');
188
- return;
189
- }
190
- const { serverToToggle } = await inquirer.prompt([
191
- {
192
- type: 'list',
193
- name: 'serverToToggle',
194
- message: 'Select server to enable/disable:',
195
- choices: Object.entries(servers).map(([name, config]) => ({
196
- name: `${name} - ${config.enabled ? '🟢 Enabled' : '🔴 Disabled'} - ${config.description}`,
197
- value: name
198
- }))
199
- }
200
- ]);
201
- const currentConfig = servers[serverToToggle];
202
- const newStatus = !currentConfig.enabled;
203
- // Update the configuration
204
- await mcpManager.loadConfig();
205
- const updatedServers = mcpManager.listServers();
206
- updatedServers[serverToToggle].enabled = newStatus;
207
- // Save the updated configuration
208
- const updatedConfig = {
209
- servers: updatedServers,
210
- globalTimeout: 30000,
211
- maxConcurrentConnections: 5,
212
- retryAttempts: 3
213
- };
214
- const mcpManagerForSave = new MCPManager(updatedConfig);
215
- await mcpManagerForSave.saveConfig();
216
- console.log(`✅ Server '${serverToToggle}' ${newStatus ? 'enabled' : 'disabled'}`);
217
- }
218
- catch (error) {
219
- console.error(`❌ Error toggling MCP server: ${error.message}`);
220
- }
221
- }
222
- /**
223
- * Test connection to a specific MCP server
224
- */
225
- export async function testMCPServer() {
226
- try {
227
- await mcpManager.loadConfig();
228
- const servers = mcpManager.listServers();
229
- const enabledServers = Object.entries(servers).filter(([_, config]) => config.enabled);
230
- if (enabledServers.length === 0) {
231
- console.log('📭 No enabled MCP servers to test');
232
- return;
233
- }
234
- const { serverToTest } = await inquirer.prompt([
235
- {
236
- type: 'list',
237
- name: 'serverToTest',
238
- message: 'Select server to test:',
239
- choices: enabledServers.map(([name, config]) => ({
240
- name: `${name} - ${config.description}`,
241
- value: name
242
- }))
243
- }
244
- ]);
245
- console.log(`🧪 Testing connection to ${serverToTest}...`);
246
- const connection = await mcpManager.connectToServer(serverToTest);
247
- if (connection) {
248
- console.log(`✅ Successfully connected to ${serverToTest}!`);
249
- console.log(`🛠️ Available tools: ${connection.capabilities.join(', ')}`);
250
- // Clean up test connection
251
- await mcpManager.disconnectServer(serverToTest);
252
- }
253
- else {
254
- console.log(`❌ Failed to connect to ${serverToTest}`);
255
- }
256
- }
257
- catch (error) {
258
- console.error(`❌ Error testing MCP server: ${error.message}`);
259
- }
260
- }
261
- /**
262
- * Get the MCP manager instance (for use in main application)
263
- */
264
- export function getMCPManager() {
265
- return mcpManager;
266
- }
@@ -1,240 +0,0 @@
1
- /**
2
- * MCP (Model Context Protocol) Manager
3
- * Handles connections to MCP servers and tool execution
4
- */
5
- import { Client } from '@modelcontextprotocol/sdk/client/index.js';
6
- import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
7
- import { DEFAULT_MCP_CONFIG } from './mcp-types.js';
8
- import path from 'path';
9
- import fs from 'fs/promises';
10
- import { getConfigDirectory } from './manager.js';
11
- export class MCPManager {
12
- constructor(config) {
13
- this.connections = new Map();
14
- this.processes = new Map();
15
- this.config = config || DEFAULT_MCP_CONFIG;
16
- }
17
- /**
18
- * Load MCP configuration from file
19
- */
20
- async loadConfig() {
21
- try {
22
- const configPath = path.join(getConfigDirectory(), 'mcp-config.json');
23
- const configData = await fs.readFile(configPath, 'utf-8');
24
- this.config = { ...DEFAULT_MCP_CONFIG, ...JSON.parse(configData) };
25
- }
26
- catch (error) {
27
- // If no config file exists, use defaults
28
- console.log('📡 Using default MCP configuration');
29
- this.config = DEFAULT_MCP_CONFIG;
30
- }
31
- }
32
- /**
33
- * Save MCP configuration to file
34
- */
35
- async saveConfig() {
36
- try {
37
- const configPath = path.join(getConfigDirectory(), 'mcp-config.json');
38
- await fs.writeFile(configPath, JSON.stringify(this.config, null, 2));
39
- }
40
- catch (error) {
41
- console.error('❌ Failed to save MCP configuration:', error);
42
- }
43
- }
44
- /**
45
- * Connect to a specific MCP server
46
- */
47
- async connectToServer(serverName) {
48
- const serverConfig = this.config.servers[serverName];
49
- if (!serverConfig || !serverConfig.enabled) {
50
- console.log(`⚠️ Server ${serverName} is not configured or disabled`);
51
- return null;
52
- }
53
- try {
54
- console.log(`🔌 Connecting to MCP server: ${serverName}`);
55
- // Create transport for stdio communication
56
- const transport = new StdioClientTransport({
57
- command: serverConfig.command,
58
- args: serverConfig.args || [],
59
- env: {
60
- ...Object.fromEntries(Object.entries(process.env).filter(([_, v]) => v !== undefined)),
61
- ...serverConfig.env
62
- }
63
- });
64
- // Create MCP client
65
- const client = new Client({
66
- name: 'protoagent',
67
- version: '1.0.0'
68
- }, {
69
- capabilities: {
70
- tools: {}
71
- }
72
- });
73
- // Connect to the server
74
- await client.connect(transport);
75
- // Get server capabilities
76
- const serverCapabilities = await client.listTools();
77
- const capabilities = serverCapabilities.tools.map(tool => tool.name);
78
- const connection = {
79
- name: serverName,
80
- client,
81
- capabilities,
82
- connected: true
83
- };
84
- this.connections.set(serverName, connection);
85
- console.log(`✅ Connected to ${serverName} with ${capabilities.length} tools`);
86
- return connection;
87
- }
88
- catch (error) {
89
- console.error(`❌ Failed to connect to MCP server ${serverName}:`, error);
90
- return null;
91
- }
92
- }
93
- /**
94
- * Connect to all enabled MCP servers
95
- */
96
- async connectToAllServers() {
97
- console.log('🚀 Initializing MCP connections...');
98
- const enabledServers = Object.keys(this.config.servers).filter(name => this.config.servers[name].enabled);
99
- if (enabledServers.length === 0) {
100
- console.log('ℹ️ No MCP servers enabled');
101
- return;
102
- }
103
- const connectionPromises = enabledServers.map(serverName => this.connectToServer(serverName).catch(error => {
104
- console.error(`Failed to connect to ${serverName}:`, error);
105
- return null;
106
- }));
107
- const results = await Promise.all(connectionPromises);
108
- const successfulConnections = results.filter(conn => conn !== null);
109
- console.log(`📡 MCP Status: ${successfulConnections.length}/${enabledServers.length} servers connected`);
110
- }
111
- /**
112
- * Get all available tools from connected servers
113
- */
114
- async getAllTools() {
115
- const allTools = [];
116
- for (const [serverName, connection] of this.connections) {
117
- if (!connection.connected)
118
- continue;
119
- try {
120
- const tools = await connection.client.listTools();
121
- for (const tool of tools.tools) {
122
- allTools.push({
123
- server: serverName,
124
- name: tool.name,
125
- description: tool.description,
126
- schema: tool.inputSchema
127
- });
128
- }
129
- }
130
- catch (error) {
131
- console.error(`❌ Failed to get tools from ${serverName}:`, error);
132
- }
133
- }
134
- return allTools;
135
- }
136
- /**
137
- * Execute a tool on a specific MCP server
138
- */
139
- async callTool(serverName, toolName, args) {
140
- const connection = this.connections.get(serverName);
141
- if (!connection || !connection.connected) {
142
- throw new Error(`Server ${serverName} is not connected`);
143
- }
144
- try {
145
- console.log(`🛠️ Calling ${serverName}:${toolName}`);
146
- const result = await connection.client.callTool({
147
- name: toolName,
148
- arguments: args
149
- });
150
- return result;
151
- }
152
- catch (error) {
153
- console.error(`❌ Tool execution failed ${serverName}:${toolName}:`, error);
154
- throw error;
155
- }
156
- }
157
- /**
158
- * Find which server has a specific tool
159
- */
160
- findToolServer(toolName) {
161
- for (const [serverName, connection] of this.connections) {
162
- if (connection.capabilities.includes(toolName)) {
163
- return serverName;
164
- }
165
- }
166
- return null;
167
- }
168
- /**
169
- * Get connection status
170
- */
171
- getConnectionStatus() {
172
- const status = {};
173
- for (const [serverName, connection] of this.connections) {
174
- status[serverName] = connection.connected;
175
- }
176
- return status;
177
- }
178
- /**
179
- * Add a custom MCP server configuration
180
- */
181
- async addCustomServer(config) {
182
- this.config.servers[config.name] = config;
183
- await this.saveConfig();
184
- console.log(`✅ Added custom MCP server: ${config.name}`);
185
- }
186
- /**
187
- * Remove a custom MCP server
188
- */
189
- async removeCustomServer(serverName) {
190
- const serverConfig = this.config.servers[serverName];
191
- if (serverConfig?.isBuiltIn) {
192
- throw new Error('Cannot remove built-in MCP servers');
193
- }
194
- // Disconnect if connected
195
- const connection = this.connections.get(serverName);
196
- if (connection) {
197
- await this.disconnectServer(serverName);
198
- }
199
- delete this.config.servers[serverName];
200
- await this.saveConfig();
201
- console.log(`✅ Removed custom MCP server: ${serverName}`);
202
- }
203
- /**
204
- * Disconnect from a specific server
205
- */
206
- async disconnectServer(serverName) {
207
- const connection = this.connections.get(serverName);
208
- if (connection) {
209
- try {
210
- await connection.client.close();
211
- connection.connected = false;
212
- this.connections.delete(serverName);
213
- console.log(`🔌 Disconnected from ${serverName}`);
214
- }
215
- catch (error) {
216
- console.error(`❌ Error disconnecting from ${serverName}:`, error);
217
- }
218
- }
219
- }
220
- /**
221
- * Disconnect from all servers
222
- */
223
- async disconnectAll() {
224
- console.log('🔌 Disconnecting from all MCP servers...');
225
- const disconnectPromises = Array.from(this.connections.keys()).map(serverName => this.disconnectServer(serverName));
226
- await Promise.all(disconnectPromises);
227
- }
228
- /**
229
- * Get server configuration
230
- */
231
- getServerConfig(serverName) {
232
- return this.config.servers[serverName] || null;
233
- }
234
- /**
235
- * List all configured servers
236
- */
237
- listServers() {
238
- return { ...this.config.servers };
239
- }
240
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * MCP (Model Context Protocol) configuration types
3
- */
4
- // Built-in server configurations
5
- export const BUILTIN_MCP_SERVERS = {
6
- filesystem: {
7
- name: 'filesystem',
8
- description: 'File system operations via MCP',
9
- command: 'npx',
10
- args: ['@modelcontextprotocol/server-filesystem'],
11
- enabled: true,
12
- isBuiltIn: true
13
- },
14
- 'sequential-thinking': {
15
- name: 'sequential-thinking',
16
- description: 'Sequential thinking and reasoning tools',
17
- command: 'npx',
18
- args: ['@modelcontextprotocol/server-sequential-thinking'],
19
- enabled: true,
20
- isBuiltIn: true
21
- }
22
- };
23
- export const DEFAULT_MCP_CONFIG = {
24
- servers: { ...BUILTIN_MCP_SERVERS },
25
- globalTimeout: 30000, // 30 seconds
26
- maxConcurrentConnections: 5,
27
- retryAttempts: 3
28
- };