From 9067b1585087879d608c5e6b924442e59df395b3 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Sun, 31 May 2026 23:33:45 -0500 Subject: [PATCH 1/3] refactor(opencode): simplify session setup flow --- packages/opencode/src/session/compaction.ts | 4 ++-- packages/opencode/src/session/instruction.ts | 12 +++++------- packages/opencode/src/session/llm.ts | 2 +- packages/opencode/src/session/message-v2.ts | 2 +- packages/opencode/src/session/processor.ts | 4 ++-- packages/opencode/src/session/prompt.ts | 18 ++++++++---------- packages/opencode/src/session/reminders.ts | 2 +- packages/opencode/src/session/revert.ts | 4 ++-- packages/opencode/src/session/run-state.ts | 2 +- packages/opencode/src/session/session.ts | 8 ++------ packages/opencode/src/session/summary.ts | 2 +- packages/opencode/src/session/tools.ts | 6 +++--- 12 files changed, 29 insertions(+), 37 deletions(-) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index ceb78e632140..c687df59bbe5 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -1,10 +1,10 @@ import { SessionLegacy } from "@opencode-ai/core/session/legacy" -import * as Session from "./session" +import { Session } from "./session" import { SessionID, MessageID, PartID } from "./schema" import { Provider } from "@/provider/provider" import { MessageV2 } from "./message-v2" import { Token } from "@/util/token" -import * as Log from "@opencode-ai/core/util/log" +import { Log } from "@opencode-ai/core/util/log" import { SessionProcessor } from "./processor" import { Agent } from "@/agent/agent" import { Plugin } from "@/plugin" diff --git a/packages/opencode/src/session/instruction.ts b/packages/opencode/src/session/instruction.ts index 855c58ba5ce0..cae261e72b23 100644 --- a/packages/opencode/src/session/instruction.ts +++ b/packages/opencode/src/session/instruction.ts @@ -12,12 +12,6 @@ import { Global } from "@opencode-ai/core/global" import type { MessageV2 } from "./message-v2" import type { MessageID } from "./schema" -const files = (disableClaudeCodePrompt: boolean) => [ - "AGENTS.md", - ...(disableClaudeCodePrompt ? [] : ["CLAUDE.md"]), - "CONTEXT.md", // deprecated -] - function extract(messages: SessionLegacy.WithParts[]) { const paths = new Set() for (const msg of messages) { @@ -65,7 +59,11 @@ export const layer: Layer.Layer< path.join(global.config, "AGENTS.md"), ...(!flags.disableClaudeCodePrompt ? [path.join(global.home, ".claude", "CLAUDE.md")] : []), ] - const instructionFiles = files(flags.disableClaudeCodePrompt) + const instructionFiles = [ + "AGENTS.md", + ...(!flags.disableClaudeCodePrompt ? ["CLAUDE.md"] : []), + "CONTEXT.md", // deprecated + ] const state = yield* InstanceState.make( Effect.fn("Instruction.state")(() => diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index eb63ee534e9e..ebaad3e9306d 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -1,7 +1,7 @@ import { Provider } from "@/provider/provider" import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { serviceUse } from "@opencode-ai/core/effect/service-use" -import * as Log from "@opencode-ai/core/util/log" +import { Log } from "@opencode-ai/core/util/log" import { Context, Effect, Layer } from "effect" import * as Stream from "effect/Stream" import { streamText, wrapLanguageModel, type ModelMessage, type Tool } from "ai" diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 862e06fc214e..2884c4cf860b 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -30,7 +30,7 @@ import { inArray } from "drizzle-orm" import { lt } from "drizzle-orm" import { or } from "drizzle-orm" import { MessageTable, PartTable, SessionTable } from "@opencode-ai/core/session/sql" -import * as ProviderError from "@/provider/error" +import { ProviderError } from "@/provider/error" import { iife } from "@/util/iife" import { errorMessage } from "@/util/error" import { isMedia } from "@/util/media" diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index f124f7eea479..1e20b6e8ef12 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -7,7 +7,7 @@ import { Config } from "@/config/config" import { Permission } from "@/permission" import { Plugin } from "@/plugin" import { Snapshot } from "@/snapshot" -import * as Session from "./session" +import { Session } from "./session" import { LLM } from "./llm" import { MessageV2 } from "./message-v2" import { isOverflow } from "./overflow" @@ -19,7 +19,7 @@ import { SessionSummary } from "./summary" import type { Provider } from "@/provider/provider" import { Question } from "@/question" import { errorMessage } from "@/util/error" -import * as Log from "@opencode-ai/core/util/log" +import { Log } from "@opencode-ai/core/util/log" import { isRecord } from "@/util/record" import { EventV2Bridge } from "@/event-v2-bridge" import { Database } from "@opencode-ai/core/database/database" diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 36e394f0aa25..ce869e14fcda 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -3,9 +3,9 @@ import { SessionLegacy } from "@opencode-ai/core/session/legacy" import os from "os" import { SessionID, MessageID, PartID } from "./schema" import { MessageV2 } from "./message-v2" -import * as Log from "@opencode-ai/core/util/log" +import { Log } from "@opencode-ai/core/util/log" import { SessionRevert } from "./revert" -import * as Session from "./session" +import { Session } from "./session" import { Agent } from "../agent/agent" import { Provider } from "@/provider/provider" @@ -130,12 +130,10 @@ export const layer = Layer.effect( const flags = yield* RuntimeFlags.Service const database = yield* Database.Service const { db } = database - const ops = Effect.fn("SessionPrompt.ops")(function* () { - return { - cancel: (sessionID: SessionID) => cancel(sessionID), - resolvePromptParts: (template: string) => resolvePromptParts(template), - prompt: (input: PromptInput) => prompt(input).pipe(Effect.catch(Effect.die)), - } satisfies TaskPromptOps + const ops = (): TaskPromptOps => ({ + cancel: (sessionID: SessionID) => cancel(sessionID), + resolvePromptParts: (template: string) => resolvePromptParts(template), + prompt: (input: PromptInput) => prompt(input).pipe(Effect.catch(Effect.die)), }) const cancel = Effect.fn("SessionPrompt.cancel")(function* (sessionID: SessionID) { @@ -311,7 +309,7 @@ export const layer = Layer.effect( }) { const { task, model, lastUser, sessionID, session, msgs } = input const ctx = yield* InstanceState.context - const promptOps = yield* ops() + const promptOps = ops() const { task: taskTool } = yield* registry.named() const taskModel = task.model ? yield* getModel(task.model.providerID, task.model.modelID, sessionID) : model const assistantMessage: SessionLegacy.Assistant = yield* sessions.updateMessage({ @@ -1387,7 +1385,7 @@ export const layer = Layer.effect( const outcome: "break" | "continue" = yield* Effect.gen(function* () { const lastUserMsg = msgs.findLast((m) => m.info.role === "user") const bypassAgentCheck = lastUserMsg?.parts.some((p) => p.type === "agent") ?? false - const promptOps = yield* ops() + const promptOps = ops() const tools = yield* SessionTools.resolve({ agent, diff --git a/packages/opencode/src/session/reminders.ts b/packages/opencode/src/session/reminders.ts index 206304b393ae..a868a59dd3df 100644 --- a/packages/opencode/src/session/reminders.ts +++ b/packages/opencode/src/session/reminders.ts @@ -7,7 +7,7 @@ import { InstanceState } from "@/effect/instance-state" import { RuntimeFlags } from "@/effect/runtime-flags" import { PartID } from "./schema" import { MessageV2 } from "./message-v2" -import * as Session from "./session" +import { Session } from "./session" import PROMPT_PLAN from "./prompt/plan.txt" import BUILD_SWITCH from "./prompt/build-switch.txt" import PLAN_MODE from "./prompt/plan-mode.txt" diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index f33330704d6f..19a372558333 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -3,8 +3,8 @@ import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { EventV2Bridge } from "@/event-v2-bridge" import { Snapshot } from "../snapshot" import { Storage } from "@/storage/storage" -import * as Log from "@opencode-ai/core/util/log" -import * as Session from "./session" +import { Log } from "@opencode-ai/core/util/log" +import { Session } from "./session" import { MessageV2 } from "./message-v2" import { SessionID, MessageID, PartID } from "./schema" import { SessionRunState } from "./run-state" diff --git a/packages/opencode/src/session/run-state.ts b/packages/opencode/src/session/run-state.ts index 1b92dce6828e..399a5b604ed9 100644 --- a/packages/opencode/src/session/run-state.ts +++ b/packages/opencode/src/session/run-state.ts @@ -3,7 +3,7 @@ import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { Runner } from "@/effect/runner" import { BackgroundJob } from "@/background/job" import { Effect, Latch, Layer, Scope, Context } from "effect" -import * as Session from "./session" +import { Session } from "./session" import { MessageV2 } from "./message-v2" import { SessionID } from "./schema" import { SessionStatus } from "./status" diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index bb6f1d6a1d7f..a8a867a425c9 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -25,7 +25,7 @@ import { or } from "drizzle-orm" import type { SQL } from "drizzle-orm" import { PartTable, SessionTable } from "@opencode-ai/core/session/sql" import { ProjectTable } from "@opencode-ai/core/project/sql" -import * as Log from "@opencode-ai/core/util/log" +import { Log } from "@opencode-ai/core/util/log" import { MessageV2 } from "./message-v2" import type { InstanceContext } from "../project/instance-context" import { InstanceState } from "@/effect/instance-state" @@ -48,10 +48,6 @@ const runtime = makeRuntime(Database.Service, Database.defaultLayer) const parentTitlePrefix = "New session - " const childTitlePrefix = "Child session - " -function createDefaultTitle(isChild = false) { - return (isChild ? childTitlePrefix : parentTitlePrefix) + new Date().toISOString() -} - export function isDefaultTitle(title: string) { return new RegExp( `^(${parentTitlePrefix}|${childTitlePrefix})\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$`, @@ -581,7 +577,7 @@ export const layer: Layer.Layer< path: input.path, workspaceID: input.workspaceID, parentID: input.parentID, - title: input.title ?? createDefaultTitle(!!input.parentID), + title: input.title ?? (input.parentID ? childTitlePrefix : parentTitlePrefix) + new Date().toISOString(), agent: input.agent, model: input.model, metadata: input.metadata, diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index 7dccec1f2e28..89652d9a3912 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -2,7 +2,7 @@ import { Effect, Layer, Context, Schema } from "effect" import { SessionLegacy } from "@opencode-ai/core/session/legacy" import { EventV2Bridge } from "@/event-v2-bridge" import { Snapshot } from "@/snapshot" -import * as Session from "./session" +import { Session } from "./session" import { SessionID, MessageID } from "./schema" import { Config } from "@/config/config" diff --git a/packages/opencode/src/session/tools.ts b/packages/opencode/src/session/tools.ts index 20ffb60e136c..35cbb4d9b52c 100644 --- a/packages/opencode/src/session/tools.ts +++ b/packages/opencode/src/session/tools.ts @@ -14,10 +14,10 @@ import type { TaskPromptOps } from "@/tool/task" import { type Tool as AITool, tool, jsonSchema, type ToolExecutionOptions, asSchema } from "ai" import { Effect } from "effect" import { MessageV2 } from "./message-v2" -import * as Session from "./session" +import { Session } from "./session" import { SessionProcessor } from "./processor" import { PartID } from "./schema" -import * as Log from "@opencode-ai/core/util/log" +import { Log } from "@opencode-ai/core/util/log" import { EffectBridge } from "@/effect/bridge" import { ProviderV2 } from "@opencode-ai/core/provider" @@ -121,7 +121,7 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: { const execute = item.execute if (!execute) continue - const schema = yield* Effect.promise(() => Promise.resolve(asSchema(item.inputSchema).jsonSchema)) + const schema = asSchema(item.inputSchema).jsonSchema const transformed = ProviderTransform.schema(input.model, schema) item.inputSchema = jsonSchema(transformed) item.execute = (args, opts) => From cc13a8490a5638637b809abcc09aabce709f0cb9 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Sun, 31 May 2026 23:39:18 -0500 Subject: [PATCH 2/3] refactor(opencode): retain session effect boundaries --- packages/opencode/src/session/prompt.ts | 14 ++++++++------ packages/opencode/src/session/tools.ts | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index ce869e14fcda..93b496cc8de2 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -130,10 +130,12 @@ export const layer = Layer.effect( const flags = yield* RuntimeFlags.Service const database = yield* Database.Service const { db } = database - const ops = (): TaskPromptOps => ({ - cancel: (sessionID: SessionID) => cancel(sessionID), - resolvePromptParts: (template: string) => resolvePromptParts(template), - prompt: (input: PromptInput) => prompt(input).pipe(Effect.catch(Effect.die)), + const ops = Effect.fn("SessionPrompt.ops")(function* () { + return { + cancel: (sessionID: SessionID) => cancel(sessionID), + resolvePromptParts: (template: string) => resolvePromptParts(template), + prompt: (input: PromptInput) => prompt(input).pipe(Effect.catch(Effect.die)), + } satisfies TaskPromptOps }) const cancel = Effect.fn("SessionPrompt.cancel")(function* (sessionID: SessionID) { @@ -309,7 +311,7 @@ export const layer = Layer.effect( }) { const { task, model, lastUser, sessionID, session, msgs } = input const ctx = yield* InstanceState.context - const promptOps = ops() + const promptOps = yield* ops() const { task: taskTool } = yield* registry.named() const taskModel = task.model ? yield* getModel(task.model.providerID, task.model.modelID, sessionID) : model const assistantMessage: SessionLegacy.Assistant = yield* sessions.updateMessage({ @@ -1385,7 +1387,7 @@ export const layer = Layer.effect( const outcome: "break" | "continue" = yield* Effect.gen(function* () { const lastUserMsg = msgs.findLast((m) => m.info.role === "user") const bypassAgentCheck = lastUserMsg?.parts.some((p) => p.type === "agent") ?? false - const promptOps = ops() + const promptOps = yield* ops() const tools = yield* SessionTools.resolve({ agent, diff --git a/packages/opencode/src/session/tools.ts b/packages/opencode/src/session/tools.ts index 35cbb4d9b52c..b91e138eda6c 100644 --- a/packages/opencode/src/session/tools.ts +++ b/packages/opencode/src/session/tools.ts @@ -121,7 +121,7 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: { const execute = item.execute if (!execute) continue - const schema = asSchema(item.inputSchema).jsonSchema + const schema = yield* Effect.promise(() => Promise.resolve(asSchema(item.inputSchema).jsonSchema)) const transformed = ProviderTransform.schema(input.model, schema) item.inputSchema = jsonSchema(transformed) item.execute = (args, opts) => From f2915afc8276bc1b7fb93e1d0131d7ec785485e6 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Sun, 31 May 2026 23:52:15 -0500 Subject: [PATCH 3/3] refactor(opencode): inline local session adapters --- packages/opencode/src/session/processor.ts | 4 +--- packages/opencode/src/session/prompt.ts | 7 ++----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index 1e20b6e8ef12..8f9b83a79cf3 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -301,8 +301,6 @@ export const layer = Layer.effect( } } - const toolInput = (value: unknown): Record => (isRecord(value) ? value : { value }) - const handleEvent = Effect.fnUntraced(function* (value: StreamEvent) { switch (value.type) { case "reasoning-start": @@ -380,7 +378,7 @@ export const layer = Layer.effect( throw new Error(`Tool call not allowed while generating summary: ${value.name}`) } const toolCall = yield* ensureToolCall(value) - const input = toolInput(value.input) + const input = isRecord(value.input) ? value.input : { value: value.input } if (!toolCall.call.inputEnded) { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. if (flags.experimentalEventSystem) { diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 93b496cc8de2..e7c6a6236340 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -148,10 +148,6 @@ export const layer = Layer.effect( const parts: Types.DeepMutable = [{ type: "text", text: template }] const files = ConfigMarkdown.files(template) const seen = new Set() - const mentionSource = (match: RegExpMatchArray) => { - const start = match.index ?? 0 - return { value: match[0], start, end: start + match[0].length } - } yield* Effect.forEach( files, Effect.fnUntraced(function* (match) { @@ -164,7 +160,8 @@ export const layer = Layer.effect( const alias = slash === -1 ? name : name.slice(0, slash) const reference = yield* references.get(alias) if (reference) { - const source = mentionSource(match) + const start = match.index ?? 0 + const source = { value: match[0], start, end: start + match[0].length } if (reference.kind === "invalid") { parts.push( referenceTextPart({ reference, source, target: slash === -1 ? undefined : name.slice(slash + 1) }),