Skip to main content
Control & Safety

Model Selection

query() offers two model-selection modes:
  • Fixed model (default): the entire session uses one model.
  • Dynamic selection: a callback is called before every LLM request to return the model. You can route by purpose (main conversation, sub-agent, context compaction, etc.).
The two modes are mutually exclusive: passing the callback switches to dynamic-selection mode and the fixed-model setting is ignored. In Java, pick one of three options with SessionOptions.Builder.modelSelection(...). ModelSelection.staticModel(...) is the fixed model. Dynamic selection has two callback styles: ModelSelection.dynamic(...) returns only a model ID, and ModelSelection.policy(...) returns a structured result that can carry parameters or keep the CLI's current model. The callback runs only when the CLI needs to choose a model, never during initialization.

Fixed model

Specify it via the model option; omit to use the account default:
import { qodercliAuth, query } from '@qoder-ai/qoder-agent-sdk';

const q = query({
  prompt: 'Analyze this code',
  options: {
    auth: qodercliAuth(),
    model: 'performance',
  },
});

Dynamic selection

Provide a callback function that is invoked before every LLM request:
import { qodercliAuth, query, type ModelPolicyProvider } from '@qoder-ai/qoder-agent-sdk';

const resolveModel: ModelPolicyProvider = (context) => {
  return { model: 'performance' };
};

const q = query({
  prompt: 'Analyze this code',
  options: {
    auth: qodercliAuth(),
    resolveModel,
  },
});
The callback receives the request's purpose, session info, and the list of currently available models. In Java these come from ModelRequestContext methods such as purpose(), sessionId(), and availableModels(); optional fields return Optional.

Routing by purpose

Use a different model per purpose:
const resolveModel: ModelPolicyProvider = (context) => {
  switch (context.purpose) {
    case 'main':       return { model: 'performance' };
    case 'subagent':   return { model: 'efficient' };
    case 'compact':    return { model: 'lite' };
    default:           return { model: 'auto' };
  }
};
Purposes cover scenarios like main conversation, sub-agent, context compaction, WebFetch, and image generation. For the full list, see SDK References.

Returning model parameters

Return parameters with the selected model to override the context window and thinking depth for that LLM request. These keys are camelCase at the SDK control boundary:
const resolveModel: ModelPolicyProvider = (context) => {
  return {
    model: 'ultimate',
    parameters: {
      contextWindow: 200000,
      reasoningEffort: 'high',
    },
  };
};
To return parameters in Java, use ModelSelection.policy(...); the parameters(JsonObject) content is passed to the CLI as is. To keep the model the CLI already chose, return ModelPolicyResult.keepCurrent(). These values should match configurations the current model supports. Context-window and thinking metadata are exposed through the callback's availableModels input in ModelInfo.context_config and ModelInfo.thinking_config. Java's ModelInfo does not model this metadata; read it from ModelInfo.raw().

Timeout

The callback has a default timeout (in milliseconds). Increase it when the callback performs remote I/O:
{ resolveModel, resolveModelTimeoutMs: 800 }
In Java the default timeout is 500 ms, and 0 means no limit.

Runtime operations

While a session is running you can fetch the account's currently available model list:
import { qodercliAuth, query } from '@qoder-ai/qoder-agent-sdk';

const q = query({
  prompt: '...',
  options: { auth: qodercliAuth() },
});

const models = await q.getAvailableModels();
for (const m of models) {
  console.log(`${m.value}\t${m.displayName}\t${m.isEnabled ?? true}`);
}
In Java, call getAvailableModels() on a QoderSession; you can query right after the session opens, without sending input. Pass ModelCatalogOptions to choose explicitly between a live read and the cache. When the CLI omits the isEnabled field, Java's ModelInfo.isEnabled() returns false, unlike the TypeScript example above, which treats it as true. You can also switch the current model in fixed-model mode. For specific method signatures, see SDK References. Java has no method for switching the current model.

Error handling

Dynamic-selection mode has no automatic fallback — a callback that times out, throws, or returns an empty model causes the query to fail. Handle errors inside the callback:
const resolveModel: ModelPolicyProvider = async (context) => {
  try {
    const policy = await fetchRemotePolicy(context);
    return { model: policy.model };
  } catch {
    return { model: 'auto' };   // fallback, must be non-empty
  }
};
In Java, if the callback throws, returns an exceptionally completed stage, or times out, that model request fails and does not fall back to the initial model. exceptionally(...) only covers the callback's own failures; waiting past resolveModelTimeoutMs still fails. These failures are not delivered to the application as exceptions. The SDK only returns an error code to the CLI (MODEL_RESOLUTION_TIMEOUT for a timeout, CALLBACK_EXECUTION_FAILED for a callback failure) and emits an inbound_control_request_failed diagnostic event whose detail contains error=<code>. To observe these failures, register a diagnostics listener, and handle the turn's result through ResultMessage.outcome(). For full field and type definitions, see SDK References.