Полный пример на Node.js: официальные SDK через наш шлюз
Node-версии официальных SDK настраиваются так же, как Python — один параметр baseURL меняет весь маршрут запроса.
2 мин · · Обновлено · разработка
Node-пакеты @anthropic-ai/sdk и openai устроены так же, как их Python-аналоги: конструктор клиента принимает baseURL, и дальше весь код — стриминг, tool use, обработка ошибок — работает без переделок.
Установка
npm install @anthropic-ai/sdk openaiКлиент Anthropic SDK
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
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:
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:
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):
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
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);Обработка ошибок
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 и заголовки стоимости
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):
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/* пакеты не нужны:
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 в миллисекундах:
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.
Похожие статьи
- Промпт-инъекция: что это и как защитить бота и агентаОбъясняем, как работает промпт-инъекция через пользовательский ввод и чужие документы, и какие меры снижают ущерб: изоляция данных, права инструментов, лимиты ключа.
- Свой прокси к нейросети на Node.js: не показывать API-ключ во фронтендеНебольшой сервер на Express между вашим фронтендом и API нейросети: ключ остаётся на сервере, а модели, длина запроса и частота ограничены.
- DeepSeek API на Python: запросы, стриминг, JSON и обработка ошибокГотовый код для DeepSeek V4.1 Flash и V4 Pro на OpenAI SDK: запрос, стриминг, разбор JSON и аккуратная обработка ошибок.
- Промпт-инжиниринг: примеры и 12 готовых шаблонов для работыДвенадцать шаблонов промптов для рабочих задач: что в них написано, почему именно так и сколько стоит один вызов по API.