Skip to content

Commit ff55a40

Browse files
committed
core: remove @effect/language-service plugin and optimize hot path type performance
- Removed @effect/language-service from both packages/core and packages/opencode tsconfig files and dependencies - Wrapped mergeDeep calls in config loading and LLM streaming to avoid expensive remeda conditional merge type instantiations in hot paths - Narrowed Drizzle migrate() overload signature to avoid expensive variance checks during database initialization These changes reduce TypeScript type-checking overhead and improve startup and runtime performance for config loading, LLM streaming, and database migrations.
1 parent 8b56d77 commit ff55a40

7 files changed

Lines changed: 30 additions & 37 deletions

File tree

bun.lock

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/tsconfig.json

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,6 @@
22
"$schema": "https://json.schemastore.org/tsconfig",
33
"extends": "@tsconfig/bun/tsconfig.json",
44
"compilerOptions": {
5-
"noUncheckedIndexedAccess": false,
6-
"plugins": [
7-
{
8-
"name": "@effect/language-service",
9-
"transform": "@effect/language-service/transform",
10-
"namespaceImportPackages": ["effect", "@effect/*"]
11-
}
12-
]
5+
"noUncheckedIndexedAccess": false
136
}
147
}

packages/opencode/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"license": "MIT",
77
"private": true,
88
"scripts": {
9-
"prepare": "effect-language-service patch || true",
109
"typecheck": "tsgo --noEmit",
1110
"test": "bun test --timeout 30000",
1211
"test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",
@@ -42,7 +41,6 @@
4241
},
4342
"devDependencies": {
4443
"@babel/core": "7.28.4",
45-
"@effect/language-service": "0.84.2",
4644
"@octokit/webhooks-types": "7.6.1",
4745
"@opencode-ai/script": "workspace:*",
4846
"@opencode-ai/core": "workspace:*",

packages/opencode/src/config/config.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import path from "path"
33
import { pathToFileURL } from "url"
44
import os from "os"
55
import z from "zod"
6-
import { mergeDeep, pipe } from "remeda"
6+
import { mergeDeep } from "remeda"
77
import { Global } from "@opencode-ai/core/global"
88
import fsNode from "fs/promises"
99
import { NamedError } from "@opencode-ai/core/util/error"
@@ -47,8 +47,13 @@ import { Npm } from "@opencode-ai/core/npm"
4747
const log = Log.create({ service: "config" })
4848

4949
// Custom merge function that concatenates array fields instead of replacing them
50+
// Keep remeda's deep conditional merge type out of hot config-loading paths; TS profiling showed it dominates here.
51+
function mergeConfig(target: Info, source: Info): Info {
52+
return mergeDeep(target, source) as Info
53+
}
54+
5055
function mergeConfigConcatArrays(target: Info, source: Info): Info {
51-
const merged = mergeDeep(target, source)
56+
const merged = mergeConfig(target, source)
5257
if (target.instructions && source.instructions) {
5358
merged.instructions = Array.from(new Set([...target.instructions, ...source.instructions]))
5459
}
@@ -387,12 +392,10 @@ export const layer = Layer.effect(
387392
})
388393

389394
const loadGlobal = Effect.fnUntraced(function* () {
390-
let result: Info = pipe(
391-
{},
392-
mergeDeep(yield* loadFile(path.join(Global.Path.config, "config.json"))),
393-
mergeDeep(yield* loadFile(path.join(Global.Path.config, "opencode.json"))),
394-
mergeDeep(yield* loadFile(path.join(Global.Path.config, "opencode.jsonc"))),
395-
)
395+
let result: Info = {}
396+
result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "config.json")))
397+
result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "opencode.json")))
398+
result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "opencode.jsonc")))
396399

397400
const legacy = path.join(Global.Path.config, "config")
398401
if (existsSync(legacy)) {
@@ -402,7 +405,7 @@ export const layer = Layer.effect(
402405
const { provider, model, ...rest } = mod.default
403406
if (provider && model) result.model = `${provider}/${model}`
404407
result["$schema"] = "https://opencode.ai/config.json"
405-
result = mergeDeep(result, rest)
408+
result = mergeConfig(result, rest)
406409
await fsNode.writeFile(path.join(Global.Path.config, "config.json"), JSON.stringify(result, null, 2))
407410
await fsNode.unlink(legacy)
408411
})

packages/opencode/src/session/llm.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as Log from "@opencode-ai/core/util/log"
33
import { Context, Effect, Layer, Record } from "effect"
44
import * as Stream from "effect/Stream"
55
import { streamText, wrapLanguageModel, type ModelMessage, type Tool, tool, jsonSchema } from "ai"
6-
import { mergeDeep, pipe } from "remeda"
6+
import { mergeDeep } from "remeda"
77
import { GitLabWorkflowLanguageModel } from "gitlab-ai-provider"
88
import { ProviderTransform } from "@/provider/transform"
99
import { Config } from "@/config/config"
@@ -29,6 +29,10 @@ const log = Log.create({ service: "llm" })
2929
export const OUTPUT_TOKEN_MAX = ProviderTransform.OUTPUT_TOKEN_MAX
3030
type Result = Awaited<ReturnType<typeof streamText>>
3131

32+
// Avoid re-instantiating remeda's deep merge types in this hot LLM path; the runtime behavior is still mergeDeep.
33+
const mergeOptions = (target: Record<string, any>, source: Record<string, any> | undefined): Record<string, any> =>
34+
mergeDeep(target, source ?? {}) as Record<string, any>
35+
3236
export type StreamInput = {
3337
user: MessageV2.User
3438
sessionID: string
@@ -134,11 +138,9 @@ const live: Layer.Layer<
134138
sessionID: input.sessionID,
135139
providerOptions: item.options,
136140
})
137-
const options: Record<string, any> = pipe(
138-
base,
139-
mergeDeep(input.model.options),
140-
mergeDeep(input.agent.options),
141-
mergeDeep(variant),
141+
const options = mergeOptions(
142+
mergeOptions(mergeOptions(base, input.model.options), input.agent.options),
143+
variant,
142144
)
143145
if (isOpenaiOauth) {
144146
options.instructions = system.join("\n")

packages/opencode/src/storage/db.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ type Client = SQLiteBunDatabase
4848

4949
type Journal = { sql: string; timestamp: number; name: string }[]
5050

51+
// Drizzle's migrate overloads trigger expensive variance checks here; narrow to the journal overload we actually use.
52+
const migrateFromJournal = migrate as unknown as (db: SQLiteBunDatabase, entries: Journal) => void
53+
54+
function applyMigrations(db: SQLiteBunDatabase, entries: Journal) {
55+
migrateFromJournal(db, entries)
56+
}
57+
5158
function time(tag: string) {
5259
const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(tag)
5360
if (!match) return 0
@@ -108,7 +115,7 @@ export const Client = lazy(() => {
108115
item.sql = "select 1;"
109116
}
110117
}
111-
migrate(db, entries)
118+
applyMigrations(db, entries)
112119
}
113120

114121
return db

packages/opencode/tsconfig.json

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,6 @@
1212
"@/*": ["./src/*"],
1313
"@tui/*": ["./src/cli/cmd/tui/*"],
1414
"@test/*": ["./test/*"]
15-
},
16-
"plugins": [
17-
{
18-
"name": "@effect/language-service",
19-
"transform": "@effect/language-service/transform",
20-
"namespaceImportPackages": ["effect", "@effect/*"]
21-
}
22-
]
15+
}
2316
}
2417
}

0 commit comments

Comments
 (0)