Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
13 changes: 7 additions & 6 deletions backend/src/get-documentation-for-query.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 1 addition & 2 deletions backend/src/websockets/server.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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: {
Expand Down
10 changes: 0 additions & 10 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
},
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
},
},
Expand Down Expand Up @@ -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=="],
Expand Down
1 change: 0 additions & 1 deletion common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 3 additions & 5 deletions common/src/project-file-tree.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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)
Expand Down
19 changes: 16 additions & 3 deletions common/src/util/array.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { compact, flattenDeep, isEqual } from 'lodash'
import { isEqual } from './lodash-replacements'

export function filterDefined<T>(array: (T | null | undefined)[]) {
return array.filter((item) => item !== null && item !== undefined) as T[]
Expand All @@ -7,8 +7,21 @@ export function filterDefined<T>(array: (T | null | undefined)[]) {
type Falsey = false | undefined | null | 0 | ''
type FalseyValueArray<T> = T | Falsey | FalseyValueArray<T>[]

export function buildArray<T>(...params: FalseyValueArray<T>[]) {
return compact(flattenDeep(params)) as T[]
export function buildArray<T>(...params: FalseyValueArray<T>[]): 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<T, U>(xs: T[], key: (x: T) => U) {
Expand Down
122 changes: 122 additions & 0 deletions common/src/util/lodash-replacements.ts
Original file line number Diff line number Diff line change
@@ -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<T>(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<T>(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<T>(arr: T[], fn: (item: T) => number): number {
return arr.reduce((sum, item) => sum + fn(item), 0)
}

// Map values of an object
export function mapValues<T extends object, R>(
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<T>(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<T, S extends T>(
array: T[],
predicate: (value: T) => value is S,
): [S[], Exclude<T, S>[]]
export function partition<T>(
array: T[],
predicate: (value: T) => boolean,
): [T[], T[]]
export function partition<T>(
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<T>(arr: T[]): T[] {
return Array.from(new Set(arr))
}

// Add debounce implementation export below
export function debounce<T extends (...args: any[]) => any>(fn: T, wait = 0) {
let timeout: ReturnType<typeof setTimeout> | null = null
let lastArgs: any
let lastThis: any
let result: ReturnType<T>

const debounced = function (this: any, ...args: Parameters<T>) {
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<T> | 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
}
20 changes: 11 additions & 9 deletions common/src/util/messages.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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 = {}
Expand All @@ -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 ?? {})
Expand All @@ -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 ?? {})
Expand Down
10 changes: 5 additions & 5 deletions common/src/util/object.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isEqual, mapValues, union } from 'lodash'
import { isEqual, mapValues, union } from './lodash-replacements'

export const removeUndefinedProps = <T extends object>(
obj: T,
Expand Down Expand Up @@ -59,8 +59,8 @@ export const subtractObjects = <T extends { [key: string]: number }>(
}

export const hasChanges = <T extends object>(obj: T, partial: Partial<T>) => {
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 = <T extends object>(
Expand All @@ -74,8 +74,8 @@ export const hasSignificantDeepChanges = <T extends object>(
}
if (typeof currValue === 'object' && typeof partialValue === 'object') {
return hasSignificantDeepChanges(
currValue,
partialValue,
currValue as any,
partialValue as any,
epsilonForNumbers,
)
}
Expand Down
2 changes: 1 addition & 1 deletion common/src/util/string.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { sumBy } from 'lodash'
import { sumBy } from './lodash-replacements'

export const truncateString = (str: string, maxLength: number) => {
if (str.length <= maxLength) {
Expand Down
2 changes: 1 addition & 1 deletion evals/git-evals/run-git-evals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Loading