diff --git a/backend/package.json b/backend/package.json index 4232c3f29..f973f7eed 100644 --- a/backend/package.json +++ b/backend/package.json @@ -40,7 +40,6 @@ "express": "4.19.2", "gpt-tokenizer": "2.8.1", "ignore": "5.3.2", - "lodash": "*", "micromatch": "^4.0.8", "openai": "^4.78.1", "pino": "9.4.0", diff --git a/backend/src/get-documentation-for-query.ts b/backend/src/get-documentation-for-query.ts index 4755857b6..86b5505fb 100644 --- a/backend/src/get-documentation-for-query.ts +++ b/backend/src/get-documentation-for-query.ts @@ -1,6 +1,5 @@ import { models } from '@codebuff/common/old-constants' import { closeXml } from '@codebuff/common/util/xml' -import { uniq } from 'lodash' import { z } from 'zod/v4' import { fetchContext7LibraryDocumentation } from '@codebuff/agent-runtime/llm-api/context7-api' @@ -75,11 +74,13 @@ export async function getDocumentationForQuery( ).flat() const maxChunks = 25 - const allUniqueChunks = uniq( - allRawChunks - .filter((chunk) => chunk !== null) - .join(DELIMITER) - .split(DELIMITER), + const allUniqueChunks = Array.from( + new Set( + allRawChunks + .filter((chunk) => chunk !== null) + .join(DELIMITER) + .split(DELIMITER), + ), ).slice(0, maxChunks) if (allUniqueChunks.length === 0) { diff --git a/backend/src/websockets/server.ts b/backend/src/websockets/server.ts index 267e87416..9cc2e4480 100644 --- a/backend/src/websockets/server.ts +++ b/backend/src/websockets/server.ts @@ -1,6 +1,5 @@ import { setSessionConnected } from '@codebuff/agent-runtime/live-user-inputs' import { CLIENT_MESSAGE_SCHEMA } from '@codebuff/common/websockets/websocket-schema' -import { isError } from 'lodash' import { WebSocketServer } from 'ws' import { Switchboard } from './switchboard' @@ -27,7 +26,7 @@ export class MessageParseError extends Error { } function serializeError(err: unknown) { - return isError(err) ? err.message : 'Unexpected error.' + return err instanceof Error ? err.message : 'Unexpected error.' } async function processMessage(params: { diff --git a/bun.lock b/bun.lock index 4f6ac5103..596ca3f0d 100644 --- a/bun.lock +++ b/bun.lock @@ -10,7 +10,6 @@ "devDependencies": { "@tanstack/react-query": "^5.59.16", "@types/bun": "^1.3.0", - "@types/lodash": "4.17.7", "@types/node": "^22.9.0", "@types/node-fetch": "^2.6.12", "@types/parse-path": "^7.1.0", @@ -20,7 +19,6 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-unused-imports": "^4.1.4", "ignore": "^6.0.2", - "lodash": "4.17.21", "prettier": "3.3.2", "ts-node": "^10.9.2", "ts-pattern": "^5.5.0", @@ -56,7 +54,6 @@ "express": "4.19.2", "gpt-tokenizer": "2.8.1", "ignore": "5.3.2", - "lodash": "*", "micromatch": "^4.0.8", "openai": "^4.78.1", "pino": "9.4.0", @@ -114,7 +111,6 @@ "drizzle-kit": "0.28.1", "drizzle-orm": "0.36.4", "ignore": "5.3.2", - "lodash": "*", "next-auth": "^4.24.7", "partial-json": "^0.1.7", "pg": "^8.14.1", @@ -143,7 +139,6 @@ "@oclif/parser": "^3.8.17", "async": "^3.2.6", "diff": "^8.0.2", - "lodash": "^4.17.21", "p-limit": "^6.2.0", "zod": "3.25.67", }, @@ -172,7 +167,6 @@ "ignore": "7.0.3", "isomorphic-git": "^1.29.0", "jimp": "^1.6.0", - "lodash": "*", "markdown-it": "^14.1.0", "markdown-it-terminal": "^0.4.0", "micromatch": "^4.0.8", @@ -266,11 +260,9 @@ "@codebuff/backend": "workspace:*", "@codebuff/bigquery": "workspace:*", "@codebuff/common": "workspace:*", - "lodash": "^4.17.21", }, "devDependencies": { "@types/bun": "^1.3.0", - "@types/lodash": "^4.14.195", "@types/node": "22", }, }, @@ -1392,8 +1384,6 @@ "@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="], - "@types/lodash": ["@types/lodash@4.17.7", "", {}, "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA=="], - "@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="], "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], diff --git a/common/package.json b/common/package.json index e3854e510..84b809e50 100644 --- a/common/package.json +++ b/common/package.json @@ -40,7 +40,6 @@ "drizzle-kit": "0.28.1", "drizzle-orm": "0.36.4", "ignore": "5.3.2", - "lodash": "*", "next-auth": "^4.24.7", "partial-json": "^0.1.7", "pg": "^8.14.1", diff --git a/common/src/project-file-tree.ts b/common/src/project-file-tree.ts index 6e52944ab..0d98c95e2 100644 --- a/common/src/project-file-tree.ts +++ b/common/src/project-file-tree.ts @@ -1,7 +1,6 @@ import path from 'path' import * as ignore from 'ignore' -import { sortBy } from 'lodash' import { DEFAULT_IGNORED_PATHS } from './old-constants' import { isValidProjectRoot } from './util/file' @@ -209,10 +208,9 @@ export function getLastReadFilePaths( flattenedNodes: FileTreeNode[], count: number, ) { - return sortBy( - flattenedNodes.filter((node) => node.lastReadTime), - 'lastReadTime', - ) + return flattenedNodes + .filter((node) => node.lastReadTime) + .sort((a, b) => (a.lastReadTime || 0) - (b.lastReadTime || 0)) .reverse() .slice(0, count) .map((node) => node.filePath) diff --git a/common/src/util/array.ts b/common/src/util/array.ts index 44ac05dc2..8cddb831f 100644 --- a/common/src/util/array.ts +++ b/common/src/util/array.ts @@ -1,4 +1,4 @@ -import { compact, flattenDeep, isEqual } from 'lodash' +import { isEqual } from './lodash-replacements' export function filterDefined(array: (T | null | undefined)[]) { return array.filter((item) => item !== null && item !== undefined) as T[] @@ -7,8 +7,21 @@ export function filterDefined(array: (T | null | undefined)[]) { type Falsey = false | undefined | null | 0 | '' type FalseyValueArray = T | Falsey | FalseyValueArray[] -export function buildArray(...params: FalseyValueArray[]) { - return compact(flattenDeep(params)) as T[] +export function buildArray(...params: FalseyValueArray[]): T[] { + const result: any[] = [] + + function flatten(arr: any): void { + for (const item of arr) { + if (Array.isArray(item)) { + flatten(item) + } else if (item) { + result.push(item) + } + } + } + + flatten(params) + return result as T[] } export function groupConsecutive(xs: T[], key: (x: T) => U) { diff --git a/common/src/util/lodash-replacements.ts b/common/src/util/lodash-replacements.ts new file mode 100644 index 000000000..2d5eec016 --- /dev/null +++ b/common/src/util/lodash-replacements.ts @@ -0,0 +1,122 @@ +// Shared lodash replacement utilities +// These functions replace lodash with native JavaScript implementations + +// Deep clone using JSON serialization (works for serializable objects) +export function cloneDeep(obj: T): T { + return JSON.parse(JSON.stringify(obj)) as T +} + +// Deep equality check using JSON serialization +export function isEqual(a: unknown, b: unknown): boolean { + try { + return JSON.stringify(a) === JSON.stringify(b) + } catch { + return a === b + } +} + +// Fisher-Yates shuffle algorithm +export function shuffle(array: T[]): T[] { + const result = [...array] + for (let i = result.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + ;[result[i], result[j]] = [result[j], result[i]] + } + return result +} + +// Generate a range of numbers +export function range(count: number): number[] { + return Array.from({ length: count }, (_, i) => i) +} + +// Sum an array by extracting numeric values with a function +export function sumBy(arr: T[], fn: (item: T) => number): number { + return arr.reduce((sum, item) => sum + fn(item), 0) +} + +// Map values of an object +export function mapValues( + obj: T, + fn: (value: any, key: keyof T) => R, +): { [K in keyof T]: R } { + return Object.fromEntries( + Object.entries(obj).map(([k, v]) => [k, fn(v, k as keyof T)]), + ) as { [K in keyof T]: R } +} + +// Union of two arrays +export function union(arr1: T[], arr2: T[]): T[] { + return Array.from(new Set([...arr1, ...arr2])) +} + +// Partition an array into two arrays based on a predicate +export function partition( + array: T[], + predicate: (value: T) => value is S, +): [S[], Exclude[]] +export function partition( + array: T[], + predicate: (value: T) => boolean, +): [T[], T[]] +export function partition( + array: T[], + predicate: (value: T) => boolean, +): [T[], T[]] { + const truthy: T[] = [] + const falsy: T[] = [] + for (const item of array) { + if (predicate(item)) { + truthy.push(item) + } else { + falsy.push(item) + } + } + return [truthy, falsy] +} + +// Remove duplicates from an array +export function uniq(arr: T[]): T[] { + return Array.from(new Set(arr)) +} + +// Add debounce implementation export below +export function debounce any>(fn: T, wait = 0) { + let timeout: ReturnType | null = null + let lastArgs: any + let lastThis: any + let result: ReturnType + + const debounced = function (this: any, ...args: Parameters) { + lastArgs = args + lastThis = this + if (timeout) clearTimeout(timeout) + timeout = setTimeout(() => { + timeout = null + result = fn.apply(lastThis, lastArgs) + }, wait) + return result + } as unknown as T & { + cancel: () => void + flush: () => ReturnType | undefined + } + + debounced.cancel = () => { + if (timeout) { + clearTimeout(timeout) + timeout = null + } + } + + debounced.flush = () => { + if (timeout) { + clearTimeout(timeout) + timeout = null + result = fn.apply(lastThis, lastArgs) + return result + } + return result + } + + return debounced +} diff --git a/common/src/util/messages.ts b/common/src/util/messages.ts index 3a3e7f10f..ae3a14131 100644 --- a/common/src/util/messages.ts +++ b/common/src/util/messages.ts @@ -1,6 +1,6 @@ -import { cloneDeep, has, isEqual } from 'lodash' - import { buildArray } from './array' +import { cloneDeep, isEqual } from './lodash-replacements' + import { getToolCallString } from '../tools/utils' import type { @@ -22,9 +22,9 @@ export function toContentString(msg: ModelMessage): string { export function withCacheControl< T extends { providerOptions?: ProviderMetadata }, >(obj: T): T { - const wrapper = cloneDeep(obj) + const wrapper = cloneDeep(obj) as T if (!wrapper.providerOptions) { - wrapper.providerOptions = {} + wrapper.providerOptions = {} as ProviderMetadata } if (!wrapper.providerOptions.anthropic) { wrapper.providerOptions.anthropic = {} @@ -40,9 +40,10 @@ export function withCacheControl< export function withoutCacheControl< T extends { providerOptions?: ProviderMetadata }, >(obj: T): T { - const wrapper = cloneDeep(obj) - if (has(wrapper.providerOptions?.anthropic?.cacheControl, 'type')) { - delete wrapper.providerOptions?.anthropic?.cacheControl?.type + const wrapper = cloneDeep(obj) as T + const anthropicCache = wrapper.providerOptions?.anthropic?.cacheControl + if (anthropicCache && typeof anthropicCache === 'object' && 'type' in anthropicCache) { + delete (anthropicCache as any).type } if ( Object.keys(wrapper.providerOptions?.anthropic?.cacheControl ?? {}) @@ -54,8 +55,9 @@ export function withoutCacheControl< delete wrapper.providerOptions?.anthropic } - if (has(wrapper.providerOptions?.openrouter?.cacheControl, 'type')) { - delete wrapper.providerOptions?.openrouter?.cacheControl?.type + const openrouterCache = wrapper.providerOptions?.openrouter?.cacheControl + if (openrouterCache && typeof openrouterCache === 'object' && 'type' in openrouterCache) { + delete (openrouterCache as any).type } if ( Object.keys(wrapper.providerOptions?.openrouter?.cacheControl ?? {}) diff --git a/common/src/util/object.ts b/common/src/util/object.ts index 3232adcb3..20df14445 100644 --- a/common/src/util/object.ts +++ b/common/src/util/object.ts @@ -1,4 +1,4 @@ -import { isEqual, mapValues, union } from 'lodash' +import { isEqual, mapValues, union } from './lodash-replacements' export const removeUndefinedProps = ( obj: T, @@ -59,8 +59,8 @@ export const subtractObjects = ( } export const hasChanges = (obj: T, partial: Partial) => { - const currValues = mapValues(partial, (_, key: keyof T) => obj[key]) - return !isEqual(currValues, partial) + const currValues = mapValues(partial as T, (_, key: keyof T) => obj[key]) + return !isEqual(currValues, partial as any) } export const hasSignificantDeepChanges = ( @@ -74,8 +74,8 @@ export const hasSignificantDeepChanges = ( } if (typeof currValue === 'object' && typeof partialValue === 'object') { return hasSignificantDeepChanges( - currValue, - partialValue, + currValue as any, + partialValue as any, epsilonForNumbers, ) } diff --git a/common/src/util/string.ts b/common/src/util/string.ts index 596350738..43460f9d7 100644 --- a/common/src/util/string.ts +++ b/common/src/util/string.ts @@ -1,4 +1,4 @@ -import { sumBy } from 'lodash' +import { sumBy } from './lodash-replacements' export const truncateString = (str: string, maxLength: number) => { if (str.length <= maxLength) { diff --git a/evals/git-evals/run-git-evals.ts b/evals/git-evals/run-git-evals.ts index 491029ab0..f30aec54d 100644 --- a/evals/git-evals/run-git-evals.ts +++ b/evals/git-evals/run-git-evals.ts @@ -5,9 +5,9 @@ import path from 'path' import { disableLiveUserInputCheck } from '@codebuff/agent-runtime/live-user-inputs' import { promptAiSdkStructured } from '@codebuff/backend/llm-apis/vercel-ai-sdk/ai-sdk' import { getErrorObject } from '@codebuff/common/util/error' +import { cloneDeep } from '@codebuff/common/util/lodash-replacements' import { withTimeout } from '@codebuff/common/util/promise' import { generateCompactId } from '@codebuff/common/util/string' -import { cloneDeep } from 'lodash' import pLimit from 'p-limit' import { resetRepoToCommit } from '../scaffolding' diff --git a/evals/package.json b/evals/package.json index a8556049e..e98339784 100644 --- a/evals/package.json +++ b/evals/package.json @@ -41,7 +41,6 @@ "@oclif/parser": "^3.8.17", "async": "^3.2.6", "diff": "^8.0.2", - "lodash": "^4.17.21", "p-limit": "^6.2.0", "zod": "3.25.67" }, diff --git a/npm-app/package.json b/npm-app/package.json index f506a69b2..a7bfca172 100644 --- a/npm-app/package.json +++ b/npm-app/package.json @@ -48,7 +48,6 @@ "ignore": "7.0.3", "isomorphic-git": "^1.29.0", "jimp": "^1.6.0", - "lodash": "*", "markdown-it": "^14.1.0", "markdown-it-terminal": "^0.4.0", "micromatch": "^4.0.8", diff --git a/npm-app/src/cli.ts b/npm-app/src/cli.ts index 13868853f..7fec51204 100644 --- a/npm-app/src/cli.ts +++ b/npm-app/src/cli.ts @@ -12,7 +12,6 @@ import { } from '@codebuff/common/util/agent-name-resolver' import { isDir } from '@codebuff/common/util/file' import { pluralize } from '@codebuff/common/util/string' -import { uniq } from 'lodash' import { blueBright, bold, @@ -526,17 +525,19 @@ export class CLI { return p } - const matchingPaths = uniq( - allFiles - .map((filePath) => { - let candidate = null - while (filePath.includes(searchTerm)) { - candidate = filePath - filePath = path.dirname(filePath) + '/' - } - return candidate - }) - .filter((filePath): filePath is string => !!filePath), + const matchingPaths = Array.from( + new Set( + allFiles + .map((filePath) => { + let candidate = null + while (filePath.includes(searchTerm)) { + candidate = filePath + filePath = path.dirname(filePath) + '/' + } + return candidate + }) + .filter((filePath): filePath is string => !!filePath), + ), ).sort((a, b) => priority(a) - priority(b)) if (matchingPaths.length > 0) { diff --git a/npm-app/src/dev-process-manager.ts b/npm-app/src/dev-process-manager.ts index 954829291..7ef77cb2b 100644 --- a/npm-app/src/dev-process-manager.ts +++ b/npm-app/src/dev-process-manager.ts @@ -2,7 +2,6 @@ import path from 'path' import { codebuffConfigFile } from '@codebuff/common/json-config/constants' import { generateCompactId } from '@codebuff/common/util/string' -import { has } from 'lodash' import { yellow } from 'picocolors' import { runBackgroundCommand } from './terminal/background' @@ -59,7 +58,8 @@ export function startDevProcesses( stderrFile, }, (result) => { - if (has(result[0].value, 'processId')) { + const value = result[0].value as any + if ('processId' in value) { console.log(yellow(`- ${name}: ${command}`)) } else { console.log(yellow(`- ${name}: ${command} — failed to start`)) diff --git a/npm-app/src/json-config/hooks.ts b/npm-app/src/json-config/hooks.ts index cafa99bcb..5e6aca537 100644 --- a/npm-app/src/json-config/hooks.ts +++ b/npm-app/src/json-config/hooks.ts @@ -1,5 +1,4 @@ import { generateCompactId } from '@codebuff/common/util/string' -import { has } from 'lodash' import micromatch from 'micromatch' import { bold, gray } from 'picocolors' @@ -67,7 +66,8 @@ export async function runFileChangeHooks(filesChanged: string[]): Promise<{ undefined, undefined, ) - if (has(result[0].value, 'exitCode') && result[0].value.exitCode !== 0) { + const value = result[0].value as any + if ('exitCode' in value && value.exitCode !== 0) { someHooksFailed = true // Show user this hook failed? // logger.warn( diff --git a/package.json b/package.json index ac7e93dd6..1a2c585a8 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "devDependencies": { "@tanstack/react-query": "^5.59.16", "@types/bun": "^1.3.0", - "@types/lodash": "4.17.7", "@types/node": "^22.9.0", "@types/node-fetch": "^2.6.12", "@types/parse-path": "^7.1.0", @@ -58,7 +57,6 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-unused-imports": "^4.1.4", "ignore": "^6.0.2", - "lodash": "4.17.21", "prettier": "3.3.2", "ts-node": "^10.9.2", "ts-pattern": "^5.5.0", diff --git a/packages/agent-runtime/src/find-files/request-files-prompt.ts b/packages/agent-runtime/src/find-files/request-files-prompt.ts index 19156a6c4..457071736 100644 --- a/packages/agent-runtime/src/find-files/request-files-prompt.ts +++ b/packages/agent-runtime/src/find-files/request-files-prompt.ts @@ -6,7 +6,7 @@ import { type FinetunedVertexModel, } from '@codebuff/common/old-constants' import { getAllFilePaths } from '@codebuff/common/project-file-tree' -import { range, shuffle, uniq } from 'lodash' +import { range, shuffle, uniq } from '@codebuff/common/util/lodash-replacements' import { promptFlashWithFallbacks } from '../llm-api/gemini-with-fallbacks' import { diff --git a/packages/agent-runtime/src/get-file-reading-updates.ts b/packages/agent-runtime/src/get-file-reading-updates.ts index c66bca927..23646fc8f 100644 --- a/packages/agent-runtime/src/get-file-reading-updates.ts +++ b/packages/agent-runtime/src/get-file-reading-updates.ts @@ -1,6 +1,5 @@ -import { uniq } from 'lodash' - import type { RequestFilesFn } from '@codebuff/common/types/contracts/client' +import { uniq } from '@codebuff/common/util/lodash-replacements' export async function getFileReadingUpdates(params: { requestFiles: RequestFilesFn diff --git a/packages/agent-runtime/src/main-prompt.ts b/packages/agent-runtime/src/main-prompt.ts index f5ae1196a..61d9d82d3 100644 --- a/packages/agent-runtime/src/main-prompt.ts +++ b/packages/agent-runtime/src/main-prompt.ts @@ -1,6 +1,5 @@ import { AgentTemplateTypes } from '@codebuff/common/types/session-state' import { generateCompactId } from '@codebuff/common/util/string' -import { uniq } from 'lodash' import { checkTerminalCommand } from './check-terminal-command' import { loopAgentSteps } from './run-agent-step' @@ -141,12 +140,13 @@ export const mainPrompt = async ( } = fileContext.codebuffConfig ?? {} updatedSubagents = spawnableAgents ?? - uniq([...mainAgentTemplate.spawnableAgents, ...availableAgents]) + Array.from( + new Set([...mainAgentTemplate.spawnableAgents, ...availableAgents]), + ) - updatedSubagents = uniq([ - ...updatedSubagents, - ...addedSpawnableAgents, - ]).filter((subagent) => !removedSpawnableAgents.includes(subagent)) + updatedSubagents = Array.from( + new Set([...updatedSubagents, ...addedSpawnableAgents]), + ).filter((subagent) => !removedSpawnableAgents.includes(subagent)) } mainAgentTemplate.spawnableAgents = updatedSubagents localAgentTemplates[agentType] = mainAgentTemplate diff --git a/packages/agent-runtime/src/run-agent-step.ts b/packages/agent-runtime/src/run-agent-step.ts index 2523d37cd..7f19e352f 100644 --- a/packages/agent-runtime/src/run-agent-step.ts +++ b/packages/agent-runtime/src/run-agent-step.ts @@ -4,7 +4,7 @@ import { supportsCacheControl } from '@codebuff/common/old-constants' import { TOOLS_WHICH_WONT_FORCE_NEXT_STEP } from '@codebuff/common/tools/constants' import { buildArray } from '@codebuff/common/util/array' import { getErrorObject } from '@codebuff/common/util/error' -import { cloneDeep } from 'lodash' +import { cloneDeep } from '@codebuff/common/util/lodash-replacements' import { checkLiveUserInput } from './live-user-inputs' import { getMCPToolData } from './mcp' diff --git a/packages/agent-runtime/src/run-programmatic-step.ts b/packages/agent-runtime/src/run-programmatic-step.ts index 4ce969c06..3b5affa30 100644 --- a/packages/agent-runtime/src/run-programmatic-step.ts +++ b/packages/agent-runtime/src/run-programmatic-step.ts @@ -1,6 +1,6 @@ import { getToolCallString } from '@codebuff/common/tools/utils' import { getErrorObject } from '@codebuff/common/util/error' -import { cloneDeep } from 'lodash' +import { cloneDeep } from '@codebuff/common/util/lodash-replacements' import { executeToolCall } from './tools/tool-executor' import { SandboxManager } from './util/quickjs-sandbox' diff --git a/packages/agent-runtime/src/tools/handlers/tool/write-file.ts b/packages/agent-runtime/src/tools/handlers/tool/write-file.ts index b50e5a533..804b4533a 100644 --- a/packages/agent-runtime/src/tools/handlers/tool/write-file.ts +++ b/packages/agent-runtime/src/tools/handlers/tool/write-file.ts @@ -1,4 +1,4 @@ -import { partition } from 'lodash' +import { partition } from '@codebuff/common/util/lodash-replacements' import { processFileBlock } from '../../../process-file-block' diff --git a/packages/agent-runtime/src/tools/stream-parser.ts b/packages/agent-runtime/src/tools/stream-parser.ts index c390be0e2..dffe23f7f 100644 --- a/packages/agent-runtime/src/tools/stream-parser.ts +++ b/packages/agent-runtime/src/tools/stream-parser.ts @@ -5,8 +5,8 @@ import { toolNames, } from '@codebuff/common/tools/constants' import { buildArray } from '@codebuff/common/util/array' +import { cloneDeep } from '@codebuff/common/util/lodash-replacements' import { generateCompactId } from '@codebuff/common/util/string' -import { cloneDeep } from 'lodash' import { processStreamWithTags } from '../tool-stream-parser' import { executeCustomToolCall, executeToolCall } from './tool-executor' diff --git a/packages/agent-runtime/src/tools/tool-executor.ts b/packages/agent-runtime/src/tools/tool-executor.ts index 76a80a7b2..c2fc20dbc 100644 --- a/packages/agent-runtime/src/tools/tool-executor.ts +++ b/packages/agent-runtime/src/tools/tool-executor.ts @@ -1,7 +1,7 @@ import { endsAgentStepParam } from '@codebuff/common/tools/constants' +import { cloneDeep } from '@codebuff/common/util/lodash-replacements' import { generateCompactId } from '@codebuff/common/util/string' import { type ToolCallPart } from 'ai' -import { cloneDeep } from 'lodash' import z from 'zod/v4' import { convertJsonSchemaToZod } from 'zod-from-json-schema' diff --git a/packages/agent-runtime/src/util/messages.ts b/packages/agent-runtime/src/util/messages.ts index 8070ba3f4..d02d7a8e7 100644 --- a/packages/agent-runtime/src/util/messages.ts +++ b/packages/agent-runtime/src/util/messages.ts @@ -2,8 +2,8 @@ import { AssertionError } from 'assert' import { buildArray } from '@codebuff/common/util/array' import { getErrorObject } from '@codebuff/common/util/error' +import { cloneDeep, isEqual } from '@codebuff/common/util/lodash-replacements' import { closeXml } from '@codebuff/common/util/xml' -import { cloneDeep, isEqual } from 'lodash' import { simplifyTerminalCommandResults } from './simplify-tool-results' import { countTokensJson } from './token-counter' diff --git a/packages/agent-runtime/src/util/simplify-tool-results.ts b/packages/agent-runtime/src/util/simplify-tool-results.ts index 8ecd1458d..8b6dc6ce5 100644 --- a/packages/agent-runtime/src/util/simplify-tool-results.ts +++ b/packages/agent-runtime/src/util/simplify-tool-results.ts @@ -1,5 +1,5 @@ import { getErrorObject } from '@codebuff/common/util/error' -import { cloneDeep } from 'lodash' +import { cloneDeep } from '@codebuff/common/util/lodash-replacements' import type { CodebuffToolOutput } from '@codebuff/common/tools/list' import type { Logger } from '@codebuff/common/types/contracts/logger' diff --git a/packages/code-map/src/parse.ts b/packages/code-map/src/parse.ts index 8c651b80d..000b81d99 100644 --- a/packages/code-map/src/parse.ts +++ b/packages/code-map/src/parse.ts @@ -1,6 +1,8 @@ import * as fs from 'fs' import * as path from 'path' +import { uniq } from '@codebuff/common/util/lodash-replacements' + import { getLanguageConfig, LanguageConfig } from './languages' import type { Parser, Query } from 'web-tree-sitter' @@ -172,8 +174,8 @@ export function parseTokens( throw new Error('Parser or query not found') } const parseResults = parseFile(parser, query, sourceCode) - const identifiers = Array.from(new Set(parseResults.identifier)) - const calls = Array.from(new Set(parseResults['call.identifier'])) + const identifiers = uniq(parseResults.identifier) + const calls = uniq(parseResults['call.identifier']) if (DEBUG_PARSING) { console.log(`\nParsing ${filePath}:`) diff --git a/scripts/analyze-edit-blocks.ts b/scripts/analyze-edit-blocks.ts index fa3835df3..472561a22 100644 --- a/scripts/analyze-edit-blocks.ts +++ b/scripts/analyze-edit-blocks.ts @@ -4,8 +4,8 @@ import path from 'path' import { db } from '@codebuff/common/db' import * as schema from '@codebuff/common/db/schema' import { fileRegex } from '@codebuff/common/util/file' +import { shuffle } from '@codebuff/common/util/lodash-replacements' import { eq, desc } from 'drizzle-orm' -import { shuffle } from 'lodash' interface EditBlock { filePath: string diff --git a/scripts/generate-ci-env.js b/scripts/generate-ci-env.js index 40e9221b6..0f4cbe0a4 100644 --- a/scripts/generate-ci-env.js +++ b/scripts/generate-ci-env.js @@ -7,6 +7,10 @@ import fs from 'fs' import path from 'path' import { fileURLToPath } from 'url' +// Native uniq implementation (lodash replacement) +function uniq(arr) { + return Array.from(new Set(arr)) +} const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) @@ -88,7 +92,7 @@ function generateGitHubEnv() { } else if (scope === 'client') { selected = varsByScope.client } else { - selected = Array.from(new Set([...varsByScope.server, ...varsByScope.client])) + selected = uniq([...varsByScope.server, ...varsByScope.client]) } if (prefix) { diff --git a/scripts/package.json b/scripts/package.json index 1777df15d..eceb3546f 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -24,12 +24,10 @@ "@ai-sdk/openai-compatible": "^1.0.19", "@codebuff/backend": "workspace:*", "@codebuff/bigquery": "workspace:*", - "@codebuff/common": "workspace:*", - "lodash": "^4.17.21" + "@codebuff/common": "workspace:*" }, "devDependencies": { "@types/bun": "^1.3.0", - "@types/lodash": "^4.14.195", "@types/node": "22" } } diff --git a/sdk/src/run-state.ts b/sdk/src/run-state.ts index ef0f58530..e6c949b58 100644 --- a/sdk/src/run-state.ts +++ b/sdk/src/run-state.ts @@ -2,7 +2,7 @@ import * as os from 'os' import path from 'path' import { getFileTokenScores } from '@codebuff/code-map/parse' -import { cloneDeep } from 'lodash' +import { cloneDeep } from '@codebuff/common/util/lodash-replacements' import { getProjectFileTree, @@ -36,7 +36,7 @@ export type InitialSessionStateOptions = { customToolDefinitions?: CustomToolDefinition[] maxAgentSteps?: number fs?: CodebuffFileSystem -}; +} /** * Processes agent definitions array and converts handleSteps functions to strings @@ -167,11 +167,11 @@ function deriveKnowledgeFiles( export function initialSessionState( options: InitialSessionStateOptions, -): Promise; +): Promise export function initialSessionState( cwd: string, options?: Omit, -): Promise; +): Promise export async function initialSessionState( arg1: string | InitialSessionStateOptions, arg2?: Omit, diff --git a/sdk/src/run.ts b/sdk/src/run.ts index 1187d6ba8..cbda50509 100644 --- a/sdk/src/run.ts +++ b/sdk/src/run.ts @@ -1,6 +1,6 @@ import path from 'path' -import { cloneDeep } from 'lodash' +import { cloneDeep } from '@codebuff/common/util/lodash-replacements' import { initialSessionState, applyOverridesToSessionState } from './run-state' import { stripToolCallPayloads } from './tool-xml-buffer' @@ -466,8 +466,7 @@ export async function run({ chunkType !== 'subagent-finish' ) { await emitPendingSection(ROOT_AGENT_KEY) - const pendingAgentId = - 'agentId' in chunk ? chunk.agentId : undefined + const pendingAgentId = 'agentId' in chunk ? chunk.agentId : undefined if (pendingAgentId && pendingAgentId !== ROOT_AGENT_KEY) { await emitPendingSection(pendingAgentId, pendingAgentId) } diff --git a/web/src/hooks/use-auto-topup.ts b/web/src/hooks/use-auto-topup.ts index 7e21b0ad1..5adbcf829 100644 --- a/web/src/hooks/use-auto-topup.ts +++ b/web/src/hooks/use-auto-topup.ts @@ -1,6 +1,6 @@ import { convertStripeGrantAmountToCredits } from '@codebuff/common/util/currency' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import debounce from 'lodash/debounce' +import { debounce } from '@codebuff/common/util/lodash-replacements' import { useState, useCallback, useRef, useEffect } from 'react' import type { AutoTopupState } from '@/components/auto-topup/types' @@ -49,12 +49,12 @@ export function useAutoTopup(): AutoTopupState { auto_topup_threshold: clamp( thresholdCredits, MIN_THRESHOLD_CREDITS, - MAX_THRESHOLD_CREDITS + MAX_THRESHOLD_CREDITS, ), initialTopUpDollars: clamp( topUpDollars > 0 ? topUpDollars : MIN_TOPUP_DOLLARS, MIN_TOPUP_DOLLARS, - MAX_TOPUP_DOLLARS + MAX_TOPUP_DOLLARS, ), } }, @@ -76,7 +76,7 @@ export function useAutoTopup(): AutoTopupState { setIsEnabled(userProfile.auto_topup_enabled ?? false) setThreshold(userProfile.auto_topup_threshold ?? MIN_THRESHOLD_CREDITS) setTopUpAmountDollars( - userProfile.initialTopUpDollars ?? MIN_TOPUP_DOLLARS + userProfile.initialTopUpDollars ?? MIN_TOPUP_DOLLARS, ) setTimeout(() => { isInitialLoad.current = false @@ -91,7 +91,7 @@ export function useAutoTopup(): AutoTopupState { UserProfile, 'auto_topup_enabled' | 'auto_topup_threshold' | 'auto_topup_amount' > - > + >, ) => { const payload = { enabled: settings.auto_topup_enabled, @@ -121,20 +121,20 @@ export function useAutoTopup(): AutoTopupState { const topUpCredits = convertStripeGrantAmountToCredits( payload.amount * 100, - CENTS_PER_CREDIT + CENTS_PER_CREDIT, ) const minTopUpCredits = convertStripeGrantAmountToCredits( MIN_TOPUP_DOLLARS * 100, - CENTS_PER_CREDIT + CENTS_PER_CREDIT, ) const maxTopUpCredits = convertStripeGrantAmountToCredits( MAX_TOPUP_DOLLARS * 100, - CENTS_PER_CREDIT + CENTS_PER_CREDIT, ) if (topUpCredits < minTopUpCredits || topUpCredits > maxTopUpCredits) { throw new Error( - `Top-up amount must result in between ${minTopUpCredits} and ${maxTopUpCredits} credits.` + `Top-up amount must result in between ${minTopUpCredits} and ${maxTopUpCredits} credits.`, ) } } @@ -196,7 +196,7 @@ export function useAutoTopup(): AutoTopupState { setIsEnabled(updatedData.auto_topup_enabled ?? false) setThreshold(updatedData.auto_topup_threshold ?? MIN_THRESHOLD_CREDITS) setTopUpAmountDollars( - updatedData.initialTopUpDollars ?? MIN_TOPUP_DOLLARS + updatedData.initialTopUpDollars ?? MIN_TOPUP_DOLLARS, ) return updatedData @@ -214,7 +214,7 @@ export function useAutoTopup(): AutoTopupState { setIsEnabled(userProfile.auto_topup_enabled ?? false) setThreshold(userProfile.auto_topup_threshold ?? MIN_THRESHOLD_CREDITS) setTopUpAmountDollars( - userProfile.initialTopUpDollars ?? MIN_TOPUP_DOLLARS + userProfile.initialTopUpDollars ?? MIN_TOPUP_DOLLARS, ) } pendingSettings.current = null @@ -246,7 +246,7 @@ export function useAutoTopup(): AutoTopupState { auto_topup_amount: currentTopUpDollars, }) }, 750), - [autoTopupMutation, userProfile] + [autoTopupMutation, userProfile], ) const handleThresholdChange = (rawValue: number) => { @@ -258,7 +258,7 @@ export function useAutoTopup(): AutoTopupState { const validValue = clamp( rawValue, MIN_THRESHOLD_CREDITS, - MAX_THRESHOLD_CREDITS + MAX_THRESHOLD_CREDITS, ) pendingSettings.current = { threshold: validValue, topUpAmountDollars } @@ -335,7 +335,7 @@ export function useAutoTopup(): AutoTopupState { onError: () => { setIsEnabled(false) }, - } + }, ) } else { autoTopupMutation.mutate( @@ -351,7 +351,7 @@ export function useAutoTopup(): AutoTopupState { onError: () => { setIsEnabled(true) }, - } + }, ) } } diff --git a/web/src/hooks/use-org-auto-topup.ts b/web/src/hooks/use-org-auto-topup.ts index 4bb890d98..ca26d6d91 100644 --- a/web/src/hooks/use-org-auto-topup.ts +++ b/web/src/hooks/use-org-auto-topup.ts @@ -1,6 +1,6 @@ import { CREDIT_PRICING } from '@codebuff/common/old-constants' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import debounce from 'lodash/debounce' +import { debounce } from '@codebuff/common/util/lodash-replacements' import { useCallback, useEffect, useRef, useState } from 'react' import { toast } from '@/components/ui/use-toast' @@ -52,7 +52,7 @@ export function useOrgAutoTopup(organizationId: string): OrgAutoTopupState { const queryClient = useQueryClient() const [isEnabled, setIsEnabled] = useState(false) const [threshold, setThreshold] = useState( - ORG_AUTO_TOPUP_CONSTANTS.MIN_THRESHOLD_CREDITS + ORG_AUTO_TOPUP_CONSTANTS.MIN_THRESHOLD_CREDITS, ) const [topUpAmountDollars, setTopUpAmountDollars] = useState(MIN_TOPUP_DOLLARS) @@ -85,18 +85,18 @@ export function useOrgAutoTopup(organizationId: string): OrgAutoTopupState { autoTopupThreshold: clamp( thresholdCredits, ORG_AUTO_TOPUP_CONSTANTS.MIN_THRESHOLD_CREDITS, - ORG_AUTO_TOPUP_CONSTANTS.MAX_THRESHOLD_CREDITS + ORG_AUTO_TOPUP_CONSTANTS.MAX_THRESHOLD_CREDITS, ), autoTopupAmount: clamp( topUpAmount, ORG_AUTO_TOPUP_CONSTANTS.MIN_TOPUP_CREDITS, (ORG_AUTO_TOPUP_CONSTANTS.MAX_TOPUP_DOLLARS * 100) / - ORG_AUTO_TOPUP_CONSTANTS.CENTS_PER_CREDIT + ORG_AUTO_TOPUP_CONSTANTS.CENTS_PER_CREDIT, ), initialTopUpDollars: clamp( topUpDollars > 0 ? topUpDollars : MIN_TOPUP_DOLLARS, MIN_TOPUP_DOLLARS, - ORG_AUTO_TOPUP_CONSTANTS.MAX_TOPUP_DOLLARS + ORG_AUTO_TOPUP_CONSTANTS.MAX_TOPUP_DOLLARS, ), } }, @@ -110,7 +110,7 @@ export function useOrgAutoTopup(organizationId: string): OrgAutoTopupState { setIsEnabled(organizationSettings.autoTopupEnabled ?? false) setThreshold( organizationSettings.autoTopupThreshold ?? - ORG_AUTO_TOPUP_CONSTANTS.MIN_THRESHOLD_CREDITS + ORG_AUTO_TOPUP_CONSTANTS.MIN_THRESHOLD_CREDITS, ) const topUpDollars = (organizationSettings.autoTopupAmount * @@ -129,7 +129,7 @@ export function useOrgAutoTopup(organizationId: string): OrgAutoTopupState { autoTopupEnabled: boolean autoTopupThreshold: number autoTopupAmount: number - }> + }>, ) => { const payload = { autoTopupEnabled: settings.autoTopupEnabled, @@ -216,18 +216,18 @@ export function useOrgAutoTopup(organizationId: string): OrgAutoTopupState { setIsEnabled(updatedData.autoTopupEnabled ?? false) setThreshold( updatedData.autoTopupThreshold ?? - ORG_AUTO_TOPUP_CONSTANTS.MIN_THRESHOLD_CREDITS + ORG_AUTO_TOPUP_CONSTANTS.MIN_THRESHOLD_CREDITS, ) const topUpDollars = (updatedData.autoTopupAmount * ORG_AUTO_TOPUP_CONSTANTS.CENTS_PER_CREDIT) / 100 setTopUpAmountDollars( - topUpDollars > 0 ? topUpDollars : MIN_TOPUP_DOLLARS + topUpDollars > 0 ? topUpDollars : MIN_TOPUP_DOLLARS, ) return updatedData - } + }, ) pendingSettings.current = null @@ -242,14 +242,14 @@ export function useOrgAutoTopup(organizationId: string): OrgAutoTopupState { setIsEnabled(organizationSettings.autoTopupEnabled ?? false) setThreshold( organizationSettings.autoTopupThreshold ?? - ORG_AUTO_TOPUP_CONSTANTS.MIN_THRESHOLD_CREDITS + ORG_AUTO_TOPUP_CONSTANTS.MIN_THRESHOLD_CREDITS, ) const topUpDollars = (organizationSettings.autoTopupAmount * ORG_AUTO_TOPUP_CONSTANTS.CENTS_PER_CREDIT) / 100 setTopUpAmountDollars( - topUpDollars > 0 ? topUpDollars : MIN_TOPUP_DOLLARS + topUpDollars > 0 ? topUpDollars : MIN_TOPUP_DOLLARS, ) } pendingSettings.current = null @@ -265,7 +265,7 @@ export function useOrgAutoTopup(organizationId: string): OrgAutoTopupState { topUpAmountDollars: currentTopUpDollars, } = pendingSettings.current const currentTopUpCredits = Math.round( - (currentTopUpDollars * 100) / ORG_AUTO_TOPUP_CONSTANTS.CENTS_PER_CREDIT + (currentTopUpDollars * 100) / ORG_AUTO_TOPUP_CONSTANTS.CENTS_PER_CREDIT, ) if ( @@ -283,7 +283,7 @@ export function useOrgAutoTopup(organizationId: string): OrgAutoTopupState { autoTopupAmount: currentTopUpCredits, }) }, 750), - [autoTopupMutation, organizationSettings] + [autoTopupMutation, organizationSettings], ) const handleThresholdChange = (rawValue: number) => { @@ -295,7 +295,7 @@ export function useOrgAutoTopup(organizationId: string): OrgAutoTopupState { const validValue = clamp( rawValue, ORG_AUTO_TOPUP_CONSTANTS.MIN_THRESHOLD_CREDITS, - ORG_AUTO_TOPUP_CONSTANTS.MAX_THRESHOLD_CREDITS + ORG_AUTO_TOPUP_CONSTANTS.MAX_THRESHOLD_CREDITS, ) pendingSettings.current = { threshold: validValue, topUpAmountDollars } @@ -318,7 +318,7 @@ export function useOrgAutoTopup(organizationId: string): OrgAutoTopupState { const validValue = clamp( rawValue, MIN_TOPUP_DOLLARS, - ORG_AUTO_TOPUP_CONSTANTS.MAX_TOPUP_DOLLARS + ORG_AUTO_TOPUP_CONSTANTS.MAX_TOPUP_DOLLARS, ) pendingSettings.current = { threshold, topUpAmountDollars: validValue } @@ -365,7 +365,7 @@ export function useOrgAutoTopup(organizationId: string): OrgAutoTopupState { } const topUpCredits = Math.round( - (topUpAmountDollars * 100) / ORG_AUTO_TOPUP_CONSTANTS.CENTS_PER_CREDIT + (topUpAmountDollars * 100) / ORG_AUTO_TOPUP_CONSTANTS.CENTS_PER_CREDIT, ) try { diff --git a/web/src/lib/docs.ts b/web/src/lib/docs.ts index fa9a66696..7d4c772ba 100644 --- a/web/src/lib/docs.ts +++ b/web/src/lib/docs.ts @@ -34,5 +34,5 @@ export async function getNewsArticles(): Promise { // export function getAllCategories() { // if (!allDocs) return [] -// return Array.from(new Set((allDocs as Doc[]).map((doc: Doc) => doc.category))) +// return uniq((allDocs as Doc[]).map((doc: Doc) => doc.category)) // }