Skip to content

Commit 3d8b3b7

Browse files
Merge pull request Patrick-Ehimen#33 from Patrick-Ehimen/feat/vscode-extension-impl
feat: implement VSCode extension with AI integration
2 parents f54c086 + 38c8e80 commit 3d8b3b7

21 files changed

Lines changed: 2723 additions & 57 deletions

.vscode/settings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@
55
"eslint.validate": ["javascript", "typescript", "typescriptreact"],
66
"editor.codeActionsOnSave": {
77
"source.fixAll.eslint": "explicit"
8-
}
8+
},
9+
"lighthouse.vscode.mcpServerUrl": "http://localhost:3000",
10+
"lighthouse.vscode.apiKey": "725ca23b.fcd4fe7b53b74a308d7c537fe0e60f79"
911
}

packages/sdk-wrapper/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"@lighthouse-web3/sdk": "^0.2.3",
2323
"@lighthouse-web3/kavach": "^0.2.1",
2424
"eventemitter3": "^5.0.1",
25-
"axios": "^1.6.0"
25+
"axios": "^1.6.0",
26+
"form-data": "^4.0.0"
2627
},
2728
"devDependencies": {
2829
"@types/jest": "^29.5.0",

packages/sdk-wrapper/src/LighthouseAISDK.ts

Lines changed: 106 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { EventEmitter } from "eventemitter3";
22
import lighthouse from "@lighthouse-web3/sdk";
3+
import { readFileSync } from "fs";
34
import { AuthenticationManager } from "./auth/AuthenticationManager";
45
import { ProgressTracker } from "./progress/ProgressTracker";
56
import { ErrorHandler } from "./errors/ErrorHandler";
@@ -217,19 +218,70 @@ export class LighthouseAISDK extends EventEmitter {
217218
// Update progress to uploading phase
218219
this.progress.updateProgress(operationId, 0, "uploading");
219220

220-
// Upload file using Lighthouse SDK
221-
const uploadResponse = await lighthouse.upload(
222-
filePath,
223-
apiKey,
224-
false, // dealStatus - set to false for now
225-
undefined, // endDate
226-
(data: any) => {
227-
// Convert Lighthouse progress format to our format
228-
if (data.loaded !== undefined) {
229-
progressCallback(data.loaded, data.total);
221+
// Calculate dynamic timeout based on file size (minimum 2 minutes, +30s per MB)
222+
const fileSizeMB = fileStats.size / (1024 * 1024);
223+
const dynamicTimeout = Math.max(120000, 120000 + fileSizeMB * 30000); // 2 min base + 30s per MB
224+
225+
// Read file as buffer to avoid fs-extra dependency issues
226+
const fileBuffer = readFileSync(filePath);
227+
228+
// Upload file using Lighthouse SDK buffer method with timeout
229+
let uploadResponse;
230+
try {
231+
const uploadPromise = lighthouse.uploadBuffer(fileBuffer, apiKey);
232+
uploadResponse = await this.withTimeout(uploadPromise, dynamicTimeout);
233+
} catch (error) {
234+
// Try fallback to direct API call if standard method fails
235+
const errorMessage = error instanceof Error ? error.message : String(error);
236+
237+
if (
238+
errorMessage.includes("ETIMEDOUT") ||
239+
errorMessage.includes("timeout") ||
240+
errorMessage.includes("ENOTFOUND") ||
241+
errorMessage.includes("ECONNREFUSED")
242+
) {
243+
console.warn("Standard upload failed, trying direct API fallback:", errorMessage);
244+
245+
try {
246+
uploadResponse = await this.uploadViDirectAPI(
247+
fileBuffer,
248+
apiKey,
249+
options.fileName || filePath.split("/").pop() || "file",
250+
);
251+
console.log("Fallback upload successful");
252+
} catch (fallbackError) {
253+
// If both methods fail, provide comprehensive error message
254+
throw new Error(`Upload failed with both methods:
255+
256+
Primary error: ${errorMessage}
257+
Fallback error: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}
258+
259+
This could be due to:
260+
• Network connectivity issues
261+
• Large file size (current: ${(fileStats.size / 1024 / 1024).toFixed(1)}MB)
262+
• Lighthouse servers being temporarily unavailable
263+
• Firewall or proxy blocking the connection
264+
• Invalid API key
265+
266+
Try uploading a smaller file or check your network connection.`);
230267
}
231-
},
232-
);
268+
} else if (errorMessage.includes("401") || errorMessage.includes("unauthorized")) {
269+
throw new Error(`Authentication failed. Please check:
270+
• Your API key is correct and valid
271+
• Your API key has upload permissions
272+
• Your API key hasn't expired
273+
274+
Current API key: ${apiKey.substring(0, 8)}...`);
275+
} else if (errorMessage.includes("413") || errorMessage.includes("too large")) {
276+
throw new Error(`File too large (${(fileStats.size / 1024 / 1024).toFixed(1)}MB).
277+
Maximum file size may be exceeded. Try uploading a smaller file.`);
278+
} else if (errorMessage.includes("429") || errorMessage.includes("rate limit")) {
279+
throw new Error(`Rate limit exceeded. Please wait a moment before trying again.`);
280+
}
281+
282+
// Re-throw original error if we can't classify it
283+
throw error;
284+
}
233285

234286
if (!uploadResponse || !uploadResponse.data || !uploadResponse.data.Hash) {
235287
throw new Error("Invalid upload response from Lighthouse");
@@ -257,6 +309,48 @@ export class LighthouseAISDK extends EventEmitter {
257309
}, "uploadFile");
258310
}
259311

312+
/**
313+
* Add timeout wrapper for promises
314+
*/
315+
private withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
316+
return Promise.race([
317+
promise,
318+
new Promise<never>((_, reject) =>
319+
setTimeout(() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)), timeoutMs),
320+
),
321+
]);
322+
}
323+
324+
/**
325+
* Upload file via direct API call as fallback when SDK fails
326+
*/
327+
private async uploadViDirectAPI(
328+
fileBuffer: Buffer,
329+
apiKey: string,
330+
fileName: string,
331+
): Promise<any> {
332+
// This is a fallback method that uses direct HTTP calls to api.lighthouse.storage
333+
// when the standard SDK fails (usually due to node.lighthouse.storage being down)
334+
335+
const FormData = eval("require")("form-data");
336+
const axios = eval("require")("axios");
337+
338+
const formData = new FormData();
339+
formData.append("file", fileBuffer, fileName);
340+
341+
const response = await axios.post("https://api.lighthouse.storage/api/v0/add", formData, {
342+
headers: {
343+
...formData.getHeaders(),
344+
Authorization: `Bearer ${apiKey}`,
345+
},
346+
timeout: 180000, // 3 minutes
347+
maxContentLength: Infinity,
348+
maxBodyLength: Infinity,
349+
});
350+
351+
return response;
352+
}
353+
260354
/**
261355
* Download a file from Lighthouse with comprehensive error handling and progress tracking.
262356
*

packages/vscode-extension/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 0xOse
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
const esbuild = require("esbuild");
2+
3+
const production = process.argv.includes("--production");
4+
const watch = process.argv.includes("--watch");
5+
6+
async function main() {
7+
const ctx = await esbuild.context({
8+
entryPoints: ["src/index.ts"],
9+
bundle: true,
10+
format: "cjs",
11+
minify: production,
12+
sourcemap: !production,
13+
sourcesContent: false,
14+
platform: "node",
15+
outfile: "dist/index.js",
16+
external: ["vscode"],
17+
// Mark optional encryption dependencies that may not be installed
18+
alias: {
19+
"@lighthouse-web3/kavach": "@lighthouse-web3/kavach",
20+
},
21+
logLevel: "info",
22+
plugins: [stubMissingModules, esbuildProblemMatcherPlugin],
23+
});
24+
25+
if (watch) {
26+
await ctx.watch();
27+
} else {
28+
await ctx.rebuild();
29+
await ctx.dispose();
30+
}
31+
}
32+
33+
/**
34+
* Stub missing optional modules (encryption features not used by extension)
35+
* @type {import('esbuild').Plugin}
36+
*/
37+
const stubMissingModules = {
38+
name: "stub-missing-modules",
39+
setup(build) {
40+
build.onResolve({ filter: /^(joi|bls-eth-wasm|@lighthouse-web3\/kavach)/ }, (args) => {
41+
return { path: args.path, namespace: "stub" };
42+
});
43+
build.onLoad({ filter: /.*/, namespace: "stub" }, () => {
44+
return {
45+
contents: "module.exports = {}",
46+
loader: "js",
47+
};
48+
});
49+
},
50+
};
51+
52+
/**
53+
* @type {import('esbuild').Plugin}
54+
*/
55+
const esbuildProblemMatcherPlugin = {
56+
name: "esbuild-problem-matcher",
57+
58+
setup(build) {
59+
build.onStart(() => {
60+
console.log("[watch] build started");
61+
});
62+
build.onEnd((result) => {
63+
result.errors.forEach(({ text, location }) => {
64+
console.error(`✘ [ERROR] ${text}`);
65+
console.error(` ${location.file}:${location.line}:${location.column}:`);
66+
});
67+
console.log("[watch] build finished");
68+
});
69+
},
70+
};
71+
72+
main().catch((e) => {
73+
console.error(e);
74+
process.exit(1);
75+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module.exports = {
2+
preset: "ts-jest",
3+
testEnvironment: "node",
4+
roots: ["<rootDir>/src"],
5+
testMatch: ["**/__tests__/**/*.test.ts"],
6+
transform: {
7+
"^.+\\.ts$": "ts-jest",
8+
},
9+
collectCoverageFrom: ["src/**/*.ts", "!src/**/*.d.ts", "!src/__tests__/**"],
10+
moduleNameMapper: {
11+
"^@lighthouse-tooling/(.*)$": "<rootDir>/../$1/src",
12+
"^vscode$": "<rootDir>/src/__mocks__/vscode.ts",
13+
},
14+
setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
15+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Jest setup file for VSCode extension tests
2+
// Mock fetch for tests
3+
global.fetch = jest.fn();

0 commit comments

Comments
 (0)