npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@qodo/sdk

v2.2.1

Published

Qodo SDK 2.0 — QAR-native typed bindings for the Qodo Agent Runtime

Readme

@qodo/sdk

Official TypeScript SDK for building AI-powered agents with Qodo.

Migrating from 1.x? The full 1.x → 2.0 upgrade guide is at docs/sdk2/migration-from-1x.md, with paired side-by-side examples in examples/migration-rosetta/.

1.x users (legacy line, published as 0.13.x): SDK 2.0 targets the Qodo Agent Runtime (QAR). The legacy line continues on the release/1.x branch with security and critical-bug patches only, on a calendar tied to command-backend's Sept 2026 EOL.

Installation

# 2.0 (stable — QAR-native)
npm install @qodo/sdk

# 1.x (legacy, command-backend-native — published as 0.13.x)
npm install @qodo/[email protected]

@qodo/sdk@latest now resolves to 2.0. Existing legacy applications should keep their 0.13.x pin until they complete the migration above.

Requirements: Node.js >= 20, ESM only

WebSocket connections behind a proxy

The Node default transport automatically uses HTTP/HTTPS CONNECT proxies from HTTPS_PROXY for wss:// and HTTP_PROXY for ws://, with ALL_PROXY as a fallback. Lower-case forms are supported and take precedence. NO_PROXY (or no_proxy) bypasses matching hosts, domain suffixes and optional ports; * bypasses all hosts. Loopback hosts (localhost, .localhost, 127/8 and ::1, including trailing-dot localhost and IPv4-mapped 127/8 forms) always connect directly unless a caller supplies an agent. Other bypassed hosts still need direct DNS and connectivity. SOCKS proxies, PAC discovery and integrated Windows proxy authentication require a caller-provided agent.

Certificate and hostname validation remain enabled. For a TLS-inspecting corporate proxy, configure Node's trusted CA before starting the process, for example with NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.pem.

To override environment routing, wrap defaultTransportFactory and pass your own agent (for custom CA, proxy authentication or PAC support). agent: false explicitly selects a fresh direct agent. The SDK owns only agents it creates; caller agents stay caller-owned. Proxy packages are loaded lazily by the Node transport; browser transports and custom transport factories are unchanged.

Applications with their own routing policy must apply it to HTTP and WebSocket connections together. CLI-specific overrides such as QODO_HTTPS_PROXY and QODO_NO_PROXY are not SDK environment variables: an integrating application must select and pass its proxy agent, or agent: false for a direct route.

Quick Start (2.0)

import { QodoClient } from '@qodo/sdk';

const client = new QodoClient({
  baseUrl: 'https://qar.qodo.ai',
  auth: 'env',     // reads QODO_API_KEY; ~/.qodo/auth.key fallback
});
await client.connect();

for await (const ev of client.tasks.start({
  agent_id: 'my-agent',
  input: { user_query: 'Analyze this codebase and summarize the main components.' },
})) {
  switch (ev.kind) {
    case 'task.delta':
      process.stdout.write(JSON.stringify(ev.payload.delta) + '\n');
      break;
    case 'task.done':
      console.log(`\n[done status=${ev.payload.status}]`);
      break;
    case 'error':
      console.error(`[error ${ev.payload.code}] ${ev.payload.message}`);
      break;
  }
}

await client.disconnect();

for await is the canonical streaming primitive — there's no separate run() / stream() / prompt(). The full vocabulary of envelope kinds plus qar.client.* lifecycle events for reconnect/replay is documented in the Public API reference.

Attach files & images (multimodal input)

A turn can carry an image or PDF instead of a plain string. Pass an ordered content list of parts built with textPart / filePart / imagePart (mutually exclusive with user_query — set one, never both). File parts accept a Buffer, Uint8Array, ArrayBuffer, base64 string, or base64 data: URL, and require an explicit mediaType:

import { QodoClient, textPart, imagePart } from '@qodo/sdk';
import { readFile } from 'node:fs/promises';

const png = await readFile('./screenshot.png');

for await (const ev of client.tasks.start({
  agent_id: 'my-agent',
  input: {
    // Order is meaningful — the text frames the image that follows it.
    content: [
      textPart('What does this screenshot show?'),
      imagePart(png, { mediaType: 'image/png', filename: 'screenshot.png' }),
    ],
  },
})) {
  // … same event loop as above
}

mediaType is always explicit (never inferred, even from a data: URL) so the wire part is unambiguous; which media types the runtime accepts and any per-message size caps are operator-side policy, not baked into the SDK. The builders and tasks.start* / tasks.continue reject a malformed turn (both user_query and content, an empty part list, a missing mediaType, …) synchronously with QodoContentValidationError, before the wire round-trip. See examples/sdk2/attach-image.ts.

Cross-pod session identity (opt-in)

For consumers that need cross-pod serialization of "the same logical work-unit" — Slack thread, GitHub PR, customer issue — pass idempotencyKey to opt into QAR's deterministic session-id derivation. Two pods receiving the same (tenant_id, idempotencyKey) derive the same session_id and serialize through QAR's lease table; without the field, every call gets a fresh session_id (DX default — right for one-shot scripts and single-process consumers).

const stream = client.tasks.startWithAgent(
  { agent: myAgent, input: { user_query: 'review this PR' } },
  // Encode your fine scope into the key — two-layer scoping principle:
  // tenant_id is added server-side; the consumer-supplied key carries
  // the finer subject (PR number, thread id, issue id).
  { idempotencyKey: `${owner}/${repo}:pr:${prNumber}` },
);

// `stream.taskId` is synchronous and equals the FIRST outbound
// task.start.message_id. Use it for the pre-admission cancel race —
// `client.tasks.cancel(stream.taskId)` immediately after the start call.
console.log('taskId:', stream.taskId);

// `stream.admittedTaskId` is a Promise that resolves to the SERVER's
// canonical task_id once `task.started` arrives. On the omitted-key
// path it equals `taskId`. On the opt-in deterministic-key path with
// admission_in_progress retries it MAY DIFFER from `taskId` — the SDK
// rotates message_ids per retry while the canonical id is whatever
// the winning attempt's `task.started` reports. Use this id for
// cancel / continue / resubscribe AFTER admission completes.
const admittedTaskId = await stream.admittedTaskId;

// `stream.sessionId` resolves on the same admission ack. Await it
// before any cross-pod operation (e.g. forwarding to another pod via
// Bulletin for `task.resubscribe`).
const sessionId = await stream.sessionId;

for await (const ev of stream) { /* … */ }

Both admittedTaskId and sessionId settle independently of consumer iteration — the SDK's internal admission driver advances the retry loop and resolves/rejects both Promises within PENDING_ADMISSION_TIMEOUT (5 min default). await stream.sessionId BEFORE the first for await cannot hang.

Validation: keys must be 1–512 Unicode code points with no NUL byte. Invalid keys throw QodoIdempotencyKeyValidationError synchronously, before the wire round-trip.

Don't pass Date.now() or a fresh randomUUID() per call — the first collides on rapid-fire calls within clock resolution; the second works but defeats the purpose. Omit the field instead and let the server mint.

Lost-ack retry semantics: the omitted-key path is intentionally non-idempotent across task.start retries. The SDK does NOT silently retry transport-failed task.start envelopes when idempotencyKey is absent — a retry without a sticky key starts a SECOND fresh session. Consumers requiring lost-ack idempotency MUST pass a deterministic key (domain-derived or a sticky per-attempt UUIDv7).

admission_in_progress retry: when two pods race the same (tenant_id, idempotencyKey), QAR returns the retryable admission_in_progress error. The SDK honors the server's retry_after_ms hint when present and falls back to exponential backoff with jitter (initial 100 ms, doubling, capped at 5 min) otherwise. After the cap the SDK throws QodoAdmissionTimeoutError carrying the scoped session_id so the caller can task.resubscribe if recovery is possible. The terminal admission_stalled server emission surfaces immediately as QodoAdmissionStalledError (same sessionId field).

Full design rationale and worked examples: QAR D-16 ADR.

Tools

Register a handler for inbound tool.request envelopes. The wire is parallel-batched: req.payload.calls: ToolCall[] (always a list, even for a single call) plus a required req.payload.node_name. Return one ToolResponseResult per call (positional array) or — when you know every request is single-call — a single result that the SDK auto-batches.

import { ToolDeniedError } from '@qodo/sdk';

const sub = client.tools.onRequest((req) =>
  req.payload.calls.map((call) => {
    if (call.name === 'echo') {
      return { outcome: 'success', result: { echoed: call.arguments } };
    }
    if (call.name.startsWith('forbidden_')) {
      // Return-style denial keeps per-call attribution; a `throw new
      // ToolDeniedError(...)` would deny the whole batch.
      return { outcome: 'denied', error: 'blocked by policy' };
    }
    return { outcome: 'error', error: `unknown tool: ${call.name}` };
  }),
);
// later: sub.unsubscribe();

Stacking handlers compose approval middleware (policy first, dispatcher second). Deep dive in the Tools guide.

Migrating from 1.x

TL;DR: QodoSDK → QodoClient, sdk.run/stream/prompt → for await client.tasks.start({...}), sdkAgent/sdkCommand removed (agents are server-side now), IDERetrievalRequest → ToolRequestEnvelope, Resume/ResumeAck machinery removed (server-driven replay), onTransportEvent removed (lifecycle events on the same iterator). Auth is unchanged.

Full guide with per-export migration table: docs/sdk2/migration-from-1x.md. Paired side-by-side examples: examples/migration-rosetta/.

Authentication

# Option 1: Environment variable (recommended)
export QODO_API_KEY=your_api_key_here

# Option 2: Auth file
mkdir -p ~/.qodo && echo "your_api_key_here" > ~/.qodo/auth.key

On Windows (PowerShell):

$env:QODO_API_KEY = "your_api_key_here"   # current session; use setx to persist

New-Item -ItemType Directory -Force "$HOME\.qodo" | Out-Null
Set-Content -NoNewline -Encoding ascii "$HOME\.qodo\auth.key" "your_api_key_here"

In code, auth: 'env' reads QODO_API_KEY first and falls back to ~/.qodo/auth.key. Pass auth: { apiKey: '...' } to supply the token directly, or auth: { provider: () => Promise<string> } to plug in a custom token source.

TypeScript / Node

Full type definitions ship in the package. The SDK targets ES2022 + NodeNext modules, requires Node.js >= 20, and is ESM only — require() is not supported. See the Public API reference for the canonical exported surface.

Documentation

Full documentation lives in the repository. Repository access is currently restricted to Qodo organization members — if you need a deep reference link to resolve, contact [email protected].

2.0 (canonical)

Learn the SDK (in order):

| Track | Description | |-------|-------------| | Tutorial | 8 chapters: npm install → first task → tools → MCP → HITL → multi-agent → OTel → production checklist | | Concept guides | Mental model behind each primitive: agent spec, tools, HITL, skills, sessions, observability, error handling | | Use cases | Three flagship reference implementations: doc-qa, slack-bot, agentic-docs-updater | | Cookbook | Lookup-shaped recipes: cancel, resume, reconnect, MCP subset, HITL, multi-agent, OTel, errors, testing, auth |

Reference (every field, every error):

| Guide | Description | |-------|-------------| | Migration from 1.x | Canonical 1.x → 2.0 upgrade guide; per-export table, recipes, FAQ | | Public API | QodoClient, connect, tasks.start/continue/cancel, errors, backpressure | | Tools | client.tools.onRequest / respond, stacking, error mapping | | MCP tools | mcpTools projection, auto-discovery, fallback bridge | | Reconnect + replay | Auto-reconnect, tasks.resubscribe, stale-anchor behaviour + replay TTL | | Observability | OTel attribute taxonomy, span boundaries, skip-list, cardinality | | Event surfaces | bulletin / artifacts / state / agents observers | | Release policy | Versioning, branch rules, backport policy, EOL calendar |

Examples

End-to-end runnable scripts live in examples/. Most are env-gated on QAR_BASE_URL and exit cleanly when unset — safe to clone and run.

  • examples/sdk2/ — hello-task, tool-roundtrip, multi-agent-observer, MCP auto-discovery, end-to-end live-cluster smoke.
  • examples/tutorial/ — companion code for the tutorial chapters (HITL, MCP, OTel, multi-agent).
  • examples/usecases/ — three flagship reference implementations: doc-qa, slack-bot, agentic-docs-updater.
  • examples/migration-rosetta/ — paired 1.x/2.0 files for every common upgrade pattern.
export QAR_BASE_URL=https://qar.dev.qodo.ai
export QODO_API_KEY=your_key
export QAR_AGENT_ID=health
npx tsx examples/sdk2/hello-task.ts

License

See LICENSE. The SDK ships under an MIT-style license with a protective exception that bars re-use to build competing AI agent runtimes; consumer agents that interoperate with the Qodo Agent Runtime are unaffected.

Managed-tools API versions

QodoClient defaults to managed-tools API v1. To adopt the consolidated review Finding contract, set managedToolsApiVersion: 2 in the client options. Discovery, invocation and operation polling then use /v2/tools; the runtime must confirm v2 support. Missing v2 support produces an error without silently falling back to v1. Other SDK surfaces retain their existing transport contracts. MCP callers continue to use /v1/tools/mcp. After a runtime supporting the v2 review API is deployed, callers can explicitly select /v2/tools/mcp for the new contract. The SDK option does not change MCP URLs.