From 2f0e1d741561acc3ddc94a09f2f119c6b972e868 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 6 May 2026 14:59:48 -0700 Subject: [PATCH] docs(agent-sdk): add README with quickstart and API reference --- sdks/typescript-agent-sdk/README.md | 218 ++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 sdks/typescript-agent-sdk/README.md diff --git a/sdks/typescript-agent-sdk/README.md b/sdks/typescript-agent-sdk/README.md new file mode 100644 index 00000000000..833f23621e3 --- /dev/null +++ b/sdks/typescript-agent-sdk/README.md @@ -0,0 +1,218 @@ +# @litellm/agent-sdk + +TypeScript SDK for the LiteLLM Agents API. Mirrors the Cursor SDK's style but +fixes its conflation of agent definition and VM session by exposing a clean +3-level hierarchy: + +``` +Agent — definition (model, system prompt, tools) + └─ Session — VM sandbox running under an agent + └─ Run — single execution within a session +``` + +## Install + +```bash +npm i @litellm/agent-sdk +# or +pnpm add @litellm/agent-sdk +``` + +Requires Node 18+ (Node 20+ recommended for `await using` support). + +## Quickstart + +```ts +import { Agent } from "@litellm/agent-sdk"; + +// 1. Define an agent (do this once at app startup). +const agent = await Agent.create({ + apiKey: process.env.LITELLM_API_KEY, + baseUrl: process.env.LITELLM_BASE_URL, // e.g. http://localhost:4000 + name: "shin-cursor", + model: { id: "claude-4.6-sonnet" }, + systemPrompt: "You are a senior engineer fixing GitHub issues.", +}); + +// 2. Spin up a session per workflow (per Slack issue, per ticket, etc). +const session = await agent.createSession({ + repos: [{ url: "https://github.com/example/repo", startingRef: "main" }], + envVars: { GITHUB_TOKEN: process.env.GITHUB_TOKEN! }, +}); + +// 3. Kick off a run and stream the events. +const run = await session.send("Fix the bug described in issue #123."); +for await (const event of run.stream()) { + console.log(event.type, event.data); +} + +// 4. Wait for the final result. +const result = await run.wait(); +console.log(result.status, result.result); + +// 5. Tear down the VM when you're done. +await session.delete(); +``` + +## Multi-turn (followups) + +```ts +const run = await session.send("Implement the feature."); +const stream = run.stream(); + +// In another async context, queue a follow-up message into the active run. +await session.followup("Also handle the empty-input edge case, please."); + +for await (const event of stream) { + // events from the original run continue to flow, including any + // tool calls / deltas resulting from the follow-up. +} +``` + +## Resumable streams + +Every event carries a monotonic `seq`. Pass `startingSeq` to reconnect from a +specific point — useful if your process restarts mid-stream: + +```ts +let lastSeq = 0; +for await (const event of run.stream({ startingSeq: lastSeq + 1 })) { + lastSeq = event.seq; + // ... +} +``` + +The SDK also auto-reconnects internally when the underlying socket drops. It +sends both `Last-Event-ID: ` (per the SSE spec) and +`?starting_seq=` (LiteLLM-specific); the proxy honors whichever it +understands and replays from there. Already-seen events are de-duped. + +## `await using` (Symbol.asyncDispose) + +If you're on TypeScript 5.2+ / Node 20+, you can scope a session to a block: + +```ts +{ + await using session = await agent.createSession(); + const run = await session.send("..."); + await run.wait(); +} // session is DELETEd automatically here +``` + +`session.terminate()` is a manual alias for `delete()` for older runtimes. + +## Reusing a single agent across sessions + +```ts +const agent = await Agent.create({ /* ... */ }); + +// Same agent definition, three independent VM sessions. +const [s1, s2, s3] = await Promise.all([ + agent.createSession(), + agent.createSession(), + agent.createSession(), +]); +``` + +## Authentication + +```ts +const agent = await Agent.create({ + apiKey: "sk-...", // or set LITELLM_API_KEY in the environment + baseUrl: "https://api.litellm.ai", // default; override for your deployment + // ... +}); +``` + +`apiKey` falls back to `process.env.LITELLM_API_KEY` if not provided. The SDK +sends it as `Authorization: Bearer `. + +## Errors + +All errors are instances of `LiteLLMAgentError`: + +```ts +import { LiteLLMAgentError } from "@litellm/agent-sdk"; + +try { + await agent.createSession(); +} catch (e) { + if (e instanceof LiteLLMAgentError) { + console.error(e.code, e.status, e.retryable); + } +} +``` + +| Code | Meaning | +| ----------------- | --------------------------------------------- | +| `missing_api_key` | No `apiKey` provided and `LITELLM_API_KEY` unset | +| `not_found` | Agent / session / run does not exist | +| `session_busy` | `send()` called while a run is in flight (409) | +| `rate_limited` | 429 — SDK retried `maxRetries` times | +| `timeout` | Request exceeded `timeoutMs` | +| `http_` | Generic HTTP error fallback | + +5xx and 429 are retried automatically with exponential backoff. Configure +`maxRetries` and `timeoutMs` per call via `ClientOptions`. + +## Configuration + +| Option | Default | Purpose | +| ------------ | ----------------------------- | ------------------------------------ | +| `apiKey` | `process.env.LITELLM_API_KEY` | Bearer token for the proxy | +| `baseUrl` | `https://api.litellm.ai` | Proxy URL | +| `fetch` | `globalThis.fetch` | Override for tests / custom transport | +| `timeoutMs` | `60_000` | Per-request timeout | +| `maxRetries` | `3` | Retries on 5xx/429/network errors | + +## API reference + +### `Agent` + +- `Agent.create(options: AgentCreateOptions): Promise` +- `Agent.get(agentId: string, options: ClientOptions): Promise` +- `Agent.list(options?: ClientOptions & ListOptions): Promise>` + +### `AgentHandle` + +- `agent.id: string` +- `agent.name: string` +- `agent.createSession(options?: CreateSessionOptions): Promise` +- `agent.getSession(sessionId: string): Promise` +- `agent.listSessions(options?: ListOptions): Promise>` +- `agent.update(patch: Partial): Promise` +- `agent.delete(): Promise` — cascades to sessions + +### `SessionHandle` + +- `session.id: string` +- `session.agentId: string` +- `session.status: SessionStatus` +- `session.send(input: string | { text; images? }): Promise` — 409 if a run is in flight +- `session.followup(message: string): Promise` — queues into the active run +- `session.getRun(runId: string): Promise` +- `session.listRuns(options?: ListOptions): Promise>` +- `session.conversation(): Promise` +- `session.delete(): Promise` / `session.terminate()` +- `session[Symbol.asyncDispose]()` — for `await using` + +### `Run` + +- `run.id: string` +- `run.sessionId: string` +- `run.status: RunStatus` +- `run.result: string | null` +- `run.git?: { branches: { branch; prUrl }[] }` +- `run.stream(opts?: { startingSeq?, signal? }): AsyncIterable` +- `run.wait(): Promise` +- `run.cancel(): Promise` +- `run.conversation(): Promise` + +## Examples + +See [`examples/basic.ts`](./examples/basic.ts) and +[`examples/followup.ts`](./examples/followup.ts). + +## License + +MIT — © BerriAI