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
6 changes: 6 additions & 0 deletions docs-site/src/content/docs/fr/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ Si le fichier ne peut pas être analysé, opencodex le sauvegarde en tant que
`config.json.invalid-<timestamp>`, affiche un avertissement dans la console et démarre avec les valeurs par défaut. Si le fichier est absent,
la valeur par défaut d'une nouvelle installation est également utilisée : un fournisseur `openai` en mode transfert.

Les commandes locales `ocx config set`, `unset`, `import`, la commande hors ligne `ocx effort`,
les opérations add/remove sur les modèles personnalisés et add/remove/set-default sur les fournisseurs
refusent un fichier `config.json` existant invalide ou illisible sans en modifier les octets.
Exécutez `ocx config validate` et réparez le fichier avant de réessayer. Si une configuration existe déjà,
import exige également que le fichier cible soit lisible et valide.

## Priorité et valeurs par défaut

Les valeurs valides de `config.json` remplacent les valeurs par défaut intégrées. Les champs facultatifs absents utilisent les valeurs par défaut
Expand Down
5 changes: 5 additions & 0 deletions docs-site/src/content/docs/ja/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ opencodex は、永続的な設定を `$OPENCODEX_HOME/config.json` (通常は `

ファイルを解析できない場合、opencodex はファイルを `config.json.invalid-<timestamp>` としてバックアップし、コンソールに警告を表示し、デフォルトで起動します。欠落しているファイルでも、新規インストールのデフォルトである 1 つの `openai` フォワード プロバイダーが使用されます。

ローカルの `ocx config set`、`unset`、`import`、オフラインの `ocx effort`、カスタムモデルの add/remove、
プロバイダーの add/remove/set-default は、既存の `config.json` が無効または読み取り不能な場合、
元のバイト列を変更せずに拒否します。`ocx config validate` を実行し、ファイルを修復してから再試行してください。
既存の設定がある場合、import にも読み取り可能で有効な保存先ファイルが必要です。

## 優先順位とデフォルト

`config.json` の有効な値は、組み込みのデフォルトをオーバーライドします。省略可能なフィールドが欠落している場合は、ドメイン ページに記載されているデフォルトが使用されます。 `OPENCODEX_HOME` は、デフォルトの構成ディレクトリよりも優先されます。 `apiKey: "${PROVIDER_API_KEY}"` などの環境参照を受け入れるフィールドは、リクエスト時にその変数を解決します。送信プロキシの場合、すでに設定されている `HTTP_PROXY` または `HTTPS_PROXY` が最上位の `proxy` フィールドよりも優先されます。
Expand Down
5 changes: 5 additions & 0 deletions docs-site/src/content/docs/ko/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ opencodex는 지속 설정을 `$OPENCODEX_HOME/config.json`에 저장합니다.
남긴 뒤 기본값으로 시작합니다. 파일이 없어도 새로 설치한 경우의 기본값을 그대로 사용하며, 그것은 단일
`openai` forward 프로바이더입니다.

로컬 `ocx config set`, `unset`, `import`, 오프라인 `ocx effort`, 커스텀 모델 추가·삭제,
프로바이더 추가·삭제·기본값 변경은 기존 `config.json`이 유효하지 않거나 읽을 수 없으면
원본 바이트를 바꾸지 않고 거부합니다. `ocx config validate`로 확인하고 파일을 고친 뒤
다시 실행하세요. import도 기존 설정 파일이 있다면 읽을 수 있고 유효한 대상 파일을 요구합니다.

## 우선순위와 기본값

`config.json`의 유효한 값은 내장 기본값보다 우선합니다. 선택 사항으로 비어 있는 필드는 각 도메인 페이지에
Expand Down
5 changes: 5 additions & 0 deletions docs-site/src/content/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ If the file cannot be parsed, opencodex backs it up as
`config.json.invalid-<timestamp>`, warns on the console, and starts with defaults. A missing file also
uses the fresh-install default: one `openai` forward provider.

Local `ocx config set`, `unset`, `import`, offline `ocx effort`, custom-model add/remove, and
provider add/remove/set-default refuse an invalid or unreadable existing `config.json` without
changing its bytes. Run `ocx config validate` and repair the file before retrying. Import also
requires a readable, valid target when a config already exists.

Comment thread
lidge-jun marked this conversation as resolved.
Resolved
## Precedence and defaults

### Provider and model aliases
Expand Down
6 changes: 6 additions & 0 deletions docs-site/src/content/docs/ru/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ Dashboard, management API и mutating-команды CLI записывают в
`config.json.invalid-<timestamp>`, пишет предупреждение в консоль и стартует с настройками по
умолчанию. Если файла нет, используется тот же свежий дефолт: один forward-провайдер `openai`.

Локальные команды `ocx config set`, `unset`, `import`, офлайн-команда `ocx effort`,
операции add/remove для пользовательских моделей и add/remove/set-default для провайдеров
отказываются изменять существующий некорректный или нечитаемый `config.json`, сохраняя исходные байты.
Выполните `ocx config validate` и исправьте файл перед повторной попыткой. Если конфигурация уже существует,
для import целевой файл также должен быть читаемым и корректным.

## Приоритет и значения по умолчанию

Корректные значения из `config.json` перекрывают встроенные дефолты. Для отсутствующих
Expand Down
6 changes: 6 additions & 0 deletions docs-site/src/content/docs/tr/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ Dosya ayrıştırılamazsa opencodex dosyayı `config.json.invalid-<zaman-damgas
olarak yedekler, konsolda uyarır ve varsayılanlarla başlar. Eksik bir dosya da
yeni yükleme varsayılanını kullanır: bir `openai` iletme sağlayıcısı.

Yerel `ocx config set`, `unset`, `import`, çevrimdışı `ocx effort`, özel modeller için add/remove
ve sağlayıcılar için add/remove/set-default işlemleri, mevcut `config.json` geçersiz veya okunamaz
olduğunda dosyanın baytlarını değiştirmeden işlemi reddeder. Yeniden denemeden önce
`ocx config validate` komutunu çalıştırıp dosyayı onarın. Yapılandırma zaten varsa import işlemi de
okunabilir ve geçerli bir hedef dosya gerektirir.

## Öncelik ve varsayılanlar

`config.json` içindeki geçerli değerler yerleşik varsayılanları geçersiz kılar.
Expand Down
5 changes: 5 additions & 0 deletions docs-site/src/content/docs/zh-cn/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ opencodex 会把持久化配置存放在 `$OPENCODEX_HOME/config.json`,通常
`config.json.invalid-<timestamp>`,在控制台警告,并以默认值启动。文件缺失时也会使用新安装默认值:
一个 `openai` forward provider。

本地 `ocx config set`、`unset`、`import`、离线 `ocx effort`、自定义模型的 add/remove
以及 provider 的 add/remove/set-default 操作,会拒绝修改已有的无效或不可读的 `config.json`,
并保留原始字节。请运行 `ocx config validate` 并修复文件后再重试。已有配置时,
import 同样要求目标文件可读且有效。

## 优先级与默认值

`config.json` 中的有效值会覆盖内置默认值。缺失的可选字段使用各 domain 页面文档中说明的默认值。
Expand Down
5 changes: 5 additions & 0 deletions docs-site/src/content/docs/zh-tw/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ opencodex 把持久化設定存放在 `$OPENCODEX_HOME/config.json`,通常是
如果檔案無法解析,opencodex 會把它備份為 `config.json.invalid-<timestamp>`、在 console
警告,並以預設值啟動。檔案缺失時也使用全新安裝的預設:一個 `openai` forward provider。

本機的 `ocx config set`、`unset`、`import`、離線 `ocx effort`、自訂模型的 add/remove
與 provider 的 add/remove/set-default 操作,會拒絕修改既有但無效或無法讀取的 `config.json`,
並保留原始位元組。請執行 `ocx config validate` 並修復檔案後再重試。已有設定時,
import 也要求目標檔案可讀且有效。

## 優先順序與預設值

`config.json` 中的有效值會覆寫內建預設。缺失的選用欄位使用各領域頁面記載的預設值。
Expand Down
1 change: 1 addition & 0 deletions scripts/test-layout/layout-clients.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"windows-config-root-async-writers.test.ts": "windows",
"t3code-client-lifecycle.test.ts": "clients",
"fix-jsonc-cr-comments.test.ts": "lib",
"fix-jsonc-empty-comma.test.ts": "lib",
Expand Down
13 changes: 9 additions & 4 deletions src/cli/config-command.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { readFileSync, writeFileSync } from "node:fs";
import { clearCodexAccountPin } from "../codex/account-priority";
import { getConfigPath, mutatePersistedConfig, readConfigDiagnostics, sanitizeModelCostsForDisplay, saveConfig, validateConfigCandidate } from "../config";
import { getConfigDir, withConfigDirHardeningSettled } from "../config/paths";
import { readCliConfigForMutation } from "./config-mutation";
import { VISION_REASONING_EFFORTS, isVisionReasoningEffort } from "../reasoning-effort";
import type { OcxConfig } from "../types";
import { normalizeVisionReasoningForModel } from "../vision/reasoning";
Expand Down Expand Up @@ -260,7 +262,7 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
// `mutatePersistedConfig` reruns this callback against the latest validated disk
// state, so the operation is applied to what is actually there at commit time.
let savedValue: unknown = null;
const outcome = mutatePersistedConfig(fresh => {
const outcome = await withConfigDirHardeningSettled(getConfigDir(), () => mutatePersistedConfig(fresh => {
// Snapshot BEFORE mutating: comparing after the write compares a value with
// itself and would report every no-op as a change, bumping the generation.
const before = JSON.stringify(fresh);
Expand All @@ -285,9 +287,9 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
}
Object.assign(fresh, config);
return { changed: JSON.stringify(fresh) !== before, value: undefined };
});
}));
if (outcome.status === "unavailable") {
throw new Error(outcome.reason === "conflict"
throw new CliUsageError(outcome.reason === "conflict"
? "config changed while applying this update; retry"
: `config is ${outcome.reason}`);
}
Expand Down Expand Up @@ -327,7 +329,10 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
if (!path) throw new CliUsageError("import path is required", USAGE);
if (!yes) throw new CliUsageError("import requires --yes", USAGE);
rejectArgs(args, USAGE);
saveConfig(validate(loadInput(path)));
await withConfigDirHardeningSettled(getConfigDir(), () => {
readCliConfigForMutation();
saveConfig(validate(loadInput(path)));
});
printData({ ok: true, source: path }, wantsJson, [`Imported config from ${path}. Restart or run ocx sync if needed.`]);
return;
}
Expand Down
13 changes: 13 additions & 0 deletions src/cli/config-mutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { readConfigDiagnostics } from "../config/diagnostics";
import type { OcxConfig } from "../types";
import { CliUsageError } from "./runtime-api";

/** Inside admission unless this is a passive preliminary selection read. */
export function readCliConfigForMutation(options: { passive?: boolean } = {}): OcxConfig {
const diagnostics = readConfigDiagnostics(options);
if (diagnostics.source === "fallback" || diagnostics.error) {
throw new CliUsageError("config.json is invalid or unreadable; run ocx config validate and repair it before retrying. Nothing was saved.");
}
// Fresh installs retain the full-config commands' existing default initialization.
return diagnostics.config;
}
9 changes: 5 additions & 4 deletions src/cli/dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { ProxyRestartStartOutcome } from "./tray-proxy";
* never needs to import the entry module back (no cycle).
*/
import { CLI_COMMANDS } from "./registry";
import { runCliAction } from "./runtime-api";
import { uninstallArgsError, type CliHead } from "./root";
import type { ReadyArgs } from "./ready";
import type { LivenessIo, LiveProxy } from "../server/proxy-liveness";
Expand Down Expand Up @@ -870,12 +871,12 @@ const commandRunners: Record<string, CommandRunner> = {
// Reset first, like the service runner below: reading process.exitCode only
// reports THIS command's outcome if nothing earlier in the process set it.
process.exitCode = 0;
await handleProviderCommand(deps.args.slice(1));
const code = await runCliAction(() => handleProviderCommand(deps.args.slice(1)));
// handleProviderCommand reports failure through process.exitCode, which it sets
// from handleProviderRuntimeCommand. Returning a literal 0 here made index.ts
// call process.exit(0) and erase it, so `ocx provider quota` against a stopped
// proxy printed an error and still exited 0 (#2697).
return Number(process.exitCode ?? 0);
return code || Number(process.exitCode ?? 0);
},
account: async deps => {
const { cmdAccount } = await import("./account");
Expand All @@ -884,10 +885,10 @@ const commandRunners: Record<string, CommandRunner> = {
models: async deps => {
const { handleModels } = await import("./models");
process.exitCode = 0;
await handleModels(deps.args.slice(1));
const code = await runCliAction(() => handleModels(deps.args.slice(1)));
// Same as the provider runner above: handleModels sets process.exitCode from
// handleModelsRuntimeCommand, and a literal 0 discarded it (#2697).
return Number(process.exitCode ?? 0);
return code || Number(process.exitCode ?? 0);
},
alias: async deps => {
const { handleAliasCommand } = await import("./alias");
Expand Down
33 changes: 19 additions & 14 deletions src/cli/effort.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { loadConfig, saveConfig } from "../config";
import { readCliConfigForMutation } from "./config-mutation";
import { getConfigDir, withConfigDirHardeningSettled } from "../config/paths";
import {
CODEX_REASONING_LEVELS,
configuredReasoningEfforts,
Expand Down Expand Up @@ -232,20 +234,23 @@ async function setEffort(
}

// Offline persistence path: only reached when no live proxy was found before mutation
const config = loadConfig();
if (validatedMain !== undefined) {
if (validatedMain === null) delete config.effortCap;
else config.effortCap = validatedMain;
}
if (validatedSubagent !== undefined) {
if (validatedSubagent === null) delete config.subagentEffortCap;
else config.subagentEffortCap = validatedSubagent;
}
if (validatedInjection !== undefined) {
if (validatedInjection === null) delete config.injectionEffort;
else config.injectionEffort = validatedInjection;
}
saveConfig(config);
const config = await withConfigDirHardeningSettled(getConfigDir(), () => {
const config = readCliConfigForMutation();
if (validatedMain !== undefined) {
if (validatedMain === null) delete config.effortCap;
else config.effortCap = validatedMain;
}
if (validatedSubagent !== undefined) {
if (validatedSubagent === null) delete config.subagentEffortCap;
else config.subagentEffortCap = validatedSubagent;
}
if (validatedInjection !== undefined) {
if (validatedInjection === null) delete config.injectionEffort;
else config.injectionEffort = validatedInjection;
}
saveConfig(config);
return config;
});

const result = {
ok: true,
Expand Down
Loading
Loading