Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Initial version
  • Loading branch information
Krzysztof-Cieslak committed Mar 11, 2026
commit 03bb5e598a9e29f7fdb40fcdc3fdfa7e80b6cd26
20 changes: 18 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

129 changes: 129 additions & 0 deletions packages/opencode-mini/examples/basic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/**
* opencode-mini usage examples
*
* Run with: bun run packages/opencode-mini/examples/basic.ts
*/
import { create, tool, Session } from "../src/index"

// 1. Create an instance pointed at your project directory
const mini = create({ directory: process.cwd() })

// 2. Bootstrap opencode (loads plugins, DB, tools, etc.)
await mini.init()

// 3. Create a session
const session = await mini.session.create({ title: "My first session" })
console.log("Created session:", session.id)

// 4. Send a message and get the assistant response
const response = await mini.prompt({
sessionID: session.id,
parts: [{ type: "text", text: "What files are in the current directory?" }],
})
console.log("Assistant responded with", response.parts.length, "parts")

// 5. Retrieve conversation history
const messages = await mini.session.messages(session.id)
for (const msg of messages) {
console.log(`[${msg.info.role}]`, msg.parts.length, "parts")
}

// 6. List all sessions
const all = await mini.session.list()
console.log("Total sessions:", all.length)

// 7. Restore a session by ID
const restored = await mini.session.get(session.id)
console.log("Restored session:", restored.title)

// ---------------------------------------------------------------------------
// Multi-tenant: per-user API credentials
// ---------------------------------------------------------------------------

// Register credentials for different users
mini.credentials.set("user-alice", {
providerID: "anthropic",
apiKey: "sk-ant-alice-key",
})
mini.credentials.set("user-bob", {
providerID: "openai",
apiKey: "sk-bob-key",
})

// Each prompt specifies which user's credentials to use.
// Credentials are scoped to the prompt lifetime only — different users
// can take turns in the same session, each using their own API key.
const shared = await mini.session.create({ title: "Shared session" })

// Alice sends a message (uses her Anthropic key)
await mini.prompt({
sessionID: shared.id,
parts: [{ type: "text", text: "Hello from Alice" }],
userId: "user-alice",
model: { providerID: "anthropic", modelID: "claude-sonnet-4-20250514" },
})

// Bob continues the same conversation (uses his OpenAI key)
await mini.prompt({
sessionID: shared.id,
parts: [{ type: "text", text: "Hello from Bob" }],
userId: "user-bob",
model: { providerID: "openai", modelID: "gpt-4o" },
})

// Remove credentials when a user logs out
mini.credentials.remove("user-alice")

// ---------------------------------------------------------------------------
// Custom tools
// ---------------------------------------------------------------------------

await mini.tools.register(
"current_time",
tool({
description: "Returns the current date and time",
args: {},
async execute() {
return new Date().toISOString()
},
}),
)

// ---------------------------------------------------------------------------
// Event subscription
// ---------------------------------------------------------------------------

// Subscribe to all events (raw bus)
const unsub = await mini.subscribeAll((event) => {
console.log("Event:", event.type)
})

// Subscribe to specific events
await mini.subscribe(Session.Event.Created, (event) => {
console.log("Session created:", event.properties.info.id)
})

// Unsubscribe when done
unsub()

// ---------------------------------------------------------------------------
// Cancel an in-progress prompt
// ---------------------------------------------------------------------------

const long = await mini.session.create({ title: "Cancellable" })

// Start a prompt in the background
const pending = mini.prompt({
sessionID: long.id,
parts: [{ type: "text", text: "Write a very long essay about the history of computing" }],
})

// Cancel it after 2 seconds
setTimeout(() => mini.cancel(long.id), 2000)
await pending.catch(() => console.log("Prompt cancelled"))

// ---------------------------------------------------------------------------
// Cleanup
// ---------------------------------------------------------------------------

await mini.dispose()
23 changes: 23 additions & 0 deletions packages/opencode-mini/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/opencode-mini",
"version": "0.0.1",
"type": "module",
"license": "MIT",
"scripts": {
"typecheck": "tsgo --noEmit"
},
"exports": {
".": "./src/index.ts"
},
"dependencies": {
"opencode": "workspace:*",
"@opencode-ai/plugin": "workspace:*"
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:"
}
}
137 changes: 137 additions & 0 deletions packages/opencode-mini/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import type { Hooks, ToolDefinition } from "@opencode-ai/plugin"
import { Session } from "opencode/session"
import { SessionPrompt } from "opencode/session/prompt"
import { Bus } from "opencode/bus"
import { Instance } from "opencode/project/instance"
import { InstanceBootstrap } from "opencode/project/bootstrap"
import { Plugin } from "opencode/plugin"
import type { MessageV2 } from "opencode/session/message-v2"
import type { BusEvent } from "opencode/bus/bus-event"

type Credentials = {
providerID: string
apiKey: string
}

export function create(opts: { directory: string }) {
const dir = opts.directory
const creds = new Map<string, Credentials>()
const sessions = new Map<string, string>()
let ready = false

function wrap<T>(fn: () => T) {
return Instance.provide({
directory: dir,
init: InstanceBootstrap,
fn,
})
}

async function init() {
if (ready) return
await wrap(async () => {
const hooks = await Plugin.list()
hooks.push({
"chat.headers": async (input, output) => {
const uid = sessions.get(input.sessionID)
if (!uid) return
const cred = creds.get(uid)
if (!cred) return
if (cred.providerID !== input.model.providerID) return
output.headers["Authorization"] = "Bearer " + cred.apiKey
},
})
})
ready = true
}

return {
init,

credentials: {
set(id: string, value: Credentials) {
creds.set(id, value)
},
remove(id: string) {
creds.delete(id)
},
},

session: {
create(input?: { parentID?: string; title?: string }) {
return wrap(() => Session.create(input))
},
get(id: string) {
return wrap(() => Session.get(id))
},
list() {
return wrap(() => [...Session.list()])
},
messages(id: string, limit?: number) {
return wrap(() => Session.messages({ sessionID: id, limit }))
},
},

async prompt(input: {
sessionID: string
parts: SessionPrompt.PromptInput["parts"]
model?: { providerID: string; modelID: string }
userId?: string
system?: string
agent?: string
}) {
if (input.userId) sessions.set(input.sessionID, input.userId)
try {
return await wrap(() =>
SessionPrompt.prompt({
sessionID: input.sessionID,
parts: input.parts,
model: input.model,
system: input.system,
agent: input.agent,
}),
)
} finally {
sessions.delete(input.sessionID)
}
},

cancel(sessionID: string) {
return wrap(() => SessionPrompt.cancel(sessionID))
},

subscribe<D extends BusEvent.Definition>(
event: D,
handler: (payload: { type: D["type"]; properties: any }) => void,
) {
return wrap(() => Bus.subscribe(event, handler))
},

subscribeAll(handler: (event: any) => void) {
return wrap(() => Bus.subscribeAll(handler))
},

tools: {
register(name: string, definition: ToolDefinition) {
return wrap(async () => {
const hooks = await Plugin.list()
hooks.push({ tool: { [name]: definition } })
})
},
},

dispose() {
return wrap(() => Instance.dispose())
},
}
}

export type MiniInstance = ReturnType<typeof create>

export { Session } from "opencode/session"
export { SessionPrompt } from "opencode/session/prompt"
export { Bus } from "opencode/bus"
export { BusEvent } from "opencode/bus/bus-event"
export { MessageV2 } from "opencode/session/message-v2"
export { tool } from "@opencode-ai/plugin"
export type { Hooks, ToolDefinition, ToolContext } from "@opencode-ai/plugin"
14 changes: 14 additions & 0 deletions packages/opencode-mini/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"noUncheckedIndexedAccess": false,
"paths": {
"opencode/*": ["../opencode/src/*"],
"@/*": ["../opencode/src/*"],
"@tui/*": ["../opencode/src/cli/cmd/tui/*"]
}
},
"include": ["src", "examples"]
}