diff --git a/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/messages-constructors/chat-messages-constructor.ts b/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/messages-constructors/chat-messages-constructor.ts index 961ab49..b9dd63a 100644 --- a/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/messages-constructors/chat-messages-constructor.ts +++ b/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/messages-constructors/chat-messages-constructor.ts @@ -1,6 +1,7 @@ import type { ChatContext } from '@extension/webview-api/chat-context-processor/types/chat-context' import type { LangchainMessage } from '@extension/webview-api/chat-context-processor/types/langchain-message' import { HumanMessage, SystemMessage } from '@langchain/core/messages' +import { settledPromiseResults } from '@shared/utils/common' import { CHAT_WITH_FILE_SYSTEM_PROMPT, COMMON_SYSTEM_PROMPT } from './constants' import { ConversationMessageConstructor } from './conversation-message-constructor' @@ -70,15 +71,7 @@ ${explicitContext} hasAttachedFiles ).buildMessages() ) - const messageArrays = await Promise.allSettled(messagePromises) - const messages: LangchainMessage[] = [] - - messageArrays.forEach(result => { - if (result.status === 'fulfilled') { - messages.push(...result.value) - } - }) - - return messages + const messageArrays = await settledPromiseResults(messagePromises) + return messageArrays.flat() } } diff --git a/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/codebase-search-node.ts b/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/codebase-search-node.ts index 41506da..01e0b91 100644 --- a/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/codebase-search-node.ts +++ b/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/codebase-search-node.ts @@ -5,6 +5,7 @@ import { findCurrentToolsCallParams } from '@extension/webview-api/chat-context- import { mergeCodeSnippets } from '@extension/webview-api/chat-context-processor/utils/merge-code-snippets' import type { ToolMessage } from '@langchain/core/messages' import { DynamicStructuredTool } from '@langchain/core/tools' +import { settledPromiseResults } from '@shared/utils/common' import { z } from 'zod' import { @@ -39,13 +40,12 @@ export const createCodebaseSearchTool = async (state: ChatGraphState) => { if (!indexer) return searchResults - const searchPromisesResult = await Promise.allSettled( + const searchPromisesResult = await settledPromiseResults( queryParts?.map(queryPart => indexer.searchSimilarRow(queryPart)) || [] ) const searchCodeSnippets: CodeSnippet[] = searchPromisesResult - .filter(result => result.status === 'fulfilled') - .flatMap(result => (result as any).value) + .flat() .map(row => { // eslint-disable-next-line unused-imports/no-unused-vars const { embedding, ...others } = row @@ -117,7 +117,7 @@ export const codebaseSearchNode: ChatGraphNode = async state => { ] }) - await Promise.allSettled(toolCallsPromises) + await settledPromiseResults(toolCallsPromises) return { chatContext diff --git a/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/doc-retriever-node.ts b/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/doc-retriever-node.ts index 2544cb8..7bffaad 100644 --- a/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/doc-retriever-node.ts +++ b/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/doc-retriever-node.ts @@ -7,7 +7,7 @@ import { DocIndexer } from '@extension/webview-api/chat-context-processor/vector import { docSitesDB } from '@extension/webview-api/lowdb/doc-sites-db' import type { ToolMessage } from '@langchain/core/messages' import { DynamicStructuredTool } from '@langchain/core/tools' -import { removeDuplicates } from '@shared/utils/common' +import { removeDuplicates, settledPromiseResults } from '@shared/utils/common' import { z } from 'zod' import { @@ -51,21 +51,16 @@ export const createDocRetrieverTool = async (state: ChatGraphState) => { await docIndexer.initialize() - const searchResults = await Promise.allSettled( + const searchResults = await settledPromiseResults( keywords.map(keyword => docIndexer.searchSimilarRow(keyword)) ) const searchRows = removeDuplicates( - searchResults - .filter( - (result): result is PromiseFulfilledResult => - result.status === 'fulfilled' - ) - .flatMap(result => result.value), + searchResults.flatMap(result => result), ['fullPath'] ).slice(0, 3) - const docInfoResults = await Promise.allSettled( + const docInfoResults = await settledPromiseResults( searchRows.map(async row => ({ content: await docIndexer.getRowFileContent(row), path: docSite.url @@ -73,22 +68,10 @@ export const createDocRetrieverTool = async (state: ChatGraphState) => { ) return docInfoResults - .filter( - (result): result is PromiseFulfilledResult => - result.status === 'fulfilled' - ) - .map(result => result.value) }) - const results = await Promise.allSettled(docPromises) - const relevantDocs = results - .filter( - (result): result is PromiseFulfilledResult => - result.status === 'fulfilled' - ) - .flatMap(result => result.value) - - return relevantDocs + const results = await settledPromiseResults(docPromises) + return results.flatMap(result => result) } return new DynamicStructuredTool({ @@ -150,7 +133,7 @@ export const docRetrieverNode: ChatGraphNode = async state => { ] }) - await Promise.allSettled(toolCallsPromises) + await settledPromiseResults(toolCallsPromises) return { chatContext diff --git a/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/web-search-node.ts b/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/web-search-node.ts index ce63704..eb9ec17 100644 --- a/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/web-search-node.ts +++ b/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/web-search-node.ts @@ -7,6 +7,7 @@ import { CheerioWebBaseLoader } from '@langchain/community/document_loaders/web/ import type { Document } from '@langchain/core/documents' import { HumanMessage, type ToolMessage } from '@langchain/core/messages' import { DynamicStructuredTool } from '@langchain/core/tools' +import { settledPromiseResults } from '@shared/utils/common' import { z } from 'zod' import { ChatMessagesConstructor } from '../messages-constructors/chat-messages-constructor' @@ -38,17 +39,11 @@ export const createWebSearchTool = async (state: ChatGraphState) => { const searxngSearchResult = await searxngSearch(keywords) const urls = searxngSearchResult.results.map(result => result.url) - const docsLoadResult = await Promise.allSettled( + const docsLoadResult = await settledPromiseResults( urls.map(url => new CheerioWebBaseLoader(url).load()) ) - const docs: Document>[] = [] - - docsLoadResult.forEach(result => { - if (result.status === 'fulfilled') { - docs.push(...result.value) - } - }) + const docs: Document>[] = docsLoadResult.flat() const docsContent = docs .map(doc => doc.pageContent) @@ -156,7 +151,7 @@ export const webSearchNode: ChatGraphNode = async state => { ] }) - await Promise.allSettled(toolCallsPromises) + await settledPromiseResults(toolCallsPromises) return { chatContext diff --git a/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/web-visit-node.ts b/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/web-visit-node.ts index 8a59f93..c5e096b 100644 --- a/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/web-visit-node.ts +++ b/src/extension/webview-api/chat-context-processor/strategies/chat-strategy/nodes/web-visit-node.ts @@ -3,6 +3,7 @@ import { DocCrawler } from '@extension/webview-api/chat-context-processor/utils/ import { findCurrentToolsCallParams } from '@extension/webview-api/chat-context-processor/utils/find-current-tools-call-params' import type { ToolMessage } from '@langchain/core/messages' import { DynamicStructuredTool } from '@langchain/core/tools' +import { settledPromiseResults } from '@shared/utils/common' import { z } from 'zod' import { @@ -20,21 +21,15 @@ export const createWebVisitTool = async (state: ChatGraphState) => { const getPageContents = async ( urls: string[] ): Promise<{ url: string; content: string }[]> => { - const docCrawler = new DocCrawler(urls![0]!) - const promises = await Promise.allSettled( + const docCrawler = new DocCrawler(urls[0]!) + const contents = await settledPromiseResults( urls.map(async url => ({ url, content: (await docCrawler.getPageContent(url)) || 'Failed to retrieve content' })) ) - return promises - .filter(promise => promise.status === 'fulfilled') - .map( - promise => - (promise as PromiseFulfilledResult<{ url: string; content: string }>) - .value - ) + return contents } return new DynamicStructuredTool({ @@ -89,7 +84,7 @@ export const webVisitNode: ChatGraphNode = async state => { ] }) - await Promise.allSettled(toolCallsPromises) + await settledPromiseResults(toolCallsPromises) return { chatContext diff --git a/src/extension/webview-api/chat-context-processor/utils/combine-node.ts b/src/extension/webview-api/chat-context-processor/utils/combine-node.ts index e3cd152..00950d8 100644 --- a/src/extension/webview-api/chat-context-processor/utils/combine-node.ts +++ b/src/extension/webview-api/chat-context-processor/utils/combine-node.ts @@ -1,6 +1,6 @@ /* eslint-disable prefer-destructuring */ -import { logger } from '@extension/logger' import { BinaryOperatorAggregate } from '@langchain/langgraph' +import { settledPromiseResults } from '@shared/utils/common' import type { CreateAnnotationRoot } from '../types/langgraph' @@ -14,25 +14,18 @@ export const combineNode = >( ): GraphNode => { const combined: GraphNode = async state => { const promises = nodes.map(async node => await node(state)) - const promisesResults = await Promise.allSettled(promises) + const states = await settledPromiseResults(promises) const keys = new Set() - const states: Partial[] = [] - - promisesResults.forEach((result, index) => { - if (result.status === 'fulfilled') { - const partialState = result.value as Partial - Object.keys(partialState).forEach(key => - keys.add(key as keyof GraphState) - ) - states.push(partialState) - } else { - logger.warn(`Error in node ${index}:`, result.reason) - } + + states.forEach(partialState => { + Object.keys(partialState).forEach(key => + keys.add(key as keyof GraphState) + ) }) const combinedResult = {} as Partial - for (const _key in keys) { + for (const _key of keys) { const key = _key as keyof GraphState const annotation = stateDefinition.spec[key] diff --git a/src/extension/webview-api/chat-context-processor/utils/doc-crawler.ts b/src/extension/webview-api/chat-context-processor/utils/doc-crawler.ts index 52872f9..4f6848f 100644 --- a/src/extension/webview-api/chat-context-processor/utils/doc-crawler.ts +++ b/src/extension/webview-api/chat-context-processor/utils/doc-crawler.ts @@ -159,7 +159,7 @@ export class DocCrawler { while (this.queue.length > 0 && this.visited.size < this.options.maxPages) { const batch = this.queue.splice(0, this.options.concurrency) const promises = batch.map(item => this.crawlPage(item.url, item.depth)) - await Promise.all(promises) + await Promise.allSettled(promises) await new Promise(resolve => setTimeout(resolve, this.options.delay)) this.progressReporter.setProcessedItems(this.visited.size) } diff --git a/src/extension/webview-api/chat-context-processor/vectordb/base-indexer.ts b/src/extension/webview-api/chat-context-processor/vectordb/base-indexer.ts index e679a75..6ac714a 100644 --- a/src/extension/webview-api/chat-context-processor/vectordb/base-indexer.ts +++ b/src/extension/webview-api/chat-context-processor/vectordb/base-indexer.ts @@ -198,7 +198,7 @@ export abstract class BaseIndexer { this.progressReporter.reset() const filePaths = await this.getAllIndexedFilePaths() const filePathsNeedReindex: string[] = [] - const tasks = filePaths.map(async filePath => { + const tasksPromises = filePaths.map(async filePath => { try { const currentHash = await this.generateFileHash(filePath) const existingRows = await this.getFileRows(filePath) @@ -215,7 +215,7 @@ export abstract class BaseIndexer { } }) - await Promise.allSettled(tasks) + await Promise.allSettled(tasksPromises) this.totalFiles = filePathsNeedReindex.length this.progressReporter.setTotalItems(this.totalFiles) diff --git a/src/extension/webview-api/chat-context-processor/vectordb/codebase-indexer.ts b/src/extension/webview-api/chat-context-processor/vectordb/codebase-indexer.ts index 6f092b9..e735a90 100644 --- a/src/extension/webview-api/chat-context-processor/vectordb/codebase-indexer.ts +++ b/src/extension/webview-api/chat-context-processor/vectordb/codebase-indexer.ts @@ -4,6 +4,7 @@ import { getExt, getSemanticHashName } from '@extension/file-utils/paths' import { traverseFileOrFolders } from '@extension/file-utils/traverse-fs' import { VsCodeFS } from '@extension/file-utils/vscode-fs' import { logger } from '@extension/logger' +import { settledPromiseResults } from '@shared/utils/common' import { languageIdExts } from '@shared/utils/vscode-lang' import * as vscode from 'vscode' @@ -61,7 +62,7 @@ export class CodebaseIndexer extends BaseIndexer { } }) - return Promise.all(chunkRowsPromises) + return settledPromiseResults(chunkRowsPromises) } private async chunkCodeFile(filePath: string): Promise { diff --git a/src/extension/webview-api/chat-context-processor/vectordb/doc-indexer.ts b/src/extension/webview-api/chat-context-processor/vectordb/doc-indexer.ts index 4f2734a..884ff07 100644 --- a/src/extension/webview-api/chat-context-processor/vectordb/doc-indexer.ts +++ b/src/extension/webview-api/chat-context-processor/vectordb/doc-indexer.ts @@ -4,6 +4,7 @@ import { getSemanticHashName } from '@extension/file-utils/paths' import { traverseFileOrFolders } from '@extension/file-utils/traverse-fs' import { VsCodeFS } from '@extension/file-utils/vscode-fs' import { logger } from '@extension/logger' +import { settledPromiseResults } from '@shared/utils/common' import { CodeChunkerManager, type TextChunk } from '../tree-sitter/code-chunker' import { ProgressReporter } from '../utils/process-reporter' @@ -59,7 +60,7 @@ export class DocIndexer extends BaseIndexer { } }) - return Promise.all(chunkRowsPromises) + return settledPromiseResults(chunkRowsPromises) } private async chunkCodeFile(filePath: string): Promise { diff --git a/src/extension/webview-api/controllers/git.controller.ts b/src/extension/webview-api/controllers/git.controller.ts index ac74fd3..48cad36 100644 --- a/src/extension/webview-api/controllers/git.controller.ts +++ b/src/extension/webview-api/controllers/git.controller.ts @@ -1,6 +1,7 @@ import type { CommandManager } from '@extension/commands/command-manager' import type { RegisterManager } from '@extension/registers/register-manager' import { getWorkspaceFolder } from '@extension/utils' +import { settledPromiseResults } from '@shared/utils/common' import simpleGit, { SimpleGit } from 'simple-git' import type { @@ -27,7 +28,7 @@ export class GitController extends Controller { const { maxCount = 50 } = req const log = await this.git.log({ maxCount }) - const commits: GitCommit[] = await Promise.all( + const commits: GitCommit[] = await settledPromiseResults( log.all.map(async commit => { const diff = await this.git.diff([`${commit.hash}^`, commit.hash]) return { diff --git a/src/shared/utils/common.ts b/src/shared/utils/common.ts index 0ffd83a..2cd2b77 100644 --- a/src/shared/utils/common.ts +++ b/src/shared/utils/common.ts @@ -36,3 +36,14 @@ export const tryStringifyJSON = (obj: any) => { return null } } + +export async function settledPromiseResults( + promises: Promise[] +): Promise { + const results = await Promise.allSettled(promises) + return results + .map((result, index) => ({ result, index })) + .filter(item => item.result.status === 'fulfilled') + .sort((a, b) => a.index - b.index) + .map(item => (item.result as PromiseFulfilledResult).value) +}