# Wiring a Model into SolonCode: Dialects, the apiUrl Rules, and the Timeouts Nobody Warns You About

> Source: <https://dev.to/solonjava/wiring-a-model-into-soloncode-dialects-the-apiurl-rules-and-the-timeouts-nobody-warns-you-about-14ke>
> Published: 2026-08-03 22:29:49+00:00

SolonCode ships with no model. That is a deliberate product choice, not an omission: no bundled provider, no default key, no telemetry-backed endpoint you did not ask for. The upside is you can point it at DeepSeek, a local Ollama box, or an air-gapped corporate gateway with equal ease. The downside is that installing the binary does not give you a working assistant. You have to wire a model up first, and that is where most first-time users stall.

This is the follow-up to getting SolonCode installed. Here we get one model connected and *stable* — meaning it survives long tool-calling sessions, weak networks, and multi-file refactors without mysterious mid-task failures.

You can hand-edit `settings.json`

. You should not, at least not on day one. The settings page validates your input and gives you a **Test connection** button, which is the single most useful debugging affordance in the whole product.

```
soloncode web        # default port 4808
soloncode web 0      # pick any free port
soloncode web 1212   # explicit port
```

Then go to **Settings → LLM**, add a model, and hit test. Do not skip the test. A green test is the boundary between "config problem" and "everything else."

If the port is taken, `soloncode web 0`

sidesteps it. That is the official answer too.

This is the part that saves the most time. SolonCode sits on Solon AI, and Solon AI does not have a list of supported *vendors*. It has a list of supported **chat dialects** — request/response shapes. Any service that speaks one of those shapes works.

The built-in dialects and how they are detected:

| Dialect | Detected by |
|---|---|
`openai` |
default, or `apiUrl` ending in `.../chat/completions`
|
`openai-responses` |
`standard=openai-responses` , or `apiUrl` ending in `.../v1/responses`
|
`ollama` |
`standard=ollama` |
`gemini` |
`standard=gemini` , or `apiUrl` matching `.../v1beta/models/*` (usable since v3.8.1) |
`gemini-interactions` |
`standard=gemini-interactions` , or `.../v1beta/interactions/*` (usable since v4.0.3) |
`anthropic` |
`standard=anthropic` , or `apiUrl` ending in `.../v1/messages` (usable since v3.9.1) |
`dashscope` |
`standard=dashscope` , or `apiUrl` matching `.../v1/services/*`
|

`ChatConfig`

picks the dialect from either `standard`

or the shape of `apiUrl`

. So the question is never "does SolonCode support vendor X" — it is "which of these seven shapes does vendor X's endpoint speak."

Two useful notes straight from the docs: Claude also offers an OpenAI-compatible mode, so you can run it through the `openai`

dialect instead of `anthropic`

. Alibaba's Bailian exposes both its native DashScope protocol and an OpenAI-compatible one. When a provider gives you both, **the OpenAI-compatible endpoint is almost always the faster path to a green test.**

`apiUrl`

Underneath, a model entry maps to Solon AI's `ChatConfig`

:

| Field | Required | Notes |
|---|---|---|
`apiUrl` |
yes | the endpoint address |
`apiKey` |
depends | token; may be empty for unauthenticated local services |
`model` |
yes | the API model id, not the console display name |
`standard` |
no | interface spec; defaults to `openai`
|
`timeout` |
no |
`Duration` , defaults to 60s
|
`headers` |
no | extra headers — other options are all passed as headers |
`proxy` |
no | network proxy |
`contextLength` |
no | model context length; the docs flag this one as important |
`defaultAutoToolCall` |
no | defaults to `true` , since v3.8.4 |

There is also `provider`

, which served the role of `standard`

before v4.0. If you are reading an old config, that is what it is.

Now the `apiUrl`

rules, which are where the real failures live:

`/chat/completions`

or `/api/chat`

) and the dialect is inferred from the tail.`standard`

`#`

`#`

onward is stripped.One honest wrinkle: the `ChatConfig`

reference describes `apiUrl`

as "the full address, not a baseUrl," while the dialect page explicitly supports baseUrl + `standard`

. Both statements are in the official docs. My practical read: **paste the full address.** It removes a whole class of ambiguity, and the `#`

rule exists precisely for the cases where appending would break you.

Field shapes are stable. Hostnames and model ids are not — providers rename models and retire old ones. Always confirm the current id in the vendor console, and never put a real key in a doc, a screenshot, or a commit.

**A cloud provider over an OpenAI-compatible endpoint** — the common case, and the one to start with:

```
apiUrl: "https://api.example.com/v1/chat/completions"
apiKey: "sk-xxxxxx"
model: "<copy the exact id from the console>"
timeout: "180s"
```

Find the "OpenAI compatible" base URL in your provider's console, leave `standard`

at its default, and copy the model id rather than typing it from memory. The console's display name is frequently not the API id.

**A local Ollama instance**, from the official example:

```
ChatModel chatModel = ChatModel.of("http://127.0.0.1:11434/api/chat")
        .standard("ollama")
        .model("llama3.2")
        .build();
```

The `model`

value is whatever you pulled — if you ran `ollama run deepseek-r1:7b`

, you write `deepseek-r1:7b`

. Note that the dialect table lists only `standard=ollama`

as the detection rule; the `/api/chat`

tail-matching behavior appears in the dialect's source, not the table. Declaring `standard`

explicitly is the safe move.

Also worth setting expectations: a small local model that connects fine may still be unreliable at multi-file refactors and long tool chains. That is a capability ceiling, not a config bug.

**A corporate gateway.** Fill `apiUrl`

per the gateway's own docs, put tenant or custom auth headers in `headers`

, use the model names from the gateway's allow-list rather than the public cloud names, and if the gateway path is unusual, apply the `#`

suffix. Keep this one in the **workspace** scope so a client-specific gateway does not leak into every other project on your machine.

Settings persist to `settings.json`

in one of two places:

| Scope | Path | Good for |
|---|---|---|
| user | `~/.soloncode/settings.json` |
models, skill pool, and MCP servers shared across projects |
| workspace | `.soloncode/settings.json` |
models, APIs, LSP, and mounts for this project only |

The merge order is documented plainly: the user-level file is read first, then the workspace file, and **workspace config overrides or supplements user config**. Saving from the settings page tries to write into the running engine, so a restart is often unnecessary — the docs say "not necessarily," so if your model list does not refresh, restarting the web process is the cheap fix.

Practical split: your personal default model goes global; anything client- or gateway-specific goes workspace.

If `.soloncode/settings.json`

holds a real key, it does not belong in version control:

```
.soloncode/settings.json
.soloncode/logs/
```

Ship a `settings.example.json`

with placeholder values instead, and let people copy it locally.

One exposure path deserves explicit attention: **if you bind the web UI to anything beyond localhost, set webAuthUser and webAuthPass.** Leave them blank and authentication is off. An unauthenticated SolonCode web endpoint on your LAN hands over your conversations, your tool permissions, and — since the keys live in the settings file it manages — effectively your API credentials. Sandboxing (

`sandboxMode`

, `sandboxAllowUserHome`

, both default `true`

) and human-in-the-loop review are a separate line of defense. Getting a model connected is not a reason to switch either of them off.A coding agent makes many round trips per task, each carrying context and tool descriptions. So connectivity is necessary but not sufficient. The general settings that govern stability, with their documented defaults:

| Setting | Default | What it controls |
|---|---|---|
`apiRetries` |
3 | API retry count |
`modelRetries` |
3 | model retry count |
`mcpRetries` |
3 | MCP retry count |
`maxTurns` |
20 | max rounds in a single task |
`autoRethink` |
true | allow self-reflection retries |
`logLevel` |
INFO | TRACE / DEBUG / INFO / WARN / ERROR |
`logMaxHistory` |
7 | days of log archives kept |

Retries help with flaky networks and nothing else. A 401 or a wrong model id will fail identically three times and just triple your log volume.

That 60-second `timeout`

default deserves a second look. For plain chat it is fine. For a reasoning model chewing on a long context behind a slow proxy, it is easy to hit. Raising it is reasonable; raising it to effectively-infinite is not, because an invisible hang is harder to diagnose than a fast failure.

Long tasks accumulate conversation turns and tool results. Compression summarizes older content to relieve context pressure. Two settings and a naming caveat:

`compressionThresholdPercent`

defaults to **75** — compression triggers when 75% of the context window is used. Both doc pages agree on this one.

The message-count trigger defaults to **100**, but appears under two different names in the docs: the settings reference calls it `summaryWindowSize`

, while the compression guide calls it `compressionThresholdMessages`

. Similarly, `sessionWindowSize`

(how many recent messages a new instruction carries) is documented as **8** on one page and **12** on the other. I am not going to guess which is authoritative — check what your settings page actually shows and treat that as truth for your version.

The official tuning ranges, by model context window:

| Context | Message threshold | Percent threshold | Rationale |
|---|---|---|---|
| 128k | 30–50 | 65–70 | balanced, fits most coding tasks |
| 256k | 60–100 | 70–75 | keeps more tool chain, suits complex refactors |
| 1m | 100–200 | 75–80 | compress less; watch cost and latency |

And the symptom-to-knob mapping, which is the genuinely useful part:

| Symptom | Adjustment |
|---|---|
| agent keeps re-reading a file it just read | raise the message threshold |
| model reports context overflow | lower the percent threshold |
| summarization is slow or expensive | raise both thresholds |
| small model flaky on complex tasks | lower `sessionWindowSize`
|

If a task consistently gets cut off mid-way, `maxTurns`

may be too low. But if the model is spinning in circles, tighten the task description instead. More turns will not fix an underspecified request.

The official checklist is short: verify `apiUrl`

, `apiKey`

, the model name, and your network proxy. Expanded into something you can actually run through:

`apiUrl`

the chat endpoint, not the vendor's homepage? Did you drop the `/v1`

or `/chat/completions`

segment?`standard=ollama`

set, or the full `/api/chat`

address used?`#`

suffix.Still stuck? Set `logLevel`

to DEBUG or TRACE, reproduce the failure once, and read `.soloncode/logs/`

. Grep for `timeout`

, `401`

, `403`

, `dialect`

, `model`

. On dialect mismatch specifically, the docs name two causes: a wrong `standard`

, or a missing dependency package.

There is one more failure mode worth naming, because it looks like a config problem and is not: **the connection tests green but the coding output is poor.** Check whether you launched from the project root (wrong working directory means no project context), whether the project has an `AGENTS.md`

describing build and test commands, and whether the session is simply too polluted — a fresh session often fixes it. If none of that helps, you are looking at a model capability limit, and the fix is a different model, not a different config value.

Run these in your project root before trusting the setup with real work:

| Step | Input | Pass condition |
|---|---|---|
| 1 | `hello` |
clean response, no auth error |
| 2 | "list the top-level files in this workspace, change nothing" | output matches `ls`
|
| 3 | same question on a second model, if configured | both answer |
| 4 | temporarily break the key, hit Test connection | it fails — proving the button is honest |
| 5 | restore the key, test again | green |

Step 4 is the one people skip and the one that matters most. A test button you have never seen fail is a test button you cannot trust.

Once all five pass, add a second model and split your traffic: a cheap fast model as the default for reading code and small fixes, a stronger one you switch to deliberately for cross-module refactors and hard bugs. Running every task on your most expensive model is the fastest way to a surprising invoice, and it is rarely the difference between success and failure. Task description quality usually matters more than model tier.

| Topic | Link |
|---|---|
| SolonCode |
|

Defaults and field names shift between versions, and the two documentation pages disagree on a couple of them today. When in doubt, the settings page in your installed build is the authority. Model ids and endpoint paths belong to your provider — re-check those in the console before you file a bug against the config.
