Host (reference only): XiaomiMiMo/MiMo-Code
User-facing package: @opencode-compat/ocp (umbrella + ocp setup)
Bridge packages (internal): facade-plugin, facade-sdk, adapter, host-promise-v2
HostProfile id: mimo
OCP attaches as an external compatibility layer. MiMo is a read-only host reference.
Where the host installs npm plugins into its cache (typically under the MiMo XDG/cache packages/ tree — see packages/opencode/src/npm/index.ts and packages/opencode/src/plugin/shared.ts), OpenCode plugins that import @opencode-ai/plugin / @opencode-ai/sdk should resolve OCP facades via install overrides:
{
"@opencode-ai/plugin": "npm:@opencode-compat/facade-plugin@0.1.x",
"@opencode-ai/sdk": "npm:@opencode-compat/facade-sdk@0.1.x"
}(ocp setup --mode npm writes the concrete published train version.)
Preferred UX: follow the step-by-step guide in INSTALL.md (install @opencode-compat/ocp from npm → install consumer plugins → ocp setup --mode npm). Equivalent CLI names: compat setup / print-only compat overrides / opencode-compat overrides.
# summary — full steps in INSTALL.md
bun add -g @opencode-compat/ocp
mimo plugin -g cursor-opencode-provider
ocp setup --host mimo --mode npmMiMo installs each npm plugin into an isolated child dir (~/.cache/mimocode/packages/<name>@<version>/). A root-level packages/package.json override alone is not enough — ocp setup --deep (default) patches those children and auto-reifies when they already have node_modules. Re-run ocp setup after installing or upgrading plugins.
Listing OCP itself in plugin is optional bootstrap only — it does not intercept other plugins’ @opencode-ai/plugin imports.
Absolute-path / file:// plugins (common in local dev: plugin: ["/…/dist/index.js"]) resolve @opencode-ai/* from the checkout’s own node_modules. ocp setup (default --absolute-plugins) symlinks those to the OCP facades. Required for unmodified catalog plugins that import @opencode-ai/sdk/v2/client during classic config — stock v2.model.list re-enters GET /api/model and deadlocks on MiMo; facade-sdk polyfills from models.dev instead.
From this checkout, --mode auto may use local file: facade paths; published installs use --mode npm. See INSTALL.md and npm-publish.md.
Do not override @opencode-ai/plugin straight to @mimo-ai/plugin — that skips OCP (v2 surface, doctor, shared host kit).
MiMo’s SessionProcessor has no ensureToolCall: tool parts are created only on tool-input-start. A bare AI SDK tool-call (common from stock Cursor / OpenCode custom providers) therefore never materializes a tool part → finish tool-calls with zero tools → empty loop. Separately, MiMo’s bash schema requires a string description (z.string()); stock remaps that omit it fail validation. MiMo tool schemas also use names such as file_path, while some OpenCode providers emit filePath.
OCP policy (HostProfile mimo):
| Capability | Value | OCP adoption |
|---|---|---|
streamToolCallEnsure |
false |
Emit tool-input-start before bare tool-call |
bashDescriptionRequired |
true |
Fill missing bash.description only (never swap host tool catalogs) |
Argument spelling is not a MiMo-specific policy table. The shim reads the tools advertised on every LanguageModel call and maps only unique case/separator variants to their exact schema property names, recursively. That covers read, write, edit/StrReplace, future fork conventions, and MCP tools without hard-coding any of them; exact, unknown, and ambiguous keys are preserved.
Why in-place entry (not package.json exports): classic plugins such as cursor-opencode-provider set npm: MODULE_URL → a direct file://…/dist/index.js URL. Package exports shims never run. So after Layer A reify, ocp setup (default --provider-shim):
- Renames the install-tree entry to
*.ocp-original.js(cache only — upstream tarball / plugin repo untouched) - Writes the shim at the original entry path
- Drops sibling
ocp-lm-runtime.js+ocp-shim-meta.json
ocp setup --host mimo
# skip Option B only: ocp setup --host mimo --no-provider-shimExpect beside …/cursor-opencode-provider/dist/:
index.js— OCP shim (generated by ocp setup)index.ocp-original.js— stock entry backupocp-lm-runtime.js— host-dynamicwrapProviderModule/ stream adoptionocp-shim-meta.json— setup-time host hint + export metadata (sibling of the shim)
The generated entry resolves the live host from an explicit override, binary,
or host-owned environment marker (MIMOCODE / MIMOCODE_*). Provider workers
may hide all of those, so it then uses the setup-time host recorded in
ocp-shim-meta.json. The hint is only a fallback; a stronger live identity wins.
Re-run ocp setup after plugin install/upgrade/reify — npm install restores stock files and would wipe the shim. Do not fork the provider (cursor-kilocode-provider-style) or patch MiMo source for this gap.
MiMo today walks .mimocode in ConfigPaths.directories. OCP’s HostProfile for mimo records compatProjectDirs: [".opencode"] for matrix / doctor honesty.
Closing path gaps is the bridge’s job (docs, doctor, optional operator copy/symlink into .mimocode).
| Path | What works on MiMo today |
|---|---|
Classic Hooks (@opencode-ai/plugin) |
After Layer A (ocp setup + reify), MiMo loads the plugin and can surface models (e.g. cursor/* via mimo models) through the classic provider/auth hooks. |
Promise v2 (@opencode-ai/plugin/v2/promise) |
Plugin define() + setup can register ctx.aisdk.sdk / ctx.aisdk.language listeners, but MiMo never calls those hooks during native provider-resolve. There is no @mimo-ai/plugin/v2/promise and no in-process aisdk emit in packages/opencode/src/provider/provider.ts. |
So: classic prove-out ≠ Promise v2 prove-out. A v2/promise plugin that only sits in MiMo’s plugin list will not inject LanguageModelV3 into MiMo’s native model path by itself.
OCP ships the missing host kit externally:
- Facade:
@opencode-ai/plugin/v2/promise→packages/facade-plugin/src/v2/promise.ts(requires Layer A overrides) - Kit:
@opencode-compat/host-promise-v2— OpenCode-shapedawait ctx.aisdk.sdk(cb)/await ctx.aisdk.language(cb)(mutable events) - Operator entry:
wirePromiseV2()from@opencode-compat/ocp/ adapter
HostProfile for mimo sets capabilities.promiseV2 / aisdkProviderHooks to true because this OCP kit is the provider — not because MiMo exports a native v2 surface.
Until MiMo gains an in-process seam, T3 live proof is an external sidecar (operator script / helper process) that:
- Loads the unchanged consumer
plugin/v2entry from the MiMo install tree wirePromiseV2({ env: { OPENCODE_COMPAT_HOST: "mimo" } })await host.register(plugin)— runssetup, attaches aisdk listenersawait host.resolveProvider({ providerID, modelID, package })— emitssdkthenlanguage, returns mutated{ language, sdk, model }
Prerequisites
- Consumer plugin installed into MiMo (
mimo plugin -g …) ocp setup --host mimo(deep overrides + reify) so@opencode-ai/plugininside that tree is the OCP facadeOPENCODE_COMPAT_HOST=mimo(or passenvintowirePromiseV2)
Copy-paste smoke (from this checkout; adjust plugin path if the cache layout differs):
# Layer A first (once per plugin install/upgrade)
bun packages/ocp/bin/ocp.ts setup --host mimo
# Sidecar: register + resolveProvider (does not edit MiMo)
OPENCODE_COMPAT_HOST=mimo bun -e '
import { wirePromiseV2 } from "./packages/adapter/src/index.ts"
const pluginPath =
`${process.env.HOME}/.cache/mimocode/packages/cursor-opencode-provider@latest/node_modules/cursor-opencode-provider/dist/plugin-v2.js`
const plugin = (await import(pluginPath)).default
const host = wirePromiseV2({
env: { ...process.env, OPENCODE_COMPAT_HOST: "mimo" },
})
await host.register(plugin)
const { language, sdk } = await host.resolveProvider({
providerID: "cursor",
modelID: "grok-4.5",
// package string must match what the plugin’s sdk hook expects
package: pluginPath,
})
if (!language?.specificationVersion || language.specificationVersion !== "v3") {
throw new Error("resolveProvider did not return LanguageModelV3")
}
console.log({
pluginIds: host.plugins(), // e.g. ["cursor.provider"]
provider: language.provider, // "cursor"
modelId: language.modelId, // "grok-4.5"
hasSdkFactory: typeof sdk?.languageModel === "function",
})
'Success criteria: language.specificationVersion === "v3", language.provider / language.modelId match the request, and sdk.languageModel is present when the plugin’s sdk hook ran. That is the T3 bar for the OCP kit on MiMo.
What this does not do
- It does not patch MiMo source or add a native
v2/promiseexport. - It does not automatically feed
languageintomimo models/ the running TUI. Native listing still uses classic Hooks (or built-in providers). - A future in-process seam would be: wherever MiMo builds a language model for
providerID/modelID, call the sameresolveProvider(orinjectLanguageModel) and use the returnedlanguage. Until then, keep the sidecar as the operator bridge.
resolveProvider input tips
| Field | Role |
|---|---|
providerID |
Matched by plugin language hooks (e.g. "cursor") |
modelID |
Becomes event.model.id / event.model.api.id |
package |
String passed to event.package — many plugins (incl. cursor-opencode-provider) gate the sdk hook on this containing their package name or install path |
options / sdk / model |
Optional seeds; hooks mutate event.sdk / event.language in place |
API reference: packages/host-promise-v2, contract docs/ocp/0.1.md §7.
MiMo’s published classic surface lacks dispose and experimental.provider.small_model vs OCP core. Facade / doctor: accept + no-op + warn. Do not surface MiMo actor.* / session.* on the portable @opencode-ai/plugin path (ADR-7).
OCP does not kill MiMo analytics. See docs/guides/mimocode-telemetry-disable.md (MIMOCODE_ENABLE_ANALYSIS=false).
OPENCODE_COMPAT_HOST=mimo opencode-compat doctor
opencode-compat matrix --host mimo
opencode-compat matrix --host mimo --compat-scanDoctor should report streamToolCallEnsure: false and bashDescriptionRequired: true.
Live smoke (classic + Option B): after mimo plugin -g cursor-opencode-provider + ocp setup --host mimo:
- Confirm shim files under
~/.cache/mimocode/packages/cursor-opencode-provider@latest/node_modules/cursor-opencode-provider/dist/(index.ocp-original.js,ocp-lm-runtime.js, shimmedindex.js). mimo modelsshould listcursor/*when Cursor auth/cache is available (classic Hooks; Layer A).- A Cursor-backed
mimo runthat triggers tools should emit tool parts (Option B preamble), andread/write/editarguments should validate against MiMo's advertised schemas without editingcursor-opencode-providersources.
Live smoke (Promise v2): run the §3.3 sidecar. Expect language.specificationVersion === "v3" for cursor / grok-4.5. Do not expect mimo models to change from the sidecar alone — native listing is still classic Hooks until an in-process resolveProvider seam exists. Optional: wirePromiseV2().resolveProvider also adapts returned language / sdk via the same profile policy.