Skip to content

Commit 5431716

Browse files
refactor: consolidate lodash replacement utilities into a shared module to reduce duplication and standardize usage across backend, sdk, and tooling code.
🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
1 parent ba665a8 commit 5431716

File tree

16 files changed

+27
-163
lines changed

16 files changed

+27
-163
lines changed

backend/src/run-agent-step.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@ import { supportsCacheControl } from '@codebuff/common/old-constants'
1919
import { TOOLS_WHICH_WONT_FORCE_NEXT_STEP } from '@codebuff/common/tools/constants'
2020
import { buildArray } from '@codebuff/common/util/array'
2121
import { getErrorObject } from '@codebuff/common/util/error'
22-
23-
// Deep clone using JSON serialization (works for serializable objects)
24-
function cloneDeep<T>(obj: T): T {
25-
return JSON.parse(JSON.stringify(obj))
26-
}
22+
import { cloneDeep } from '@codebuff/common/util/lodash-replacements'
2723

2824
import { runProgrammaticStep } from './run-programmatic-step'
2925
import { processStreamWithTools } from './tools/stream-parser'

backend/src/run-programmatic-step.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import { SandboxManager } from '@codebuff/agent-runtime/util/quickjs-sandbox'
22
import { getToolCallString } from '@codebuff/common/tools/utils'
33
import { getErrorObject } from '@codebuff/common/util/error'
4-
5-
// Deep clone using JSON serialization (works for serializable objects)
6-
function cloneDeep<T>(obj: T): T {
7-
return JSON.parse(JSON.stringify(obj))
8-
}
4+
import { cloneDeep } from '@codebuff/common/util/lodash-replacements'
95

106
import { executeToolCall } from './tools/tool-executor'
117

backend/src/tools/stream-parser.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,9 @@ import {
77
toolNames,
88
} from '@codebuff/common/tools/constants'
99
import { buildArray } from '@codebuff/common/util/array'
10+
import { cloneDeep } from '@codebuff/common/util/lodash-replacements'
1011
import { generateCompactId } from '@codebuff/common/util/string'
1112

12-
// Deep clone using JSON serialization (works for serializable objects)
13-
function cloneDeep<T>(obj: T): T {
14-
return JSON.parse(JSON.stringify(obj))
15-
}
16-
1713
import { executeCustomToolCall, executeToolCall } from './tool-executor'
1814

1915
import type { CustomToolCall, ExecuteToolCallParams } from './tool-executor'

backend/src/tools/tool-executor.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,10 @@ import { checkLiveUserInput } from '@codebuff/agent-runtime/live-user-inputs'
22
import { getMCPToolData } from '@codebuff/agent-runtime/mcp'
33
import { codebuffToolDefs } from '@codebuff/agent-runtime/tools/definitions/list'
44
import { endsAgentStepParam } from '@codebuff/common/tools/constants'
5+
import { cloneDeep } from '@codebuff/common/util/lodash-replacements'
56
import { generateCompactId } from '@codebuff/common/util/string'
67
import { type ToolCallPart } from 'ai'
78
import z from 'zod/v4'
8-
9-
// Deep clone using JSON serialization (works for serializable objects)
10-
function cloneDeep<T>(obj: T): T {
11-
return JSON.parse(JSON.stringify(obj))
12-
}
139
import { convertJsonSchemaToZod } from 'zod-from-json-schema'
1410

1511
import { codebuffToolHandlers } from './handlers/list'

common/src/util/array.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
// Deep equality check using JSON serialization
2-
// Note: Not exported to avoid type recursion issues
3-
function isEqual(a: unknown, b: unknown): boolean {
4-
try {
5-
return JSON.stringify(a) === JSON.stringify(b)
6-
} catch {
7-
return a === b
8-
}
9-
}
1+
import { isEqual } from './lodash-replacements'
102

113
export function filterDefined<T>(array: (T | null | undefined)[]) {
124
return array.filter((item) => item !== null && item !== undefined) as T[]

common/src/util/messages.ts

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
11
import { buildArray } from './array'
2+
import { cloneDeep, isEqual } from './lodash-replacements'
23

3-
// Deep clone using JSON serialization (works for serializable objects)
4-
function cloneDeep<T>(obj: T): T {
5-
return JSON.parse(JSON.stringify(obj))
6-
}
7-
8-
// Deep equality check using JSON serialization
9-
function isEqual(a: unknown, b: unknown): boolean {
10-
try {
11-
return JSON.stringify(a) === JSON.stringify(b)
12-
} catch {
13-
return a === b
14-
}
15-
}
164
import { getToolCallString } from '../tools/utils'
175

186
import type {
@@ -34,9 +22,9 @@ export function toContentString(msg: ModelMessage): string {
3422
export function withCacheControl<
3523
T extends { providerOptions?: ProviderMetadata },
3624
>(obj: T): T {
37-
const wrapper = cloneDeep(obj)
25+
const wrapper = cloneDeep(obj) as T
3826
if (!wrapper.providerOptions) {
39-
wrapper.providerOptions = {}
27+
wrapper.providerOptions = {} as ProviderMetadata
4028
}
4129
if (!wrapper.providerOptions.anthropic) {
4230
wrapper.providerOptions.anthropic = {}
@@ -52,12 +40,10 @@ export function withCacheControl<
5240
export function withoutCacheControl<
5341
T extends { providerOptions?: ProviderMetadata },
5442
>(obj: T): T {
55-
const wrapper = cloneDeep(obj)
56-
if (
57-
wrapper.providerOptions?.anthropic?.cacheControl &&
58-
'type' in wrapper.providerOptions.anthropic.cacheControl
59-
) {
60-
delete (wrapper.providerOptions.anthropic.cacheControl as any).type
43+
const wrapper = cloneDeep(obj) as T
44+
const anthropicCache = wrapper.providerOptions?.anthropic?.cacheControl
45+
if (anthropicCache && typeof anthropicCache === 'object' && 'type' in anthropicCache) {
46+
delete (anthropicCache as any).type
6147
}
6248
if (
6349
Object.keys(wrapper.providerOptions?.anthropic?.cacheControl ?? {})
@@ -69,11 +55,9 @@ export function withoutCacheControl<
6955
delete wrapper.providerOptions?.anthropic
7056
}
7157

72-
if (
73-
wrapper.providerOptions?.openrouter?.cacheControl &&
74-
'type' in wrapper.providerOptions.openrouter.cacheControl
75-
) {
76-
delete (wrapper.providerOptions.openrouter.cacheControl as any).type
58+
const openrouterCache = wrapper.providerOptions?.openrouter?.cacheControl
59+
if (openrouterCache && typeof openrouterCache === 'object' && 'type' in openrouterCache) {
60+
delete (openrouterCache as any).type
7761
}
7862
if (
7963
Object.keys(wrapper.providerOptions?.openrouter?.cacheControl ?? {})

common/src/util/object.ts

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,4 @@
1-
// Deep equality check using JSON serialization
2-
function isEqual(a: unknown, b: unknown): boolean {
3-
try {
4-
return JSON.stringify(a) === JSON.stringify(b)
5-
} catch {
6-
return a === b
7-
}
8-
}
9-
10-
// Map values of an object
11-
function mapValues<T extends object, R>(
12-
obj: T,
13-
fn: (value: any, key: keyof T) => R,
14-
): { [K in keyof T]: R } {
15-
return Object.fromEntries(
16-
Object.entries(obj).map(([k, v]) => [k, fn(v, k as keyof T)]),
17-
) as { [K in keyof T]: R }
18-
}
19-
20-
// Union of two arrays
21-
function union<T>(arr1: T[], arr2: T[]): T[] {
22-
return Array.from(new Set([...arr1, ...arr2]))
23-
}
1+
import { isEqual, mapValues, union } from './lodash-replacements'
242

253
export const removeUndefinedProps = <T extends object>(
264
obj: T,
@@ -81,7 +59,7 @@ export const subtractObjects = <T extends { [key: string]: number }>(
8159
}
8260

8361
export const hasChanges = <T extends object>(obj: T, partial: Partial<T>) => {
84-
const currValues = mapValues(partial, (_, key: keyof T) => obj[key])
62+
const currValues = mapValues(partial as T, (_, key: keyof T) => obj[key])
8563
return !isEqual(currValues, partial as any)
8664
}
8765

common/src/util/string.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
// Sum an array by extracting numeric values with a function
2-
function sumBy<T>(arr: T[], fn: (item: T) => number): number {
3-
return arr.reduce((sum, item) => sum + fn(item), 0)
4-
}
1+
import { sumBy } from './lodash-replacements'
52

63
export const truncateString = (str: string, maxLength: number) => {
74
if (str.length <= maxLength) {

evals/git-evals/run-git-evals.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,11 @@ import path from 'path'
55
import { disableLiveUserInputCheck } from '@codebuff/agent-runtime/live-user-inputs'
66
import { promptAiSdkStructured } from '@codebuff/backend/llm-apis/vercel-ai-sdk/ai-sdk'
77
import { getErrorObject } from '@codebuff/common/util/error'
8+
import { cloneDeep } from '@codebuff/common/util/lodash-replacements'
89
import { withTimeout } from '@codebuff/common/util/promise'
910
import { generateCompactId } from '@codebuff/common/util/string'
1011
import pLimit from 'p-limit'
1112

12-
// Deep clone using JSON serialization (works for serializable objects)
13-
function cloneDeep<T>(obj: T): T {
14-
return JSON.parse(JSON.stringify(obj))
15-
}
16-
1713
import { resetRepoToCommit } from '../scaffolding'
1814
import { createInitialSessionState } from '../test-setup'
1915
import { judgeEvalRun } from './judge-git-eval'

packages/agent-runtime/src/find-files/request-files-prompt.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,7 @@ import {
66
type FinetunedVertexModel,
77
} from '@codebuff/common/old-constants'
88
import { getAllFilePaths } from '@codebuff/common/project-file-tree'
9-
10-
// Fisher-Yates shuffle algorithm
11-
function shuffle<T>(array: T[]): T[] {
12-
const result = [...array]
13-
for (let i = result.length - 1; i > 0; i--) {
14-
const j = Math.floor(Math.random() * (i + 1))
15-
;[result[i], result[j]] = [result[j], result[i]]
16-
}
17-
return result
18-
}
19-
20-
// Generate a range of numbers
21-
function range(count: number): number[] {
22-
return Array.from({ length: count }, (_, i) => i)
23-
}
9+
import { range, shuffle } from '@codebuff/common/util/lodash-replacements'
2410

2511
import { promptFlashWithFallbacks } from '../llm-api/gemini-with-fallbacks'
2612
import {

0 commit comments

Comments
 (0)