opencode با یوتاپن — opencode.json، انتخاب مدل و سقف خروجی

تنظیم opencode با یوتاپن در opencode.json: provider سازگار با OpenAI، شناسهٔ مدل، limit.output برای رزرو کوچک، small_model ارزان برای عنوان و رفع خطاها.

به‌روزرسانی: ۳۱ شهریور ۱۴۰۵

opencode یک ایجنت کدنویسی ترمینالی است که هر سرویس سازگار با Chat Completions را می‌پذیرد. یوتاپن را در آن به‌صورت یک provider سفارشی تعریف می‌کنی: آدرس https://api.uttapen.ir/v1، کلید sk-up-… و فهرست مدل‌هایی که می‌خواهی. این صفحه روی opencode نسخهٔ 1.18.31 بررسی شده و درخواست‌هایی که می‌فرستد را با یک سرور آزمایشی ثبت کرده‌ایم.

opencode اگر سقف خروجی مدل را ندانَد، روی هر درخواست max_tokens: 32000 می‌فرستد و عنوان گفتگو را هم با همان مدل اصلی می‌سازد؛ یعنی هر پیام دو درخواست هم‌زمان با رزرو ۳۲ هزار توکنی. با مدلی مثل Claude Sonnet همین کافی است که با موجودی معقول هم 402 بگیری. دو تنظیم limit.output و small_model که پایین آمده‌اند این مشکل را حل می‌کنند.

پیش‌نیاز

  • یک کلید از داشبورد ← کلیدها. برای opencode یک کلید جدا با سقف ماهانه بساز تا مصرفش جدا دیده شود.
  • موجودی در کیف پول.
  • کلید را در متغیر محیطی بگذار، نه در فایل:
export UTTAPEN_API_KEY="sk-up-..."

فایل opencode.json

این فایل را در ریشهٔ پروژه (opencode.json) یا برای همهٔ پروژه‌ها در ~/.config/opencode/opencode.json بگذار. opencode هر دو را با هم ادغام می‌کند و فرمت JSONC (با کامنت) را هم می‌پذیرد.

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "uttapen": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "uttapen",
      "options": {
        "baseURL": "https://api.uttapen.ir/v1",
        "apiKey": "{env:UTTAPEN_API_KEY}"
      },
      "models": {
        "anthropic/claude-sonnet-5": {
          "name": "Claude Sonnet 5",
          "limit": { "context": 200000, "output": 8192 }
        },
        "qwen/qwen3-coder": {
          "name": "Qwen3 Coder",
          "limit": { "context": 262144, "output": 8192 }
        },
        "deepseek/deepseek-v3.2": {
          "name": "DeepSeek V3.2",
          "limit": { "context": 163840, "output": 8192 }
        },
        "openai/gpt-5-nano": {
          "name": "GPT-5 nano",
          "limit": { "context": 400000, "output": 1024 }
        }
      }
    }
  },
  "model": "uttapen/anthropic/claude-sonnet-5",
  "small_model": "uttapen/openai/gpt-5-nano"
}

نکته‌های هر فیلد:

  • npm باید @ai-sdk/openai-compatible باشد. این پکیج فقط /v1/chat/completions را صدا می‌زند. پکیج @ai-sdk/openai را نگذار؛ آن سراغ /v1/responses می‌رود که یوتاپن ندارد و 404 می‌گیری.
  • uttapen شناسهٔ provider در opencode است و هر نامی می‌تواند باشد؛ فقط باید در model و small_model همان را بنویسی.
  • options.apiKey با {env:UTTAPEN_API_KEY} کلید را از متغیر محیطی می‌خواند. اگر ترجیح می‌دهی کلید را opencode نگه دارد، این خط را حذف کن، در opencode دستور /connect را بزن، Other را انتخاب کن، شناسهٔ uttapen را بده و کلید را بچسبان.
  • کلیدهای models دقیقاً شناسهٔ مدل در یوتاپن‌اند؛ opencode همین رشته را در فیلد model درخواست می‌فرستد.
  • limit.output همان max_tokens است که opencode می‌فرستد (با سقف ۳۲٬۰۰۰). اگر نگذاری، ۳۲٬۰۰۰ فرستاده می‌شود.
  • limit.context را حتماً بگذار. بدون آن opencode هیچ‌وقت گفتگو را خودکار خلاصه (compact) نمی‌کند و prompt هر درخواست بزرگ‌تر و گران‌تر می‌شود. عدد کمتر از context واقعی مدل یعنی خلاصه‌سازی زودتر و prompt ارزان‌تر؛ Claude Sonnet 5 در واقع یک میلیون توکن context دارد، ولی ۲۰۰ هزار برای کار روزمره کافی و ارزان‌تر است.
  • small_model مدلی است که opencode برای کارهای سبک مثل ساختن عنوان گفتگو استفاده می‌کند. opencode برای provider سفارشی نمی‌تواند خودش مدل ارزان پیدا کند و بدون این خط، عنوان را با همان مدل اصلی و همان رزرو بزرگ می‌سازد.

opencode فهرست مدل را از /v1/models نمی‌خواند؛ فقط مدل‌هایی که در models نوشته‌ای در انتخابگر ظاهر می‌شوند.

انتخاب مدل

شناسهٔ هر مدل را از فهرست مدل‌ها یا صفحهٔ همان مدل کپی کن؛ context و سقف خروجی هر مدل هم همان‌جاست (یا در GET https://api.uttapen.ir/v1/models در فیلدهای context_length و top_provider.max_completion_tokens). در opencode نام کامل مدل به شکل providerID/modelID است، پس مدل anthropic/claude-sonnet-5 در provider uttapen می‌شود uttapen/anthropic/claude-sonnet-5. opencode فقط تا اولین / را شناسهٔ provider می‌گیرد و بقیه را دست نمی‌زند.

چند انتخاب رایج برای کد:

نقششناسه
مدل اصلی قویanthropic/claude-sonnet-5، openai/gpt-5
مدل اصلی اقتصادیqwen/qwen3-coder، deepseek/deepseek-v3.2، openai/gpt-5-mini
small_model (عنوان)openai/gpt-5-nano، google/gemini-2.5-flash-lite

فهرست مدل‌های تعریف‌شده را با این دستور ببین و داخل opencode با /models بین‌شان جابه‌جا شو:

opencode models uttapen

هزینه را مهار کن

یوتاپن پیش از فرستادن هر درخواست، هزینهٔ بیشترین خروجی ممکن را از موجودی رزرو می‌کند و بعد از پاسخ فقط مصرف واقعی را کم می‌کند (قیمت‌گذاری). پس max_tokens بزرگ پول بیشتری مصرف نمی‌کند، ولی موجودی بیشتری را موقتاً قفل می‌کند؛ و opencode چند درخواست هم‌زمان می‌فرستد که رزروهایشان جمع می‌شوند.

  • limit.output را برای مدل‌های اصلی حدود 8192 بگذار. رزرو خروجی نسبت به پیش‌فرض ۳۲٬۰۰۰ تقریباً یک‌چهارم می‌شود و برای ویرایش کد کافی است. برای مدل‌های استدلالی که قبل از جواب فکر می‌کنند، 16000 امن‌تر است.
  • small_model را روی یک مدل ارزان با limit.output حدود 1024 بگذار.
  • اگر نمی‌خواهی برای تک‌تک مدل‌ها عدد بنویسی، متغیر OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX سقف سراسری است؛ opencode کمترین مقدار بین آن و limit.output را می‌فرستد. همان‌طور که از نامش پیداست، آزمایشی است.
  • در کلید opencode سقف ماهانه و در صورت نیاز فهرست مدل‌های مجاز بگذار (کلیدها).

با تنظیمات بالا، opencode نسخهٔ 1.18.31 برای یک پیام، درخواست عنوان را با openai/gpt-5-nano و max_tokens: 1024 و درخواست اصلی را با anthropic/claude-sonnet-5 و max_tokens: 8192 فرستاد. بدون آن‌ها هر دو درخواست به Claude Sonnet 5 با max_tokens: 32000 رفتند.

بررسی اتصال

اول خود کلید را جدا از opencode تست کن:

curl https://api.uttapen.ir/v1/chat/completions \
  -H "Authorization: Bearer $UTTAPEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"openai/gpt-5-nano","max_tokens":20,"messages":[{"role":"user","content":"ping"}]}'

بعد opencode را بدون رابط تمام‌صفحه اجرا کن:

opencode run -m uttapen/openai/gpt-5-nano "say hi"

اگر جواب آمد، در داشبورد ← مصرف باید دو درخواست کوچک ببینی (عنوان و پیام).

عیب‌یابی

خطاعلت در opencodeراه‌حل
401 invalid_api_keyمتغیر UTTAPEN_API_KEY در شِلی که opencode را باز کرده ست نیست، یا کلید لغو شدهecho $UTTAPEN_API_KEY را در همان ترمینال بزن؛ یا کلید را با /connect دوباره ذخیره کن
404 model_not_foundکلید models با شناسهٔ واقعی فرق دارد، مثلاً claude-sonnet-5 بدون anthropic/ یا claude-sonnet-4-5 با خط تیره به‌جای نقطهشناسه را از /models کپی کن (پیام 404 در uttapen.suggestions نزدیک‌ترین شناسه‌های محتمل را هم پیشنهاد می‌دهد)
404 not_foundnpm روی @ai-sdk/openai است و درخواست به /v1/responses رفته@ai-sdk/openai-compatible بگذار
402 insufficient_balanceموجودی حتی برای ۱٬۰۲۴ توکن خروجی این درخواست کافی نیست؛ رزروهای هم‌زمان جمع می‌شوندکیف پول را شارژ کن؛ limit.output و small_model را مثل بالا تنظیم کن
429سقف درخواست در دقیقهٔ کلید یا سقف ماهانه‌اش پر شده، یا مدل موقتاً محدود استcode خطا را ببین (محدودیت نرخ)؛ rate_limit_rpm کلید را بالا ببر
پاسخ نیمه‌کارهfinish_reason: "length"؛ یا limit.output کم است یا یوتاپن سقف را پایین آوردهپایین را ببین
opencode بی‌جواب می‌ماند؛ نه خطا نه پاسخگیر در init، قبل از رسیدن به یوتاپنپایین‌تر را ببین

اگر opencode اصلاً بالا نمی‌آید (گیر در init). لاگ ~/.local/share/opencode/log/opencode.log را نگاه کن؛ اگر آخرین خط اجرا message=init باشد و ادامه پیدا نکند، یعنی هنوز به سرور یوتاپن هم نرسیده. علتش خود یوتاپن نیست: opencode موقع بالا آمدن چند تماس شبکهٔ پس‌زمینهٔ خودش را می‌زند — فهرست مدل‌ها را از models.opencode.ai (مگر OPENCODE_DISABLE_MODELS_FETCH=1 باشد) و چک نسخهٔ جدید را (مگر OPENCODE_DISABLE_AUTOUPDATE=1 باشد)؛ این‌ها و opencode.ai و حتی registry.npmjs.org همه پشت Cloudflare‌اند، و روی یک اتصال ایرانی که دست‌دهی TLS را به‌جای رد سریع معلق نگه می‌دارد، opencode همان‌جا می‌ماند. اگر گیر کرد، این دو متغیر را قبل از اجرا بگذار:

OPENCODE_DISABLE_MODELS_FETCH=1 OPENCODE_DISABLE_AUTOUPDATE=1 opencode run -m uttapen/openai/gpt-5-nano "say hi"

این به فهرست مدل‌های provider uttapen در opencode.json کاری ندارد — آن از فایل خودت خوانده می‌شود، نه از models.opencode.ai. این گیرکردن متناوب است نه دائمی؛ همین سیستم چند بار بدون هیچ متغیری هم بدون گیر کردن جواب داد، پس اول یک بار ساده امتحان کن.

وقتی موجودی برای کل max_tokens کافی نیست. اگر موجودی قابل‌استفاده‌ات هزینهٔ کل max_tokens درخواستی را پوشش ندهد، یوتاپن درخواست را رد نمی‌کند؛ max_tokens را تا جایی که موجودی می‌کشد پایین می‌آورد (حداقل ۱٬۰۲۴ توکن خروجی) و مقدار داده‌شده را در هدر پاسخ X-Uttapen-Max-Tokens برمی‌گرداند. فقط اگر همان ۱٬۰۲۴ توکن هم ممکن نباشد 402 می‌گیری. نتیجهٔ این کاهش ممکن است پاسخی باشد که با finish_reason: "length" وسط کار قطع شده. راه‌حل شارژ کیف پول است، یا کم‌کردن limit.output تا درخواست‌ها همیشه در موجودی جا شوند.

جدول کامل کدها در خطاها است و بقیهٔ ابزارها در یکپارچه‌سازی.

هنوز کلید API نداری؟

با شماره موبایل ثبت‌نام کن، کیف پول را از ۱۰۰٬۰۰۰ تومان شارژ کن و کلید بساز. مدل‌های رایگان بدون شارژ هم کار می‌کنند — تا ۵۰ درخواست در روز. ساخت کلید API ←