refactor: move OpenCode artifacts under opencode/
arka/public is the dump for all Arka downloads. Keep a root stub so the old installer URL still works. Document in-OpenCode bash install.
This commit is contained in:
1 parent
6e0dc4d02b
commit
22938b01ca
9 files changed
+585
-490
No files matched your search
@@ -1,187 +1,35 @@
|
|||||||
# OpenCode и модели Arka AI
|
# arka/public
|
||||||
|
|
||||||
Arka AI — OpenAI-совместимый API (`https://api.arka-ai.ru/v1`). В каталоге models.dev его нет, поэтому в свежем OpenCode провайдера **Arka-AI** и списка моделей нет, пока не поставить плагин.
|
Публичные файлы Arka AI: загрузки, инструкции, скрипты. Анонимный raw, без токена.
|
||||||
|
|
||||||
Одна команда ставит плагин, skill и (по желанию) ключ. Каталог чат-моделей подтягивается сам (~116 из ~216; без TTS/картинок/видео/эмбеддингов). Снимок каталога обновляется каждый день в **05:00 по Москве**.
|
`https://git.arka-ai.ru/arka/public/raw/branch/main/<path>`
|
||||||
|
|
||||||
Нужен [API-ключ Arka AI](https://arka-ai.ru/docs/account/api-keys). Секрет ключа показывается **один раз** при создании.
|
Каждый продукт — своя папка на `main`. Приватный код сюда не кладём.
|
||||||
|
|
||||||
|
| Папка | Что |
|
||||||
|
|-------|-----|
|
||||||
|
| [`opencode/`](opencode/) | Провайдер OpenCode **Arka-AI**: установщик, плагин, skill, каталог чат-моделей |
|
||||||
|
|
||||||
|
Однострочник OpenCode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/install-arka-provider.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Быстрый старт
|
Public Arka AI downloads, docs, and scripts. Anonymous raw URLs, no token.
|
||||||
|
|
||||||
|
`https://git.arka-ai.ru/arka/public/raw/branch/main/<path>`
|
||||||
|
|
||||||
|
One top-level folder per product. No private source.
|
||||||
|
|
||||||
|
| Folder | What |
|
||||||
|
|--------|------|
|
||||||
|
| [`opencode/`](opencode/) | OpenCode **Arka-AI** provider: installer, plugin, skill, chat catalog |
|
||||||
|
|
||||||
|
OpenCode one-liner:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://git.arka-ai.ru/arka/public/raw/branch/main/install-arka-provider.sh | bash
|
curl -fsSL https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/install-arka-provider.sh | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
Скрипт спросит `ARKA_API_KEY` (можно оставить пустым и задать позже).
|
|
||||||
|
|
||||||
Ключ и модель по умолчанию сразу:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -fsSL https://git.arka-ai.ru/arka/public/raw/branch/main/install-arka-provider.sh \
|
|
||||||
| bash -s -- --key "$ARKA_API_KEY" --set-default DeepSeek-V4-Pro
|
|
||||||
```
|
|
||||||
|
|
||||||
Дальше:
|
|
||||||
|
|
||||||
1. Откройте **новый терминал** (чтобы подхватился `export ARKA_API_KEY`).
|
|
||||||
2. Перезапустите OpenCode.
|
|
||||||
3. Команда `/models` — в списке должен быть провайдер **Arka-AI** и чат-модели.
|
|
||||||
|
|
||||||
Если OpenCode запущен из GUI / иконки / сокета, а не из login-shell, переменная из `~/.bashrc` до него может не дойти. Задайте `ARKA_API_KEY` в том окружении, откуда стартует OpenCode, или введите ключ в интерактивном приглашении установщика.
|
|
||||||
|
|
||||||
### Что ставится
|
|
||||||
|
|
||||||
| Куда | Что |
|
|
||||||
|------|-----|
|
|
||||||
| `~/.config/opencode/plugins/arka-ai.ts` | Плагин провайдера **Arka-AI** |
|
|
||||||
| `~/.config/opencode/skills/arka-provider/SKILL.md` | Skill: как обновить каталог и сменить модель |
|
|
||||||
| `~/.bashrc` (или первый из `.zshrc` / `.profile`) | `export ARKA_API_KEY=…`, если ключ передали |
|
|
||||||
| `~/.config/opencode/opencode.json` | Только при `--set-default`: `"model": "Arka-AI/<id>"`. Существующий конфиг **не затирается**. |
|
|
||||||
|
|
||||||
Установщик **офлайн** и идемпотентный: сеть не трогает, повторный запуск перезаписывает плагин и skill каноническими копиями.
|
|
||||||
|
|
||||||
`python3` нужен только с `--set-default`. Отдельный `bun` на машине не нужен — у OpenCode свой runtime.
|
|
||||||
|
|
||||||
### Модель по умолчанию
|
|
||||||
|
|
||||||
Идентификаторы — как в каталоге, с префиксом провайдера:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"model": "Arka-AI/DeepSeek-V4-Pro"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Примеры: `Arka-AI/DeepSeek-V4-Pro`, `Arka-AI/qwen3.8-max`, `Arka-AI/kimi-k3`, `Arka-AI/MiniMax-M3`, `Arka-AI/GLM-5.3`, `Arka-AI/deepseek-v4-flash-0731`.
|
|
||||||
|
|
||||||
Их всегда оставляем в списке, даже если фильтр чат-моделей их бы отсёк.
|
|
||||||
|
|
||||||
### Обновить каталог
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rm -f ~/.cache/opencode/arka-ai-models.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Перезапустите OpenCode. Плагин сначала берёт публичный снимок
|
|
||||||
`https://git.arka-ai.ru/arka/public/raw/branch/main/catalog.json`
|
|
||||||
(без ключа), при ошибке — живой `https://api.arka-ai.ru/v1/models`. Локальный кэш живёт 24 часа. Конфиг OpenCode на лету не перечитывается.
|
|
||||||
|
|
||||||
Список моделей (ключ не нужен):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -sS https://git.arka-ai.ru/arka/public/raw/branch/main/catalog.json | python3 -m json.tool
|
|
||||||
```
|
|
||||||
|
|
||||||
Вызовы моделей идут на `https://api.arka-ai.ru/v1` с `ARKA_API_KEY`.
|
|
||||||
|
|
||||||
### Если что-то не так
|
|
||||||
|
|
||||||
| Симптом | Что проверить |
|
|
||||||
|---------|----------------|
|
|
||||||
| После `/models` нет Arka-AI | Есть ли `~/.config/opencode/plugins/arka-ai.ts`; экспортирован ли `ARKA_API_KEY` **в процессе OpenCode**; открыт ли новый шелл |
|
|
||||||
| Каталог не меняется | `rm -f ~/.cache/opencode/arka-ai-models.json` и рестарт |
|
|
||||||
| Ошибка импорта плагина | Снова запустить установщик — он вернёт канонический файл |
|
|
||||||
| `curl` отдаёт HTML, не скрипт | Нужен путь `/raw/branch/main/…`, не страница репозитория |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# OpenCode and Arka AI models
|
|
||||||
|
|
||||||
Arka AI is an OpenAI-compatible API at `https://api.arka-ai.ru/v1`. It is not in models.dev, so a fresh OpenCode install has no **Arka-AI** provider until you add the plugin.
|
|
||||||
|
|
||||||
One command installs the plugin, a companion skill, and (optionally) your API key. The chat catalog loads on OpenCode start (~116 of ~216 models; TTS, image, video, and embeddings are filtered out). The published catalog snapshot refreshes daily at **05:00 Europe/Moscow**.
|
|
||||||
|
|
||||||
You need an [Arka AI API key](https://arka-ai.ru/docs/account/api-keys). The secret is shown **once** at creation.
|
|
||||||
|
|
||||||
## Quick start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -fsSL https://git.arka-ai.ru/arka/public/raw/branch/main/install-arka-provider.sh | bash
|
|
||||||
```
|
|
||||||
|
|
||||||
The script prompts for `ARKA_API_KEY` (blank is allowed; set it later).
|
|
||||||
|
|
||||||
Key and default model in one go:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -fsSL https://git.arka-ai.ru/arka/public/raw/branch/main/install-arka-provider.sh \
|
|
||||||
| bash -s -- --key "$ARKA_API_KEY" --set-default DeepSeek-V4-Pro
|
|
||||||
```
|
|
||||||
|
|
||||||
Then:
|
|
||||||
|
|
||||||
1. Open a **new shell** so `export ARKA_API_KEY` is picked up.
|
|
||||||
2. Restart OpenCode.
|
|
||||||
3. Run `/models` — you should see provider **Arka-AI** and the chat models.
|
|
||||||
|
|
||||||
If OpenCode is started from a GUI, dock icon, or socket rather than a login shell, `~/.bashrc` may not reach it. Set `ARKA_API_KEY` in that process environment, or type it at the installer prompt.
|
|
||||||
|
|
||||||
### What the installer writes
|
|
||||||
|
|
||||||
| Path | Role |
|
|
||||||
|------|------|
|
|
||||||
| `~/.config/opencode/plugins/arka-ai.ts` | **Arka-AI** provider plugin |
|
|
||||||
| `~/.config/opencode/skills/arka-provider/SKILL.md` | How to refresh the catalog and change the default model |
|
|
||||||
| `~/.bashrc` (else first existing `.zshrc` / `.profile`) | `export ARKA_API_KEY=…` when a key is given |
|
|
||||||
| `~/.config/opencode/opencode.json` | Only with `--set-default`: `"model": "Arka-AI/<id>"`. An existing config is **not** replaced. |
|
|
||||||
|
|
||||||
The installer is **offline** and idempotent: no network calls; re-running overwrites plugin and skill with the canonical copies.
|
|
||||||
|
|
||||||
`python3` is required **only** with `--set-default`. You do not need `bun` on the host — OpenCode ships its own runtime.
|
|
||||||
|
|
||||||
### Default model
|
|
||||||
|
|
||||||
Use catalog ids with the provider prefix:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"model": "Arka-AI/DeepSeek-V4-Pro"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Examples: `Arka-AI/DeepSeek-V4-Pro`, `Arka-AI/qwen3.8-max`, `Arka-AI/kimi-k3`, `Arka-AI/MiniMax-M3`, `Arka-AI/GLM-5.3`, `Arka-AI/deepseek-v4-flash-0731`.
|
|
||||||
|
|
||||||
Those ids stay visible even if the chat filter would drop them.
|
|
||||||
|
|
||||||
### Refresh the catalog
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rm -f ~/.cache/opencode/arka-ai-models.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Restart OpenCode. The plugin fetches the public snapshot
|
|
||||||
`https://git.arka-ai.ru/arka/public/raw/branch/main/catalog.json`
|
|
||||||
(no key), then falls back to live `https://api.arka-ai.ru/v1/models`. Local cache TTL is 24 hours. OpenCode does not hot-reload config.
|
|
||||||
|
|
||||||
Inspect the published list (no key):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -sS https://git.arka-ai.ru/arka/public/raw/branch/main/catalog.json | python3 -m json.tool
|
|
||||||
```
|
|
||||||
|
|
||||||
Inference still uses `ARKA_API_KEY` against `https://api.arka-ai.ru/v1`.
|
|
||||||
|
|
||||||
### Troubleshooting
|
|
||||||
|
|
||||||
| Symptom | Check |
|
|
||||||
|---------|--------|
|
|
||||||
| No Arka-AI after `/models` | `~/.config/opencode/plugins/arka-ai.ts` exists; `ARKA_API_KEY` is set **in the OpenCode process**; you opened a new shell |
|
|
||||||
| Models look stale | `rm -f ~/.cache/opencode/arka-ai-models.json` and restart |
|
|
||||||
| Plugin import error | Re-run the installer to restore the canonical file |
|
|
||||||
| `curl` returns HTML | Use `/raw/branch/main/…`, not the repo page |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## This repository
|
|
||||||
|
|
||||||
Public, anonymous files. Private product code lives in other Gitea repos.
|
|
||||||
|
|
||||||
| File | Role |
|
|
||||||
|------|------|
|
|
||||||
| `install-arka-provider.sh` | Self-contained installer (the one-liner) |
|
|
||||||
| `plugins/arka-ai.ts` | Plugin source (embedded in the installer) |
|
|
||||||
| `skills/arka-provider/SKILL.md` | Skill source (embedded in the installer) |
|
|
||||||
| `catalog.json` | Filtered chat catalog, refreshed daily at 05:00 Europe/Moscow |
|
|
||||||
| `README.md` | This page — copy the Russian or English section into arka-ai.ru «агентные помощники» / agentic helpers |
|
|
||||||
+6
-302
@@ -1,304 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#
|
# Compatibility stub. Canonical installer:
|
||||||
# install-arka-provider.sh — one-shot installer for the arka-ai.ru LLM provider
|
# https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/install-arka-provider.sh
|
||||||
# in a fresh opencode install.
|
|
||||||
#
|
|
||||||
# It is fully self-contained: it embeds the provider plugin and the companion
|
|
||||||
# skill, writes them into ~/.config/opencode, and persists ARKA_API_KEY.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# bash install-arka-provider.sh # prompt for API key
|
|
||||||
# ARKA_API_KEY=xxx bash install-arka-provider.sh # key from env (no prompt)
|
|
||||||
# bash install-arka-provider.sh --key xxx --set-default DeepSeek-V4-Pro
|
|
||||||
#
|
|
||||||
# Options:
|
|
||||||
# --key KEY ARKA_API_KEY to persist (default: $ARKA_API_KEY, else prompt)
|
|
||||||
# --set-default MODEL also set "model": "Arka-AI/<MODEL>" in opencode.json
|
|
||||||
# -h, --help show this help
|
|
||||||
#
|
|
||||||
# Idempotent: safe to run multiple times. Overwrites plugin/skill with the
|
|
||||||
# embedded versions; never clobbers an existing opencode.json (only upserts the
|
|
||||||
# requested default model). Makes no network calls.
|
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
echo "==> installer lives at opencode/install-arka-provider.sh" >&2
|
||||||
usage() {
|
curl -fsSL \
|
||||||
sed -n '2,21p' "$0"
|
'https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/install-arka-provider.sh' \
|
||||||
}
|
| bash -s -- "$@"
|
||||||
|
|
||||||
KEY=""
|
|
||||||
MODEL=""
|
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
|
||||||
case "$1" in
|
|
||||||
--key) KEY="$2"; shift 2 ;;
|
|
||||||
--key=*) KEY="${1#*=}"; shift ;;
|
|
||||||
--set-default) MODEL="$2"; shift 2 ;;
|
|
||||||
--set-default=*) MODEL="${1#*=}"; shift ;;
|
|
||||||
-h|--help) usage; exit 0 ;;
|
|
||||||
*) printf 'unknown option: %s\n' "$1" >&2; usage; exit 1 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
CONF_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/opencode"
|
|
||||||
PLUGIN_DIR="$CONF_DIR/plugins"
|
|
||||||
SKILL_DIR="$CONF_DIR/skills/arka-provider"
|
|
||||||
|
|
||||||
mkdir -p "$PLUGIN_DIR" "$SKILL_DIR" "$HOME"
|
|
||||||
|
|
||||||
echo "==> writing provider plugin to $PLUGIN_DIR/arka-ai.ts"
|
|
||||||
cat > "$PLUGIN_DIR/arka-ai.ts" <<'PLUGIN'
|
|
||||||
import { homedir } from "node:os"
|
|
||||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
||||||
import { join } from "node:path"
|
|
||||||
|
|
||||||
const PROVIDER_ID = "Arka-AI"
|
|
||||||
const API_URL = "https://api.arka-ai.ru/v1"
|
|
||||||
const CATALOG_URL =
|
|
||||||
"https://git.arka-ai.ru/arka/public/raw/branch/main/catalog.json"
|
|
||||||
const CACHE_FILE = join(homedir(), ".cache", "opencode", "arka-ai-models.json")
|
|
||||||
const TTL_MS = 24 * 60 * 60 * 1000
|
|
||||||
|
|
||||||
// Ids that must always be exposed, even if the filter would drop them.
|
|
||||||
const FORCED_MODELS = [
|
|
||||||
"DeepSeek-V4-Pro",
|
|
||||||
"deepseek-v4-flash-0731",
|
|
||||||
"qwen3.8-max",
|
|
||||||
"kimi-k3",
|
|
||||||
"MiniMax-M3",
|
|
||||||
"GLM-5.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
// Heuristic: drop non-chat models (TTS/ASR/image/video/OCR/embeddings/rerankers…)
|
|
||||||
// from the catalog so they don't clutter the /models picker.
|
|
||||||
// Keep in sync with scripts/sync-catalog.py
|
|
||||||
const NON_CHAT = new RegExp(
|
|
||||||
[
|
|
||||||
"tts", "asr", "audio", "speech", "voice", "whisper", "sensevoice",
|
|
||||||
"image", "(^|[.-])img", "wan", "vidu", "video", "sora", "flux",
|
|
||||||
"diffus", "kolors", "cogview", "ocr", "embedding", "(^|[.-])embed",
|
|
||||||
"rerank", "clip", "moderation", "(^|[.-])filter", "detect", "classif",
|
|
||||||
"(^|[.-])location", "(^|[.-])search", "comfyui", "meme", "hello",
|
|
||||||
"(^|[.-])sam", "florence", "esrgan", "rmbg", "background", "poster",
|
|
||||||
"(^|[.-])face", "(^|[.-])lip", "translate", "(^|[.-])mt([2-]|$)",
|
|
||||||
"(^|[.-])bge", "(^|[.-])e5", "(^|[.-])jina", "(^|[.-])mpnet",
|
|
||||||
"spark-tts", "cosyvoice", "chattts", "indextts", "step-audio",
|
|
||||||
"fish-speech", "cosy", "mate-gguf", "tokenizer",
|
|
||||||
].join("|"),
|
|
||||||
"i",
|
|
||||||
)
|
|
||||||
|
|
||||||
type ModelMap = Record<string, { name: string }>
|
|
||||||
type Cache = { ts: number; models: ModelMap }
|
|
||||||
|
|
||||||
function loadCache(): Cache | null {
|
|
||||||
try {
|
|
||||||
if (existsSync(CACHE_FILE)) return JSON.parse(readFileSync(CACHE_FILE, "utf8"))
|
|
||||||
} catch {}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveCache(models: ModelMap): void {
|
|
||||||
try {
|
|
||||||
mkdirSync(join(homedir(), ".cache", "opencode"), { recursive: true })
|
|
||||||
writeFileSync(CACHE_FILE, JSON.stringify({ ts: Date.now(), models }))
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchJson(
|
|
||||||
url: string,
|
|
||||||
headers: Record<string, string>,
|
|
||||||
timeoutMs: number,
|
|
||||||
): Promise<unknown> {
|
|
||||||
const ctrl = new AbortController()
|
|
||||||
const timer = setTimeout(() => ctrl.abort(), timeoutMs)
|
|
||||||
try {
|
|
||||||
const res = await fetch(url, { headers, signal: ctrl.signal })
|
|
||||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
||||||
return await res.json()
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function idsFromPayload(json: unknown): string[] {
|
|
||||||
const data = (json as { data?: Array<{ id: string }> }).data
|
|
||||||
return (data ?? []).map((m) => m.id).filter(Boolean)
|
|
||||||
}
|
|
||||||
|
|
||||||
function toModelMap(ids: string[], filter: boolean): ModelMap {
|
|
||||||
const models: ModelMap = {}
|
|
||||||
for (const id of ids) {
|
|
||||||
if (!filter || FORCED_MODELS.includes(id) || !NON_CHAT.test(id)) {
|
|
||||||
models[id] = { name: id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return models
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resolveModels(): Promise<ModelMap | null> {
|
|
||||||
const cache = loadCache()
|
|
||||||
if (cache && Date.now() - cache.ts < TTL_MS) return cache.models
|
|
||||||
|
|
||||||
try {
|
|
||||||
const models = toModelMap(idsFromPayload(await fetchJson(CATALOG_URL, {}, 5000)), false)
|
|
||||||
if (Object.keys(models).length) {
|
|
||||||
saveCache(models)
|
|
||||||
return models
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const key = process.env.ARKA_API_KEY
|
|
||||||
const json = await fetchJson(
|
|
||||||
`${API_URL}/models`,
|
|
||||||
key ? { Authorization: `Bearer ${key}` } : {},
|
|
||||||
5000,
|
|
||||||
)
|
|
||||||
const models = toModelMap(idsFromPayload(json), true)
|
|
||||||
saveCache(models)
|
|
||||||
return models
|
|
||||||
} catch {
|
|
||||||
return cache?.models ?? null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function ArkaAIProviderPlugin() {
|
|
||||||
const models = await resolveModels()
|
|
||||||
|
|
||||||
return {
|
|
||||||
config: (cfg: any) => {
|
|
||||||
cfg.provider ??= {}
|
|
||||||
const prev = cfg.provider[PROVIDER_ID] ?? {}
|
|
||||||
|
|
||||||
// Static models from opencode.json act as a fallback; the fetched
|
|
||||||
// catalog is layered on top so the configured default model never
|
|
||||||
// disappears even if the catalog drops it.
|
|
||||||
cfg.provider[PROVIDER_ID] = {
|
|
||||||
name: "Arka-AI",
|
|
||||||
api: API_URL,
|
|
||||||
env: ["ARKA_API_KEY"],
|
|
||||||
...prev,
|
|
||||||
models: { ...(prev.models ?? {}), ...(models ?? {}) },
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PLUGIN
|
|
||||||
|
|
||||||
echo "==> writing companion skill to $SKILL_DIR/SKILL.md"
|
|
||||||
cat > "$SKILL_DIR/SKILL.md" <<'SKILL'
|
|
||||||
---
|
|
||||||
name: arka-provider
|
|
||||||
description: Manage the custom Arka-AI (arka-ai.ru) LLM provider in opencode config. Use when the user needs to add, refresh, or fix the arka-ai.ru provider, sync its model catalog, change the Arka-AI default model, or when arka models are missing from /models.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Arka-AI provider
|
|
||||||
|
|
||||||
arka-ai.ru is **not** in models.dev, so opencode ships no built-in config for it.
|
|
||||||
It is wired up locally with a plugin + a static `provider` block in
|
|
||||||
`~/.config/opencode/opencode.json`.
|
|
||||||
|
|
||||||
## How it works
|
|
||||||
|
|
||||||
- Plugin: `~/.config/opencode/plugins/arka-ai.ts`
|
|
||||||
- On startup it loads the published chat catalog
|
|
||||||
`https://git.arka-ai.ru/arka/public/raw/branch/main/catalog.json`
|
|
||||||
(no API key; refreshed daily at 05:00 Europe/Moscow).
|
|
||||||
- If that fetch fails, it falls back to `https://api.arka-ai.ru/v1/models`
|
|
||||||
and filters out non-chat models (TTS/ASR/image/video/OCR/embedding/reranker/…)
|
|
||||||
via a heuristic regex so the `/models` picker stays clean.
|
|
||||||
- Caches the result at `~/.cache/opencode/arka-ai-models.json` (TTL 24h).
|
|
||||||
- Injects the provider into config via the `config` hook, layered over the
|
|
||||||
static block in `opencode.json`.
|
|
||||||
- Static block (`opencode.json` → `provider["Arka-AI"]`) holds
|
|
||||||
`name`, `api`, `env`, and a small fallback `models` map that is used when the
|
|
||||||
catalog is unreachable.
|
|
||||||
- Inference still uses `ARKA_API_KEY` against `https://api.arka-ai.ru/v1`.
|
|
||||||
|
|
||||||
## Refresh the model list
|
|
||||||
|
|
||||||
Catalog ~216 raw models / ~116 chat; the public snapshot updates at 05:00
|
|
||||||
Europe/Moscow. Changes land in opencode only after cache expiry + restart
|
|
||||||
(config is not hot-reloaded).
|
|
||||||
|
|
||||||
1. `rm -f ~/.cache/opencode/arka-ai-models.json`
|
|
||||||
2. Restart opencode (or run `opencode` again) — the plugin re-fetches on the
|
|
||||||
next cold start.
|
|
||||||
|
|
||||||
Optionally inspect the live API (no key required for listing):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -sS https://api.arka-ai.ru/v1/models | python3 -m json.tool
|
|
||||||
curl -sS https://git.arka-ai.ru/arka/public/raw/branch/main/catalog.json | python3 -m json.tool
|
|
||||||
```
|
|
||||||
|
|
||||||
## Change default model
|
|
||||||
|
|
||||||
Edit `opencode.json` → `"model": "Arka-AI/<model-id>"`. Use the IDs exactly as
|
|
||||||
returned by the catalog (e.g. `Arka-AI/DeepSeek-V4-Pro`,
|
|
||||||
`Arka-AI/qwen3.8-max`, `Arka-AI/kimi-k3`).
|
|
||||||
|
|
||||||
## Adjust filtering
|
|
||||||
|
|
||||||
Edit `NON_CHAT` (regex) or `FORCED_MODELS` in the plugin file **and** the same
|
|
||||||
lists in `scripts/sync-catalog.py` in `arka/public`, then re-run the installer
|
|
||||||
and restart. `FORCED_MODELS` keeps must-have chat models even if the heuristic
|
|
||||||
would drop them.
|
|
||||||
SKILL
|
|
||||||
|
|
||||||
# --- API key -----------------------------------------------------------
|
|
||||||
|
|
||||||
if [ -z "$KEY" ]; then
|
|
||||||
read -rsp "ARKA_API_KEY (leave blank to skip): " KEY || true
|
|
||||||
echo
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$KEY" ]; then
|
|
||||||
RC=""
|
|
||||||
for f in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do
|
|
||||||
if [ -f "$f" ]; then RC="$f"; break; fi
|
|
||||||
done
|
|
||||||
RC="${RC:-$HOME/.bashrc}"
|
|
||||||
touch "$RC"
|
|
||||||
if grep -q 'ARKA_API_KEY' "$RC"; then
|
|
||||||
echo "==> ARKA_API_KEY already present in $RC (skipped)"
|
|
||||||
else
|
|
||||||
printf '\n# opencode Arka-AI provider\nexport ARKA_API_KEY="%s"\n' "$KEY" >> "$RC"
|
|
||||||
echo "==> wrote ARKA_API_KEY to $RC"
|
|
||||||
fi
|
|
||||||
export ARKA_API_KEY="$KEY"
|
|
||||||
else
|
|
||||||
echo "==> warning: no ARKA_API_KEY set; export it in your shell before starting opencode" >&2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- default model (optional) ------------------------------------------
|
|
||||||
|
|
||||||
if [ -n "$MODEL" ]; then
|
|
||||||
CONF="$CONF_DIR/opencode.json"
|
|
||||||
echo "==> setting default model Arka-AI/$MODEL in $CONF"
|
|
||||||
python3 - "$CONF" "Arka-AI/$MODEL" <<'PY'
|
|
||||||
import json, os, sys
|
|
||||||
|
|
||||||
path, model = sys.argv[1], sys.argv[2]
|
|
||||||
data = {}
|
|
||||||
if os.path.exists(path):
|
|
||||||
with open(path) as f:
|
|
||||||
data = json.load(f)
|
|
||||||
data.setdefault("$schema", "https://opencode.ai/config.json")
|
|
||||||
data["model"] = model
|
|
||||||
with open(path, "w") as f:
|
|
||||||
json.dump(data, f, indent=2)
|
|
||||||
f.write("\n")
|
|
||||||
PY
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "Done."
|
|
||||||
echo " - provider plugin: $PLUGIN_DIR/arka-ai.ts"
|
|
||||||
echo " - skill: $SKILL_DIR/SKILL.md"
|
|
||||||
echo
|
|
||||||
echo "Next steps:"
|
|
||||||
echo " 1. Restart opencode (open a new shell so ARKA_API_KEY is exported)."
|
|
||||||
echo " 2. Run /models — the Arka-AI catalog (~116 chat models) loads on first start."
|
|
||||||
echo " 3. Optional: refresh catalog later with: rm -f ~/.cache/opencode/arka-ai-models.json"
|
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
# OpenCode и модели Arka AI
|
||||||
|
|
||||||
|
Arka AI — OpenAI-совместимый API (`https://api.arka-ai.ru/v1`). В каталоге models.dev его нет, поэтому в свежем OpenCode провайдера **Arka-AI** и списка моделей нет, пока не поставить плагин.
|
||||||
|
|
||||||
|
Одна команда ставит плагин, skill и (по желанию) ключ. Каталог чат-моделей подтягивается сам (~116 из ~216; без TTS/картинок/видео/эмбеддингов). Снимок каталога обновляется каждый день в **05:00 по Москве**.
|
||||||
|
|
||||||
|
Нужен [API-ключ Arka AI](https://arka-ai.ru/docs/account/api-keys). Секрет ключа показывается **один раз** при создании.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Быстрый старт
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/install-arka-provider.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт спросит `ARKA_API_KEY` (можно оставить пустым и задать позже).
|
||||||
|
|
||||||
|
Ключ и модель по умолчанию сразу:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/install-arka-provider.sh \
|
||||||
|
| bash -s -- --key "$ARKA_API_KEY" --set-default DeepSeek-V4-Pro
|
||||||
|
```
|
||||||
|
|
||||||
|
Дальше:
|
||||||
|
|
||||||
|
1. Откройте **новый терминал** (чтобы подхватился `export ARKA_API_KEY`).
|
||||||
|
2. Перезапустите OpenCode.
|
||||||
|
3. Команда `/models` — в списке должен быть провайдер **Arka-AI** и чат-модели.
|
||||||
|
|
||||||
|
Если OpenCode запущен из GUI / иконки / сокета, а не из login-shell, переменная из `~/.bashrc` до него может не дойти. Задайте `ARKA_API_KEY` в том окружении, откуда стартует OpenCode, или введите ключ в интерактивном приглашении установщика.
|
||||||
|
|
||||||
|
### Из уже открытого OpenCode
|
||||||
|
|
||||||
|
Отдельного магазина плагинов у OpenCode нет. Можно не выходить из CLI: попросите **текущую** модель (любую, не Arka) выполнить установщик через bash.
|
||||||
|
|
||||||
|
Вставьте в чат (подставьте свой ключ):
|
||||||
|
|
||||||
|
```
|
||||||
|
Поставь провайдер Arka AI для OpenCode. Не спрашивай ключ интерактивно.
|
||||||
|
Выполни одну команду и покажи вывод:
|
||||||
|
|
||||||
|
curl -fsSL https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/install-arka-provider.sh | bash -s -- --key 'ARKA_API_KEY_HERE' --set-default DeepSeek-V4-Pro
|
||||||
|
|
||||||
|
После успеха напиши, что нужно полностью перезапустить OpenCode (конфиг не подхватывается на лету).
|
||||||
|
```
|
||||||
|
|
||||||
|
Что происходит:
|
||||||
|
|
||||||
|
1. Агент вызывает встроенный bash/shell.
|
||||||
|
2. OpenCode спросит разрешение на команду — нажмите Allow.
|
||||||
|
3. Скрипт пишет плагин, skill и ключ в `~/.config/opencode` (установщик сам по себе офлайн; `curl` только качает скрипт).
|
||||||
|
4. `--key` обязателен: приглашение «введите ключ» в неинтерактивном пайпе зависнет.
|
||||||
|
5. **Перезапустите OpenCode целиком** — плагин грузится только на старте. Затем `/models` → **Arka-AI**.
|
||||||
|
|
||||||
|
Пока сессия не перезапущена, разговор идёт на старой модели. Это нормально: установщик не требует Arka.
|
||||||
|
|
||||||
|
### Что ставится
|
||||||
|
|
||||||
|
| Куда | Что |
|
||||||
|
|------|-----|
|
||||||
|
| `~/.config/opencode/plugins/arka-ai.ts` | Плагин провайдера **Arka-AI** |
|
||||||
|
| `~/.config/opencode/skills/arka-provider/SKILL.md` | Skill: как обновить каталог и сменить модель |
|
||||||
|
| `~/.bashrc` (или первый из `.zshrc` / `.profile`) | `export ARKA_API_KEY=…`, если ключ передали |
|
||||||
|
| `~/.config/opencode/opencode.json` | Только при `--set-default`: `"model": "Arka-AI/<id>"`. Существующий конфиг **не затирается**. |
|
||||||
|
|
||||||
|
Установщик **офлайн** и идемпотентный: сеть не трогает, повторный запуск перезаписывает плагин и skill каноническими копиями.
|
||||||
|
|
||||||
|
`python3` нужен только с `--set-default`. Отдельный `bun` на машине не нужен — у OpenCode свой runtime.
|
||||||
|
|
||||||
|
### Модель по умолчанию
|
||||||
|
|
||||||
|
Идентификаторы — как в каталоге, с префиксом провайдера:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "Arka-AI/DeepSeek-V4-Pro"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Примеры: `Arka-AI/DeepSeek-V4-Pro`, `Arka-AI/qwen3.8-max`, `Arka-AI/kimi-k3`, `Arka-AI/MiniMax-M3`, `Arka-AI/GLM-5.3`, `Arka-AI/deepseek-v4-flash-0731`.
|
||||||
|
|
||||||
|
Их всегда оставляем в списке, даже если фильтр чат-моделей их бы отсёк.
|
||||||
|
|
||||||
|
### Обновить каталог
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm -f ~/.cache/opencode/arka-ai-models.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Перезапустите OpenCode. Плагин сначала берёт публичный снимок
|
||||||
|
`https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/catalog.json`
|
||||||
|
(без ключа), при ошибке — живой `https://api.arka-ai.ru/v1/models`. Локальный кэш живёт 24 часа. Конфиг OpenCode на лету не перечитывается.
|
||||||
|
|
||||||
|
Список моделей (ключ не нужен):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/catalog.json | python3 -m json.tool
|
||||||
|
```
|
||||||
|
|
||||||
|
Вызовы моделей идут на `https://api.arka-ai.ru/v1` с `ARKA_API_KEY`.
|
||||||
|
|
||||||
|
### Если что-то не так
|
||||||
|
|
||||||
|
| Симптом | Что проверить |
|
||||||
|
|---------|----------------|
|
||||||
|
| После `/models` нет Arka-AI | Есть ли `~/.config/opencode/plugins/arka-ai.ts`; экспортирован ли `ARKA_API_KEY` **в процессе OpenCode**; открыт ли новый шелл |
|
||||||
|
| Каталог не меняется | `rm -f ~/.cache/opencode/arka-ai-models.json` и рестарт |
|
||||||
|
| Ошибка импорта плагина | Снова запустить установщик — он вернёт канонический файл |
|
||||||
|
| `curl` отдаёт HTML, не скрипт | Нужен путь `/raw/branch/main/…`, не страница репозитория |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# OpenCode and Arka AI models
|
||||||
|
|
||||||
|
Arka AI is an OpenAI-compatible API at `https://api.arka-ai.ru/v1`. It is not in models.dev, so a fresh OpenCode install has no **Arka-AI** provider until you add the plugin.
|
||||||
|
|
||||||
|
One command installs the plugin, a companion skill, and (optionally) your API key. The chat catalog loads on OpenCode start (~116 of ~216 models; TTS, image, video, and embeddings are filtered out). The published catalog snapshot refreshes daily at **05:00 Europe/Moscow**.
|
||||||
|
|
||||||
|
You need an [Arka AI API key](https://arka-ai.ru/docs/account/api-keys). The secret is shown **once** at creation.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/install-arka-provider.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
The script prompts for `ARKA_API_KEY` (blank is allowed; set it later).
|
||||||
|
|
||||||
|
Key and default model in one go:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/install-arka-provider.sh \
|
||||||
|
| bash -s -- --key "$ARKA_API_KEY" --set-default DeepSeek-V4-Pro
|
||||||
|
```
|
||||||
|
|
||||||
|
Then:
|
||||||
|
|
||||||
|
1. Open a **new shell** so `export ARKA_API_KEY` is picked up.
|
||||||
|
2. Restart OpenCode.
|
||||||
|
3. Run `/models` — you should see provider **Arka-AI** and the chat models.
|
||||||
|
|
||||||
|
If OpenCode is started from a GUI, dock icon, or socket rather than a login shell, `~/.bashrc` may not reach it. Set `ARKA_API_KEY` in that process environment, or type it at the installer prompt.
|
||||||
|
|
||||||
|
### From inside OpenCode
|
||||||
|
|
||||||
|
OpenCode has no plugin store. You do not have to leave the CLI: ask the **current** model (any provider, not Arka) to run the installer via bash.
|
||||||
|
|
||||||
|
Paste this (put your real key in):
|
||||||
|
|
||||||
|
```
|
||||||
|
Install the Arka AI OpenCode provider. Do not prompt for the key.
|
||||||
|
Run this exact command and show the output:
|
||||||
|
|
||||||
|
curl -fsSL https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/install-arka-provider.sh | bash -s -- --key 'ARKA_API_KEY_HERE' --set-default DeepSeek-V4-Pro
|
||||||
|
|
||||||
|
When it succeeds, tell me I must fully restart OpenCode (config is not hot-reloaded).
|
||||||
|
```
|
||||||
|
|
||||||
|
What happens:
|
||||||
|
|
||||||
|
1. The agent uses the built-in bash/shell tool.
|
||||||
|
2. OpenCode asks permission — Allow the command.
|
||||||
|
3. The script writes the plugin, skill, and key under `~/.config/opencode` (the installer itself is offline; `curl` only fetches the script).
|
||||||
|
4. `--key` is required: the interactive key prompt hangs in a non-interactive pipe.
|
||||||
|
5. **Fully restart OpenCode** — plugins load only at startup. Then `/models` → **Arka-AI**.
|
||||||
|
|
||||||
|
Until you restart, this session keeps the old model. That is expected: the installer does not need Arka.
|
||||||
|
|
||||||
|
### What the installer writes
|
||||||
|
|
||||||
|
| Path | Role |
|
||||||
|
|------|------|
|
||||||
|
| `~/.config/opencode/plugins/arka-ai.ts` | **Arka-AI** provider plugin |
|
||||||
|
| `~/.config/opencode/skills/arka-provider/SKILL.md` | How to refresh the catalog and change the default model |
|
||||||
|
| `~/.bashrc` (else first existing `.zshrc` / `.profile`) | `export ARKA_API_KEY=…` when a key is given |
|
||||||
|
| `~/.config/opencode/opencode.json` | Only with `--set-default`: `"model": "Arka-AI/<id>"`. An existing config is **not** replaced. |
|
||||||
|
|
||||||
|
The installer is **offline** and idempotent: no network calls; re-running overwrites plugin and skill with the canonical copies.
|
||||||
|
|
||||||
|
`python3` is required **only** with `--set-default`. You do not need `bun` on the host — OpenCode ships its own runtime.
|
||||||
|
|
||||||
|
### Default model
|
||||||
|
|
||||||
|
Use catalog ids with the provider prefix:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "Arka-AI/DeepSeek-V4-Pro"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples: `Arka-AI/DeepSeek-V4-Pro`, `Arka-AI/qwen3.8-max`, `Arka-AI/kimi-k3`, `Arka-AI/MiniMax-M3`, `Arka-AI/GLM-5.3`, `Arka-AI/deepseek-v4-flash-0731`.
|
||||||
|
|
||||||
|
Those ids stay visible even if the chat filter would drop them.
|
||||||
|
|
||||||
|
### Refresh the catalog
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm -f ~/.cache/opencode/arka-ai-models.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart OpenCode. The plugin fetches the public snapshot
|
||||||
|
`https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/catalog.json`
|
||||||
|
(no key), then falls back to live `https://api.arka-ai.ru/v1/models`. Local cache TTL is 24 hours. OpenCode does not hot-reload config.
|
||||||
|
|
||||||
|
Inspect the published list (no key):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/catalog.json | python3 -m json.tool
|
||||||
|
```
|
||||||
|
|
||||||
|
Inference still uses `ARKA_API_KEY` against `https://api.arka-ai.ru/v1`.
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
| Symptom | Check |
|
||||||
|
|---------|--------|
|
||||||
|
| No Arka-AI after `/models` | `~/.config/opencode/plugins/arka-ai.ts` exists; `ARKA_API_KEY` is set **in the OpenCode process**; you opened a new shell |
|
||||||
|
| Models look stale | `rm -f ~/.cache/opencode/arka-ai-models.json` and restart |
|
||||||
|
| Plugin import error | Re-run the installer to restore the canonical file |
|
||||||
|
| `curl` returns HTML | Use `/raw/branch/main/…`, not the repo page |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files in this folder
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|------|------|
|
||||||
|
| `install-arka-provider.sh` | Self-contained installer (the one-liner) |
|
||||||
|
| `plugins/arka-ai.ts` | Plugin source (embedded in the installer) |
|
||||||
|
| `skills/arka-provider/SKILL.md` | Skill source (embedded in the installer) |
|
||||||
|
| `catalog.json` | Filtered chat catalog, refreshed daily at 05:00 Europe/Moscow |
|
||||||
|
| `README.md` | This page — copy into arka-ai.ru «агентные помощники» / agentic helpers |
|
||||||
|
|
||||||
|
This directory is `opencode/` inside the public dump [arka/public](https://git.arka-ai.ru/arka/public).
|
||||||
File renamed without changes.
Executable
+304
@@ -0,0 +1,304 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# install-arka-provider.sh — one-shot installer for the arka-ai.ru LLM provider
|
||||||
|
# in a fresh opencode install.
|
||||||
|
#
|
||||||
|
# It is fully self-contained: it embeds the provider plugin and the companion
|
||||||
|
# skill, writes them into ~/.config/opencode, and persists ARKA_API_KEY.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# bash install-arka-provider.sh # prompt for API key
|
||||||
|
# ARKA_API_KEY=xxx bash install-arka-provider.sh # key from env (no prompt)
|
||||||
|
# bash install-arka-provider.sh --key xxx --set-default DeepSeek-V4-Pro
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# --key KEY ARKA_API_KEY to persist (default: $ARKA_API_KEY, else prompt)
|
||||||
|
# --set-default MODEL also set "model": "Arka-AI/<MODEL>" in opencode.json
|
||||||
|
# -h, --help show this help
|
||||||
|
#
|
||||||
|
# Idempotent: safe to run multiple times. Overwrites plugin/skill with the
|
||||||
|
# embedded versions; never clobbers an existing opencode.json (only upserts the
|
||||||
|
# requested default model). Makes no network calls.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
sed -n '2,21p' "$0"
|
||||||
|
}
|
||||||
|
|
||||||
|
KEY=""
|
||||||
|
MODEL=""
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--key) KEY="$2"; shift 2 ;;
|
||||||
|
--key=*) KEY="${1#*=}"; shift ;;
|
||||||
|
--set-default) MODEL="$2"; shift 2 ;;
|
||||||
|
--set-default=*) MODEL="${1#*=}"; shift ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) printf 'unknown option: %s\n' "$1" >&2; usage; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
CONF_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/opencode"
|
||||||
|
PLUGIN_DIR="$CONF_DIR/plugins"
|
||||||
|
SKILL_DIR="$CONF_DIR/skills/arka-provider"
|
||||||
|
|
||||||
|
mkdir -p "$PLUGIN_DIR" "$SKILL_DIR" "$HOME"
|
||||||
|
|
||||||
|
echo "==> writing provider plugin to $PLUGIN_DIR/arka-ai.ts"
|
||||||
|
cat > "$PLUGIN_DIR/arka-ai.ts" <<'PLUGIN'
|
||||||
|
import { homedir } from "node:os"
|
||||||
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
||||||
|
import { join } from "node:path"
|
||||||
|
|
||||||
|
const PROVIDER_ID = "Arka-AI"
|
||||||
|
const API_URL = "https://api.arka-ai.ru/v1"
|
||||||
|
const CATALOG_URL =
|
||||||
|
"https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/catalog.json"
|
||||||
|
const CACHE_FILE = join(homedir(), ".cache", "opencode", "arka-ai-models.json")
|
||||||
|
const TTL_MS = 24 * 60 * 60 * 1000
|
||||||
|
|
||||||
|
// Ids that must always be exposed, even if the filter would drop them.
|
||||||
|
const FORCED_MODELS = [
|
||||||
|
"DeepSeek-V4-Pro",
|
||||||
|
"deepseek-v4-flash-0731",
|
||||||
|
"qwen3.8-max",
|
||||||
|
"kimi-k3",
|
||||||
|
"MiniMax-M3",
|
||||||
|
"GLM-5.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
// Heuristic: drop non-chat models (TTS/ASR/image/video/OCR/embeddings/rerankers…)
|
||||||
|
// from the catalog so they don't clutter the /models picker.
|
||||||
|
// Keep in sync with scripts/sync-catalog.py
|
||||||
|
const NON_CHAT = new RegExp(
|
||||||
|
[
|
||||||
|
"tts", "asr", "audio", "speech", "voice", "whisper", "sensevoice",
|
||||||
|
"image", "(^|[.-])img", "wan", "vidu", "video", "sora", "flux",
|
||||||
|
"diffus", "kolors", "cogview", "ocr", "embedding", "(^|[.-])embed",
|
||||||
|
"rerank", "clip", "moderation", "(^|[.-])filter", "detect", "classif",
|
||||||
|
"(^|[.-])location", "(^|[.-])search", "comfyui", "meme", "hello",
|
||||||
|
"(^|[.-])sam", "florence", "esrgan", "rmbg", "background", "poster",
|
||||||
|
"(^|[.-])face", "(^|[.-])lip", "translate", "(^|[.-])mt([2-]|$)",
|
||||||
|
"(^|[.-])bge", "(^|[.-])e5", "(^|[.-])jina", "(^|[.-])mpnet",
|
||||||
|
"spark-tts", "cosyvoice", "chattts", "indextts", "step-audio",
|
||||||
|
"fish-speech", "cosy", "mate-gguf", "tokenizer",
|
||||||
|
].join("|"),
|
||||||
|
"i",
|
||||||
|
)
|
||||||
|
|
||||||
|
type ModelMap = Record<string, { name: string }>
|
||||||
|
type Cache = { ts: number; models: ModelMap }
|
||||||
|
|
||||||
|
function loadCache(): Cache | null {
|
||||||
|
try {
|
||||||
|
if (existsSync(CACHE_FILE)) return JSON.parse(readFileSync(CACHE_FILE, "utf8"))
|
||||||
|
} catch {}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCache(models: ModelMap): void {
|
||||||
|
try {
|
||||||
|
mkdirSync(join(homedir(), ".cache", "opencode"), { recursive: true })
|
||||||
|
writeFileSync(CACHE_FILE, JSON.stringify({ ts: Date.now(), models }))
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchJson(
|
||||||
|
url: string,
|
||||||
|
headers: Record<string, string>,
|
||||||
|
timeoutMs: number,
|
||||||
|
): Promise<unknown> {
|
||||||
|
const ctrl = new AbortController()
|
||||||
|
const timer = setTimeout(() => ctrl.abort(), timeoutMs)
|
||||||
|
try {
|
||||||
|
const res = await fetch(url, { headers, signal: ctrl.signal })
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||||
|
return await res.json()
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function idsFromPayload(json: unknown): string[] {
|
||||||
|
const data = (json as { data?: Array<{ id: string }> }).data
|
||||||
|
return (data ?? []).map((m) => m.id).filter(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toModelMap(ids: string[], filter: boolean): ModelMap {
|
||||||
|
const models: ModelMap = {}
|
||||||
|
for (const id of ids) {
|
||||||
|
if (!filter || FORCED_MODELS.includes(id) || !NON_CHAT.test(id)) {
|
||||||
|
models[id] = { name: id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveModels(): Promise<ModelMap | null> {
|
||||||
|
const cache = loadCache()
|
||||||
|
if (cache && Date.now() - cache.ts < TTL_MS) return cache.models
|
||||||
|
|
||||||
|
try {
|
||||||
|
const models = toModelMap(idsFromPayload(await fetchJson(CATALOG_URL, {}, 5000)), false)
|
||||||
|
if (Object.keys(models).length) {
|
||||||
|
saveCache(models)
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const key = process.env.ARKA_API_KEY
|
||||||
|
const json = await fetchJson(
|
||||||
|
`${API_URL}/models`,
|
||||||
|
key ? { Authorization: `Bearer ${key}` } : {},
|
||||||
|
5000,
|
||||||
|
)
|
||||||
|
const models = toModelMap(idsFromPayload(json), true)
|
||||||
|
saveCache(models)
|
||||||
|
return models
|
||||||
|
} catch {
|
||||||
|
return cache?.models ?? null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function ArkaAIProviderPlugin() {
|
||||||
|
const models = await resolveModels()
|
||||||
|
|
||||||
|
return {
|
||||||
|
config: (cfg: any) => {
|
||||||
|
cfg.provider ??= {}
|
||||||
|
const prev = cfg.provider[PROVIDER_ID] ?? {}
|
||||||
|
|
||||||
|
// Static models from opencode.json act as a fallback; the fetched
|
||||||
|
// catalog is layered on top so the configured default model never
|
||||||
|
// disappears even if the catalog drops it.
|
||||||
|
cfg.provider[PROVIDER_ID] = {
|
||||||
|
name: "Arka-AI",
|
||||||
|
api: API_URL,
|
||||||
|
env: ["ARKA_API_KEY"],
|
||||||
|
...prev,
|
||||||
|
models: { ...(prev.models ?? {}), ...(models ?? {}) },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PLUGIN
|
||||||
|
|
||||||
|
echo "==> writing companion skill to $SKILL_DIR/SKILL.md"
|
||||||
|
cat > "$SKILL_DIR/SKILL.md" <<'SKILL'
|
||||||
|
---
|
||||||
|
name: arka-provider
|
||||||
|
description: Manage the custom Arka-AI (arka-ai.ru) LLM provider in opencode config. Use when the user needs to add, refresh, or fix the arka-ai.ru provider, sync its model catalog, change the Arka-AI default model, or when arka models are missing from /models.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Arka-AI provider
|
||||||
|
|
||||||
|
arka-ai.ru is **not** in models.dev, so opencode ships no built-in config for it.
|
||||||
|
It is wired up locally with a plugin + a static `provider` block in
|
||||||
|
`~/.config/opencode/opencode.json`.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
- Plugin: `~/.config/opencode/plugins/arka-ai.ts`
|
||||||
|
- On startup it loads the published chat catalog
|
||||||
|
`https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/catalog.json`
|
||||||
|
(no API key; refreshed daily at 05:00 Europe/Moscow).
|
||||||
|
- If that fetch fails, it falls back to `https://api.arka-ai.ru/v1/models`
|
||||||
|
and filters out non-chat models (TTS/ASR/image/video/OCR/embedding/reranker/…)
|
||||||
|
via a heuristic regex so the `/models` picker stays clean.
|
||||||
|
- Caches the result at `~/.cache/opencode/arka-ai-models.json` (TTL 24h).
|
||||||
|
- Injects the provider into config via the `config` hook, layered over the
|
||||||
|
static block in `opencode.json`.
|
||||||
|
- Static block (`opencode.json` → `provider["Arka-AI"]`) holds
|
||||||
|
`name`, `api`, `env`, and a small fallback `models` map that is used when the
|
||||||
|
catalog is unreachable.
|
||||||
|
- Inference still uses `ARKA_API_KEY` against `https://api.arka-ai.ru/v1`.
|
||||||
|
|
||||||
|
## Refresh the model list
|
||||||
|
|
||||||
|
Catalog ~216 raw models / ~116 chat; the public snapshot updates at 05:00
|
||||||
|
Europe/Moscow. Changes land in opencode only after cache expiry + restart
|
||||||
|
(config is not hot-reloaded).
|
||||||
|
|
||||||
|
1. `rm -f ~/.cache/opencode/arka-ai-models.json`
|
||||||
|
2. Restart opencode (or run `opencode` again) — the plugin re-fetches on the
|
||||||
|
next cold start.
|
||||||
|
|
||||||
|
Optionally inspect the live API (no key required for listing):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS https://api.arka-ai.ru/v1/models | python3 -m json.tool
|
||||||
|
curl -sS https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/catalog.json | python3 -m json.tool
|
||||||
|
```
|
||||||
|
|
||||||
|
## Change default model
|
||||||
|
|
||||||
|
Edit `opencode.json` → `"model": "Arka-AI/<model-id>"`. Use the IDs exactly as
|
||||||
|
returned by the catalog (e.g. `Arka-AI/DeepSeek-V4-Pro`,
|
||||||
|
`Arka-AI/qwen3.8-max`, `Arka-AI/kimi-k3`).
|
||||||
|
|
||||||
|
## Adjust filtering
|
||||||
|
|
||||||
|
Edit `NON_CHAT` (regex) or `FORCED_MODELS` in the plugin file **and** the same
|
||||||
|
lists in `scripts/sync-catalog.py` in `arka/public`, then re-run the installer
|
||||||
|
and restart. `FORCED_MODELS` keeps must-have chat models even if the heuristic
|
||||||
|
would drop them.
|
||||||
|
SKILL
|
||||||
|
|
||||||
|
# --- API key -----------------------------------------------------------
|
||||||
|
|
||||||
|
if [ -z "$KEY" ]; then
|
||||||
|
read -rsp "ARKA_API_KEY (leave blank to skip): " KEY || true
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$KEY" ]; then
|
||||||
|
RC=""
|
||||||
|
for f in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do
|
||||||
|
if [ -f "$f" ]; then RC="$f"; break; fi
|
||||||
|
done
|
||||||
|
RC="${RC:-$HOME/.bashrc}"
|
||||||
|
touch "$RC"
|
||||||
|
if grep -q 'ARKA_API_KEY' "$RC"; then
|
||||||
|
echo "==> ARKA_API_KEY already present in $RC (skipped)"
|
||||||
|
else
|
||||||
|
printf '\n# opencode Arka-AI provider\nexport ARKA_API_KEY="%s"\n' "$KEY" >> "$RC"
|
||||||
|
echo "==> wrote ARKA_API_KEY to $RC"
|
||||||
|
fi
|
||||||
|
export ARKA_API_KEY="$KEY"
|
||||||
|
else
|
||||||
|
echo "==> warning: no ARKA_API_KEY set; export it in your shell before starting opencode" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- default model (optional) ------------------------------------------
|
||||||
|
|
||||||
|
if [ -n "$MODEL" ]; then
|
||||||
|
CONF="$CONF_DIR/opencode.json"
|
||||||
|
echo "==> setting default model Arka-AI/$MODEL in $CONF"
|
||||||
|
python3 - "$CONF" "Arka-AI/$MODEL" <<'PY'
|
||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
path, model = sys.argv[1], sys.argv[2]
|
||||||
|
data = {}
|
||||||
|
if os.path.exists(path):
|
||||||
|
with open(path) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
data.setdefault("$schema", "https://opencode.ai/config.json")
|
||||||
|
data["model"] = model
|
||||||
|
with open(path, "w") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
f.write("\n")
|
||||||
|
PY
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Done."
|
||||||
|
echo " - provider plugin: $PLUGIN_DIR/arka-ai.ts"
|
||||||
|
echo " - skill: $SKILL_DIR/SKILL.md"
|
||||||
|
echo
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Restart opencode (open a new shell so ARKA_API_KEY is exported)."
|
||||||
|
echo " 2. Run /models — the Arka-AI catalog (~116 chat models) loads on first start."
|
||||||
|
echo " 3. Optional: refresh catalog later with: rm -f ~/.cache/opencode/arka-ai-models.json"
|
||||||
@@ -5,7 +5,7 @@ import { join } from "node:path"
|
|||||||
const PROVIDER_ID = "Arka-AI"
|
const PROVIDER_ID = "Arka-AI"
|
||||||
const API_URL = "https://api.arka-ai.ru/v1"
|
const API_URL = "https://api.arka-ai.ru/v1"
|
||||||
const CATALOG_URL =
|
const CATALOG_URL =
|
||||||
"https://git.arka-ai.ru/arka/public/raw/branch/main/catalog.json"
|
"https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/catalog.json"
|
||||||
const CACHE_FILE = join(homedir(), ".cache", "opencode", "arka-ai-models.json")
|
const CACHE_FILE = join(homedir(), ".cache", "opencode", "arka-ai-models.json")
|
||||||
const TTL_MS = 24 * 60 * 60 * 1000
|
const TTL_MS = 24 * 60 * 60 * 1000
|
||||||
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Daily catalog refresh for arka/public. Runs on core at 05:00 Europe/Moscow.
|
# Daily catalog refresh for arka/public/opencode.
|
||||||
|
# Runs on core at 05:00 Europe/Moscow.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
# this file lives at opencode/scripts/cron-sync.sh → repo root is ../..
|
||||||
cd "$ROOT"
|
REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||||
|
cd "$REPO"
|
||||||
|
|
||||||
export GIT_SSH_COMMAND="${GIT_SSH_COMMAND:-ssh -i /root/.ssh/arka_public_catalog -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new}"
|
export GIT_SSH_COMMAND="${GIT_SSH_COMMAND:-ssh -i /root/.ssh/arka_public_catalog -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new}"
|
||||||
|
|
||||||
@@ -11,14 +13,14 @@ git fetch origin main
|
|||||||
git checkout -q main
|
git checkout -q main
|
||||||
git reset --hard origin/main
|
git reset --hard origin/main
|
||||||
|
|
||||||
python3 scripts/sync-catalog.py
|
python3 opencode/scripts/sync-catalog.py
|
||||||
|
|
||||||
if git diff --quiet -- catalog.json; then
|
if git diff --quiet -- opencode/catalog.json; then
|
||||||
printf '%s no catalog change\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
printf '%s no catalog change\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git add catalog.json
|
git add opencode/catalog.json
|
||||||
git -c user.name='arka-catalog-bot' -c user.email='noreply@arka-ai.ru' \
|
git -c user.name='arka-catalog-bot' -c user.email='noreply@arka-ai.ru' \
|
||||||
commit -m "chore(catalog): refresh $(date -u +%Y-%m-%d)"
|
commit -m "chore(catalog): refresh $(date -u +%Y-%m-%d)"
|
||||||
git push origin main
|
git push origin main
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Rebuild catalog.json from the public arka-ai.ru OpenAI-compatible models list.
|
"""Rebuild catalog.json from the public arka-ai.ru OpenAI-compatible models list.
|
||||||
|
|
||||||
Keep FORCED_MODELS / NON_CHAT in lockstep with plugins/arka-ai.ts.
|
Keep FORCED_MODELS / NON_CHAT in lockstep with opencode/plugins/arka-ai.ts.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ It is wired up locally with a plugin + a static `provider` block in
|
|||||||
|
|
||||||
- Plugin: `~/.config/opencode/plugins/arka-ai.ts`
|
- Plugin: `~/.config/opencode/plugins/arka-ai.ts`
|
||||||
- On startup it loads the published chat catalog
|
- On startup it loads the published chat catalog
|
||||||
`https://git.arka-ai.ru/arka/public/raw/branch/main/catalog.json`
|
`https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/catalog.json`
|
||||||
(no API key; refreshed daily at 05:00 Europe/Moscow).
|
(no API key; refreshed daily at 05:00 Europe/Moscow).
|
||||||
- If that fetch fails, it falls back to `https://api.arka-ai.ru/v1/models`
|
- If that fetch fails, it falls back to `https://api.arka-ai.ru/v1/models`
|
||||||
and filters out non-chat models (TTS/ASR/image/video/OCR/embedding/reranker/…)
|
and filters out non-chat models (TTS/ASR/image/video/OCR/embedding/reranker/…)
|
||||||
@@ -40,7 +40,7 @@ Optionally inspect the live API (no key required for listing):
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sS https://api.arka-ai.ru/v1/models | python3 -m json.tool
|
curl -sS https://api.arka-ai.ru/v1/models | python3 -m json.tool
|
||||||
curl -sS https://git.arka-ai.ru/arka/public/raw/branch/main/catalog.json | python3 -m json.tool
|
curl -sS https://git.arka-ai.ru/arka/public/raw/branch/main/opencode/catalog.json | python3 -m json.tool
|
||||||
```
|
```
|
||||||
|
|
||||||
## Change default model
|
## Change default model
|
||||||
Reference in new issue
Block a user