Raycast: расширение с Claude и GPT на своём ключе

Raycast AI считает запросы по подписке, а свой ключ — по факту расхода. Собираем расширение на TypeScript, которое ходит напрямую в API мимо встроенного AI.

4 мин · · Обновлено · IDE · инструменты

Встроенный Raycast AI — это подписка с фиксированной ценой, а не оплата за фактический расход. Если нужен конкретный контроль над тем, какая модель отвечает и сколько это стоит, проще написать своё расширение, которое ходит в API напрямую по вашему ключу — Raycast это позволяет как обычное TypeScript-расширение с собственными сетевыми вызовами.

Структура расширения

raycast-zukko/ package.json src/ ask.tsx

В package.json описываются команда и её конфигурация — Preferences, куда попадёт ключ пользователя:

bash
{
  "name": "zukko-ask",
  "title": "Ask AI",
  "commands": [
    { "name": "ask", "title": "Ask", "mode": "view" }
  ],
  "preferences": [
    {
      "name": "apiKey",
      "type": "password",
      "title": "Zukko API Key",
      "description": "Ключ вида fetch_...",
      "required": true
    },

Ключ хранится в preferences с типом password — Raycast шифрует его в своём хранилище, а не держит в открытом виде в файле конфига расширения.

Команда ask.tsx

bash
import { ActionPanel, Action, Form, getPreferenceValues, showToast, Toast } from "@raycast/api";
import { useState } from "react";

interface Prefs { apiKey: string; model: string; }

export default function Command() {
  const { apiKey, model } = getPreferenceValues<Prefs>();
  const [answer, setAnswer] = useState("");

  async function ask(prompt: string) {
    const toast = await showToast({ style: Toast.Style.Animated, title: "Спрашиваю..." });

    const r = await fetch("https://api.zukko.pro/v1/chat/completions", {
      method: "POST",

Быстрая модель для интерфейса, которому важна скорость

Raycast — это про мгновенный отклик, поэтому по умолчанию в примере стоит лёгкая glm-5.3-flash ($0.04 за миллион токенов), а не тяжёлая модель с расширенным контекстом. Для коротких команд вроде «перефразируй», «сократи», «переведи одну строку» разница в задержке между glm-5.3-flash и, скажем, claude-opus-5-5 может быть заметна на глаз — проверьте на своих командах; для расширения, которое открывают на секунду, скорость часто важнее. Тяжёлую модель имеет смысл оставить отдельным пунктом дропдауна для случаев, когда нужна именно она — например, вопрос с картинкой (изображения читают модели Claude и GPT; у GLM vision нет) или разбор длинного фрагмента кода в контексте 1,000,000 токенов (у GLM окно 128K).

Кэш реально экономит на Raycast-паттернах использования

Расширения Raycast часто вызываются с одним и тем же промптом снова и снова — «объясни это регулярное выражение», «сократи заголовок». Заголовок X-Zukko-Cache: true в примере выше включает кэширование одинаковых запросов на срок до 7 суток: повторный вызов с полностью совпадающим телом запроса (тот же текст, та же модель, те же параметры) возвращается мгновенно и бесплатно, а в ответе будет X-Zukko-Cache: hit. Отличие хотя бы в одном символе — уже промах кэша; попадания в кэш не пишутся в /v1/usage. Для расширения с быстрым UX это ощущается как разница между «висит секунду» и «отвечает сразу».

Обработка типичных ошибок в интерфейсе Raycast

Raycast больше не прощает голого исключения — лучше сразу переводить коды ошибок в понятный toast:

bash
if (r.status === 401) {
  toast.title = "Проверьте API-ключ в настройках расширения";
} else if (r.status === 402) {
  toast.title = "Баланс закончился — пополните на zukko.pro/app";
} else if (r.status === 404) {
  toast.title = "Модель не найдена, проверьте имя в настройках";
} else if (r.status === 503) {
  toast.title = "Модель сейчас недоступна — выберите другую в настройках";
} else if (r.status === 429) {
  toast.title = "Исчерпан дневной или месячный лимит ключа (или провайдер перегружен)";
}

Своего rate limit у шлюза нет: 429 от Zukko означает key_daily_limit_reached (сброс в 00:00 UTC) или key_monthly_limit_reached, прочие 429 приходят от провайдера. Выключенная модель отвечает 503 model_disabled.

Ключ можно получить и обновить в кабинете на https://zukko.pro/app/api-keys — на странице конкретного ключа заодно настраиваются его дневной/месячный лимит и список разрешённых моделей, что полезно, если расширением пользуется не только один человек, а расшарено в команде через общий ключ с урезанными правами.

Если нужна генерация изображений прямо из Raycast

Генерация изображений через Zukko сейчас временно недоступна (актуальный список моделей — GET https://api.zukko.pro/v1/models). Когда она вернётся, её стоит вынести в отдельную команду с явным предупреждением о цене: модели картинок оплачиваются за штуку, а не за токены, и смешивать их с текстовым ask не стоит.

Проверка расхода прямо из расширения

Полезно добавить вторую команду, которая просто дергает /v1/balance и показывает остаток в HUD Raycast — тогда не нужно переключаться в браузер, чтобы понять, пора ли пополнять счёт:

bash
const r = await fetch("https://api.zukko.pro/v1/balance", {
  headers: { "Authorization": `Bearer ${apiKey}` }
});
const { balance_cents } = await r.json();
await showHUD(`Баланс: $${(balance_cents / 100).toFixed(2)}`);

Заголовок X-Fetch-Balance-Cents, который приходит с каждым обычным (нестриминговым) ответом на ask, тоже можно писать в локальный кэш расширения и показывать рядом с ответом — тогда видно остаток без отдельного запроса, сразу после каждого вызова модели. Учтите: несмотря на название, значение в нём в микродолларах (1 000 000 = $1), поэтому делите его на 1_000_000, а не на 100. Точный баланс в центах — поле balance_cents из /v1/balance.

Ограничение расхода на командный ключ

Если ключ в Preferences общий на всю команду, задайте на странице ключа в кабинете (https://zukko.pro/app/api-keys) дневной лимит расхода в долларах — это защитит бюджет от одного человека, который случайно зациклит вызовы через быструю модель. Список разрешённых моделей на той же странице стоит держать в синхроне с дропдауном в package.json, чтобы расширение не предлагало модели, которые ключу запрещены политикой — иначе пользователь получит 403 model_not_allowed_for_key вместо ответа.

zukko

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

за Claude Code & ChatGPT

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