summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2026-04-30 23:20:20 -0400
committerDax Raad <[email protected]>2026-04-30 23:21:05 -0400
commitff55a40749fdb749e8519cca65c0dcf8e330349e (patch)
treed8716f5e30df543356d9550937ccab6a21e56794
parent8b56d77ea13c34ab1aa97e9c26cddf2ee75cf494 (diff)
downloadopencode-ff55a40749fdb749e8519cca65c0dcf8e330349e.tar.gz
opencode-ff55a40749fdb749e8519cca65c0dcf8e330349e.zip
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.
-rw-r--r--bun.lock3
-rw-r--r--packages/core/tsconfig.json9
-rw-r--r--packages/opencode/package.json2
-rw-r--r--packages/opencode/src/config/config.ts21
-rw-r--r--packages/opencode/src/session/llm.ts14
-rw-r--r--packages/opencode/src/storage/db.ts9
-rw-r--r--packages/opencode/tsconfig.json9
7 files changed, 30 insertions, 37 deletions
diff --git a/bun.lock b/bun.lock
index 64c372661..093bb880a 100644
--- a/bun.lock
+++ b/bun.lock
@@ -456,7 +456,6 @@
},
"devDependencies": {
"@babel/core": "7.28.4",
- "@effect/language-service": "0.84.2",
"@octokit/webhooks-types": "7.6.1",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/script": "workspace:*",
@@ -1069,8 +1068,6 @@
"@drizzle-team/brocli": ["@drizzle-team/[email protected]", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="],
- "@effect/language-service": ["@effect/[email protected]", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-l04qNxpiA8rY5yXWckRPJ7Mk5MNerXuNymSFf+IdflfI5i8jgL1bpBNLuP6ijg7wgjdHc/KmTnCj2kT0SCntuA=="],
-
"@effect/opentelemetry": ["@effect/[email protected]", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.57" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-gdjZPEP0QQg4qmI1vd+443kheeQZKytrjJIzCJncy6ZEpyk/SfrqeStLqLXdTRcms3IB0ls0vOV7KNq7YmBRVA=="],
"@effect/platform-node": ["@effect/[email protected]", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.57", "mime": "^4.1.0", "undici": "^8.0.2" }, "peerDependencies": { "effect": "^4.0.0-beta.57", "ioredis": "^5.7.0" } }, "sha512-la0xxPSAYOsY0d+uVxEBxok3jYB31iPQmIaZZRUj2SNWqcGGHJc6KorKtI8guqSLuv9FGZ255kBWXRbG6hMeeg=="],
diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json
index d7745d755..fe5c4d217 100644
--- a/packages/core/tsconfig.json
+++ b/packages/core/tsconfig.json
@@ -2,13 +2,6 @@
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {
- "noUncheckedIndexedAccess": false,
- "plugins": [
- {
- "name": "@effect/language-service",
- "transform": "@effect/language-service/transform",
- "namespaceImportPackages": ["effect", "@effect/*"]
- }
- ]
+ "noUncheckedIndexedAccess": false
}
}
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index 425ddea77..cf2e574f5 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -6,7 +6,6 @@
"license": "MIT",
"private": true,
"scripts": {
- "prepare": "effect-language-service patch || true",
"typecheck": "tsgo --noEmit",
"test": "bun test --timeout 30000",
"test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",
@@ -42,7 +41,6 @@
},
"devDependencies": {
"@babel/core": "7.28.4",
- "@effect/language-service": "0.84.2",
"@octokit/webhooks-types": "7.6.1",
"@opencode-ai/script": "workspace:*",
"@opencode-ai/core": "workspace:*",
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index c79e950e9..44841fe6f 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -3,7 +3,7 @@ import path from "path"
import { pathToFileURL } from "url"
import os from "os"
import z from "zod"
-import { mergeDeep, pipe } from "remeda"
+import { mergeDeep } from "remeda"
import { Global } from "@opencode-ai/core/global"
import fsNode from "fs/promises"
import { NamedError } from "@opencode-ai/core/util/error"
@@ -47,8 +47,13 @@ import { Npm } from "@opencode-ai/core/npm"
const log = Log.create({ service: "config" })
// Custom merge function that concatenates array fields instead of replacing them
+// Keep remeda's deep conditional merge type out of hot config-loading paths; TS profiling showed it dominates here.
+function mergeConfig(target: Info, source: Info): Info {
+ return mergeDeep(target, source) as Info
+}
+
function mergeConfigConcatArrays(target: Info, source: Info): Info {
- const merged = mergeDeep(target, source)
+ const merged = mergeConfig(target, source)
if (target.instructions && source.instructions) {
merged.instructions = Array.from(new Set([...target.instructions, ...source.instructions]))
}
@@ -387,12 +392,10 @@ export const layer = Layer.effect(
})
const loadGlobal = Effect.fnUntraced(function* () {
- let result: Info = pipe(
- {},
- mergeDeep(yield* loadFile(path.join(Global.Path.config, "config.json"))),
- mergeDeep(yield* loadFile(path.join(Global.Path.config, "opencode.json"))),
- mergeDeep(yield* loadFile(path.join(Global.Path.config, "opencode.jsonc"))),
- )
+ let result: Info = {}
+ result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "config.json")))
+ result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "opencode.json")))
+ result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "opencode.jsonc")))
const legacy = path.join(Global.Path.config, "config")
if (existsSync(legacy)) {
@@ -402,7 +405,7 @@ export const layer = Layer.effect(
const { provider, model, ...rest } = mod.default
if (provider && model) result.model = `${provider}/${model}`
result["$schema"] = "https://opencode.ai/config.json"
- result = mergeDeep(result, rest)
+ result = mergeConfig(result, rest)
await fsNode.writeFile(path.join(Global.Path.config, "config.json"), JSON.stringify(result, null, 2))
await fsNode.unlink(legacy)
})
diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts
index 58677debc..69b0b27c3 100644
--- a/packages/opencode/src/session/llm.ts
+++ b/packages/opencode/src/session/llm.ts
@@ -3,7 +3,7 @@ import * as Log from "@opencode-ai/core/util/log"
import { Context, Effect, Layer, Record } from "effect"
import * as Stream from "effect/Stream"
import { streamText, wrapLanguageModel, type ModelMessage, type Tool, tool, jsonSchema } from "ai"
-import { mergeDeep, pipe } from "remeda"
+import { mergeDeep } from "remeda"
import { GitLabWorkflowLanguageModel } from "gitlab-ai-provider"
import { ProviderTransform } from "@/provider/transform"
import { Config } from "@/config/config"
@@ -29,6 +29,10 @@ const log = Log.create({ service: "llm" })
export const OUTPUT_TOKEN_MAX = ProviderTransform.OUTPUT_TOKEN_MAX
type Result = Awaited<ReturnType<typeof streamText>>
+// Avoid re-instantiating remeda's deep merge types in this hot LLM path; the runtime behavior is still mergeDeep.
+const mergeOptions = (target: Record<string, any>, source: Record<string, any> | undefined): Record<string, any> =>
+ mergeDeep(target, source ?? {}) as Record<string, any>
+
export type StreamInput = {
user: MessageV2.User
sessionID: string
@@ -134,11 +138,9 @@ const live: Layer.Layer<
sessionID: input.sessionID,
providerOptions: item.options,
})
- const options: Record<string, any> = pipe(
- base,
- mergeDeep(input.model.options),
- mergeDeep(input.agent.options),
- mergeDeep(variant),
+ const options = mergeOptions(
+ mergeOptions(mergeOptions(base, input.model.options), input.agent.options),
+ variant,
)
if (isOpenaiOauth) {
options.instructions = system.join("\n")
diff --git a/packages/opencode/src/storage/db.ts b/packages/opencode/src/storage/db.ts
index 95bb568bd..de4683b75 100644
--- a/packages/opencode/src/storage/db.ts
+++ b/packages/opencode/src/storage/db.ts
@@ -48,6 +48,13 @@ type Client = SQLiteBunDatabase
type Journal = { sql: string; timestamp: number; name: string }[]
+// Drizzle's migrate overloads trigger expensive variance checks here; narrow to the journal overload we actually use.
+const migrateFromJournal = migrate as unknown as (db: SQLiteBunDatabase, entries: Journal) => void
+
+function applyMigrations(db: SQLiteBunDatabase, entries: Journal) {
+ migrateFromJournal(db, entries)
+}
+
function time(tag: string) {
const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(tag)
if (!match) return 0
@@ -108,7 +115,7 @@ export const Client = lazy(() => {
item.sql = "select 1;"
}
}
- migrate(db, entries)
+ applyMigrations(db, entries)
}
return db
diff --git a/packages/opencode/tsconfig.json b/packages/opencode/tsconfig.json
index 5cb51012a..f09fca687 100644
--- a/packages/opencode/tsconfig.json
+++ b/packages/opencode/tsconfig.json
@@ -12,13 +12,6 @@
"@/*": ["./src/*"],
"@tui/*": ["./src/cli/cmd/tui/*"],
"@test/*": ["./test/*"]
- },
- "plugins": [
- {
- "name": "@effect/language-service",
- "transform": "@effect/language-service/transform",
- "namespaceImportPackages": ["effect", "@effect/*"]
- }
- ]
+ }
}
}