The OpenCode JS/TS SDK is published as @opencode-ai/sdk on npm and provides a type-safe client for programmatically controlling the OpenCode server.[1] Install the SDK with npm install @opencode-ai/sdk.[1] All TypeScript types (Session, Message, Part, etc.) are generated from the server's OpenAPI specification and importable directly from @opencode-ai/sdk.[1]
createOpencode() starts both a server and a client; it accepts hostname (default 127.0.0.1), port (default 4096), signal, timeout (default 5000 ms), and config options.[1] createOpencodeClient() creates a client-only connection to an already-running OpenCode server, accepting baseUrl (default http://localhost:4096), fetch, parseAs, responseStyle (data or fields, default fields), and throwOnError (default false).[1] The SDK exposes client.global.health(), client.app.log(), client.app.agents(), client.project.list(), client.project.current(), client.path.get(), client.config.get(), and client.config.providers() as top-level API methods.[1]
Example: using createOpencode() to start an embedded server, override the model, log the server URL, then shut it down.
import { createOpencode } from "@opencode-ai/sdk"
const opencode = await createOpencode({
hostname: "127.0.0.1",
port: 4096,
config: {
model: "anthropic/claude-3-5-sonnet-20241022",
},
})
console.log(`Server running at ${opencode.server.url}`)
opencode.server.close()
Creates an OpenCode server and client, then creates a session and sends a multi-part prompt (file + text) using the SDK
const server = await createOpencodeServer()
const client = createOpencodeClient({ baseUrl: server.url })
const session = await client.session.create()
await client.session.prompt({
path: { id: session.data.id },
body: {
parts: [
{ type: "file", mime: "text/plain", url: pathToFileURL(file).href },
{ type: "text", text: `Write tests for every public function in this file.` },
],
},
})
Fans out concurrent sessions over a list of files using Promise.all, each with its own session.create() call
await Promise.all(
input.map(async (file) => {
const session = await client.session.create()
await client.session.prompt({
path: { id: session.data.id },
body: {
parts: [
{ type: "file", mime: "text/plain", url: pathToFileURL(file).href },
{ type: "text", text: `Write tests for every public function in this file.` },
],
},
})
}),
)
Structured output is requested by passing a format field with type: "json_schema" and a JSON Schema schema to session.prompt(); the model uses a StructuredOutput tool internally to produce validated JSON.[1] The retryCount field on a json_schema format request controls how many validation retries the SDK attempts, defaulting to 2.[1]
If the model fails to produce valid structured output after all retries, the response contains a StructuredOutputError accessible at result.data.info.error:
if (result.data.info.error?.name === "StructuredOutputError") {
console.error("Failed to produce structured output:", result.data.info.error.message)
console.error("Attempts:", result.data.info.error.retries)
}
Create an Effect-Drizzle SQLite database service backed by an in-memory SQLite client using EffectDrizzleSqlite.makeWithDefaults
const makeDatabase = EffectDrizzleSqlite.makeWithDefaults()
const sqliteLayer = SqliteClient.layer({ filename: ":memory:", disableWAL: true })
class Database extends Context.Service<Database, DatabaseShape>()("@opencode/example/Database") {
static layer = Layer.effect(Database, makeDatabase).pipe(Layer.provide(sqliteLayer))
}
Run Drizzle migrations as an Effect using EffectDrizzleSqlite.migrate with a migrations folder path
yield* EffectDrizzleSqlite.migrate(db, { migrationsFolder: `${import.meta.dirname}/migrations` }).pipe(
Effect.mapError((cause) => new UserStoreError({ message: "Failed to migrate users", cause })),
)
Run a Drizzle transaction as an Effect using db.transaction, with configurable isolation behavior
yield* db
.transaction(
Effect.fnUntraced(function* (tx) {
yield* tx.insert(users).values({ name: from })
yield* tx.update(users).set({ name: to }).where(eq(users.name, from))
}),
{ behavior: "immediate" },
)
.pipe(Effect.asVoid, Effect.mapError(mapStoreError("Failed to rename user")))
The Effect-Drizzle SQLite integration is optional and targets projects already using the Effect framework and Drizzle ORM; it is not required to use the core SDK or session API.
Parses a single locale argument and returns default model, variant, and flag values
parseTranslationArgs(["fr"])
// => { target: "fr", concurrency: 1, model: "opencode/gpt-5.5", variant: "xhigh", dryRun: false, check: false, help: false }
Builds an agent translation config that disables share/formatter/lsp and scopes edit permissions to the target locale file
const config = translationConfig("translate-app-fr", "opencode/gpt-5.5", ["packages/app/src/i18n/fr.ts"])
// config.share === "disabled"
// config.formatter === false
// config.lsp === false
// config.agent["translate-app-fr"].permission.edit === { "*": "deny", "packages/app/src/i18n/fr.ts": "allow" }
Runs async tasks with bounded concurrency using runPool, returning results in input order
const result = await runPool([1, 2, 3, 4, 5], 2, async (item) => {
await Bun.sleep(5)
return item * 2
})
// result === [2, 4, 6, 8, 10], max concurrent === 2
Detects missing keys, extra keys, and placeholder mismatches between source and translated objects
findDrift(
{ keep: "Hello {{name}}", missing: "Missing", changed: "{{one}} {{two}}" },
{ keep: "Bonjour {{name}}", extra: "Extra", changed: "{{one}}" },
)
// => { missing: ["missing"], extra: ["extra"], placeholders: ["changed"] }
Sources