Полный пример на Node.js: официальные SDK через наш шлюз

Node-версии официальных SDK настраиваются так же, как Python — один параметр baseURL меняет весь маршрут запроса.

2 мин · · Обновлено · разработка

Node-пакеты @anthropic-ai/sdk и openai устроены так же, как их Python-аналоги: конструктор клиента принимает baseURL, и дальше весь код — стриминг, tool use, обработка ошибок — работает без переделок.

Установка

bash
npm install @anthropic-ai/sdk openai

Клиент Anthropic SDK

bash
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  baseURL: "https://api.zukko.pro",
  apiKey: "fetch_ваш_ключ",
});

const message = await client.messages.create({
  model: "claude-sonnet-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Объясни разницу между var, let и const" }],
});
console.log(message.content[0].text);

baseURL без /v1 — SDK сам добавляет /v1/messages. Это отличие от OpenAI-клиента ниже, и именно оно чаще всего путает при первой настройке: скопировали URL из документации по OpenAI-клиенту, вставили в Anthropic-клиент — получили 404 на каждом запросе.

Клиент OpenAI SDK

bash
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.zukko.pro/v1",
  apiKey: "fetch_ваш_ключ",
});

const response = await client.chat.completions.create({
  model: "gpt-5.6-sol",
  messages: [{ role: "user", content: "Объясни разницу между var, let и const" }],
});
console.log(response.choices[0].message.content);

Стриминг

Anthropic SDK:

bash
const stream = client.messages.stream({
  model: "claude-sonnet-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Напиши хайку про дедлайны" }],
});

stream.on("text", (text) => process.stdout.write(text));
await stream.finalMessage();

OpenAI SDK:

bash
const stream = await client.chat.completions.create({
  model: "gpt-5.6-sol",
  messages: [{ role: "user", content: "Напиши хайку про дедлайны" }],
  stream: true,
});

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

Tool use / function calling

Через Responses API (openai SDK, эндпоинт /v1/responses):

bash
const response = await client.responses.create({
  model: "gpt-5.6-sol",
  input: "Какая погода в Алматы?",
  tools: [{
    type: "function",
    name: "get_weather",
    description: "Возвращает текущую погоду по городу",
    parameters: {
      type: "object",
      properties: { city: { type: "string" } },
      required: ["city"],
    },
  }],
});

const call = response.output.find((item) => item.type === "function_call");
if (call) {
  console.log("Модель просит вызвать:", call.name, JSON.parse(call.arguments));
}

Вместо gpt-5.6-sol здесь можно поставить и claude-sonnet-5, и glm-5.3 — /v1/responses работает одинаково независимо от провайдера, потому что маршрутизация происходит на стороне шлюза, а не клиента. Актуальный список моделей — GET https://api.zukko.pro/v1/models.

Vision в Node

bash
import { readFileSync } from "fs";

const imageBase64 = readFileSync("screenshot.png").toString("base64");

const message = await client.messages.create({
  model: "claude-opus-5-5",
  max_tokens: 1024,
  messages: [{
    role: "user",
    content: [
      { type: "image", source: { type: "base64", media_type: "image/png", data: imageBase64 } },
      { type: "text", text: "Опиши, что на скриншоте" },
    ],
  }],
});
console.log(message.content[0].text);

Обработка ошибок

bash
import Anthropic from "@anthropic-ai/sdk";

try {
  await client.messages.create({
    model: "claude-sonnet-5",
    max_tokens: 100,
    messages: [{ role: "user", content: "тест" }],
  });
} catch (err) {
  if (err instanceof Anthropic.AuthenticationError) {
    console.error("Неверный ключ — проверьте Bearer fetch_...");
  } else if (err instanceof Anthropic.APIError && err.status === 402) {
    console.error("Баланс пуст:", err.error);
  } else if (err instanceof Anthropic.APIError && err.status === 429) {
    console.error("Лимит ключа (key_daily/monthly_limit_reached) или 429 от провайдера");
  } else {
    throw err;
  }
}

В openai SDK структура аналогичная: err instanceof OpenAI.APIError и проверка err.status, включая нестандартные для оригинального API коды 402 и 422.

Idempotency-Key и заголовки стоимости

bash
const response = await client.messages.create(
  {
    model: "claude-sonnet-5",
    max_tokens: 100,
    messages: [{ role: "user", content: "тест" }],
  },
  { headers: { "Idempotency-Key": "invoice-job-77" } }
);

Ключ передаётся в опциях конкретного вызова — уникальный на каждый логический запрос и одинаковый при его повторах. Не задавайте его в defaultHeaders клиента: тогда второй же запрос с другим телом получит 422 idempotency_conflict.

Чтобы прочитать X-Zukko-Cost-Micros и X-Fetch-Balance-Cents, нужен доступ к сырому ответу (заголовки есть только у обычных, нестриминговых ответов; X-Fetch-Balance-Cents, несмотря на название, — в микродолларах, 1 000 000 = $1):

bash
const { data, response: raw } = await client.messages
  .create({
    model: "claude-sonnet-5",
    max_tokens: 100,
    messages: [{ role: "user", content: "тест" }],
  })
  .withResponse();

console.log(raw.headers.get("x-zukko-cost-micros"));
console.log(raw.headers.get("x-fetch-balance-cents"));

TypeScript и типы ответа

Оба SDK поставляются с типами из коробки, дополнительные @types/* пакеты не нужны:

bash
import Anthropic from "@anthropic-ai/sdk";
import type { MessageParam } from "@anthropic-ai/sdk/resources/messages";

const client = new Anthropic({
  baseURL: "https://api.zukko.pro",
  apiKey: process.env.ZUKKO_API_KEY,
});

const history: MessageParam[] = [
  { role: "user", content: "Составь план миграции с Express на Fastify" },
];

const message = await client.messages.create({
  model: "claude-opus-5-5",
  max_tokens: 2048,
  messages: history,
});

Тип MessageParam берётся из самого пакета @anthropic-ai/sdk, а не пишется вручную — это удобно, когда история сообщений собирается динамически в цикле диалога, и компилятор сразу подсвечивает неверную структуру объекта до запуска кода.

Таймауты и обрывы стрима

В Node частая причина обрыва ответа на середине — дефолтный таймаут fetch или самого SDK, а не лимит по токенам на стороне шлюза. Оба клиента принимают timeout в миллисекундах:

bash
const client = new Anthropic({
  baseURL: "https://api.zukko.pro",
  apiKey: "fetch_ваш_ключ",
  timeout: 120000,
});

Если ответ обрывается стабильно на одном и том же месте при длинной генерации — сначала проверьте max_tokens в запросе, а не сетевые настройки: модель могла просто исчерпать лимит вывода, который вы сами задали. И учтите: обрыв соединения на стороне клиента списание не останавливает — шлюз дочитывает ответ провайдера и списывает всю генерацию.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.