Install
$ agentstack add skill-timmy6942025-opencode-builder-skill-opencode-sdk ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
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:
| Option | Type | Description | Default | |---|---|---|---| | hostname | string | Server hostname | 127.0.0.1 | | port | number | Server port | 4096 | | signal | AbortSignal | Abort signal for cancellation | undefined | | timeout | number | Timeout in ms for server start | 5000 | | config | Config | Configuration 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:
| Option | Type | Description | Default | |---|---|---|---| | baseUrl | string | URL of the server | http://localhost:4096 | | fetch | function | Custom fetch implementation | globalThis.fetch | | parseAs | string | Response parsing method | auto | | responseStyle | string | Return style: "data" or "fields" | "fields" | | throwOnError | boolean | Throw errors instead of returning them | false |
Response Styles
The responseStyle option controls how API responses are returned:
"fields"(default): Returns{ data, error, response }— you checkerrormanually."data": Returns just the data payload directly. When combined withthrowOnError: 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 Name | Description | |---|---| | ProviderAuthError | Authentication failure with a provider. Contains providerID and message. | | UnknownError | An unexpected error occurred. Contains message. | | MessageOutputLengthError | Model output exceeded the maximum length. | | MessageAbortedError | The message generation was aborted. Contains message. | | ApiError | An API-level error. Contains message, statusCode, isRetryable, responseHeaders, responseBody. | | BadRequest | Invalid request parameters. Contains message and kind (Params, Headers, Query, Body, Payload). | | NotFoundError | Resource 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
| Type | Description | |---|---| | "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:
| Field | Type | Description | |---|---|---| | type | "json_schema" | Required. Specifies JSON schema mode | | schema | object | Required. JSON Schema object defining the output structure | | retryCount | number | Optional. 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
- Provide clear descriptions in your schema properties to help the model understand what data to extract.
- Use
requiredto specify which fields must be present. - Keep schemas focused — complex nested schemas may be harder for the model to fill correctly.
- 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:
| Parameter | Type | Description | |---|---|---| | query | string | Required. Search string (fuzzy match) | | type | "file" or "directory" | Optional. Limit results to files or directories | | directory
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Timmy6942025
- Source: Timmy6942025/opencode-builder-skill
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.