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
7 changes: 7 additions & 0 deletions docs-site/src/content/docs/fr/guides/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ MiniMax Code recherche d’abord `MINIMAX_DATA_DIR`, puis `MAVIS_DATA_DIR`, avan
`~/.minimax`. Son bloc géré ne possède que `custom_provider.opencodex`. Il ne modifie ni `defaultModel`, ni
la source d’identification MiniMax sélectionnée, ni la connexion MiniMax de l’utilisateur. Après l’avoir
connecté, choisissez dans MCode une entrée `custom_provider:opencodex/<provider/model>`.
L’actualisation de l’intégration met également à jour les fenêtres de contexte par modèle et les choix
d’effort de raisonnement faisant autorité ; les capacités inconnues sont omises et l’effort courant,
qui appartient à la session MCode, est préservé.

Les chemins respectent les variables de remplacement propres à chaque client, lorsqu'elles existent. Pour
OMP, la présence de `OMP_PROFILE` l'emporte sur `PI_PROFILE`, même si sa valeur est explicitement vide. Un
Expand Down Expand Up @@ -149,6 +152,10 @@ ocx integration client enable --client mcode
ocx mcode
```

Une fois l’intégration connectée, `ocx sync` actualise également le bloc MCode géré avec les fenêtres de
contexte et les niveaux d’effort de raisonnement actuels. Les blocs absents, modifiés par un tiers, non sûrs
ou jamais gérés restent intacts ; réactivez explicitement l’intégration lorsque vous souhaitez la reconnecter.

Le CLI distinct de la plateforme MiniMax (`mmx`) n’est pas une intégration à commutateur de fichier. Ses
commandes textuelles utilisent le point de terminaison compatible avec Anthropic de MiniMax ; OpenCodex
fournit donc un lanceur isolant les identifiants et limité à l’adresse locale :
Expand Down
8 changes: 5 additions & 3 deletions docs-site/src/content/docs/fr/guides/minimax.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ custom_provider:
baseURL: http://127.0.0.1:10100
authMode: api-key
models:
anthropic/claude-opus-5: {}
anthropic/claude-opus-5:
limit:
context: 1000000
```

La liste de modèles réellement générée provient du catalogue OpenCodex actif. Le bloc n’écrit aucune clé réelle, ne remplace pas `defaultModel` et ne modifie pas votre connexion MiniMax. Dans MCode, choisissez un modèle sous `custom_provider:opencodex/...`.
La liste de modèles réellement générée, ainsi que les fenêtres de contexte et les niveaux d’effort de raisonnement connus, provient du catalogue OpenCodex actif. Lorsqu’aucune fenêtre de contexte ou échelle d’effort ne fait autorité pour un modèle, le champ correspondant est omis au lieu de recevoir une valeur supposée. MCode conserve l’effort actuellement sélectionné dans la session : OpenCodex exporte donc `effortOptions` sans remplacer cette sélection. Le bloc n’écrit aucune clé réelle, ne remplace pas `defaultModel` et ne modifie pas votre connexion MiniMax. Dans MCode, choisissez un modèle sous `custom_provider:opencodex/...`.

`ocx mcode` vérifie que ce fournisseur pointe vers le proxy actuellement actif avant de lancer le client. Si le port a changé, actualisez le bloc géré en relançant la commande d’activation. Désactivez-le ou restaurez-le au moyen du même système d’intégration audité :
`ocx mcode` vérifie que ce fournisseur pointe vers le proxy actuellement actif avant de lancer le client. Après l’activation initiale, `ocx sync` actualise le bloc géré lorsque le port ou les capacités du catalogue changent. La synchronisation automatique ne crée jamais un bloc non géré, ne recrée pas un bloc que vous avez supprimé et n’écrase pas un fichier modifié après l’écriture d’OpenCodex ; utilisez la commande d’activation lorsque vous souhaitez le reconnecter explicitement. Désactivez-le ou restaurez-le au moyen du même système d’intégration audité :

```bash
ocx integration client disable --client mcode
Expand Down
8 changes: 7 additions & 1 deletion docs-site/src/content/docs/guides/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ MiniMax Code follows `MINIMAX_DATA_DIR`, then `MAVIS_DATA_DIR`, before falling
back to `~/.minimax`. Its managed block owns only `custom_provider.opencodex`.
It does not change `defaultModel`, the selected MiniMax credential source, or
the user's MiniMax login. Choose a `custom_provider:opencodex/<provider/model>`
entry in MCode after connecting it.
entry in MCode after connecting it. Refreshing the integration also refreshes
authoritative per-model context windows and reasoning-effort choices; unknown
capabilities are omitted, and MCode's session-owned current effort is preserved.

Prime Agent follows `PRIME_AGENT_CODING_AGENT_DIR` before falling back to
`~/.prime/agent`; a relative value is refused so the proxy and the agent cannot
Expand Down Expand Up @@ -161,6 +163,10 @@ ocx integration client enable --client mcode
ocx mcode
```

Once connected, `ocx sync` also refreshes the owned MCode block with current context
windows and reasoning-effort ladders. It leaves missing, foreign-edited, unsafe, and
never-owned blocks untouched; re-enable explicitly when you intend to reconnect one.

The separate MiniMax platform CLI (`mmx`) is not a file-toggle integration. Its text
commands use MiniMax's Anthropic-compatible endpoint, so OpenCodex provides a
credential-isolated, loopback-only launcher:
Expand Down
21 changes: 15 additions & 6 deletions docs-site/src/content/docs/guides/minimax.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,25 @@ custom_provider:
baseURL: http://127.0.0.1:10100
authMode: api-key
models:
anthropic/claude-opus-5: {}
anthropic/claude-opus-5:
limit:
context: 1000000
```

The real generated model list comes from the running OpenCodex catalog. The block does
not write a real key, does not replace `defaultModel`, and does not change your MiniMax
login. In MCode, choose a model under `custom_provider:opencodex/...`.
The real generated model list and its known context windows and reasoning-effort ladders
come from the running OpenCodex catalog. A model with no authoritative context window or
effort ladder omits that field instead of receiving a guessed value. MCode keeps the
currently selected effort in the session, so OpenCodex exports `effortOptions` without
overwriting that selection. The block does not write a real key, does not replace
`defaultModel`, and does not change your MiniMax login. In MCode, choose a model under
`custom_provider:opencodex/...`.

`ocx mcode` verifies that this provider points at the currently running proxy before it
launches the client. If the port changed, refresh the managed block by running the enable
command again. Disable or restore it through the same audited integration system:
launches the client. After the one-time enable, `ocx sync` refreshes the owned block when
the port or catalog capabilities change. Automatic sync never creates an unowned block,
recreates one you removed, or overwrites a file that changed after OpenCodex wrote it; use
the enable command when you intentionally want to reconnect. Disable or restore it through
the same audited integration system:

```bash
ocx integration client disable --client mcode
Expand Down
8 changes: 8 additions & 0 deletions docs-site/src/content/docs/tr/guides/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ son olarak `~/.minimax` dizinine geri döner. Yönetilen blok yalnızca
`custom_provider.opencodex` alanına sahiptir; `defaultModel` değerini, seçilen
MiniMax kimlik bilgisi kaynağını veya kullanıcının MiniMax oturumunu değiştirmez.
Bağladıktan sonra MCode içinde bir `custom_provider:opencodex/<provider/model>` girdisi seçin.
Entegrasyon yenilendiğinde model başına doğrulanmış bağlam pencereleri ve akıl yürütme
çabası seçenekleri de yenilenir; bilinmeyen yetenekler atlanır ve MCode oturumunun
yönettiği geçerli çaba seçimi korunur.

Yollar, varsa her istemcinin kendi ortam geçersiz kılmalarını dikkate alır. OMP
için `OMP_PROFILE`, açıkça boş olduğunda bile varlığıyla `PI_PROFILE`'a üstün
Expand Down Expand Up @@ -172,6 +175,11 @@ ocx integration client enable --client mcode
ocx mcode
```

Bağlandıktan sonra `ocx sync`, yönetilen MCode bloğunu güncel bağlam pencereleri ve
akıl yürütme çabası seçenekleriyle de yeniler. Eksik, dışarıdan düzenlenmiş, güvenli
olmayan veya hiç sahiplenilmemiş bloklara dokunmaz; yeniden bağlamak istediğinizde
entegrasyonu açıkça yeniden etkinleştirin.

Ayrı MiniMax platform CLI'si (`mmx`) bir dosya anahtarı entegrasyonu değildir.
Metin komutları MiniMax'ın Anthropic uyumlu uç noktasını kullandığı için OpenCodex,
kimlik bilgilerini yalıtan ve yalnızca geri döngüde çalışan bir başlatıcı sağlar:
Expand Down
8 changes: 7 additions & 1 deletion docs-site/src/content/docs/zh-tw/guides/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ loopback,而且絕不會寫入真實憑證。
MiniMax Code 依序遵循 `MINIMAX_DATA_DIR`、`MAVIS_DATA_DIR`,最後才回退到
`~/.minimax`。其受管理區塊只擁有 `custom_provider.opencodex`,不會變更
`defaultModel`、MiniMax 憑證來源或使用者的 MiniMax 登入。連接後請在 MCode
中選擇 `custom_provider:opencodex/<provider/model>`。
中選擇 `custom_provider:opencodex/<provider/model>`。重新整理整合也會更新有可靠來源的
逐模型 context window 與 reasoning-effort 選項;未知能力會省略,而 MCode session
目前選取的 effort 不會被覆寫。

路徑遵循客戶端自己的環境覆寫(environment override)。對 OMP 而言,`OMP_PROFILE` 以存在與否優先於 `PI_PROFILE`,即使明確為空也一樣。具名 profile 會把 `PI_CONFIG_DIR` 當作相對於使用者家目錄的目錄名稱,並忽略 `PI_CODING_AGENT_DIR`;沒有具名 profile 時,`PI_CODING_AGENT_DIR` 勝出。OMP 支援 provider 層級的 headers,但這個最初的整合刻意只支援 loopback;遠端 `x-opencodex-api-key` 的連線設定被延後。搬移過的 `HERMES_HOME`、`KIMI_CODE_HOME` 與 `XDG_CONFIG_HOME` 路徑同樣會被遵循,而非猜測。表格列出每個客戶端的預設值。

Expand Down Expand Up @@ -85,6 +87,10 @@ ocx integration client enable --client mcode
ocx mcode
```

完成一次連接後,`ocx sync` 也會以目前的 context window 與 reasoning-effort 階梯更新
OpenCodex 已擁有的 MCode 區塊。若區塊已刪除、遭外部修改、不安全或從未由 OpenCodex
建立,sync 會保持原檔不動;只有在你確定要重新連接時才再次執行 enable。

另一個 MiniMax 平台 CLI(`mmx`)不是檔案開關整合。其文字命令使用 MiniMax 的
Anthropic 相容端點,因此 OpenCodex 提供憑證隔離、僅限 loopback 的 launcher:

Expand Down
25 changes: 24 additions & 1 deletion src/cli/dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,9 @@ const commandRunners: Record<string, CommandRunner> = {
},
sync: async deps => {
const restartCodex = deps.args.slice(1).includes("--restart-codex");
const live = await deps.findLiveProxy();
const synced = await syncModelsToCodex(
(await deps.findLiveProxy())?.port,
live?.port,
undefined,
undefined,
undefined,
Expand All @@ -229,6 +230,28 @@ const commandRunners: Record<string, CommandRunner> = {
if (synced.catalogWritten || synced.cacheSynced) {
afterCatalogWriteHandleAppServers({ restart: restartCodex, log: console });
}
// `ocx sync` is a direct CLI path; it does not call the management
// `/api/sync` route. Refresh the already-connected MCode block here too,
// after Codex has published the catalog that supplies its capabilities.
if (synced.status !== "refused" && live) {
try {
const config = deps.loadConfig();
const { refreshOwnedIntegration } = await import("../integrations/owned-refresh");
const result = await refreshOwnedIntegration({
clientId: "mcode",
models: async () => {
const { loadExportModels } = await import("../server/management/model-rows");
return loadExportModels(config);
},
config,
port: live.port,
});
if (result?.changed) console.log("MCode integration refreshed from the current catalog.");
else if (result?.reason) console.warn(`MCode integration was not refreshed: ${result.reason}`);
} catch (error) {
console.warn(`MCode integration was not refreshed: ${error instanceof Error ? error.message : String(error)}`);
}
}
return code;
},
v2: async deps => {
Expand Down
38 changes: 31 additions & 7 deletions src/clients/config-export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { isAbsolute, join, resolve } from "node:path";
import { shouldInjectApiAuthHeader } from "../codex/inject";
import { FORMAT_MEDIA_TYPE, serializeDocument, type ConfigFormat } from "../integrations/serialize";
import { providerCodexAccountMode } from "../providers/registry";
import { sanitizeCodexReasoningEfforts } from "../reasoning-effort";
import { probeHostname } from "../server/proxy-liveness";
import type { OcxConfig } from "../types";

Expand Down Expand Up @@ -928,7 +929,14 @@ export interface McodeProviderBlock {
baseURL: string;
authMode: "api-key";
};
models: Record<string, Record<string, never>>;
models: Record<string, McodeModelEntry>;
}

export interface McodeModelEntry {
/** MCode uses this value for context accounting and compaction. */
limit?: { context: number };
/** MCode exposes these exact levels in `/model` and sends the selected effort. */
thinking?: { effortOptions: string[] };
}

export interface McodeGeneratedConfig {
Expand Down Expand Up @@ -1273,12 +1281,29 @@ function buildDshClientConfig(ctx: ExportContext): DshGeneratedConfig {

/**
* MiniMax Code's `provider add` command persists custom providers under
* `custom_provider.<id>`. Do not emit `defaultModel`: connecting a client must
* not silently replace the user's current model selection.
* `custom_provider.<id>`. Its current model schema reads `limit.context` for
* context accounting and `thinking.effortOptions` for the `/model` effort
* control. Do not emit the removed `thinking.effort` / `defaultEffort` fields:
* MCode 0.1.6 migrates those into options and keeps the selected effort in the
* session. Do not emit `defaultModel` either: connecting a client must not
* silently replace the user's current model selection.
*/
function buildMcodeClientConfig(ctx: ExportContext): McodeGeneratedConfig {
const models: Record<string, Record<string, never>> = {};
for (const model of normalizeExportModels(ctx.models)) models[model.namespaced] = {};
const models: Record<string, McodeModelEntry> = {};
for (const model of normalizeExportModels(ctx.models)) {
const entry: McodeModelEntry = {};
const context = authoritativeContextWindow(model.contextWindow);
if (context !== undefined) entry.limit = { context };
// `none` is an internal Codex catalog sentinel, not an MCode effort. MCode
// forwards every option as `output_config.effort` while keeping adaptive
// thinking enabled, and the Anthropic ingress deliberately accepts only
// minimal..ultra. Advertising `none` would therefore create a selectable
// value that cannot disable reasoning and is not forwarded as an effort.
const efforts = sanitizeCodexReasoningEfforts(model.reasoningEfforts)
?.filter(effort => effort !== "none");
if (efforts && efforts.length > 0) entry.thinking = { effortOptions: efforts };
models[model.namespaced] = entry;
}
return {
custom_provider: {
[OPENCODE_PROVIDER_ID]: {
Expand Down Expand Up @@ -1395,8 +1420,7 @@ function summarizeDsh(document: unknown): { modelCount: number; modelsWithoutLim

function summarizeMcode(document: unknown): { modelCount: number; modelsWithoutLimits: number } {
const models = Object.values((document as McodeGeneratedConfig | undefined)?.custom_provider?.[OPENCODE_PROVIDER_ID]?.models ?? {});
// MCode's custom-provider schema does not expose per-model context limits.
return { modelCount: models.length, modelsWithoutLimits: 0 };
return { modelCount: models.length, modelsWithoutLimits: models.filter(model => !model.limit).length };
}

function summarizeZcode(document: unknown): { modelCount: number; modelsWithoutLimits: number } {
Expand Down
71 changes: 71 additions & 0 deletions src/integrations/mutation-flight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type { IntegrationClientId } from "./registry";

const INTEGRATION_MUTATION_JOIN_MS = 120_000;
export const INTEGRATION_MUTATION_TERMINAL_MS = 10 * 60_000;

interface IntegrationMutationFlight {
key: string;
startedAt: number;
promise: Promise<unknown>;
}

export class IntegrationMutationBusyError extends Error {
constructor(readonly clientId: IntegrationClientId) {
super("integration_mutation_busy");
}
}

const integrationMutationFlights = new Map<IntegrationClientId, IntegrationMutationFlight>();
let integrationMutationRunTestHook: ((operation: () => Promise<unknown>) => Promise<unknown>) | null = null;

/**
* One in-process flight per client, shared by explicit HTTP mutations and
* implicit catalog refreshes. Equal operations join; different semantics are
* busy instead of, for example, letting an explicit apply join a refresh-only
* no-op for an absent block.
*/
export function runIntegrationMutationFlight<T>(
clientId: IntegrationClientId,
key: string,
now: () => number,
operation: () => Promise<T>,
): Promise<T> {
const startedAt = now();
const current = integrationMutationFlights.get(clientId);
if (current) {
const age = startedAt - current.startedAt;
if (current.key === key && age < INTEGRATION_MUTATION_JOIN_MS) {
return current.promise as Promise<T>;
}
if (age <= INTEGRATION_MUTATION_TERMINAL_MS) {
return Promise.reject(new IntegrationMutationBusyError(clientId));
}
if (integrationMutationFlights.get(clientId) === current) {
integrationMutationFlights.delete(clientId);
}
}

const flight: IntegrationMutationFlight = {
key,
startedAt,
promise: Promise.resolve(),
};
const run = async (): Promise<unknown> => operation();
flight.promise = (integrationMutationRunTestHook
? integrationMutationRunTestHook(run)
: run()
).finally(() => {
if (integrationMutationFlights.get(clientId) === flight) {
integrationMutationFlights.delete(clientId);
}
});
integrationMutationFlights.set(clientId, flight);
return flight.promise as Promise<T>;
}

export function setIntegrationMutationFlightTestHook(
run: ((operation: () => Promise<unknown>) => Promise<unknown>) | null,
): void {
integrationMutationRunTestHook = run;
integrationMutationFlights.clear();
}
Loading
Loading