Skip to content

ACP layer: no model catalog, no session/set_model, no reasoning-effort control #813

Description

@maxivillus

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)

  1. 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.

  1. 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).

  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    autonomous: noNeeds your brain: a product/design decision is required before anyone acts.enhancementNew feature or requesttriage: needs-decisionNeeds maintainer decision/design thought

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions