Skip to content

feat(sdk): add Kimi Code as a built-in ACP provider (parity contract) #4819

Description

@simonrosenberg

Parent epic: #4820

Goal

Add Kimi Code as a built-in ACP provider, meeting the same bar as claude-code, codex and gemini-cli.

Community PR #4714 by @ysntony is open and implements the core of this. This issue tracks the harness to the full parity contract so "supported" means the same thing it does for the three reference providers.

Parity contract

1. Registry record in openhands-sdk/openhands/sdk/settings/acp_providers.py — every field populated from Kimi's actual server behaviour:

  • key, display_name
  • default_commandnpx -y --prefer-offline <pkg>@<KIMI_VERSION> (may need a trailing arg, as gemini-cli appends --acp)
  • binary_name — the wrapper binary _prefer_pinned_binary() looks for; omit it and Kimi always pays the npx path
  • data_dir_env_var — the env var pointed at a per-conversation dir so credentials, sessions and caches don't leak between runs
  • api_key_env_var, base_url_env_var (or None)
  • default_session_mode — the mode that disables permission prompts
  • agent_name_patterns
  • supports_set_session_model — does the initial model need a protocol call?
  • supports_runtime_model_switch — does mid-conversation switching work?
  • session_meta_key (or None)
  • available_models + default_model — the model picker
  • file_secrets — only if Kimi authenticates from a file on disk rather than an env var

Determine the two supports_* flags from Kimi's real behaviour rather than copying a neighbour's values. Reporting supports_runtime_model_switch=False when the server supports it silently drops a user-visible feature.

2. Version constant — [ ] KIMI_*_VERSION alongside CLAUDE_AGENT_ACP_VERSION / CODEX_ACP_VERSION / GEMINI_CLI_VERSION.

3. Pin-parity test — [ ] add the key to _REGISTRY_ACP_PACKAGES in tests/cross/test_agent_server_build_metadata.py.

Warning

This one is easy to miss and fails silently. test_agent_server_dockerfile_acp_package_versions_match_registry does registry_package = _REGISTRY_ACP_PACKAGES.get(provider_key) followed by if registry_package is None: continue — a provider present in the Dockerfile but absent from that dict is skipped, not flagged. Its pin can then drift from the registry indefinitely with CI green. #4714 currently adds a Dockerfile arm without a dict entry, so the pin would be uncovered.

4. TypeScript mirror — [ ] update clients/typescript/src/models/acp-providers.json.

clients/typescript/scripts/check-acp-drift.py compares the whole registry field-for-field and runs in typescript-client-ci.yml, so this will fail CI if skipped. #4714 does not currently touch this file.

5. Preinstall decision — [ ] decide explicitly whether Kimi joins the image, and record why.

Important

Registry membership is not image membership. Since #4805, a provider absent from the image is not broken — resolve_acp_command() falls back to the version-pinned npx install, cached under ~/.openhands. Adding Kimi to the default INSTALL_ACP_PROVIDERS=claude-code,codex,gemini-cli grows every published image for every user, so registry-only is the default and preinstalling is a deliberate, recorded decision. #4714 currently adds a Dockerfile arm; worth confirming that is intended rather than assumed necessary.

Note the default list is also hardcoded in .github/workflows/server.yml and asserted verbatim by a test, so preinstalling means changing both.

6. Tests — [ ] tests/sdk/settings/test_acp_providers.py (its set(ACP_PROVIDERS) == {...} assertion forces this), plus the paths covering the reference providers: tests/sdk/agent/test_acp_agent.py, tests/agent_server/test_credential_binding.py, test_settings_router.py.

7. Live verification — [ ] a real ACP turn against the actual Kimi server: session created, tool call executed, reply returned. The registry record is a claim about server behaviour; only a live run substantiates it.

Notes

@ysntony's PR is the starting point, not a gap list — the items above are what separates a working addition from parity with the three reference providers, and several are repo conventions that aren't discoverable from the diff alone.


What the existing providers actually set

The contract above is abstract; this is the concrete shape, read from the registry on main. Use it to decide what each field should be for this harness — and to see which values are genuinely provider-specific versus conventional.

Field claude-code codex gemini-cli
display_name Claude Code Codex Gemini CLI
default_command npx -y --prefer-offline @agentclientprotocol/claude-agent-acp@0.63.0 npx -y --prefer-offline @agentclientprotocol/codex-acp@1.1.7 npx -y --prefer-offline @google/gemini-cli@0.46.0 --acp
binary_name claude-agent-acp codex-acp gemini
api_key_env_var ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY
base_url_env_var ANTHROPIC_BASE_URL OPENAI_BASE_URL GEMINI_BASE_URL
data_dir_env_var CLAUDE_CONFIG_DIR CODEX_HOME HOME
default_session_mode bypassPermissions agent-full-access default
agent_name_patterns ["claude-agent"] ["codex-acp"] ["gemini-cli"]
supports_set_session_model True True True
supports_runtime_model_switch True True True
session_meta_key "claudeCode" None None
default_model opus[1m] gpt-5.5 auto
available_models 5 7 7
file_secrets none CODEX_AUTH_JSON GOOGLE_APPLICATION_CREDENTIALS_JSON

What to take from it

  • binary_name and data_dir_env_var are part of the record and easy to overlook. binary_name is what _prefer_pinned_binary() looks for to use an installed binary instead of npx; omit it and the provider always pays the npx path. data_dir_env_var is the env var pointed at a per-conversation directory so credentials, sessions, plugins and caches don't leak between runs.
  • data_dir_env_var is not always a dedicated variable. gemini-cli uses plain HOME, because the CLI has no config-dir override — which is why isolation for it means relocating HOME rather than setting a scoped var. If this harness has a dedicated config-dir variable, prefer it; if it only respects HOME, say so explicitly on the issue.
  • All three set both supports_* flags to True. That makes False the value that needs justifying: it should mean this server genuinely cannot do it, verified against the running server, not not implemented yet. A silent False removes the model picker or mid-conversation switching for users of this harness.
  • session_meta_key is the exception, not the rule — only claude-code sets one ("claudeCode"), because claude-agent-acp ignores session-_meta model selection and needs the model pushed via a protocol call. Default to None unless the server actually reads a _meta key.
  • default_command may carry arguments, not just a package: gemini-cli appends --acp to put the CLI into ACP mode.

file_secrets, if the CLI authenticates from a file

Only needed when the credential is a file on disk rather than an env var. The two live specs:

// codex — env var points at the DIRECTORY containing auth.json
{ "secret_name": "CODEX_AUTH_JSON", "filename": "auth.json",
  "env_var": "CODEX_HOME", "subdir": "codex",
  "env_points_to": "dir", "warn_if_unset": [] }

// gemini-cli — env var points at the FILE itself
{ "secret_name": "GOOGLE_APPLICATION_CREDENTIALS_JSON", "filename": "gcloud-credentials.json",
  "env_var": "GOOGLE_APPLICATION_CREDENTIALS", "subdir": "gemini-cli",
  "env_points_to": "file", "warn_if_unset": ["GOOGLE_CLOUD_PROJECT", "GOOGLE_CLOUD_LOCATION"] }

env_points_to distinguishes the two shapes and is the field most likely to be set wrong — Codex wants a directory, Gemini wants the file. warn_if_unset names companion variables that are useless alone (Gemini's Vertex project and location). The SDK owns the mechanism — materialising the file under the conversation's durable root, seed-if-absent, setting the env var; the spec is only policy.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acpAbout ACPenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions