Skip to content

Repository files navigation

Hawk SDK for TypeScript

Dependency-free TypeScript client for the Hawk daemon API

Node License


Hawk SDK for TypeScript is the official TypeScript client library for the Hawk daemon API. It provides a dependency-free, type-safe client for interacting with Hawk's HTTP API from Node.js applications (any runtime with a global fetch, Node 18+).

Ecosystem

Hawk SDK for TypeScript is part of the hawk-eco mono-ecosystem:

Component Purpose
hawk AI-powered coding agent for the terminal
sparrow Go SDK for the Hawk daemon API
robin Python SDK for the Hawk daemon API
wren TypeScript SDK for the Hawk daemon API (this repo)
eagle Shared cross-repo contracts (types, events, tools)

Installation

npm install wren
# or
pnpm add wren

Quick start

import { HawkClient, defaultRetryConfig } from "wren";

const client = new HawkClient({
  baseURL: "http://127.0.0.1:4590", // default
  apiKey: process.env.HAWK_API_KEY, // optional
  retry: defaultRetryConfig(), // optional: automatic retries w/ backoff
});

const health = await client.health();
console.log(health.status); // "ok"

const reply = await client.chat({ prompt: "Explain closures in JavaScript." });
console.log(reply.response);

Streaming

const reader = await client.chatStream({ prompt: "Write a haiku." });
for await (const event of reader) {
  process.stdout.write(event.data);
}

Sessions & stats

const sessions = await client.sessions();
const detail = await client.session(sessions[0].id);
const messages = await client.messages(detail.id, { offset: 0, limit: 50 });
const stats = await client.stats();
await client.deleteSession(detail.id);

Agents

Agent wraps a client with declarative configuration and tracks the session automatically for multi-turn conversations:

import { Agent, HawkClient } from "wren";

const agent = new Agent(new HawkClient(), {
  model: "gpt-4o",
  systemPrompt: "You are a concise assistant.",
  memory: { enabled: true },
});

const first = await agent.chat("Remember the number 7.");
const second = await agent.chat("What number did I give you?"); // same session

Tools

Provide tools and the SDK runs the full tool-execution loop until the model stops requesting calls:

const agent = new Agent(client, {
  tools: [
    {
      schema: {
        name: "get_weather",
        description: "Get the weather for a city.",
        parameters: {
          type: "object",
          properties: { city: { type: "string" } },
          required: ["city"],
        },
      },
      run: async (args) => `It is sunny in ${args.city}.`,
    },
  ],
});

const reply = await agent.chat("What's the weather in Tokyo?");

Error handling

All non-2xx responses throw a typed error extending APIError, so you can branch with instanceof:

import { NotFoundError, RateLimitError } from "wren";

try {
  await client.session("missing");
} catch (err) {
  if (err instanceof NotFoundError) {
    console.log("no such session");
  } else if (err instanceof RateLimitError) {
    console.log(`retry after ${err.retryAfterMs}ms`);
  }
}

Retries

The client performs no retries by default. Pass a retry config to enable exponential backoff with full jitter and Retry-After support:

import { defaultRetryConfig } from "wren";

const client = new HawkClient({ retry: defaultRetryConfig() });

Idempotent requests (GET/DELETE) retry on 429/500/502/503/504. Non-idempotent requests (POST /v1/chat) retry only on 429, since a 5xx may mean the daemon already began processing the request.

Timeouts

Every request has a whole-request deadline of timeoutMs milliseconds (default: 30000), so a hung daemon can never block a caller indefinitely. The deadline is a logical-request deadline: it bounds the entire request, including every retry attempt and backoff sleep — retries never extend it. When it elapses, the call rejects with a DOMException whose name is "TimeoutError" (the APIError hierarchy covers HTTP status errors; transport-level aborts propagate as-is):

try {
  await client.chat({ prompt: "Hello!" });
} catch (err) {
  if (err instanceof Error && err.name === "TimeoutError") {
    console.log("daemon did not respond in time");
  }
}

A per-call AbortSignal composes with the deadline: whichever fires first aborts the request, and the caller's own abort reason propagates unchanged. For chatStream, the deadline covers obtaining the SSE response; consuming the returned stream is caller-controlled. Disable the deadline with timeoutMs: 0:

const client = new HawkClient({ timeoutMs: 5000 });

Defaults & divergences across SDKs

The three Hawk SDKs (Go, TypeScript, Python) share wire behavior but have drifted in transport defaults. Actual current values:

Default TypeScript (this SDK) Go Python
Retries Off — opt in with { retry: defaultRetryConfig() } (src/client.ts) Off — opt in with WithRetry(DefaultRetryConfig()) (client.go) Onretry_config or DEFAULT_RETRY_CONFIG (src/hawk/client.py)
Initial backoff 1s (src/retry.ts, defaultRetryConfig) 1s (retry.go, DefaultRetryConfig) 0.5s (src/hawk/retry.py, RetryConfig)
Backoff jitter Full jitter: rand(0, backoff) (src/retry.ts, backoffDurationMs) Full jitter: rand(0, backoff) (retry.go, backoffDuration) Equal + jitter: backoff + rand(0, backoff/2) (src/hawk/retry.py, _compute_backoff)
Request timeout Whole-request deadline, 30s, includes retries (src/client.ts, timeoutMs) ResponseHeaderTimeout: 5s, headers only (client.go) httpx timeout, 30s (src/hawk/client.py, DEFAULT_TIMEOUT)

Max retries (3), max backoff (30s), retryable statuses (429/500/502/503/504), and the non-idempotent rule (only 429 is retried for POST /v1/chat) are identical in all three SDKs. This table documents current behavior; it is not a compatibility contract between the SDKs.

API reference

Method Description
health(signal?) Daemon connectivity and version
chat(req, signal?) Send a prompt, return the full response
chatStream(req, signal?) Send a prompt, stream SSE events
chatWithTools(req, tools, maxRounds?, signal?) Run the tool-execution loop
sessions(signal?) List active sessions
session(id, signal?) Get a session by ID
messages(id, opts?, signal?) Paginated session messages
graph(id, opts?, signal?) Get and validate a privacy-safe session execution graph
deleteSession(id, signal?) Delete a session
stats(signal?) Aggregated usage statistics

Every method accepts an optional AbortSignal as its last argument for cancellation.

Portable graph models

GraphExport, GraphNode, GraphEdge, and GraphEvent describe the shared *.graph/v1 wire format. Use parseGraphExport(value) at an untrusted API boundary to validate vocabulary, timestamps, unique IDs, provenance, and self-contained topology. client.graph() retrieves the authenticated /v1/sessions/{id}/graph projection and applies this parser before returning. These models are consumers only; the SDK does not own graph facts or storage.

Development

npm install       # install dev dependencies (typescript, tsx)
npm run build     # compile to dist/
npm run typecheck # type-check without emitting
npm test          # run the test suite (node:test via tsx)

The runtime build in dist/ has zero dependencies — it relies only on the global fetch, ReadableStream, and TextDecoder available in Node 18+.

License

MIT © GrayCode AI

About

Dependency-free TypeScript client for the Hawk daemon API. Official Hawk SDK for TypeScript.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages