agentsclimarketplace

Opencode sdk

Skill Timmy6942025/opencode-builder-skill/skills/opencode-sdk

Kilo/agent skill for building OpenCode extensions, plugins, and integrations

Install
npx -y skills add Timmy6942025/opencode-builder-skill --skill opencode-sdk

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use this skill when using the OpenCode JavaScript/TypeScript SDK (@opencode-ai/sdk) to programmatically control OpenCode, create sessions, send prompts, manage files, control the TUI, stream events, or build external integrations. Covers both full lifecycle (server+client) and client-only modes, all API methods, structured output, and type safety.

SKILL.md

44.7 KB, as published. Nobody here has run it

OpenCode JavaScript/TypeScript SDK (@opencode-ai/sdk)

๐Ÿ“š Official Docs: For the latest information, always refer to the official documentation: https://opencode.ai/docs/sdk/

Type-safe JavaScript/TypeScript client for the OpenCode server. Use it to build integrations, automate workflows, and control OpenCode programmatically.

All types are auto-generated from the server's OpenAPI 3.1 specification and available in the types file.


Installation

npm install @opencode-ai/sdk
# or
bun add @opencode-ai/sdk

Two Client Modes

Full Lifecycle (createOpencode)

Starts an OpenCode server and returns a connected client. Use this when you own the server lifecycle.

import { createOpencode } from "@opencode-ai/sdk"

const { client, server } = await createOpencode({
  hostname: "127.0.0.1",
  port: 4096,
  timeout: 5000,
  config: {
    model: "anthropic/claude-3-5-sonnet-20241022",
  },
})

console.log(`Server running at ${server.url}`)

// Use the client...

server.close()

Options:

OptionTypeDescriptionDefault
hostnamestringServer hostname127.0.0.1
portnumberServer port4096
signalAbortSignalAbort signal for cancellationundefined
timeoutnumberTimeout in ms for server start5000
configConfigConfiguration object{}

The instance still picks up your opencode.json, but inline config overrides or adds to it. The returned server object has a .url property and a .close() method to shut down the server process.

Client Only (createOpencodeClient)

Connects to an already-running OpenCode server. Use this when the server is managed externally (e.g., opencode serve or a TUI instance).

import { createOpencodeClient } from "@opencode-ai/sdk"

const client = createOpencodeClient({
  baseUrl: "http://localhost:4096",
  throwOnError: true,
  responseStyle: "data",
})

Options:

OptionTypeDescriptionDefault
baseUrlstringURL of the serverhttp://localhost:4096
fetchfunctionCustom fetch implementationglobalThis.fetch
parseAsstringResponse parsing methodauto
responseStylestringReturn style: "data" or "fields""fields"
throwOnErrorbooleanThrow errors instead of returning themfalse

Response Styles

The responseStyle option controls how API responses are returned:

  • "fields" (default): Returns { data, error, response } โ€” you check error manually.
  • "data": Returns just the data payload directly. When combined with throwOnError: true, throws on error.
// "fields" style โ€” check error manually
const result = await client.session.get({ path: { id: "abc" } })
if (result.error) {
  console.error("Error:", result.error)
} else {
  console.log(result.data)
}

// "data" style โ€” returns payload directly
const session = await client.session.get({ path: { id: "abc" } })
// session is the Session object directly

Error Handling

throwOnError

When throwOnError is false (default), errors are returned in the error field of the response object. When true, they throw and should be caught:

try {
  const session = await client.session.get({ path: { id: "invalid-id" } })
} catch (error) {
  console.error("Failed to get session:", (error as Error).message)
}

Error Types

The SDK can return the following error types:

Error NameDescription
ProviderAuthErrorAuthentication failure with a provider. Contains providerID and message.
UnknownErrorAn unexpected error occurred. Contains message.
MessageOutputLengthErrorModel output exceeded the maximum length.
MessageAbortedErrorThe message generation was aborted. Contains message.
ApiErrorAn API-level error. Contains message, statusCode, isRetryable, responseHeaders, responseBody.
BadRequestInvalid request parameters. Contains message and kind (Params, Headers, Query, Body, Payload).
NotFoundErrorResource not found. Contains message.

StructuredOutputError

If the model fails to produce valid structured output after all retries, the response will include a StructuredOutputError:

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)
}

Types

Import TypeScript definitions directly from the SDK:

import type {
  Session,
  Message,
  AssistantMessage,
  UserMessage,
  Part,
  TextPart,
  FilePart,
  ToolPart,
  ReasoningPart,
  Config,
  Project,
  Provider,
  Agent,
  Symbol,
  FileNode,
  FileContent,
  File,
  Command,
  Todo,
  AgentConfig,
  ProviderConfig,
  Auth,
  Event,
  GlobalEvent,
} from "@opencode-ai/sdk"

All types are generated from the server's OpenAPI specification.


Structured Output

Request validated JSON from the model by specifying a format with a JSON schema. The model uses a StructuredOutput tool to return matching JSON.

Basic Usage

const result = await client.session.prompt({
  path: { id: sessionId },
  body: {
    parts: [{ type: "text", text: "Research Anthropic and provide company info" }],
    format: {
      type: "json_schema",
      schema: {
        type: "object",
        properties: {
          company: { type: "string", description: "Company name" },
          founded: { type: "number", description: "Year founded" },
          products: {
            type: "array",
            items: { type: "string" },
            description: "Main products",
          },
        },
        required: ["company", "founded"],
      },
    },
  },
})

console.log(result.data.info.structured_output)
// { company: "Anthropic", founded: 2021, products: ["Claude", "Claude API"] }

Output Format Types

TypeDescription
"text"Default. Standard text response (no structured output)
"json_schema"Returns validated JSON matching the provided schema

JSON Schema Format

When using type: "json_schema", provide:

FieldTypeDescription
type"json_schema"Required. Specifies JSON schema mode
schemaobjectRequired. JSON Schema object defining the output structure
retryCountnumberOptional. Number of validation retries (default: 2)

Error Handling

If the model fails to produce valid structured output after all retries, the response will include a StructuredOutputError:

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)
}

Best Practices

  1. Provide clear descriptions in your schema properties to help the model understand what data to extract.
  2. Use required to specify which fields must be present.
  3. Keep schemas focused โ€” complex nested schemas may be harder for the model to fill correctly.
  4. Set appropriate retryCount โ€” increase for complex schemas, decrease for simple ones.

Core Workflows

Session Management

import { createOpencode } from "@opencode-ai/sdk"

const { client, server } = await createOpencode()

// Create a session
const session = await client.session.create({
  body: { title: "My session" },
})
console.log("Session ID:", session.id)

// Create a child session
const childSession = await client.session.create({
  body: { title: "Child session", parentID: session.id },
})

// List all sessions
const sessions = await client.session.list()

// Get a session by ID
const retrieved = await client.session.get({ path: { id: session.id } })

// Update session title
await client.session.update({
  path: { id: session.id },
  body: { title: "Updated Title" },
})

// List child sessions
const children = await client.session.children({ path: { id: session.id } })

// Get todo list for a session
const todos = await client.session.todo({ path: { id: session.id } })

// Delete a session
await client.session.delete({ path: { id: childSession.id } })

Prompting

// Send a basic prompt
const result = await client.session.prompt({
  path: { id: session.id },
  body: {
    parts: [{ type: "text", text: "Hello!" }],
  },
})
console.log(result.data.info) // AssistantMessage

// Send prompt with a specific model
const result2 = await client.session.prompt({
  path: { id: session.id },
  body: {
    model: { providerID: "anthropic", modelID: "claude-3-5-sonnet-20241022" },
    parts: [{ type: "text", text: "Explain this codebase" }],
  },
})

// Send prompt with a specific agent
const result3 = await client.session.prompt({
  path: { id: session.id },
  body: {
    agent: "plan",
    parts: [{ type: "text", text: "Create a plan for this feature" }],
  },
})

// Send prompt with system override
const result4 = await client.session.prompt({
  path: { id: session.id },
  body: {
    system: "You are a senior Go developer. Be concise.",
    parts: [{ type: "text", text: "Review this function" }],
  },
})

// Send prompt with tool control
const result5 = await client.session.prompt({
  path: { id: session.id },
  body: {
    tools: { read: true, write: false, bash: false },
    parts: [{ type: "text", text: "Read and analyze the code" }],
  },
})

// Inject context without triggering AI response (noReply)
await client.session.prompt({
  path: { id: session.id },
  body: {
    noReply: true,
    parts: [{ type: "text", text: "You are a helpful assistant." }],
  },
})

// Send prompt with file parts
const result6 = await client.session.prompt({
  path: { id: session.id },
  body: {
    parts: [
      { type: "text", text: "Review this file" },
      {
        type: "file",
        mime: "text/plain",
        url: "file:///path/to/file.ts",
      },
    ],
  },
})

// Send prompt with agent invocation part
const result7 = await client.session.prompt({
  path: { id: session.id },
  body: {
    parts: [
      {
        type: "agent",
        name: "explore",
        source: { value: "Find all usages of createOpencode", start: 0, end: 30 },
      },
    ],
  },
})

// Send prompt asynchronously (fire and forget)
await client.session.prompt_async({
  path: { id: session.id },
  body: {
    parts: [{ type: "text", text: "Process this in the background" }],
  },
})

Commands

// Execute a slash command
const cmdResult = await client.session.command({
  path: { id: session.id },
  body: {
    command: "compact",
    arguments: "",
  },
})

// List available commands
const commands = await client.command.list()

Shell

// Run a shell command
const shellResult = await client.session.shell({
  path: { id: session.id },
  body: {
    agent: "build",
    command: "ls -la",
  },
})

// Run shell with specific model
const shellResult2 = await client.session.shell({
  path: { id: session.id },
  body: {
    agent: "build",
    model: { providerID: "anthropic", modelID: "claude-3-5-sonnet-20241022" },
    command: "git status",
  },
})

Messages

// List messages in a session
const messages = await client.session.messages({ path: { id: session.id } })
for (const msg of messages) {
  console.log(msg.info.role, msg.parts.map(p => p.type))
}

// List messages with limit
const recentMessages = await client.session.messages({
  path: { id: session.id },
  query: { limit: 10 },
})

// Get a single message
const msg = await client.session.message({
  path: { id: session.id, messageID: "msg-123" },
})

Abort

// Abort a running session
await client.session.abort({ path: { id: session.id } })

Share / Unshare

// Share a session (generates a shareable URL)
const shared = await client.session.share({ path: { id: session.id } })
console.log("Share URL:", shared.share?.url)

// Unshare a session
const unshared = await client.session.unshare({ path: { id: session.id } })

Diff

// Get the diff for a session
const diffs = await client.session.diff({ path: { id: session.id } })
for (const diff of diffs) {
  console.log(`${diff.file}: +${diff.additions} -${diff.deletions}`)
}

// Get diff for a specific message
const msgDiffs = await client.session.diff({
  path: { id: session.id },
  query: { messageID: "msg-123" },
})

Summarize

// Summarize a session
await client.session.summarize({
  path: { id: session.id },
  body: {
    providerID: "anthropic",
    modelID: "claude-3-5-sonnet-20241022",
  },
})

Revert / Unrevert

// Revert a specific message
await client.session.revert({
  path: { id: session.id },
  body: {
    messageID: "msg-123",
    partID: "part-456", // optional โ€” revert a specific part
  },
})

// Restore all reverted messages
await client.session.unrevert({ path: { id: session.id } })

Permissions Response

// Respond to a permission request
await client.postSessionByIdPermissionsByPermissionId({
  path: {
    id: session.id,
    permissionID: "perm-123",
  },
  body: {
    response: "always", // "once" | "always" | "reject"
  },
})

Fork

// Fork a session at a specific message
const forked = await client.session.fork({
  path: { id: session.id },
  body: {
    messageID: "msg-123", // optional โ€” fork from the beginning if omitted
  },
})
console.log("Forked session:", forked.id)

Init

// Analyze the current app and create AGENTS.md
await client.session.init({
  path: { id: session.id },
  body: {
    messageID: "msg-123",
    providerID: "anthropic",
    modelID: "claude-3-5-sonnet-20241022",
  },
})

Session Status

// Get status for all sessions
const statuses = await client.session.status()
// Returns: { [sessionID: string]: SessionStatus }
// SessionStatus is: { type: "idle" } | { type: "busy" } | { type: "retry", attempt: number, message: string, next: number }

File Operations

// Read a file
const content = await client.file.read({
  query: { path: "src/index.ts" },
})
console.log(content.content) // file content string

// List files and directories at a path
const fileList = await client.file.list({
  query: { path: "src" },
})
// Returns FileNode[] with { name, path, absolute, type, ignored }

// Search for text in files (ripgrep-style)
const textResults = await client.find.text({
  query: { pattern: "function.*opencode" },
})
for (const match of textResults) {
  console.log(`${match.path.text}:${match.line_number}: ${match.lines.text}`)
}

// Find files by name (fuzzy match)
const files = await client.find.files({
  query: { query: "*.ts" },
})

// Find directories
const dirs = await client.find.files({
  query: { query: "packages", type: "directory", limit: 20 },
})

// Find files with directory override
const customDirFiles = await client.find.files({
  query: { query: "*.json", directory: "/some/other/path" },
})

// Find workspace symbols
const symbols = await client.find.symbols({
  query: { query: "createOpencode" },
})

// Get file status (tracked files with git status)
const status = await client.file.status()
// Returns File[] with { path, added, removed, status: "added" | "deleted" | "modified" }

find.files query parameters:

ParameterTypeDescription
querystringRequired. Search string (fuzzy match)
type"file" or "directory"Optional. Limit results to files or directories
directorystringOptional. Override the project root for the search
limitnumberOptional. Max results (1โ€“200)
dirs"true" or "false"Optional. Legacy flag ("false" returns only files)

TUI Control

// Build a prompt incrementally
await client.tui.appendPrompt({ body: { text: "Fix the bug in" } })
await client.tui.appendPrompt({ body: { text: " src/index.ts" } })
await client.tui.submitPrompt()

// Clear the prompt
await client.tui.clearPrompt()

// Show toast notifications
await client.tui.showToast({
  body: { message: "Task completed", variant: "success" },
})

// Toast with title and duration
await client.tui.showToast({
  body: {
    title: "Build",
    message: "Compilation finished",
    variant: "info",
    duration: 3000,
  },
})

// Toast variants: "info" | "success" | "warning" | "error"
await client.tui.showToast({
  body: { message: "Something went wrong", variant: "error" },
})

// Open UI panels
await client.tui.openModels()
await client.tui.openThemes()
await client.tui.openSessions()
await client.tui.openHelp()

// Execute a command
await client.tui.executeCommand({ body: { command: "/compact" } })

// TUI control (for advanced use โ€” wait for control requests and respond)
const control = await client.tui.control.next()
// control = { path: string, body: unknown }
await client.tui.control.response({ body: control.body })

Event Streaming

// Subscribe to server-sent events
const events = await client.event.subscribe()

for await (const event of events.stream) {
  console.log("Event:", event.type, event.properties)
}

Filtering Events

const events = await client.event.subscribe()

for await (const event of events.stream) {
  switch (event.type) {
    case "session.created":
      console.log("New session:", event.properties.info.id)
      break
    case "session.updated":
      console.log("Session updated:", event.properties.info.title)
      break
    case "session.deleted":
      console.log("Session deleted:", event.properties.info.id)
      break
    case "message.updated":
      console.log("Message:", event.properties.info.role)
      break
    case "message.part.updated":
      if (event.properties.part.type === "text") {
        process.stdout.write(event.properties.delta ?? event.properties.part.text)
      }
      break
    case "message.removed":
      console.log("Message removed:", event.properties.messageID)
      break
    case "permission.updated":
      console.log("Permission request:", event.properties.title)
      break
    case "session.status":
      console.log("Session status:", event.properties.status.type)
      break
    case "session.idle":
      console.log("Session idle:", event.properties.sessionID)
      break
    case "session.error":
      console.error("Session error:", event.properties.error)
      break
    case "file.edited":
      console.log("File edited:", event.properties.file)
      break
    case "todo.updated":
      console.log("Todos:", event.properties.todos)
      break
    case "session.diff":
      console.log("Diff:", event.properties.diff)
      break
    case "session.compacted":
      console.log("Session compacted:", event.properties.sessionID)
      break
    case "vcs.branch.updated":
      console.log("Branch changed:", event.properties.branch)
      break
    case "server.connected":
      console.log("Server connected")
      break
    case "installation.updated":
      console.log("Installed version:", event.properties.version)
      break
    case "installation.update-available":
      console.log("Update available:", event.properties.version)
      break
    default:
      console.log("Unknown event:", event.type)
  }
}

Complete Event Type List

Event TypeDescription
server.connectedServer connection established
server.instance.disposedServer instance disposed
installation.updatedVersion installed
installation.update-availableUpdate available
session.createdSession created
session.updatedSession updated
session.deletedSession deleted
session.statusSession status changed (idle, busy, retry)
session.idleSession became idle
session.compactedSession was compacted
session.diffSession diff available
session.errorSession error occurred
message.updatedMessage created or updated
message.removedMessage removed
message.part.updatedMessage part updated (includes delta for streaming)
message.part.removedMessage part removed
permission.updatedPermission request pending
permission.repliedPermission request replied
file.editedFile was edited
file.watcher.updatedFile system change detected
todo.updatedTodo list updated
command.executedCommand executed
vcs.branch.updatedGit branch changed
tui.prompt.appendPrompt text appended
tui.command.executeTUI command executed
tui.toast.showToast notification shown
pty.createdPTY session created
pty.updatedPTY session updated
pty.exitedPTY session exited
pty.deletedPTY session deleted
lsp.client.diagnosticsLSP diagnostics received
lsp.updatedLSP status updated

Authentication

// Set API key for a provider
await client.auth.set({
  path: { id: "anthropic" },
  body: { type: "api", key: "your-api-key" },
})

// Set API key with metadata
await client.auth.set({
  path: { id: "openai" },
  body: {
    type: "api",
    key: "sk-...",
    metadata: { org: "org-..." },
  },
})

// Set OAuth credentials
await client.auth.set({
  path: { id: "github-copilot" },
  body: {
    type: "oauth",
    refresh: "refresh-token",
    access: "access-token",
    expires: Date.now() + 3600000,
  },
})

// Set well-known auth
await client.auth.set({
  path: { id: "some-provider" },
  body: {
    type: "wellknown",
    key: "some-key",
    token: "some-token",
  },
})

Logging

// Write a log entry
await client.app.log({
  body: {
    service: "my-app",
    level: "info",
    message: "Operation completed",
  },
})

// Log with extra metadata
await client.app.log({
  body: {
    service: "my-app",
    level: "error",
    message: "Request failed",
    extra: {
      statusCode: 500,
      url: "/api/users",
      retryCount: 3,
    },
  },
})

// Log levels: "debug" | "info" | "warn" | "error"
await client.app.log({
  body: {
    service: "my-app",
    level: "debug",
    message: "Debug details",
    extra: { userId: "123", action: "login" },
  },
})

Complete API Reference

Global

MethodDescriptionResponse
client.global.health()Check server health and version{ healthy: true, version: string }
const health = await client.global.health()
console.log(`Server v${health.data.version} is healthy: ${health.data.healthy}`)

Global Event (Global SSE Stream)

MethodDescriptionResponse
client.global.event.subscribe()Subscribe to global event stream (cross-project)GlobalEvent stream
const globalEvents = await client.global.event.subscribe()
for await (const event of globalEvents.stream) {
  console.log(`[${event.directory}]`, event.payload.type, event.payload.properties)
}

App

MethodDescriptionResponse
client.app.log()Write a log entryboolean
client.app.agents()List all available agentsAgent[]
await client.app.log({
  body: {
    service: "my-app",
    level: "info",
    message: "Operation completed",
  },
})

const agents = await client.app.agents()
for (const agent of agents) {
  console.log(agent.name, agent.mode)
}

Project

MethodDescriptionResponse
client.project.list()List all projectsProject[]
client.project.current()Get current projectProject
const projects = await client.project.list()
for (const p of projects) {
  console.log(`${p.id}: ${p.worktree}`)
}

const current = await client.project.current()
console.log("Current project:", current.worktree)

Path

MethodDescriptionResponse
client.path.get()Get current path infoPath
const pathInfo = await client.path.get()
console.log("State:", pathInfo.state)
console.log("Config:", pathInfo.config)
console.log("Worktree:", pathInfo.worktree)
console.log("Directory:", pathInfo.directory)

Config

MethodDescriptionResponse
client.config.get()Get configConfig
client.config.update()Update config (PATCH)Config
client.config.providers()List providers and default models{ providers: Provider[], default: { [key: string]: string } }
// Get config
const config = await client.config.get()
console.log("Model:", config.model)

// Update config (partial patch)
const updated = await client.config.update({
  body: { model: "anthropic/claude-3-5-sonnet-20241022" },
})

// List providers and default models
const { providers, default: defaults } = await client.config.providers()
for (const provider of providers) {
  console.log(`${provider.id}: ${Object.keys(provider.models).length} models`)
}

Provider

MethodDescriptionResponse
client.provider.list()List all providers with full details{ all: Provider[], default: {...}, connected: string[] }
client.provider.auth()Get provider auth methods{ [providerID: string]: ProviderAuthMethod[] }
client.provider.oauth.authorize()Authorize via OAuthProviderAuthAuthorization
client.provider.oauth.callback()Handle OAuth callbackboolean
// List all providers
const { all, connected } = await client.provider.list()
console.log("Connected providers:", connected)

// Get auth methods
const authMethods = await client.provider.auth()
console.log("Anthropic auth methods:", authMethods["anthropic"])

// OAuth authorize
const auth = await client.provider.oauth.authorize({
  path: { id: "github-copilot" },
  body: { method: 0 },
})
console.log("Open this URL:", auth.url)

Sessions

MethodDescriptionResponse
client.session.list()List all sessionsSession[]
client.session.create({ body })Create a sessionSession
client.session.get({ path })Get session by IDSession
client.session.delete({ path })Delete a sessionboolean
client.session.update({ path, body })Update session propertiesSession
client.session.children({ path })List child sessionsSession[]
client.session.todo({ path })Get todo list for a sessionTodo[]
client.session.status()Get status for all sessions{ [sessionID: string]: SessionStatus }
client.session.init({ path, body })Analyze app, create AGENTS.mdboolean
client.session.fork({ path, body })Fork a sessionSession
client.session.abort({ path })Abort a running sessionboolean
client.session.share({ path })Share a sessionSession
client.session.unshare({ path })Unshare a sessionSession
client.session.diff({ path, query })Get diff for a sessionFileDiff[]
client.session.summarize({ path, body })Summarize a sessionboolean
client.session.revert({ path, body })Revert a messageSession
client.session.unrevert({ path })Restore reverted messagesSession
client.postSessionByIdPermissionsByPermissionId()Respond to permission requestboolean

Messages

MethodDescriptionResponse
client.session.messages({ path, query })List messages in a session{ info: Message, parts: Part[] }[]
client.session.message({ path })Get message details{ info: Message, parts: Part[] }
client.session.prompt({ path, body })Send a prompt (waits for response){ info: AssistantMessage, parts: Part[] }
client.session.prompt_async({ path, body })Send prompt asynchronously (no wait)void (204)
client.session.command({ path, body })Execute a slash command{ info: AssistantMessage, parts: Part[] }
client.session.shell({ path, body })Run a shell commandAssistantMessage

session.prompt body fields:

FieldTypeDescription
partsArray<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput>Required. Message parts
model{ providerID: string, modelID: string }Optional. Model to use
agentstringOptional. Agent to use
noReplybooleanOptional. Inject context without triggering AI response
systemstringOptional. System prompt override
tools{ [key: string]: boolean }Optional. Enable/disable specific tools
messageIDstringOptional. Message ID for updates
formatFormatOptional. Structured output format

session.shell body fields:

FieldTypeDescription
commandstringRequired. Shell command to execute
agentstringRequired. Agent to use
model{ providerID: string, modelID: string }Optional. Model to use

session.command body fields:

FieldTypeDescription
commandstringRequired. Command name
argumentsstringRequired. Command arguments
agentstringOptional. Agent to use
modelstringOptional. Model to use
messageIDstringOptional. Message ID

Files

MethodDescriptionResponse
client.file.read({ query })Read a fileFileContent
client.file.list({ query })List files/directories at a pathFileNode[]
client.file.status({ query })Get status for tracked filesFile[]
client.find.text({ query })Search for text in filesMatch objects with path, lines, line_number, absolute_offset, submatches
client.find.files({ query })Find files and directories by namestring[] (paths)
client.find.symbols({ query })Find workspace symbolsSymbol[]

file.read response (FileContent):

{
  type: "text" | "binary"
  content: string
  diff?: string
  patch?: {
    oldFileName: string
    newFileName: string
    oldHeader?: string
    newHeader?: string
    hunks: Array<{
      oldStart: number
      oldLines: number
      newStart: number
      newLines: number
      lines: string[]
    }>
    index?: string
  }
  encoding?: "base64"
  mimeType?: string
}

file.list response (FileNode):

{
  name: string
  path: string
  absolute: string
  type: "file" | "directory"
  ignored: boolean
}

file.status response (File):

{
  path: string
  added: number
  removed: number
  status: "added" | "deleted" | "modified"
}

find.symbols response (Symbol):

{
  name: string
  kind: number
  location: {
    uri: string
    range: {
      start: { line: number, character: number }
      end: { line: number, character: number }
    }
  }
}

TUI

MethodDescriptionResponse
client.tui.appendPrompt({ body })Append text to the promptboolean
client.tui.submitPrompt()Submit the current promptboolean
client.tui.clearPrompt()Clear the promptboolean
client.tui.openHelp()Open the help dialogboolean
client.tui.openSessions()Open the session selectorboolean
client.tui.openModels()Open the model selectorboolean
client.tui.openThemes()Open the theme selectorboolean
client.tui.executeCommand({ body })Execute a commandboolean
client.tui.showToast({ body })Show a toast notificationboolean
client.tui.control.next()Wait for the next control request{ path: string, body: unknown }
client.tui.control.response({ body })Respond to a control requestboolean

showToast body:

FieldTypeDescription
titlestringOptional. Toast title
messagestringRequired. Toast message
variant"info" | "success" | "warning" | "error"Required. Toast variant
durationnumberOptional. Duration in milliseconds

Available TUI commands (for executeCommand):

CommandDescription
session.listList sessions
session.newNew session
session.shareShare session
session.interruptInterrupt session
session.compactCompact session
session.page.upScroll up one page
session.page.downScroll down one page
session.half.page.upScroll up half page
session.half.page.downScroll down half page
session.firstNavigate to first message
session.lastNavigate to last message
prompt.clearClear prompt
prompt.submitSubmit prompt
agent.cycleCycle through agents
(custom string)Any custom command

Auth

MethodDescriptionResponse
client.auth.set({ path, body })Set authentication credentialsboolean

Auth body types:

// API key auth
{ type: "api", key: string, metadata?: { [key: string]: string } }

// OAuth auth
{ type: "oauth", refresh: string, access: string, expires: number, enterpriseUrl?: string }

// Well-known auth
{ type: "wellknown", key: string, token: string }

Events

MethodDescriptionResponse
client.event.subscribe()Subscribe to server-sent eventsEvent stream

Type Safety

All API methods are fully typed. The SDK uses the OpenAPI spec to generate TypeScript types for every request and response.

Key Types

import type {
  // Session types
  Session,
  SessionStatus,

  // Message types
  Message,
  UserMessage,
  AssistantMessage,

  // Part types (message components)
  Part,
  TextPart,
  FilePart,
  ToolPart,
  ReasoningPart,
  StepStartPart,
  StepFinishPart,
  SnapshotPart,
  PatchPart,
  AgentPart,
  RetryPart,
  CompactionPart,

  // Input types
  TextPartInput,
  FilePartInput,
  AgentPartInput,
  SubtaskPartInput,

  // Config types
  Config,
  AgentConfig,
  ProviderConfig,
  KeybindsConfig,

  // Provider types
  Provider,
  Model,

  // File types
  FileNode,
  FileContent,
  File,
  FileDiff,

  // Project types
  Project,

  // Other types
  Symbol,
  Command,
  Agent,
  Todo,
  Auth,
  Path,

  // Event types
  Event,
  GlobalEvent,
} from "@opencode-ai/sdk"

Tool State Types

import type {
  ToolState,
  ToolStatePending,
  ToolStateRunning,
  ToolStateCompleted,
  ToolStateError,
} from "@opencode-ai/sdk"

// ToolState is a union of:
// ToolStatePending   { status: "pending", input, raw }
// ToolStateRunning   { status: "running", input, title?, metadata?, time }
// ToolStateCompleted { status: "completed", input, output, title, metadata, time, attachments? }
// ToolStateError     { status: "error", input, error, metadata?, time }

Complete End-to-End Example

import { createOpencode } from "@opencode-ai/sdk"

async function main() {
  const { client, server } = await createOpencode({
    port: 4096,
    config: { model: "anthropic/claude-3-5-sonnet-20241022" },
  })

  try {
    // 1. Health check
    const health = await client.global.health()
    console.log(`Server v${health.data.version} is healthy`)

    // 2. Configure auth
    await client.auth.set({
      path: { id: "anthropic" },
      body: { type: "api", key: process.env.ANTHROPIC_API_KEY! },
    })

    // 3. Check available providers
    const { providers } = await client.config.providers()
    console.log("Available providers:", providers.map(p => p.id))

    // 4. Create a session
    const session = await client.session.create({
      body: { title: "Code Review" },
    })
    console.log("Created session:", session.id)

    // 5. Subscribe to events in the background
    const eventPromise = (async () => {
      const events = await client.event.subscribe()
      for await (const event of events.stream) {
        if (event.type === "message.part.updated" && event.properties.part.type === "text") {
          process.stdout.write(event.properties.delta ?? event.properties.part.text)
        }
      }
    })()

    // 6. Send a prompt with structured output
    const result = await client.session.prompt({
      path: { id: session.id },
      body: {
        parts: [{ type: "text", text: "Review this codebase for issues" }],
        format: {
          type: "json_schema",
          schema: {
            type: "object",
            properties: {
              issues: {
                type: "array",
                items: {
                  type: "object",
                  properties: {
                    file: { type: "string" },
                    severity: { type: "string" },
                    description: { type: "string" },
                  },
                },
              },
            },
          },
        },
      },
    })

    console.log("\nStructured output:", result.data.info.structured_output)

    // 7. Search files
    const tsFiles = await client.find.files({ query: { query: "*.ts", type: "file" } })
    console.log("TypeScript files:", tsFiles.length)

    // 8. Search for text
    const matches = await client.find.text({ query: { pattern: "function" } })
    console.log("Function matches:", matches.length)

    // 9. Read a file
    if (tsFiles.length > 0) {
      const fileContent = await client.file.read({ query: { path: tsFiles[0] } })
      console.log(`First file (${tsFiles[0]}): ${fileContent.content.length} chars`)
    }

    // 10. Run a shell command
    const shell = await client.session.shell({
      path: { id: session.id },
      body: { agent: "build", command: "git log --oneline -5" },
    })
    console.log("Shell output:", shell)

    // 11. Get diff
    const diffs = await client.session.diff({ path: { id: session.id } })
    console.log("Diffs:", diffs)

    // 12. Share the session
    const shared = await client.session.share({ path: { id: session.id } })
    console.log("Share URL:", shared.share?.url)

    // 13. Get session status
    const statuses = await client.session.status()
    console.log("Session statuses:", statuses)

    // 14. Update session title
    await client.session.update({
      path: { id: session.id },
      body: { title: "Code Review - Completed" },
    })

    // 15. Log the operation
    await client.app.log({
      body: {
        service: "review-bot",
        level: "info",
        message: "Code review completed",
        extra: { sessionID: session.id, issuesFound: 5 },
      },
    })

    // 16. Show a success toast
    await client.tui.showToast({
      body: { message: "Code review completed!", variant: "success" },
    })

  } finally {
    // Always close the server
    server.close()
  }
}

main()

Additional Examples

Build Agent โ€” Multi-turn Conversation

const { client, server } = await createOpencode()

const session = await client.session.create({
  body: { title: "Multi-turn" },
})

// First message
await client.session.prompt({
  path: { id: session.id },
  body: {
    parts: [{ type: "text", text: "Create a simple HTTP server in Go" }],
  },
})

// Follow-up message in same session (context preserved)
await client.session.prompt({
  path: { id: session.id },
  body: {
    parts: [{ type: "text", text: "Now add error handling" }],
  },
})

// List all messages
const messages = await client.session.messages({ path: { id: session.id } })
for (const msg of messages) {
  console.log(`[${msg.info.role}]`, msg.parts.length, "parts")
}

server.close()

Background Prompting with Async

const { client, server } = await createOpencode()

const session = await client.session.create({
  body: { title: "Background Task" },
})

// Fire and forget โ€” returns immediately
await client.session.prompt_async({
  path: { id: session.id },
  body: {
    parts: [{ type: "text", text: "Analyze this codebase" }],
  },
})

// Do other work...
console.log("Prompt submitted in background")

// Monitor via events
const events = await client.event.subscribe()
for await (const event of events.stream) {
  if (event.type === "session.idle" && event.properties.sessionID === session.id) {
    console.log("Background task completed!")
    break
  }
}

// Now fetch the results
const messages = await client.session.messages({ path: { id: session.id } })
console.log("Messages:", messages.length)

server.close()

Permission Auto-Approval

const events = await client.event.subscribe()

for await (const event of events.stream) {
  if (event.type === "permission.updated") {
    const perm = event.properties
    console.log(`Permission request: ${perm.title} (${perm.type})`)

    // Auto-approve all permissions
    await client.postSessionByIdPermissionsByPermissionId({
      path: {
        id: perm.sessionID,
        permissionID: perm.id,
      },
      body: {
        response: "always",
      },
    })
  }
}

Config Update at Runtime

// Get current config
const config = await client.config.get()
console.log("Current model:", config.model)

// Update config
const updated = await client.config.update({
  body: {
    model: "anthropic/claude-sonnet-4-20250514",
    agent: {
      build: {
        model: "anthropic/claude-sonnet-4-20250514",
        maxSteps: 20,
      },
      plan: {
        model: "anthropic/claude-sonnet-4-20250514",
      },
    },
  },
})
console.log("Updated model:", updated.model)

Revert a Specific Message

// Revert a message and its changes
await client.session.revert({
  path: { id: session.id },
  body: {
    messageID: "msg-123",
    partID: "part-456", // optional โ€” if omitted, reverts the entire message
  },
})

// Check the revert state
const session = await client.session.get({ path: { id: session.id } })
console.log("Revert info:", session.revert)

// Undo the revert
await client.session.unrevert({ path: { id: session.id } })

Fork from a Specific Point

// Fork a session at a specific message to create a branch
const forkedSession = await client.session.fork({
  path: { id: originalSession.id },
  body: {
    messageID: "msg-42", // fork from this message
  },
})

console.log("Forked session:", forkedSession.id)

// The forked session has all messages up to the fork point
const messages = await client.session.messages({
  path: { id: forkedSession.id },
})
console.log("Messages in fork:", messages.length)

MCP Server Management

// Check MCP server status
const mcpStatus = await client.mcp.status()
console.log("MCP servers:", mcpStatus)

// Add a local MCP server
await client.mcp.add({
  body: {
    name: "my-tools",
    config: {
      type: "local",
      command: ["node", "my-mcp-server.js"],
      environment: { API_KEY: "..." },
      enabled: true,
      timeout: 10000,
    },
  },
})

// Add a remote MCP server
await client.mcp.add({
  body: {
    name: "remote-tools",
    config: {
      type: "remote",
      url: "https://mcp.example.com/sse",
      headers: { Authorization: "Bearer token" },
      enabled: true,
    },
  },
})

// Connect / disconnect
await client.mcp.connect({ path: { name: "my-tools" } })
await client.mcp.disconnect({ path: { name: "my-tools" } })

LSP and Formatter Status

// Check LSP server status
const lsp = await client.lsp.status()
for (const server of lsp) {
  console.log(`${server.name}: ${server.status}`)
}

// Check formatter status
const formatters = await client.formatter.status()
for (const fmt of formatters) {
  console.log(`${fmt.name}: ${fmt.enabled ? "enabled" : "disabled"}`)
}

Key Points

  • Two modes: createOpencode (starts server + client) or createOpencodeClient (connects to existing server).
  • Response styles: "fields" (default, returns { data, error }) or "data" (returns payload directly).
  • Structured output: Use format: { type: "json_schema", schema: {...} } in prompt body for validated JSON responses.
  • noReply: true: Inject context into a session without triggering an AI response.
  • Events: Use client.event.subscribe() for real-time streaming of session, message, and system events.
  • All types are auto-generated from the OpenAPI spec โ€” import them from @opencode-ai/sdk.
  • Permission handling: Listen for permission.updated events and respond via postSessionByIdPermissionsByPermissionId.
  • TUI control: Use client.tui.* methods to drive the TUI from external code (used by IDE plugins).
  • Always close: Call server.close() when using createOpencode to clean up resources.
  • OpenAPI spec: View at http://<hostname>:<port>/doc for the full API definition.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.