./packages/sdk/js/script/build.ts.HttpApi, run bun run generate from packages/client. Do not edit src/generated or src/generated-effect directly.sdk-next composes Client, Core, and Server.dev.main ref may not exist; use dev or origin/dev for diffs.Use a short branch name of at most three words, separated by hyphens. Do not use slashes or type prefixes such as feat/ or fix/.
Examples: session-recovery, fix-scroll-state, regenerate-sdk.
Use conventional commit-style messages and PR titles: type(scope): summary.
Valid types are feat, fix, docs, chore, refactor, and test. Scopes are optional; use the affected package or area when helpful, e.g. core, opencode, tui, app, desktop, sdk, or plugin.
Examples: fix(tui): simplify thinking toggle styling, docs: update contributing guide, chore(sdk): regenerate types.
try/catch where possibleany typeBun.file()src/config, follow the existing self-export pattern at the top of the file (for example export * as ConfigAgent from "./agent") when adding a new config module.yield* (yield* Foo.Service).bar().Reduce total variable count by inlining when a value is only used once.
// Good
const journal = await Bun.file(path.join(dir, "journal.json")).json()
// Bad
const journalPath = path.join(dir, "journal.json")
const journal = await Bun.file(journalPath).json()
Avoid unnecessary destructuring. Use dot notation to preserve context.
// Good
obj.a
obj.b
// Bad
const { a, b } = obj
import { foo as bar } from "..." or renamed imports like resolve as pathResolve.import * as Foo from "..." or import type * as Foo from "...".import { Project } from "@opencode-ai/core/project", then reference Project.ID.await import("./module").then((mod) => mod.value()) or (await import("./module")).value(). Keep branch-specific imports inside the branch that needs them to preserve lazy loading.Prefer const over let. Use ternaries or early returns instead of reassignment.
// Good
const foo = condition ? 1 : 2
// Bad
let foo
if (condition) foo = 1
else foo = 2
Avoid else statements. Prefer early returns.
// Good
function foo() {
if (condition) return 1
return 2
}
// Bad
function foo() {
if (condition) return 1
else return 2
}
When a function has several validation branches or supporting details, make the main function read as the happy path and move supporting details into small helpers below it.
// Good
export function loadThing(input: unknown) {
const config = requireConfig(input)
const metadata = readMetadata(input)
return createThing({ config, metadata })
}
function requireConfig(input: unknown) {
...
}
requireConfig or readMetadata.Effect from helpers unless they actually perform effectful work. Synchronous parsing, validation, and option building should stay synchronous.Schema.UnknownFromJsonString and Schema.decodeUnknownOption over manual JSON.parse wrapped in Effect.try when parsing untrusted JSON strings.Use snake_case for field names so column names don't need to be redefined as strings.
// Good
const table = sqliteTable("session", {
id: text().primaryKey(),
project_id: text().notNull(),
created_at: integer().notNull(),
})
// Bad
const table = sqliteTable("session", {
id: text("id").primaryKey(),
projectID: text("project_id").notNull(),
createdAt: integer("created_at").notNull(),
})
do-not-run-tests-from-root); run from package dirs like packages/opencode.bun typecheck from package directories (e.g., packages/opencode), never tsc directly.SessionV2.prompt(...) admits one durable session_input row before scheduling advisory SessionExecution.wake(sessionID) unless resume: false requests admit-only behavior. The serialized runner promotes admitted inputs into visible user messages at safe boundaries.SessionExecution process-global and Session-ID based. Its local implementation owns the process-local Session coordinator and discovers placement through SessionStore plus LocationServiceMap.get(session.location) only when a drain starts; no layer should take a Session ID. V2 interruption targets the active process-local ownership chain for that Session; idle or missing interruption is a no-op.SessionRunner, model resolution, tool registry, permissions, and filesystem Location-scoped. Omitted Location.workspaceID means implicit-local placement; explicit workspace identity remains reserved for future placement semantics.llm.stream(request) call per provider turn and reload projected history before durable continuation. Do not bridge through legacy SessionPrompt.loop(...) or delegate orchestration to an in-memory tool loop.SessionRunCoordinator joins explicit same-Session resumes, coalesces prompt wakeups, and allows different Sessions to run concurrently. Advisory wakes drain eligible durable inbox rows only; post-crash continuation recovery requires a separate explicit design before it may retry provider work. A drain has no durable identity or transcript boundary.queue input remains pending until the Session would otherwise become idle; promote one queued input at that boundary, then reevaluate continuation before promoting another. Promoting any new user input resets the selected agent's provider-turn allowance; a batch of steers resets it once.src/system-context; keep Context Source producers with their observed domains, and keep Session History selection plus Context Epoch persistence Session-owned.