Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
- Codex CLI uses a user-level profile-v2 file, `https://codex.neuroapi.host/v1`, and WebSocket by default; the optional Codex Desktop setup uses the same host with HTTP/SSE. Publish installers only after authenticated catalog and transport checks.
- Keep unsupported hosted Codex tools (`web_search`, multi-agent namespace, goals, apps and browser use) disabled in this profile until the server can execute and bill them safely; local file and shell tools must remain available.
- Managed launchers fetch and validate fresh key-scoped catalogs into private per-launch snapshots; never fall back to stale/bundled lists or accept executable server settings. Claude Code's base URL is `https://claude.neuroapi.host`; Claude Desktop gateway is configured separately in its UI. Keep ordinary keys, preserve unrelated configuration, and document managed-policy/explicit-override boundaries.

- Claude client-settings v2 is opt-in via `X-NeuroAPI-Client-Settings-Version: 2`; accept only the three reviewed limit/hint env keys with canonical bounded decimal strings (or hint `1`), retain the 4096 output fallback for old servers. `--doctor` performs no generation; `--doctor-generate` explicitly opts into one bounded HTTP probe with no retries, secret-free output and no claim of WebSocket/tool compatibility.
11 changes: 10 additions & 1 deletion README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ This prevents accidental plaintext disclosure. It does not protect a key from ma
- For Codex Desktop, check a new local task, `https://codex.neuroapi.host/v1` in the user config, and the request in your NeuroAPI usage logs. Setup uses HTTP/SSE for this GUI integration.
- Check current model IDs and pricing at [neuroapi.host/price](https://neuroapi.host/price).

Each launcher fetches a fresh catalog scoped to the ordinary NeuroAPI key. Codex uses a private `model_catalog_json`; Claude receives a configured picker. There are no hardcoded default models. Tested minimum versions are Codex 0.158.0 and Claude Code 2.1.284. The Claude launcher limits initial output to 4096 tokens to bound quota reservation. Invalid, empty or unavailable catalogs stop launch instead of restoring stale lists. Organization policies and deliberate CLI overrides retain their documented precedence; these launchers do not support host-managed provider mode.
Each launcher fetches a fresh catalog scoped to the ordinary NeuroAPI key. Codex uses a private `model_catalog_json`; Claude receives a configured picker. There are no hardcoded default models. Tested minimum versions are Codex 0.158.0 and Claude Code 2.1.284. The Claude launcher requests v2 settings and accepts strictly validated context/output limits and gateway hint headers. Older servers retain the 4096-token output fallback; output limits are independent of financial reservation estimates. Invalid, empty or unavailable catalogs stop launch instead of restoring stale lists. Organization policies and deliberate CLI overrides retain their documented precedence; these launchers do not support host-managed provider mode.

Recommended server selection: GPT-6 Sol, Astra and Luna for Codex; **Opus 5.5** (`claude-opus-5-5`, preferred), Sonnet 5.5, Sonnet 5 and Fable 5.1 for Claude Code. An entry appears only when its published model, tariff and compatible upstream route are available to the key. Existing administrator catalog settings override source defaults.

Expand All @@ -76,3 +76,12 @@ Claude Code also uses the `haiku` alias for background work. If no recommended H
- [Codex Desktop guide](https://neuroapi.host/docs/codex-desktop)
- [Claude Desktop guide](https://neuroapi.host/docs/claude-desktop)
- [NeuroAPI documentation](https://neuroapi.host/docs/getting-started)

## Connection diagnostics

Run `codex-neuroapi --doctor` or `claude-neuroapi --doctor` to validate the client version
and protected key's access to the current model catalog. The key is not printed and no
paid generation occurs. `--doctor-generate` explicitly requests one small **billable** HTTP
generation and measures its duration, without retries. It validates a completed nonempty
answer; it does not prove WebSocket, client tools, GUI sessions or deliberate overrides.
Organization provider policies remain authoritative. Temporary snapshots are removed.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ Claude Code:
2. Выполните `/status`.
3. Проверьте base URL `https://claude.neuroapi.host` и credential source `apiKeyHelper`.

При каждом запуске launcher получает актуальный список для вашего обычного ключа NeuroAPI. Codex использует отдельный каталог, Claude Code — настроенное меню; фиксированных моделей в установщике нет. Проверенные минимумы: Codex 0.158.0 и Claude Code 2.1.284. Для Claude Code установщик задаёт начальный лимит вывода 4096 токенов, чтобы запросы с большим стандартным лимитом не резервировали избыточную квоту. При ошибке обновления или пустом списке запуск останавливается, не возвращаясь к старым моделям. Подробности и ограничения managed-политик: [ручная настройка](docs/manual-setup.md).
При каждом запуске launcher получает актуальный список для вашего обычного ключа NeuroAPI. Codex использует отдельный каталог, Claude Code — настроенное меню; фиксированных моделей в установщике нет. Проверенные минимумы: Codex 0.158.0 и Claude Code 2.1.284. Claude Code получает проверенные сервером пределы контекста и вывода и служебные диагностические заголовки. Если сервер ещё не передаёт предел вывода, сохраняется совместимый лимит 4096 токенов. Лимит ответа независим от оценки финансового резерва. При ошибке обновления или пустом списке запуск останавливается, не возвращаясь к старым моделям. Подробности и ограничения managed-политик: [ручная настройка](docs/manual-setup.md).

Рекомендуемый серверный набор: Codex — GPT-6 Sol, Astra и Luna; Claude Code — **Opus 5.5** (`claude-opus-5-5`, приоритетный), Sonnet 5.5, Sonnet 5 и Fable 5.1. Модель появляется в меню только после публикации на сервисе и появления совместимого маршрута для тарифа и ключа. Если администратор сохранил собственный список, он имеет приоритет над рекомендуемым набором.

Expand Down Expand Up @@ -165,3 +165,18 @@ GitHub Actions выполняет:
- поиск случайно добавленных секретов и небезопасных способов передачи ключа.

Реальный пользовательский ключ никогда не нужен CI.

## Проверить подключение

В терминале выполните `codex-neuroapi --doctor` или `claude-neuroapi --doctor`.
Диагностика проверяет совместимость версии, доступ защищённого ключа к свежему каталогу
и показывает адрес и модель по умолчанию. Генерация не выполняется и токены не оплачиваются.
Ключ остаётся скрыт; временные каталоги удаляются.

Для одного короткого **платного** API-запроса выполните `codex-neuroapi --doctor-generate`
или `claude-neuroapi --doctor-generate`. Этот режим проверяет законченный непустой ответ
по HTTP и показывает время ожидания; автоматических повторов нет. Он не проверяет
WebSocket, инструменты или текущую сессию GUI. Явные CLI overrides и политики организации
могут менять обычный запуск, поэтому doctor не подтверждает их работу.

Если проверка не прошла, смотрите [устранение проблем](docs/troubleshooting.md).
24 changes: 24 additions & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,27 @@ Windows и macOS требуют точное подтверждение `DELETE`
## Сообщить об ошибке

Обычные ошибки можно описать в GitHub Issues без API-ключа. Уязвимости и credential-handling проблемы отправляйте по [SECURITY.md](../SECURITY.md).

## Диагностика без раскрытия ключа

1. Откройте новый терминал и запустите `codex-neuroapi --doctor` либо `claude-neuroapi --doctor`.
Успех означает совместимую версию клиента и доступ вашего ключа к актуальному каталогу;
баланс генерации и WebSocket при этом не проверяются.
2. Для проверки реального ответа используйте `--doctor-generate` вместо `--doctor`.
Будет один короткий платный API-запрос с вашего баланса (ожидание до 180 секунд),
без автоматического повтора. Пустой, незаконченный ответ или одна статистика usage
не считаются успехом. Идентификатор модели в ответе должен точно совпадать с запрошенным;
отсутствующий или другой идентификатор даёт `model_identity_unverified`. Это может быть
сопоставление имени на сервисе, а не сетевой сбой. Сверка проверяет заявленное имя,
но не устанавливает подлинность модели провайдера. Роль сообщения должна быть `assistant`. Этот режим не запускает инструменты и не создаёт сессию клиента.
3. Если doctor проходит, а обычный запуск нет, проверьте явные `--model`, `--settings`
и `-c`, а также политики организации: doctor использует модель и адрес из управляемого
профиля, а параметры вашего запуска могут их переопределять.

Не публикуйте содержимое Keychain/DPAPI, полный конфиг или API-ключ. Диагностику можно
передать поддержке: она выводит только адрес, модель, результат и время проверки.

Claude Code теперь использует проверенные сервером лимиты вместо постоянного ограничения
вывода 4096 токенами. На старом сервере этот лимит остаётся запасным значением.
Не увеличивайте окно контекста вручную сверх доступного модели: серверная оценка резерва
не увеличивает контекст и не фиксирует окончательную стоимость запроса.
55 changes: 48 additions & 7 deletions scripts/macos/catalog-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function keys(value, allowed) {
function text(value, limit) {
return typeof value === 'string' && value.length <= limit && !/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/.test(value)
}
function modelID(value) { return typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/.test(value) }
function modelID(value) { return typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/.test(value) && !/\s/.test(value) }
function integer(value, min, max) { return Number.isSafeInteger(value) && value >= min && value <= max }
function readBounded(handle, limit) {
var data = $.NSMutableData.data
Expand Down Expand Up @@ -83,9 +83,17 @@ function validateClaude(data) {
if (option.description !== undefined) requireValue(text(option.description, 4096))
})
requireValue(options[data.model] === true)
keys(data.env, ['ANTHROPIC_DEFAULT_SONNET_MODEL', 'ANTHROPIC_DEFAULT_OPUS_MODEL', 'ANTHROPIC_DEFAULT_HAIKU_MODEL', 'ANTHROPIC_DEFAULT_FABLE_MODEL'])
keys(data.env, ['ANTHROPIC_DEFAULT_SONNET_MODEL', 'ANTHROPIC_DEFAULT_OPUS_MODEL', 'ANTHROPIC_DEFAULT_HAIKU_MODEL', 'ANTHROPIC_DEFAULT_FABLE_MODEL', 'CLAUDE_CODE_AUTO_COMPACT_WINDOW', 'CLAUDE_CODE_MAX_OUTPUT_TOKENS', 'CLAUDE_CODE_GATEWAY_HINT_HEADERS'])
Object.keys(data.env).forEach(function (key) {
requireValue(modelID(data.env[key]) && seen[data.env[key]])
var value = data.env[key]
if (key === 'CLAUDE_CODE_GATEWAY_HINT_HEADERS') { requireValue(value === '1'); return }
if (key === 'CLAUDE_CODE_AUTO_COMPACT_WINDOW' || key === 'CLAUDE_CODE_MAX_OUTPUT_TOKENS') {
requireValue(typeof value === 'string' && /^[1-9][0-9]{0,6}$/.test(value) && String(Number(value)) === value)
requireValue(integer(Number(value), key === 'CLAUDE_CODE_AUTO_COMPACT_WINDOW' ? 100000 : 1,
key === 'CLAUDE_CODE_AUTO_COMPACT_WINDOW' ? 1000000 : 32000))
return
}
requireValue(modelID(value) && seen[value])
var family = key.slice('ANTHROPIC_DEFAULT_'.length, -'_MODEL'.length).toLowerCase()
if (family === 'haiku') {
// Claude Code also uses the Haiku alias for background calls. The
Expand All @@ -97,8 +105,28 @@ function validateClaude(data) {
}
})
}
function validateDoctor(data, client) {
requireValue(object(data) && !data.error)
var blocks
if (client === 'codex') {
requireValue(data.status === 'completed' && Array.isArray(data.output))
blocks = []
data.output.forEach(function (item) {
if (item.type === 'message' && item.role === 'assistant' && Array.isArray(item.content)) blocks = blocks.concat(item.content)
})
} else {
requireValue(data.type === 'message' && data.role === 'assistant' && data.stop_reason === 'end_turn' && Array.isArray(data.content))
blocks = data.content
}
// An empty 200, metadata or usage alone is not a working generation.
requireValue(blocks.some(function (block) {
return object(block) && block.type === (client === 'codex' ? 'output_text' : 'text') &&
typeof block.text === 'string' && block.text.trim().length > 0
}))
return 'Ответ ассистента получен по HTTP; заявленное имя модели совпадает. Эта проверка не проверяет WebSocket или инструменты клиента.'
}
function run(args) {
requireValue(args.length === 3 && ['codex', 'claude'].indexOf(args[0]) !== -1)
requireValue(args.length === 3 && ['codex', 'claude', 'doctor-codex', 'doctor-claude'].indexOf(args[0]) !== -1)
var raw = readBounded($.NSFileHandle.fileHandleWithStandardInput, 4194310)
var secretHandle = $.NSFileHandle.fileHandleForReadingAtPath('/dev/fd/3')
requireValue(!secretHandle.isNil())
Expand All @@ -107,6 +135,18 @@ function run(args) {
requireValue(raw.slice(-4) === '\n200')
var data = JSON.parse(raw.slice(0, -4))
requireValue(JSON.stringify(data).indexOf(secret) === -1)
if (args[0].indexOf('doctor-') === 0) {
var modelHandle = $.NSFileHandle.fileHandleForReadingAtPath($(args[1] + '/model.txt'))
requireValue(!modelHandle.isNil())
var expectedModel = readBounded(modelHandle, 257).trim()
requireValue(modelID(expectedModel))
requireValue(object(data) && !data.error)
if (data.model !== expectedModel) {
write(args[1] + '/doctor-error.txt', 'model_identity_unverified')
requireValue(false)
}
return validateDoctor(data, args[0].slice(7))
}
if (args[0] === 'codex') {
validateCodex(data)
write(args[1] + '/models.json', JSON.stringify({models: data.models}))
Expand All @@ -123,10 +163,11 @@ function run(args) {
})
data.env.ANTHROPIC_BASE_URL = 'https://claude.neuroapi.host'
data.env.ANTHROPIC_MODEL = data.model
// Keep first-request quota reservations bounded. Claude Code can continue
// generation in another turn when a response reaches this limit.
data.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = '4096'
// v1 servers provide no verified limits. v2 output limits are independent
// of financial reservation estimates and must survive validation.
if (data.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS === undefined) data.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = '4096'
data.apiKeyHelper = "'" + args[2].replace(/'/g, "'\\''") + "'"
write(args[1] + '/settings.json', JSON.stringify(data))
write(args[1] + '/model.txt', data.model + '\n')
}
}
Loading
Loading