Summary
The jcode daemon protocol already supports model switching (Request::SetModel), reasoning effort (Request::SetReasoningEffort), and a model catalog, but the ACP server (src/cli/acp.rs) exposes none of them to ACP hosts. As a result:
session/new returns only sessionId — no models block, so ACP hosts that render a model dropdown show it empty (manual entry only).
- There is no
session/set_model method, so hosts cannot switch the model an agent runs on.
- There is no way to set the reasoning/thinking effort for a session.
Why it matters
ACP hosts in the hermes family (Multica, and others following the same conventions) drive agents through ACP JSON-RPC:
- They read the model catalog from the
session/new result: models.availableModels (modelId/name) and models.currentModelId, falling back to configOptions.
- They call
session/set_model with { sessionId, modelId } when the user picks a model, and fail the task if it errors.
- They advertise per-model thinking levels (from the same catalog /
configOptions) and pass the chosen level to the backend.
Without these, jcode is usable only with whatever default model the daemon was started with, and per-agent model / thinking configuration is impossible from the host.
Proposed features (implemented and verified locally in a patch)
- Advertise the model catalog in
session/new:
{
"sessionId": "...",
"models": {
"availableModels": [
{ "modelId": "deepseek-v4-flash", "name": "DeepSeek V4 Flash" },
{ "modelId": "deepseek-v4-pro", "name": "DeepSeek V4 Pro" }
],
"currentModelId": "deepseek-v4-flash"
}
}
The list can be provider-driven (DeepSeek GET /models) or configured; omit the block when empty so hosts fall back to manual entry.
-
Add session/set_model — takes { sessionId, modelId }, forwards Request::SetModel to the daemon, and returns {} on success (or a JSON-RPC error matching the ACP error format, e.g. -32602 for unknown session, -32603 for a failed switch).
-
Reasoning effort — apply a configurable effort per session. A clean approach is an env/config hook (e.g. JCODE_REASONING_EFFORT=none|low|medium|high|max) read at session creation and applied via Request::SetReasoningEffort, so ACP hosts can set it in the child environment.
Additional note: bare provider ids vs. model-name heuristics
We hit a real edge case while implementing this: an unprefixed id like deepseek-v4-flash does not match jcode's global model-name heuristics (provider_for_model in jcode-provider-core), so set_model "deepseek-v4-flash" fell through to the OpenRouter rebind path and failed with OPENROUTER_API_KEY not found. We worked around it by prefixing with the provider (deepseek:<id>) in the ACP layer, but it would be better for the model resolver (or the ACP layer) to route unprefixed ids that belong to the active provider to that provider instead of rebinding to OpenRouter.
Environment
- jcode v0.68.0, Linux x86_64, DeepSeek provider (OpenAI-compatible).
- Verified end-to-end with Multica: model dropdown populated from the advertised catalog;
session/set_model with both bare (deepseek-v4-flash) and prefixed (deepseek:deepseek-v4-flash) ids works; JCODE_REASONING_EFFORT=max is applied at session creation.
Summary
The jcode daemon protocol already supports model switching (
Request::SetModel), reasoning effort (Request::SetReasoningEffort), and a model catalog, but the ACP server (src/cli/acp.rs) exposes none of them to ACP hosts. As a result:session/newreturns onlysessionId— nomodelsblock, so ACP hosts that render a model dropdown show it empty (manual entry only).session/set_modelmethod, so hosts cannot switch the model an agent runs on.Why it matters
ACP hosts in the hermes family (Multica, and others following the same conventions) drive agents through ACP JSON-RPC:
session/newresult:models.availableModels(modelId/name) andmodels.currentModelId, falling back toconfigOptions.session/set_modelwith{ sessionId, modelId }when the user picks a model, and fail the task if it errors.configOptions) and pass the chosen level to the backend.Without these, jcode is usable only with whatever default model the daemon was started with, and per-agent model / thinking configuration is impossible from the host.
Proposed features (implemented and verified locally in a patch)
session/new:{ "sessionId": "...", "models": { "availableModels": [ { "modelId": "deepseek-v4-flash", "name": "DeepSeek V4 Flash" }, { "modelId": "deepseek-v4-pro", "name": "DeepSeek V4 Pro" } ], "currentModelId": "deepseek-v4-flash" } }The list can be provider-driven (DeepSeek
GET /models) or configured; omit the block when empty so hosts fall back to manual entry.Add
session/set_model— takes{ sessionId, modelId }, forwardsRequest::SetModelto the daemon, and returns{}on success (or a JSON-RPC error matching the ACP error format, e.g.-32602for unknown session,-32603for a failed switch).Reasoning effort — apply a configurable effort per session. A clean approach is an env/config hook (e.g.
JCODE_REASONING_EFFORT=none|low|medium|high|max) read at session creation and applied viaRequest::SetReasoningEffort, so ACP hosts can set it in the child environment.Additional note: bare provider ids vs. model-name heuristics
We hit a real edge case while implementing this: an unprefixed id like
deepseek-v4-flashdoes not match jcode's global model-name heuristics (provider_for_modelinjcode-provider-core), soset_model "deepseek-v4-flash"fell through to the OpenRouter rebind path and failed withOPENROUTER_API_KEY not found. We worked around it by prefixing with the provider (deepseek:<id>) in the ACP layer, but it would be better for the model resolver (or the ACP layer) to route unprefixed ids that belong to the active provider to that provider instead of rebinding to OpenRouter.Environment
session/set_modelwith both bare (deepseek-v4-flash) and prefixed (deepseek:deepseek-v4-flash) ids works;JCODE_REASONING_EFFORT=maxis applied at session creation.