> ## Documentation Index
> Fetch the complete documentation index at: https://silmaril.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> Install the TypeScript SDK, classify user input and tool output, and route the verdict.

## Prerequisites

Use Node.js 20 or later.

Set `SILMARIL_API_KEY` and `SILMARIL_API_URL` from your [deployment access](/docs/start/quickstart#get-access).

## Install

```sh theme={"theme":"github-light-default"}
npm install @silmaril-security/sdk@0.6.3
```

<a id="initialize" />

<a id="classify" />

## Classify your first request

Create one client per protected system and reuse it. Label each call with the boundary it checks.

```typescript theme={"theme":"github-light-default"}
import { Firewall, HookLabel } from "@silmaril-security/sdk"

const fw = new Firewall({
  apiKey: process.env.SILMARIL_API_KEY!,
  apiUrl: process.env.SILMARIL_API_URL!,
})

const result = await fw.classify("Summarize this release note.", {
  hook: HookLabel.USER_INPUT,
})

const blocked =
  result.prediction === "MALICIOUS" || result.governance?.action === "block"

console.log(result.prediction, result.score.toFixed(4), blocked ? "block" : "allow")
```

For tool output, pass the tool name as well.

```typescript theme={"theme":"github-light-default"}
await fw.classify(toolOutput, {
  hook: HookLabel.TOOL_RESPONSE,
  toolName: "read_file",
})
```

### Expected result

A benign request logs `BENIGN`, its score, and `allow`. Direct `classify` calls return a result in every mode. They do not throw on a block. Treat the request as blocked when `prediction` is `MALICIOUS` or `governance.action` is `block`, then route it with [firewall outcomes](/docs/sdk/outcomes).

A rejected promise means the call did not produce a verdict. That includes `SilmarilApiError` for a non-2xx API response, and a network, timeout, or abort error. Decide whether that boundary fails open or closed.

Vercel, LangChain, and LangGraph integrations that wrap this client are documented in [Framework SDKs](/docs/integrations/frameworks).

## Concurrent requests

Reuse one client for concurrent calls in a JavaScript runtime, with a hook on each call. Pass `signal` to `classify` or `classifyBatch` to cancel one request and its retry wait. Create a client in each worker thread.

```typescript theme={"theme":"github-light-default"}
const [first, second] = await Promise.all([
  fw.classify(firstInput, { hook: HookLabel.USER_INPUT }),
  fw.classify(secondInput, { hook: HookLabel.TOOL_RESPONSE }),
])

await fw.classify(userInput, {
  hook: HookLabel.USER_INPUT,
  signal: request.signal,
})
```

`request.signal` is the caller's `AbortSignal`.

## Shadow mode

Shadow mode reports would-block decisions with the same thresholds while traffic continues. Framework adapters inherit the client setting and can override it when a surface is ready to enforce.

```typescript theme={"theme":"github-light-default"}
const fw = new Firewall({
  apiKey: process.env.SILMARIL_API_KEY!,
  apiUrl: process.env.SILMARIL_API_URL!,
  shadowMode: true,
})

const handler = await fw.asLangChainHandler({
  onClassify: (event) => {
    if (event.blocked && event.shadowMode) {
      console.warn("would block", event.hook, event.result.score)
    }
  },
})

const enforcingHandler = await fw.asLangChainHandler({
  shadowMode: false,
})
```

## Errors

Framework adapters throw `FirewallBlockedException` when a malicious or governance-block decision is enforced. The exception carries `score` and `threshold`.

```typescript theme={"theme":"github-light-default"}
import { FirewallBlockedException } from "@silmaril-security/sdk"

try {
  await generateText({ model, prompt: userInput })
} catch (err) {
  if (err instanceof FirewallBlockedException) {
    console.warn("blocked", err.score, err.threshold)
    return
  }
  throw err
}
```

`generateText` and `model` come from the [Vercel integration](/docs/sdk/vercel-ai). Rethrow any error that is not a firewall block.
