Task 12 of the slice plan. Implements the slice's only provider:
- generate(): POST {endpoint}/api/generate with Korean-first
prompt; AbortController-driven 120s timeout; parses
body.response as JSON and runs it through parseAiResponse.
- healthCheck(): GET /api/tags; returns ok when configured model
is in the listing, otherwise reports the missing-model reason.
- Constructor takes opts.endpoint / opts.model so Task 30 main
entry can inject INKLING_OLLAMA_ENDPOINT for LAN dogfood;
defaults are http://localhost:11434 and gemma4:e4b.
Tests: 6 unit cases via undici MockAgent (parse, non-JSON,
timeout abort, healthCheck ok / missing / connection error).
Integration test gated by INKLING_INTEGRATION=1 hits real
Ollama for Korean / English-stack / mixed input cases with a
180s per-test budget — also reads INKLING_OLLAMA_ENDPOINT so
LAN dogfood can be exercised end-to-end.
Plan deviation: undici@8 types treat MockAgent's `.reply()`
callback as sync-return-only, so the 500ms-delayed reply used
in the timeout test is cast `as never` to bypass the overload
mismatch. Behavior is correct at runtime; the cast is local to
the test.
Verification: `npx vitest run tests/unit/LocalOllamaProvider.test.ts`
6 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>