Dropstone SDK
Type-safe JS client for the Dropstone agent runtime. SDK sessions inherit Dropstone's cross-surface memory (Continuity) — one persistent memory shared with the CLI, chat, and SDK.
The Dropstone JS/TS SDK provides a type-safe client for interacting with the Dropstone agent runtime. It spawns dropstone serve as a subprocess and gives you a typed HTTP client pointed at it. Because it runs the same local agent the CLI uses, every SDK session inherits your account memory — teach it once in the CLI, chat, or SDK and every surface already knows it (see Memory (Continuity)).
Need headless API access in CI?:
For pure programmatic use (CI pipelines, automation, serverless), prefer the HTTP API with a DROPSTONE_API_KEY. The SDK on this page is intended for embedding the interactive agent in a Node process where the CLI binary is installed alongside.
See the Server page for how the underlying HTTP API works.
Install
Install the SDK from npm:
npm install @blankline/dropstone-sdk
Headless API client
For CI pipelines, automation, and serverless you do not need the CLI at all. Use the headless client, which talks directly to the Dropstone HTTP API with an API key:
import { createDropstoneApi } from "@blankline/dropstone-sdk"
// Reads DROPSTONE_API_KEY from the environment automatically.
const dropstone = createDropstoneApi()
const resp = await dropstone.chat.completions.create({
model: "dropstone-fast", // or "dropstone-pro" / "dropstone-heavy"
messages: [{ role: "user", content: "Write a haiku about debugging." }],
})
console.log(resp.choices[0].message.content)
console.log("Cost: $" + resp.usage?.cost) // billed amount, in USD
Get an API key
- Sign in at dropstone.io/dashboard.
- Open Settings → API at dropstone.io/dashboard/settings and create a key — it looks like
dsk_live_<43 chars>. - Set it as an environment variable (or pass
apiKeytocreateDropstoneApi):
export DROPSTONE_API_KEY=dsk_live_...
Note:
Treat your API key like a password. Never commit it to git or embed it in a frontend bundle. API-key requests are billed pay-per-use from your prepaid credit balance — plan allowances do not apply. See Usage & limits.
Streaming works the same way, and the client is OpenAI-compatible — you can point the OpenAI SDK at Dropstone's base URL instead:
const stream = await dropstone.chat.completions.create({
model: "dropstone-fast",
stream: true,
messages: [{ role: "user", content: "Count to 5." }],
})
for await (const chunk of stream) {
process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "")
}
See the HTTP API page for the full reference.
Create client
Create an instance of dropstone:
import { createDropstone } from "@blankline/dropstone-sdk"
const { client } = await createDropstone()
This starts both a server and a client. Every SDK method returns { data, request, response }, so access the payload via .data.
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 | {} |
Config
You can pass a configuration object to customize behavior. The instance still picks up your dropstone.json, but you can override or add configuration inline:
import { createDropstone } from "@blankline/dropstone-sdk"
const dropstone = await createDropstone({
hostname: "127.0.0.1",
port: 4096,
config: {
model: "dropstone/dropstone-pro",
},
})
console.log(`Server running at ${dropstone.server.url}`)
dropstone.server.close()
Memory (Continuity)
Dropstone keeps one persistent memory per account. We call it Continuity — a cross-surface memory shared by the CLI, chat, VS Code, and the SDK. Teach it once anywhere and every surface already knows it.
Sessions you run through the SDK use the same account memory as the CLI. What you teach the CLI is already known to SDK sessions, and what an SDK session records is available back in the CLI and chat. On every turn, the agent auto-recalls relevant memory before answering, so it does not re-learn what it already knows.
The SDK agent can also read and write memory directly, with the same tools as the CLI:
| Tool | Purpose |
|---|---|
memory_recall | Recall the most relevant lessons for the current task |
record_lesson | Save a durable lesson (a rule or a fact) |
list_lessons | Show everything it has learned about you |
forget_lesson | Remove a lesson |
Note:
Memory requires being signed in to Dropstone. The SDK server reads the same auth.json as the CLI, so sign in once with dropstone and SDK sessions inherit the same account memory. Nothing is stored if you are not signed in.
Example
State a preference from code and it is recorded the same way it would be in the CLI — visible in the CLI and chat afterwards:
const { client } = await createDropstone()
const session = await client.session.create({ body: { title: "Teach memory" } })
await client.session.prompt({
path: { id: session.data.id },
body: {
parts: [{ type: "text", text: "Remember this as a standing rule: always use bun, not npm." }],
},
})
Continuity vs AGENTS.md
AGENTS.md is a project file you commit to Git for team conventions — stable, repo-scoped, and shared with anyone who clones it. Continuity is your private account memory: what you teach in the CLI, chat, or SDK follows your account across surfaces and projects. They solve different problems and work best together — project rules in AGENTS.md, personal cross-surface memory in Continuity. See Rules and Memory.
What gets remembered
Memory stores only the rules and facts you explicitly teach it — preferences, conventions, and corrections that are worth carrying across sessions. It is distinct from the ephemeral context of a single session, which is not kept after the session ends.
See the Memory page for how Dropstone decides what to keep.
Client only
If you already have a running instance of dropstone, you can create a client instance to connect to it:
import { createDropstoneClient } from "@blankline/dropstone-sdk"
const client = createDropstoneClient({
baseUrl: "http://localhost:4096",
})
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 return | false |
Types
The SDK includes TypeScript definitions for all API types. Import them directly:
import type { Session, Message, Part } from "@blankline/dropstone-sdk"
All types are generated from the server's OpenAPI specification, so the names you see in TypeScript map one-to-one to the server request and response shapes.
Errors
The SDK can throw errors that you can catch and handle:
try {
await client.session.get({ path: { id: "invalid-id" } })
} catch (error) {
console.error("Failed to get session:", (error as Error).message)
}
Structured Output
You can request structured JSON output from the model by specifying an format with a JSON schema. The model will use a StructuredOutput tool to return validated JSON matching your schema.
Basic Usage
const result = await client.session.prompt({
path: { id: sessionId },
body: {
parts: [{ type: "text", text: "Research Dropstone 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"],
},
},
},
})
// Access the structured output
console.log(result.data.info.structured_output)
// { company: "Dropstone", founded: 2024, products: ["Dropstone CLI"] }
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
APIs
The SDK exposes all server APIs through a type-safe client.
Global
| Method | Description | Response |
|---|---|---|
global.health() | Check server health and version | { healthy: true, version: string } |
Examples
const health = await client.global.health()
console.log(health.data.version)
App
| Method | Description | Response |
|---|---|---|
app.log() | Write a log entry | boolean |
app.agents() | List all available agents | Agent[] |
Examples
// Write a log entry
await client.app.log({
body: {
service: "my-app",
level: "info",
message: "Operation completed",
},
})
// List available agents
const agents = await client.app.agents()
Project
| Method | Description | Response |
|---|---|---|
project.list() | List all projects | Project[] |
project.current() | Get current project | Project |
Examples
// List all projects
const projects = await client.project.list()
// Get current project
const currentProject = await client.project.current()
Path
| Method | Description | Response |
|---|---|---|
path.get() | Get current path | Path |
Examples
// Get current path information
const pathInfo = await client.path.get()
Config
| Method | Description | Response |
|---|---|---|
config.get() | Get config info | Config |
Examples
const config = await client.config.get()
Sessions
| Method | Description | Notes |
|---|---|---|
session.list() | List sessions | Returns Session[] |
session.get({ path }) | Get session | Returns Session |
session.children({ path }) | List child sessions | Returns Session[] |
session.create({ body }) | Create session | Returns Session |
session.delete({ path }) | Delete session | Returns boolean |
session.update({ path, body }) | Update session properties | Returns Session |
session.init({ path, body }) | Analyze app and create AGENTS.md | Returns boolean |
session.abort({ path }) | Abort a running session | Returns boolean |
session.summarize({ path, body }) | Summarize session | Returns boolean |
session.messages({ path }) | List messages in a session | Returns { info: Message, parts: Part[]}[] |
session.message({ path }) | Get message details | Returns { info: Message, parts: Part[]} |
session.prompt({ path, body }) | Send prompt message | body.noReply: true returns UserMessage (context only). Default returns AssistantMessage with AI response. Supports body.outputFormat for structured output |
session.command({ path, body }) | Send command to session | Returns { info: AssistantMessage, parts: Part[]} |
session.shell({ path, body }) | Run a shell command | Returns AssistantMessage |
session.revert({ path, body }) | Revert a message | Returns Session |
session.unrevert({ path }) | Restore reverted messages | Returns Session |
postSessionByIdPermissionsByPermissionId({ path, body }) | Respond to a permission request | Returns boolean |
Examples
// Create and manage sessions
const session = await client.session.create({
body: { title: "My session" },
})
const sessions = await client.session.list()
// Send a prompt message
const result = await client.session.prompt({
path: { id: session.data.id },
body: {
model: { providerID: "dropstone", modelID: "dropstone-pro" },
parts: [{ type: "text", text: "Hello!" }],
},
})
// Inject context without triggering AI response (useful for plugins)
await client.session.prompt({
path: { id: session.data.id },
body: {
noReply: true,
parts: [{ type: "text", text: "You are a helpful assistant." }],
},
})
Files
| Method | Description | Response |
|---|---|---|
find.text({ query }) | Search for text in files | Array of match objects with path, lines, line_number, absolute_offset, submatches |
find.files({ query }) | Find files and directories by name | string[] (paths) |
find.symbols({ query }) | Find workspace symbols | Symbol[] |
file.read({ query }) | Read a file | { type: "raw" | "patch", content: string } |
file.status({ query? }) | Get status for tracked files | File[] |
find.files supports a few optional query fields:
type:"file"or"directory"directory: override the project root for the searchlimit: max results (1–200)
Examples
// Search and read files
const textResults = await client.find.text({
query: { pattern: "function.*dropstone" },
})
const files = await client.find.files({
query: { query: "*.ts", type: "file" },
})
const directories = await client.find.files({
query: { query: "packages", type: "directory", limit: 20 },
})
const content = await client.file.read({
query: { path: "src/index.ts" },
})
Auth
| Method | Description | Response |
|---|---|---|
auth.set({ ... }) | Set authentication credentials | boolean |
Examples
await client.auth.set({
path: { id: "dropstone" },
body: { type: "api", key: "your-dropstone-api-key" },
})
Events
| Method | Description | Response |
|---|---|---|
event.subscribe() | Server-sent events stream | Server-sent events stream |
Examples
// Listen to real-time events
const events = await client.event.subscribe()
for await (const event of events.stream) {
console.log("Event:", event.type, event.properties)
}
v2 API
The SDK ships a stable v1 surface (used in the examples above) and a v2 surface that mirrors the newer Effect HttpApi contract. Prefer v2 for new integrations:
import { createDropstone } from "@blankline/dropstone-sdk/v2"
const { client, server } = await createDropstone()
// v2 exposes a richer resource hierarchy: workspace, worktree, file, find, etc.
const files = await client.file.list({ path: "src" })
The v1 surface is kept for backward compatibility. New endpoints only land on v2.