Skip to content

feat(providers): add Ollama System One handler and decision-model discovery - #15410

Draft
yourspraveen wants to merge 4 commits into
diegosouzapw:release/v3.8.52from
yourspraveen:feat/ollama-systemone
Draft

yourspraveen wants to merge 4 commits into
diegosouzapw:release/v3.8.52from
yourspraveen:feat/ollama-systemone

Conversation

@yourspraveen

@yourspraveen yourspraveen commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Draft: this depends on the shared POST /v1/systemone route, which is still being decided on #13987. Candidates: #14031, #14498, #14667, and now #15278 (a TypeSafe System One client, no public route yet). Once a base is chosen, I'll rebase onto it, connect this handler to the route, and mark the PR ready.

⚠️ base-red inherited: #15306

What

Ollama ships a local System One API (POST /v1/systemone, docs). It uses the same request/response format as TypeSafe Jev (choice / noul / score questions with calibrated probabilities). This PR adds the Ollama-specific pieces so ollama-local/<model> can serve it alongside the TypeSafe / OpenRouter backends proposed upstream. Models covered:

  • Nimble and Tev (Ollama ≥ 0.35.0)
  • Clef (27B) and Clef Flash (9B), tagged vision + decision (Ollama ≥ 0.35.1)

1. Discovery

2. Handler (open-sse/handlers/ollamaSystemOne.ts)

  • Validation matches Ollama's own limits: 400 for invalid bodies, and 413 above 64 KiB, or above 32 MiB when images are present.
  • Forwards only known fields, with the provider prefix stripped.
  • images support for Clef / Clef Flash: base64 only. URLs and data URLs are rejected with a 400, as Ollama does.
  • 60s default timeout to cover cold model loads; echoes the caller's model id; logs usage at $0 cost.
  • Failure mapping:
    • 404 missing model → model lockout
    • 400 "not supported by System One" → returned as-is
    • 5xx / unreachable / timeout → connection cooldown
    • client abort → nothing marked
    • errors go through errorResponse() sanitization

3. Decision-only guard: keeping System One models out of the chat path

Ollama reports Clef as ["vision","decision"] with no completion. Before this guard, such a row looked like a chat model: /v1/models gave it no type, and a chat call went upstream only to come back as a 400.

  • modelEndpointPolicy: a decision kind for systemone-only rows (chatSelectable: false). Nimble and Tev also advertise chat, so they stay chat.
  • /v1/models tags systemone-only rows type: "decision", alongside embedding / rerank / image.
  • decisionOnlyChatRejection runs in getModelInfoOrRetirementResponse, the resolver behind resolveModelOrError, which handles both direct chat calls and every combo target. It refuses such a model with a clear 400 before any credential is picked, so nothing is marked on the connection. The message reads as a model-scoped 400, so a combo advances to its next target (comboTargetDecision → advance). Only System One-capable providers (ollama-local) pay the stored-model lookup.

Related

Testing

  • tests/unit/ollama-systemone.test.ts (23):
    • capability mapping, including Clef's vision + decision
    • validation, including images: base64 forwarded, URL / data URL / non-string rejected, the 32 MiB vs 64 KiB limits
    • URL building and failure classification
    • handler behaviour with injected fetch
  • tests/unit/ollama-systemone-decision-guard.test.ts (5):
    • policy kind
    • /v1/models type
    • the 400 and that combos advance on it
    • chat / unknown / other-provider models untouched, with no lookup for other providers
    • resolveModelOrError refusing the model
  • Regression: 102 test files touching the endpoint policy, the catalog classification and model resolution: 709 pass, 2 skipped, 0 fail.
  • Live run against Ollama 0.35.0 on a Jetson:
    • tev1:4b → 200 (1.5s)
    • nimble → 200 (2.2s)
    • unpulled model → 404 + model lockout
    • gemma3:4b → 400, connection left untouched
  • Clef / Clef Flash have not been run live yet; they need Ollama ≥ 0.35.1 and 11–18 GB.

Local quality gates

I ran the quality.yml fast-gate set locally against release/v3.8.52. The branch is rebased onto the current tip (23a1148486).

  • Passing (36), including complexity-ratchets, file-size, mutation-test-coverage, typecheck:core, dashboard typecheck, cycles, and ESLint --max-warnings 0 on the changed files. The first run caught two problems:
    • handleOllamaSystemOne was at complexity 33. It's now split into small helpers with no behaviour change.
    • The handler test was missing from stryker.conf.json. It's added now.
  • Not run locally: workflows, which needs actionlint and zizmor. This PR doesn't touch workflows.

Remaining before ready

  • Rebase onto the chosen /v1/systemone base PR and connect the handler to the route
  • Docs: docs/openapi.yaml, API_REFERENCE.md, LOCAL_OLLAMA_DOCKER.md
  • changelog.d/features/15410-ollama-systemone.md
  • Live test of Clef / Clef Flash (images path included) on Ollama ≥ 0.35.1

Refs #13987

…covery

Ollama 0.35 ships a local System One API (POST /v1/systemone) that answers
typed choice / noul / score questions with calibrated probabilities, using
the same request/response format as TypeSafe Jev. This adds the
Ollama-specific pieces so `ollama-local/<model>` can serve that API once the
shared /v1/systemone route lands (coordination on diegosouzapw#13987).

- Map Ollama's `decision` capability (Nimble, Tev models) to a new
  `systemone` supported-endpoint value, same spelling as diegosouzapw#14498.
- A decision-only model no longer falls through to the images apiFormat.
- open-sse/handlers/ollamaSystemOne.ts: validation mirroring Ollama's own
  limits (400 / 413 for >64 KiB), forwards only known fields with the
  provider prefix stripped, 60s default timeout for cold model loads,
  echoes the caller's model id, logs usage at $0 cost.
- Failure mapping: 404 missing model -> model lockout; 400 "not supported
  by System One" -> returned as-is; 5xx / unreachable / timeout ->
  connection cooldown; client abort -> nothing marked. Errors go through
  errorResponse() sanitization.

Validated live against Ollama 0.35.0 with nimble and tev1:4b:
tev1:4b 200 (1.5s), nimble 200 (2.2s), unpulled model 404 + lockout,
gemma3:4b 400 with the connection untouched.

Refs diegosouzapw#13987
…Flash

Ollama 0.35.1 adds Clef (27B) and Clef Flash (9B), System One models tagged
`vision` + `decision`. The System One API takes base64 `images` shared by
all questions (URLs and data URLs are not supported) and accepts bodies up
to 32 MiB when images are present, versus 64 KiB without.

The handler dropped `images` as an unknown field and capped every body at
64 KiB, so a Clef request could not use its vision weights and any real
image was refused with 413. `images` is now validated (non-empty raw
base64, URLs/data URLs rejected with 400) and forwarded, and the 32 MiB
limit applies when images are present. Decision-model discovery already
maps Clef's `vision` + `decision` to the systemone endpoint with
supportsVision; a test now pins that.

Refs diegosouzapw#13987
… path

Ollama reports Clef / Clef Flash as `["vision", "decision"]` with no
`completion`, and discovery stores that as supportedEndpoints
["systemone"]. Such a row reached /v1/models with no `type`, so agents and
the OpenCode plugins listed it as a chat model, and a chat call went
upstream only to come back as a 400.

- modelEndpointPolicy: a `decision` kind for systemone-only rows
  (chatSelectable false); a model that also serves chat stays chat.
- classifyModelSupportedEndpoints: systemone-only rows are tagged
  `type: "decision"` in /v1/models, alongside embedding/rerank/image.
- decisionOnlyChatRejection, called from getModelInfoOrRetirementResponse
  (the resolver behind resolveModelOrError, used by direct chat calls and
  every combo target): refuses a decision-only model with a clear 400
  before any credential is picked, so nothing is marked on the connection.
  The message reads as a model-scoped 400, so a combo advances to its next
  target. Only System One-capable providers (ollama-local) pay the lookup.

Refs diegosouzapw#13987
…mplexity gates

handleOllamaSystemOne (complexity 33, cognitive 19) is split into
buildUpstreamBody / buildUpstreamHeaders / resolveUpstreamUrl /
classifyFetchFailure / parseJsonText / readAnswersPayload /
clearRecoveredConnection / buildSuccessResponse, and the decision-only test
in classifyModelSupportedEndpoints moves to isDecisionOnly, so no touched
function exceeds the complexity ratchets. Behaviour unchanged (28 tests).

tests/unit/ollama-systemone.test.ts covers open-sse/services/accountFallback.ts,
a mutated module, so it joins stryker.conf.json tap.testFiles.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant