From 27c903e3afb0aded50ea8f69d8c20dcbd714c821 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 21 Aug 2026 13:27:50 -0400 Subject: [PATCH 1/3] refactor(core): reuse persistence primitives --- packages/core/src/credential.ts | 27 +++++----- packages/core/src/database/sqlite.workerd.ts | 24 +-------- packages/core/src/worktree.ts | 54 ++++++++++---------- 3 files changed, 42 insertions(+), 63 deletions(-) diff --git a/packages/core/src/credential.ts b/packages/core/src/credential.ts index b16346a1dc62..926337d7016b 100644 --- a/packages/core/src/credential.ts +++ b/packages/core/src/credential.ts @@ -62,31 +62,30 @@ const layer = Layer.effect( value: decode(row.value), }) } + const storedRows = (rows: ReadonlyArray) => + rows.flatMap((row) => { + const credential = stored(row) + return credential ? [credential] : [] + }) return Service.of({ - all: Effect.fn("Credential.all")(function* () { - return (yield* db + all: Effect.fn("Credential.all")(() => + db .select() .from(CredentialTable) .orderBy(asc(CredentialTable.time_created)) .all() - .pipe(Effect.orDie)).flatMap((row) => { - const credential = stored(row) - return credential ? [credential] : [] - }) - }), - list: Effect.fn("Credential.list")(function* (integrationID) { - return (yield* db + .pipe(Effect.orDie, Effect.map(storedRows)), + ), + list: Effect.fn("Credential.list")((integrationID) => + db .select() .from(CredentialTable) .where(eq(CredentialTable.integration_id, integrationID)) .orderBy(asc(CredentialTable.time_created)) .all() - .pipe(Effect.orDie)).flatMap((row) => { - const credential = stored(row) - return credential ? [credential] : [] - }) - }), + .pipe(Effect.orDie, Effect.map(storedRows)), + ), get: Effect.fn("Credential.get")(function* (id) { const row = yield* db.select().from(CredentialTable).where(eq(CredentialTable.id, id)).get().pipe(Effect.orDie) return row ? stored(row) : undefined diff --git a/packages/core/src/database/sqlite.workerd.ts b/packages/core/src/database/sqlite.workerd.ts index d0f4e5d2bf85..35208647b2f4 100644 --- a/packages/core/src/database/sqlite.workerd.ts +++ b/packages/core/src/database/sqlite.workerd.ts @@ -1,5 +1,4 @@ -import { Context, Effect, Exit, Fiber, Layer, Scope, Semaphore, Stream } from "effect" -import { identity } from "effect/Function" +import { Context, Effect, Exit, Fiber, Layer, Scope, Semaphore } from "effect" import { Reactivity } from "effect/unstable/reactivity" import { SqlClient, Statement } from "effect/unstable/sql" import type { Connection } from "effect/unstable/sql/SqlConnection" @@ -167,26 +166,7 @@ const make = (options: Config) => }), }) - const connection = identity({ - execute(query, params, transformRows) { - return transformRows ? Effect.map(run(query, params), transformRows) : run(query, params) - }, - executeRaw(query, params) { - return run(query, params) - }, - executeValues(query, params) { - return runValues(query, params) - }, - executeValuesUnprepared(query, params) { - return runValues(query, params) - }, - executeUnprepared(query, params, transformRows) { - return this.execute(query, params, transformRows) - }, - executeStream() { - return Stream.die("executeStream not implemented") - }, - }) + const connection = Sqlite.makeConnection(run, runValues, {}) const semaphore = yield* Semaphore.make(1) const acquirer = semaphore.withPermits(1)(Effect.succeed(connection)) diff --git a/packages/core/src/worktree.ts b/packages/core/src/worktree.ts index 8eff88d50688..26305dfd8e65 100644 --- a/packages/core/src/worktree.ts +++ b/packages/core/src/worktree.ts @@ -180,33 +180,33 @@ const layer = Layer.effect( .get() .pipe(Effect.orDie) }), - create: Effect.fnUntraced(function* (input: StoredInput, tx?: Transaction) { - return ( - (yield* (tx ?? db) - .insert(WorktreeTable) - .values({ project_id: input.projectID, directory: input.directory, strategy: input.strategy }) - .onConflictDoUpdate({ - target: [WorktreeTable.project_id, WorktreeTable.directory], - set: { strategy: input.strategy ?? null }, - setWhere: input.strategy - ? or(isNull(WorktreeTable.strategy), ne(WorktreeTable.strategy, input.strategy)) - : isNotNull(WorktreeTable.strategy), - }) - .returning({ directory: WorktreeTable.directory }) - .get() - .pipe(Effect.orDie)) !== undefined - ) - }), - remove: Effect.fnUntraced(function* (projectID: ProjectSchema.ID, directory: AbsolutePath, tx?: Transaction) { - return ( - (yield* (tx ?? db) - .delete(WorktreeTable) - .where(and(eq(WorktreeTable.project_id, projectID), eq(WorktreeTable.directory, directory))) - .returning({ directory: WorktreeTable.directory }) - .get() - .pipe(Effect.orDie)) !== undefined - ) - }), + create: (input: StoredInput, tx?: Transaction) => + (tx ?? db) + .insert(WorktreeTable) + .values({ project_id: input.projectID, directory: input.directory, strategy: input.strategy }) + .onConflictDoUpdate({ + target: [WorktreeTable.project_id, WorktreeTable.directory], + set: { strategy: input.strategy ?? null }, + setWhere: input.strategy + ? or(isNull(WorktreeTable.strategy), ne(WorktreeTable.strategy, input.strategy)) + : isNotNull(WorktreeTable.strategy), + }) + .returning({ directory: WorktreeTable.directory }) + .get() + .pipe( + Effect.orDie, + Effect.map((row) => row !== undefined), + ), + remove: (projectID: ProjectSchema.ID, directory: AbsolutePath, tx?: Transaction) => + (tx ?? db) + .delete(WorktreeTable) + .where(and(eq(WorktreeTable.project_id, projectID), eq(WorktreeTable.directory, directory))) + .returning({ directory: WorktreeTable.directory }) + .get() + .pipe( + Effect.orDie, + Effect.map((row) => row !== undefined), + ), } const registry = new Map() From 0dd35fe3ff808b8266597b2e538595e654bafa47 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 21 Aug 2026 13:26:26 -0400 Subject: [PATCH 2/3] refactor(core): centralize boundary normalization --- packages/core/src/mcp/client.ts | 15 ++++++++------- packages/core/src/provider.ts | 14 ++------------ packages/core/src/ripgrep.ts | 24 +++++++++--------------- 3 files changed, 19 insertions(+), 34 deletions(-) diff --git a/packages/core/src/mcp/client.ts b/packages/core/src/mcp/client.ts index 31021eed194a..925af3dbe139 100644 --- a/packages/core/src/mcp/client.ts +++ b/packages/core/src/mcp/client.ts @@ -34,6 +34,7 @@ import { MCPStdio } from "./stdio.js" const DEFAULT_STARTUP_TIMEOUT = 30_000 const DEFAULT_CATALOG_TIMEOUT = 30_000 const DEFAULT_EXECUTION_TIMEOUT = 12 * 60 * 60 * 1_000 // 12 hours +const toError = (error: unknown) => (error instanceof Error ? error : new Error(String(error))) // Some servers advertise tool outputSchemas the SDK's strict validator can't resolve; this drops // only that field so a single bad schema doesn't blank out the whole tool list. @@ -261,7 +262,7 @@ export const connect = Effect.fnUntraced(function* ( }, (result) => result.tools, ), - catch: (error) => (error instanceof Error ? error : new Error(String(error))), + catch: toError, }).pipe( Effect.tapError((error) => Effect.logWarning("failed to list MCP tools", { server, error: error.message })), ) @@ -286,7 +287,7 @@ export const connect = Effect.fnUntraced(function* ( }, (result) => result.prompts, ), - catch: (error) => (error instanceof Error ? error : new Error(String(error))), + catch: toError, }).pipe( Effect.tapError((error) => Effect.logWarning("failed to list MCP prompts", { server, error: error.message }), @@ -312,7 +313,7 @@ export const connect = Effect.fnUntraced(function* ( client.listResources(cursor === undefined ? undefined : { cursor }, { timeout: catalogTimeout }), (result) => result.resources, ), - catch: (error) => (error instanceof Error ? error : new Error(String(error))), + catch: toError, }).pipe( Effect.tapError((error) => Effect.logWarning("failed to list MCP resources", { server, error: error.message }), @@ -337,7 +338,7 @@ export const connect = Effect.fnUntraced(function* ( }), (result) => result.resourceTemplates, ), - catch: (error) => (error instanceof Error ? error : new Error(String(error))), + catch: toError, }).pipe( Effect.tapError((error) => Effect.logWarning("failed to list MCP resource templates", { server, error: error.message }), @@ -355,7 +356,7 @@ export const connect = Effect.fnUntraced(function* ( if (!client.getServerCapabilities()?.resources) return undefined const result = yield* Effect.tryPromise({ try: (signal) => client.readResource({ uri: input.uri }, { signal, timeout: executionTimeout }), - catch: (error) => (error instanceof Error ? error : new Error(String(error))), + catch: toError, }).pipe( Effect.tapError((error) => Effect.logWarning("failed to read MCP resource", { server, uri: input.uri, error: error.message }), @@ -378,7 +379,7 @@ export const connect = Effect.fnUntraced(function* ( GetPromptResultSchema, { signal, timeout: executionTimeout }, ), - catch: (error) => (error instanceof Error ? error : new Error(String(error))), + catch: toError, }).pipe( Effect.map((result) => ({ messages: result.messages.map((message) => ({ role: message.role, content: message.content })), @@ -393,7 +394,7 @@ export const connect = Effect.fnUntraced(function* ( // Keep progress tokens available while enforcing a hard wall-clock execution timeout. { signal, timeout: executionTimeout, onprogress: () => {} }, ), - catch: (error) => (error instanceof Error ? error : new Error(String(error))), + catch: toError, }).pipe( Effect.map((result) => ({ isError: result.isError === true, diff --git a/packages/core/src/provider.ts b/packages/core/src/provider.ts index 0b121785275c..307c644b7008 100644 --- a/packages/core/src/provider.ts +++ b/packages/core/src/provider.ts @@ -3,6 +3,7 @@ export * as Provider from "./provider.js" import { Effect, Schema } from "effect" import { Provider } from "@opencode-ai/schema/provider" import type { ProviderPackageDefinition } from "@opencode-ai/ai" +import { isRecord } from "@opencode-ai/ai/utils/record" import { Npm } from "@opencode-ai/util/npm" import type { DeepMutable } from "./schema.js" import { importModule, resolveModule } from "@opencode-ai/util/runtime-import" @@ -108,18 +109,7 @@ export function mergeOverlay( const left = base[key] const right = overlay[key] if (right === undefined) return [key, left] - if ( - typeof left === "object" && - left !== null && - !Array.isArray(left) && - typeof right === "object" && - right !== null && - !Array.isArray(right) - ) - return [ - key, - mergeOverlay(left as Readonly>, right as Readonly>) ?? {}, - ] + if (isRecord(left) && isRecord(right)) return [key, mergeOverlay(left, right) ?? {}] return [key, right] }), ), diff --git a/packages/core/src/ripgrep.ts b/packages/core/src/ripgrep.ts index 1e029f7b5bcf..544add53f0d0 100644 --- a/packages/core/src/ripgrep.ts +++ b/packages/core/src/ripgrep.ts @@ -88,6 +88,12 @@ export class Service extends Context.Service()("@opencode/Ri const failure = (message: string, cause?: unknown) => new Error({ message, cause }) +const normalizePath = (value: string) => + value + .replace(/^(?:\.[\\/])+/u, "") + .replace(/^[\\/]+/u, "") + .replaceAll("\\", "/") + const isInvalidPattern = (stderr: string) => stderr.includes("regex parse error") || stderr.includes("error parsing regex") @@ -169,13 +175,7 @@ const layer = Layer.effect( "--glob=!**/.git/**", ".", ], - parse: (line) => - Effect.succeed( - line - .replace(/^(?:\.[\\/])+/u, "") - .replace(/^[\\/]+/u, "") - .replaceAll("\\", "/"), - ), + parse: (line) => Effect.succeed(normalizePath(line)), }).pipe( Effect.map((result) => result.items.map((relative) => @@ -203,10 +203,7 @@ const layer = Layer.effect( ".", ], parse: (line) => { - const relative = line - .replace(/^(?:\.[\\/])+/u, "") - .replace(/^[\\/]+/u, "") - .replaceAll("\\", "/") + const relative = normalizePath(line) return Effect.succeed( Entry.make({ path: RelativePath.make(relative), @@ -252,10 +249,7 @@ const layer = Layer.effect( }).pipe( Effect.map((result) => result.items.map((match) => { - const relative = match.path.text - .replace(/^(?:\.[\\/])+/u, "") - .replace(/^[\\/]+/u, "") - .replaceAll("\\", "/") + const relative = normalizePath(match.path.text) return Match.make({ entry: Entry.make({ path: RelativePath.make(relative), From b7f807cd0025e5e65ea3537daeea8579aae060c6 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 21 Aug 2026 13:36:20 -0400 Subject: [PATCH 3/3] refactor(core): reuse path normalization --- packages/core/src/ripgrep.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/core/src/ripgrep.ts b/packages/core/src/ripgrep.ts index 544add53f0d0..ee07b10c13e9 100644 --- a/packages/core/src/ripgrep.ts +++ b/packages/core/src/ripgrep.ts @@ -239,7 +239,7 @@ const layer = Layer.effect( return Schema.decodeUnknownEffect(RawMatch)(json).pipe( Effect.map((match) => ({ ...match.data, - path: { text: match.data.path.text.replace(/^\.[\\/]/, "") }, + path: { text: normalizePath(match.data.path.text) }, submatches: match.data.submatches.slice(0, MAX_SUBMATCHES), })), Effect.mapError((cause) => failure("Invalid ripgrep match output", cause)), @@ -248,11 +248,10 @@ const layer = Layer.effect( ), }).pipe( Effect.map((result) => - result.items.map((match) => { - const relative = normalizePath(match.path.text) - return Match.make({ + result.items.map((match) => + Match.make({ entry: Entry.make({ - path: RelativePath.make(relative), + path: RelativePath.make(match.path.text), type: "file", }), line: match.line_number, @@ -263,8 +262,8 @@ const layer = Layer.effect( start: submatch.start, end: submatch.end, })), - }) - }), + }), + ), ), ), })