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
hawk-sdk-go Go SDK for the Hawk daemon API
hawk-sdk-python Python SDK for the Hawk daemon API
hawk-sdk-typescript TypeScript SDK for the Hawk daemon API (this repo)
hawk-core-contracts Shared cross-repo contracts (types, events, tools)

Installation

npm install hawk-sdk
# or
pnpm add hawk-sdk

Quick start

import { HawkClient, defaultRetryConfig } from "hawk-sdk";

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 "hawk-sdk";

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 "hawk-sdk";

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 "hawk-sdk";

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

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages