From 88cc2e505efeeaf6a0449bcf4a5c8c4335367982 Mon Sep 17 00:00:00 2001 From: "timothy.vang" Date: Thu, 9 Jul 2026 17:27:33 +0000 Subject: [PATCH] test(opencode): lock Ollama OpenAI-compat baseURL /v1 contract Document that caseforge agent 404 (plain "404 page not found") is from a bare Ollama root missing /v1, not a provider path bug in this runtime. Preserve baseURL as configured; optional smoke for live /v1/chat/completions. --- VERDICT-FORK.md | 20 ++++++ .../opencode/test/provider/provider.test.ts | 50 ++++++++++++++ scripts/check-ollama-openai-compat.sh | 68 +++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100755 scripts/check-ollama-openai-compat.sh diff --git a/VERDICT-FORK.md b/VERDICT-FORK.md index 43a42d3..9662a83 100644 --- a/VERDICT-FORK.md +++ b/VERDICT-FORK.md @@ -44,6 +44,26 @@ the client to providers' billing APIs).

Compiled VERDICT binary

The compiled standalone verdict binary — VERDICT wordmark + titlebar, no auto-update dialog.

+## Local LLM / Ollama OpenAI-compat + +caseforge drives this runtime with a custom `@ai-sdk/openai-compatible` provider +whose `options.baseURL` is `VERDICT_LLM_BASEURL`. The SDK POSTs +`${baseURL}/chat/completions`. + +| `VERDICT_LLM_BASEURL` | Request path | Typical Ollama response | +|---|---|---| +| `http://host:11434/v1` | `/v1/chat/completions` | OpenAI-shaped JSON (success or `model … not found`) | +| `http://host:11434` (no `/v1`) | `/chat/completions` | plain `404 page not found` | + +This fork does **not** rewrite bare Ollama roots — missing `/v1` is a caller +config issue (normalize in caseforge / env). Residual checks: + +```bash +bash scripts/check-httpapi-error-contracts.sh # always offline +# optional live probe (SKIP when host unset/down): +VERDICT_LLM_BASEURL=http://host:11434/v1 bash scripts/check-ollama-openai-compat.sh +``` + ## Build & run Same as upstream (Bun ≥ 1.3.14, no Go needed): diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 18ec8f9..18ca14b 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -847,6 +847,56 @@ it.instance( }, ) +// Ollama OpenAI-compat: @ai-sdk/openai-compatible POSTs ${baseURL}/chat/completions. +// Ollama only serves that under /v1; a bare host:port root yields "404 page not found". +// This runtime does not auto-append /v1 — callers (caseforge VERDICT_LLM_BASEURL) must. +it.instance( + "Ollama-style openai-compatible baseURL with /v1 is preserved on the provider", + Effect.gen(function* () { + const providers = yield* list + const id = ProviderV2.ID.make("ollama-local") + expect(providers[id].options.baseURL).toBe("http://10.126.60.100:11434/v1") + expect(providers[id].models["local"].api.npm).toBe("@ai-sdk/openai-compatible") + }), + { + config: { + provider: { + "ollama-local": { + name: "Ollama local", + npm: "@ai-sdk/openai-compatible", + env: [], + models: { local: { name: "Local", tool_call: true, limit: { context: 8192, output: 2048 } } }, + options: { apiKey: "local", baseURL: "http://10.126.60.100:11434/v1" }, + }, + }, + }, + }, +) + +it.instance( + "Ollama-style openai-compatible baseURL without /v1 is not rewritten by the provider", + Effect.gen(function* () { + const providers = yield* list + const id = ProviderV2.ID.make("ollama-bare") + // Intentionally bare: documents that missing /v1 is a caller config bug, not engine path join. + expect(providers[id].options.baseURL).toBe("http://10.126.60.100:11434") + expect(String(providers[id].options.baseURL).endsWith("/v1")).toBe(false) + }), + { + config: { + provider: { + "ollama-bare": { + name: "Ollama bare root", + npm: "@ai-sdk/openai-compatible", + env: [], + models: { local: { name: "Local", tool_call: true, limit: { context: 8192, output: 2048 } } }, + options: { apiKey: "local", baseURL: "http://10.126.60.100:11434" }, + }, + }, + }, + }, +) + // Edge cases for model configuration it.instance( diff --git a/scripts/check-ollama-openai-compat.sh b/scripts/check-ollama-openai-compat.sh new file mode 100755 index 0000000..12916d5 --- /dev/null +++ b/scripts/check-ollama-openai-compat.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Optional smoke: Ollama OpenAI-compat /v1/chat/completions is reachable. +# +# Distinguishes: +# - correct base (.../v1) → OpenAI-shaped error JSON for unknown model (route exists) +# - bare root (no /v1) → plain "404 page not found" (the m13 agent-path failure mode) +# +# Offline-friendly: SKIP (exit 0) when host is unset or unreachable. +# Usage: +# VERDICT_LLM_BASEURL=http://host:11434/v1 bash scripts/check-ollama-openai-compat.sh +# OLLAMA_HOST=http://host:11434 bash scripts/check-ollama-openai-compat.sh +set -euo pipefail + +raw="${VERDICT_LLM_BASEURL:-${OLLAMA_HOST:-${CASEFORGE_SPARK_ENDPOINT:-}}}" +if [[ -z "${raw}" ]]; then + echo "SKIP: set VERDICT_LLM_BASEURL, OLLAMA_HOST, or CASEFORGE_SPARK_ENDPOINT" + exit 0 +fi + +# Strip trailing slash; strip a single trailing /v1 for origin probes. +origin="${raw%/}" +origin="${origin%/v1}" +v1_chat="${origin}/v1/chat/completions" +bare_chat="${origin}/chat/completions" +tags="${origin}/api/tags" + +if ! curl -sS -m 2 -o /dev/null "${tags}"; then + echo "SKIP: Ollama unreachable at ${origin} (tags probe failed)" + exit 0 +fi + +body='{"model":"__opencode_ollama_smoke_missing__","messages":[{"role":"user","content":"ping"}]}' + +v1_body="$(mktemp)" +bare_body="$(mktemp)" +trap 'rm -f "${v1_body}" "${bare_body}"' EXIT + +v1_code="$(curl -sS -m 5 -o "${v1_body}" -w "%{http_code}" \ + -X POST "${v1_chat}" -H "Content-Type: application/json" -d "${body}")" +bare_code="$(curl -sS -m 5 -o "${bare_body}" -w "%{http_code}" \ + -X POST "${bare_chat}" -H "Content-Type: application/json" -d "${body}")" + +v1_text="$(cat "${v1_body}")" +bare_text="$(cat "${bare_body}")" + +# Route under /v1 must not be the Go/Ollama plain page-not-found body. +if [[ "${v1_text}" == *"404 page not found"* ]]; then + echo "FAIL: ${v1_chat} returned plain page-not-found (http=${v1_code})" + echo " body: ${v1_text}" + exit 1 +fi + +# Accept any non-page-not-found response that looks like OpenAI-compat JSON error or success. +if [[ "${v1_text}" != *"error"* && "${v1_text}" != *"choices"* ]]; then + echo "FAIL: ${v1_chat} unexpected body (http=${v1_code}): ${v1_text}" + exit 1 +fi + +# Document the bare-root failure mode (expected when baseURL omits /v1). +if [[ "${bare_text}" != *"404 page not found"* ]]; then + echo "WARN: bare ${bare_chat} did not return page-not-found (http=${bare_code}); body=${bare_text}" +fi + +echo "PASS: Ollama OpenAI-compat reachable" +echo " origin=${origin}" +echo " v1_chat=${v1_chat} http=${v1_code}" +echo " bare_chat=${bare_chat} http=${bare_code} (expect page-not-found when /v1 omitted from baseURL)" +echo " note: @ai-sdk/openai-compatible POSTs \${baseURL}/chat/completions — baseURL must include /v1"