# Ekx Anthropic Sdk

> Building with Claude across Ekinoxis products — the Anthropic SDK, tool use for agentic features, streaming, prompt caching, model selection and cost control. Use when adding an AI feature, wiring tool use over a database, streaming a response to a UI, or deciding which Claude model a workload needs.

- Skill: `ekinoxis-evm/ekx-anthropic-sdk` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ekinoxis-evm/ekx-anthropic-sdk`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ekinoxis-evm/ekx-anthropic-sdk/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: Ekinoxis-evm (https://skillmd.com/u/ekinoxis-evm)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/ekinoxis-evm/ekx-anthropic-sdk

---


# Anthropic SDK

Claude is the default model at Ekinoxis. `@anthropic-ai/sdk` direct in most apps;
`@ai-sdk/anthropic` where the Vercel AI SDK is already in play.

**The bundled `claude-api` skill is authoritative** for model ids, pricing and
parameters — read it before answering anything about models or cost, rather than
relying on memory.

The one exception in the portfolio: an older agent app uses LangChain + OpenAI, inherited
from the Coinbase AgentKit starter ([`../ekx-coinbase-cdp/SKILL.md`](../ekx-coinbase-cdp/SKILL.md)).

---

## Environment

```bash
ANTHROPIC_API_KEY=      # SECRET — server only, never NEXT_PUBLIC_
ANTHROPIC_MODEL=        # pin the model in env so it can change without a deploy
```

**Never call the API from the browser.** A key in the client bundle is a metered,
billable secret handed to anyone who opens devtools. Always proxy through a route handler.

---

## Tool use — the pattern that matters

The reference shape: the user asks about P&L in plain language,
Claude calls tools that query Supabase, and answers from real rows.

```ts
const tools = [{
  name: "get_positions",
  description: "Fetch the user's open positions with entry price and current value.",
  input_schema: {
    type: "object",
    properties: { chain: { type: "string", enum: ["evm", "solana", "xrpl"] } },
    required: [],
  },
}];

let messages = [{ role: "user", content: prompt }];

while (true) {
  const res = await anthropic.messages.create({
    model: process.env.ANTHROPIC_MODEL!,
    max_tokens: 2048,
    tools,
    messages,
  });

  if (res.stop_reason !== "tool_use") return res;

  const results = await Promise.all(
    res.content.filter(c => c.type === "tool_use").map(async (c) => ({
      type: "tool_result" as const,
      tool_use_id: c.id,
      content: JSON.stringify(await runTool(c.name, c.input, userId)),   // ← userId from the session
    })),
  );

  messages.push({ role: "assistant", content: res.content },
                { role: "user", content: results });
}
```

Three rules for that loop:

1. **Scope every tool to the authenticated user server-side.** Pass `userId` from the verified session, never from the model's arguments. A tool that accepts a `user_id` parameter is a data-leak waiting for the right prompt.
2. **Cap the iterations.** An unbounded `while(true)` with a model that keeps calling tools is an unbounded bill. Ten rounds is generous.
3. **The tool `description` is the prompt.** Most "the model called the wrong tool" problems are a vague description, not a model problem.

---

## Streaming

```ts
const stream = await anthropic.messages.stream({ model, max_tokens, messages });
for await (const event of stream) {
  if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
    controller.enqueue(encoder.encode(event.delta.text));
  }
}
```

Return it as a `ReadableStream` from a route handler. Anything over ~2 seconds of
generation needs this — `live` uses the Vercel AI SDK, which wraps it with React hooks
and is the easier path for a chat UI.

---

## Cost control

- **Prompt caching** on a long system prompt or a large document reused across turns. This is the single largest saving available and costs one `cache_control` marker.
- **Pin the model in env**, so switching tiers is a config change.
- **Match the model to the job.** A classification or extraction step does not need the same tier as multi-step agentic reasoning.
- **Set `max_tokens` deliberately.** It is a cap on the bill, not just on length.
- **Log token usage per request** — `res.usage` — into Supabase. Without it, a cost spike has no explanation.

---

## Gotchas

1. **Key in the browser.** The expensive one.
2. **Tool args are model-controlled.** Never trust them for authorization.
3. **Unbounded tool loops.**
4. **`max_tokens` is required** and truncates silently at the limit — check `stop_reason === "max_tokens"`.
5. **Rate limits are per-organisation.** A batch job and the interactive app share them; queue the batch.
6. **Vague tool descriptions** cause wrong-tool calls far more often than model capability does.

