LIQ CODE

SDK reference

LIQ CODE TypeScript SDK

Drive the agent from a script and stream everything it does - files edited, commands run, searches, tokens spent - as a typed async iterable. The SDK is a thin wrapper around the CLI: it spawns liq run --print --output-format stream-json and turns the NDJSON event stream into typed messages, so a script gets exactly what the terminal session gets.

The package is @liq-code/sdk. It is written in TypeScript, ships its own types, has zero runtime dependencies, is ESM-only, and needs Node 18 or newer.

Install

The SDK talks to the installed CLI, so install both. The CLI is what actually runs the agent; the SDK only starts it and reads its stream.

bash
npm install @liq-code/sdk
npm install -g @liq-code/cli

Preview. The SDK is still developed inside the product repository under sdk/ and is not published to npm yet. Until it is, import it from source or link the folder. The API below is what ships in 0.1.0 and is what the published package will expose.

Quick start

Usagequery({ prompt, options? }): AsyncGenerator<LiqCodeMessage>

One call runs one turn of the agent. Iterate it and you see the work as it happens rather than after it finishes.

typescript
import { query } from "@liq-code/sdk";

for await (const message of query({
  prompt: "Find and fix the bug in auth.ts",
  options: { model: "liq-ai" },
})) {
  if (message.kind === "block" && message.block.type === "file") {
    console.log(`${message.block.action}: ${message.block.path}`);
  }
  if (message.kind === "message-end") {
    console.log(message.message.parts);
  }
}

Authentication

Create a key in the desktop app under Settings → API Keys, then give it to the SDK in one of three ways.

  • Environment variable (recommended): LIQ_CODE_API_KEY=gsk_live_…
  • Per call: query({ prompt, options: { apiKey } })
  • Nothing at all: the CLI falls back to a provider key in the environment (OPENAI_API_KEY / OPENROUTER_API_KEY) or to the key configured in the desktop app.

Resolution order is options.apiKey, then the environment variable, then the provider or app configuration.

query()

Returns an async generator of events. The stream ends when the CLI exits. A failure the agent reports itself arrives as an error event and then the stream ends normally; any other non-zero exit - a crash, a missing key, a failed spawn - throws a LiqCodeError instead.

The generator is lazy: nothing is spawned until the first iteration, and abandoning the loop (break, return, an exception) kills the subprocess.

collect()

Usagecollect(stream): Promise<QueryResult>

When you want the answer rather than the play-by-play, drain the stream into a single result: final text, sessionId, usage, and isError / error. Every event stays available on messages.

typescript
import { query, collect } from "@liq-code/sdk";

const result = await collect(query({ prompt: "Summarise README.md" }));
console.log(result.text);

// Continue the same conversation:
await collect(query({
  prompt: "Now shorten it",
  options: { resume: result.sessionId },
}));

Events

Each yielded message is one of five kinds. All of them carry sessionId.

kindwhenpayload
message-startA new agent message begins.model
blockA tool ran or advanced.a command, file, search, image or choice block
usageToken spend was updated.usage
message-endThe answer is complete.the full ChatMessage, including its text
errorThe turn failed.error

The token-by-token text and thinking deltas that the interactive UI renders are deliberately not on this stream. The complete answer arrives once, on message-end.

Action blocks

A block event describes one thing the agent did. Each block carries an id, so repeated events for the same id are updates rather than new work.

typewhat it reports
commandA shell command: command, title, status (running / done / error), captured output, and a parsed shell analysis.
fileA file written: path, action (created / modified), the new content, and a line diff for anything but very large files.
searchA content search: query, matches with file, line and text, a truncated flag and durationMs.
imageA generated image: workspace-relative path and mime.
choiceA question with options. Headless runs have nobody to ask, so the CLI answers with the first option and this block is generally not surfaced.

Options

Every option maps to a real CLI flag or to subprocess behaviour.

optionmaps tonotes
cwd--dirWorkspace directory. Defaults to process.cwd().
model--modelliq-ai (default), auto, or liq-local.
maxEffort--max-effortStrongest tier and highest reasoning effort.
maxTurns--max-turnsCaps main-loop turns. The safety valve for unattended runs.
apiKey--api-keySee Authentication.
files--fileFiles to attach; the agent inlines their contents.
skills--skillSkills to load before the turn.
continue--continueContinue the most recent session. Excludes resume.
resume--resumeResume a session by id. Excludes continue.
sessionPersistence--no-session-persistencefalse keeps the run in memory, so it leaves no session file and cannot be resumed.
codeIndex--no-code-indexfalse skips the semantic index for this run.
mode--modecode (default) or swarm, which fans parallel workers over the task, verifies, and reduces. Swarm needs a CLI build with that feature enabled.
swarmKind--swarm-kindaudit (read-only, default) or build. Swarm only.
workers--workersMaximum parallel workers. Swarm only.
signal-An AbortSignal. Aborting kills the subprocess.
timeoutMs-Kills the run after N milliseconds.
env-Extra environment variables, merged over the parent process.
pathToExecutable-Explicit argv for the CLI, bypassing discovery.
onStderr-Receives the CLI stderr as it arrives. Usually empty on success.

Tool scoping

allowedTools and disallowedTools exist in the type surface for compatibility with the Anthropic Agent SDK, but the CLI does not enforce them yet, so passing either one throws UNSUPPORTED_OPTION. An allow-list that silently does nothing is worse than none. Omit them and the run uses the default tool set.

Sessions

Every event carries the sessionId, and collect() hands it back on the result. Pass it as resume to continue that conversation, or set continue: true to pick up the most recent one. Set sessionPersistence: false for throwaway queries that should leave nothing behind.

Token usage

usage events and QueryResult.usage report turns, inputTokens, cachedInputTokens, outputTokens and reasoningTokens, plus the context-window fill of the latest turn. When routing escalates mid-prompt, byModel breaks the spend down per model actually used.

Errors

Everything the SDK throws is a LiqCodeError. Discriminate on .code rather than on subclasses; .exitCode and .stderr are filled in where they are relevant.

  • INVALID_OPTION - the prompt or options were rejected before anything was spawned.
  • UNSUPPORTED_OPTION - an option the CLI cannot honour yet, such as tool scoping.
  • EXECUTABLE_NOT_FOUND - the CLI could not be located.
  • SPAWN_FAILED - the subprocess did not start.
  • CLI_EXITED - a non-zero exit with no structured error event.
  • ABORTED - the run was cancelled by a signal or a timeout.
typescript
import { query, collect, LiqCodeError } from "@liq-code/sdk";

try {
  const result = await collect(query({ prompt }));
  if (result.isError) console.error(`Run failed: ${result.error}`);
} catch (err) {
  if (err instanceof LiqCodeError) {
    console.error(`[${err.code}] ${err.message}`, err.exitCode, err.stderr);
  } else {
    throw err;
  }
}

Cancellation

typescript
const controller = new AbortController();
setTimeout(() => controller.abort(), 5_000);

for await (const message of query({ prompt, options: { signal: controller.signal } })) {
  // Aborting kills the CLI subprocess; the loop then throws
  // LiqCodeError { code: "ABORTED" }. options.timeoutMs does the same on a timer.
}

Finding the CLI

The SDK looks for the executable in this order:

  • options.pathToExecutable, an explicit argv override.
  • LIQ_CODE_CLI, an environment variable pointing at the binary.
  • liq-code or liq on PATH, which is the normal installed case.
  • A product checkout around the SDK, which runs the live CLI source. This is the in-repo development path.

The same resolution is exported as resolveExecutable(), with which() and resolveApiKey() alongside it, so a script can report what it is about to run before it runs it.

Exported types

Besides query, collect and LiqCodeError, the package exports the full event and message surface: LiqCodeMessage and its five event types, Options, QueryResult, ChatMessage, MessagePart, ActionBlock and each block type, TokenUsage, MessageSource and LiqCodeModel. Narrow on kind for events and on type for blocks and parts: both unions are discriminated, so TypeScript resolves the payload for you.