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

# Decorate a client

> Wrap the completions function you already call, including streams and Anthropic.

`guard.decorate` wraps a completions function. It is called with the arguments it would have received, so you keep using the provider SDK directly and no base URL is rewritten.

<CodeGroup>
  ```ts TypeScript theme={null}
  const create = fw.guard.decorate(
    (body: OpenAI.Chat.ChatCompletionCreateParamsNonStreaming) =>
      openai.chat.completions.create(body),
    { baseUrl: "https://api.openai.com/v1", provider: "openai" },
  );

  const completion = await create({
    model: "gpt-4o-mini",
    messages: [{ role: "user", content: userInput }],
  });

  if ("error" in completion) {
    return res.status(400).json(completion);
  }
  ```

  ```python Python theme={null}
  create = fw.guard.decorate(
      lambda **body: openai.chat.completions.create(**body),
      DecorateOptions(base_url="https://api.openai.com/v1", provider="openai"),
  )

  completion = create(
      model="gpt-4o-mini",
      messages=[{"role": "user", "content": user_input}],
  )
  ```
</CodeGroup>

The request is inspected first. The guard then refuses the call, redacts the body and forwards it, or forwards it untouched, according to `onMatch`. A sync function gets a sync wrapper and a coroutine function gets an async one, so an `AsyncOpenAI` client stays async.

## Streaming

A stream is teed, not buffered. Each chunk reaches you before it is recorded, so decoration adds no latency.

```ts theme={null}
const createStream = fw.guard.decorate(
  (body: OpenAI.Chat.ChatCompletionCreateParamsStreaming) =>
    openai.chat.completions.create(body),
  { provider: "openai" },
);

const stream = await createStream({ model: "gpt-4o-mini", messages, stream: true });

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
```

## Sanitise in place

With `onMatch: "sanitise"` the body is rewritten before it is sent. Provider parameters survive untouched and only matched spans change.

```ts theme={null}
const create = fw.guard.decorate(
  (body: OpenAI.Chat.ChatCompletionCreateParamsNonStreaming) =>
    openai.chat.completions.create(body),
  { onMatch: "sanitise" },
);
```

## Anthropic

The same decorator wraps the Anthropic SDK. `messages.create` and `messages.stream` both work. The request layout understands the Messages API: a top-level `system` parameter (a string or a list of text blocks), and content blocks that nest text inside `tool_result` and `tool_use`.

```ts theme={null}
const create = fw.guard.decorate(
  (body: Anthropic.MessageCreateParamsNonStreaming) =>
    anthropic.messages.create(body),
  { baseUrl: "https://api.anthropic.com", provider: "anthropic" },
);
```

Tool results and replayed tool calls are scanned too, because they are first-class indirect injection surfaces. Image and document payloads are not: base64 is neither text nor a plausible carrier for an instruction.

## Options

| Option | Purpose |
| - | - |
| `baseUrl` | The wrapped client's base URL. Recorded for attribution, never rewritten. |
| `provider` | A label such as `openai` or `anthropic`. Inferred from the request shape if omitted. |
| `onMatch` | Overrides the guard-level action for this call. |
| `capture` | Overrides capture settings for this call. See [Telemetry and capture](/operations/telemetry-and-capture). |
| `cloak` | Protect personal data in the request. See [Cloak](/cloak/protect-and-restore). |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.