194 lines
10 KiB
Markdown
194 lines
10 KiB
Markdown
---
|
|
phase: 28-ollama-integration
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- server/src/services/ollama.ts
|
|
- server/src/routes/ollama.ts
|
|
- server/src/routes/index.ts
|
|
- server/src/app.ts
|
|
- server/src/data/ollama-model-catalog.json
|
|
- server/src/__tests__/ollama-service.test.ts
|
|
autonomous: true
|
|
requirements: [OLLA-01, OLLA-02, OLLA-04, OLLA-05]
|
|
|
|
must_haves:
|
|
truths:
|
|
- "detectOllama() returns installed:true + version when Ollama responds at /api/version"
|
|
- "detectOllama() returns installed:false + installUrl when Ollama is absent or times out"
|
|
- "listOllamaModels() returns model list with parameterSize, family, quantization from /api/tags"
|
|
- "getRecommendedModel() returns highest-quality model that fits within 75% system RAM"
|
|
- "GET /companies/:companyId/ollama/status returns OllamaStatus JSON"
|
|
- "GET /companies/:companyId/ollama/models returns OllamaModel[] + ramGb"
|
|
artifacts:
|
|
- path: "server/src/services/ollama.ts"
|
|
provides: "Ollama detection, model listing, recommendation logic"
|
|
exports: ["detectOllama", "listOllamaModels", "OllamaStatus", "OllamaModel"]
|
|
- path: "server/src/routes/ollama.ts"
|
|
provides: "HTTP routes for Ollama status and model listing"
|
|
exports: ["ollamaRoutes"]
|
|
- path: "server/src/data/ollama-model-catalog.json"
|
|
provides: "Static model catalog for RAM-based recommendations"
|
|
contains: "qwen2.5-coder"
|
|
- path: "server/src/__tests__/ollama-service.test.ts"
|
|
provides: "Unit tests for ollamaService"
|
|
min_lines: 60
|
|
key_links:
|
|
- from: "server/src/routes/ollama.ts"
|
|
to: "server/src/services/ollama.ts"
|
|
via: "import detectOllama, listOllamaModels"
|
|
pattern: "import.*from.*services/ollama"
|
|
- from: "server/src/app.ts"
|
|
to: "server/src/routes/ollama.ts"
|
|
via: "api.use(ollamaRoutes())"
|
|
pattern: "ollamaRoutes"
|
|
---
|
|
|
|
<objective>
|
|
Create the server-side Ollama integration service, HTTP routes, model catalog, and unit tests.
|
|
|
|
Purpose: Provides the backend API surface that the UI (Plan 02) will consume to detect Ollama, list available models, and recommend a model based on system RAM.
|
|
Output: Working server routes at /companies/:companyId/ollama/status and /models, plus unit tests.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/28-ollama-integration/28-RESEARCH.md
|
|
|
|
@server/src/app.ts
|
|
@server/src/routes/index.ts
|
|
@server/src/routes/agents.ts (for assertCompanyAccess pattern)
|
|
@ui/src/api/client.ts (for API client pattern reference)
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 1: Create ollamaService + model catalog + unit tests</name>
|
|
<files>server/src/services/ollama.ts, server/src/data/ollama-model-catalog.json, server/src/__tests__/ollama-service.test.ts</files>
|
|
<read_first>
|
|
- server/src/services/heartbeat.ts (lines 1-30 for import patterns)
|
|
- .planning/phases/28-ollama-integration/28-RESEARCH.md (Pattern 1, Pattern 5, Code Examples)
|
|
</read_first>
|
|
<behavior>
|
|
- detectOllama returns { installed: true, version: "0.5.x" } when fetch to /api/version succeeds
|
|
- detectOllama returns { installed: false, version: null, installUrl: "https://ollama.com/download" } when fetch rejects (ECONNREFUSED)
|
|
- detectOllama returns { installed: false, version: null, installUrl } when fetch times out (AbortController)
|
|
- listOllamaModels returns OllamaModel[] mapped from /api/tags response with name, parameterSize, quantization, sizeBytes, family
|
|
- listOllamaModels returns empty array when Ollama is absent
|
|
- getRecommendedModel marks the highest-quality model that fits within 75% of given RAM budget as recommended=true
|
|
- getRecommendedModel with 8GB RAM recommends a 7b model, not a 32b model
|
|
- Model catalog JSON contains at least qwen2, llama, mistral, phi, deepseek families
|
|
</behavior>
|
|
<action>
|
|
1. Create `server/src/data/ollama-model-catalog.json` with the static catalog from RESEARCH Pattern 5. Include families: qwen2, llama, mistral, phi, deepseek. Each variant has name, ramGb, vramGb, quality fields.
|
|
|
|
2. Create `server/src/services/ollama.ts`:
|
|
- `const OLLAMA_BASE_URL = process.env.OLLAMA_BASE_URL ?? "http://localhost:11434"`
|
|
- `const OLLAMA_TIMEOUT_MS = 3000`
|
|
- `const INSTALL_URL = "https://ollama.com/download"`
|
|
- Export interface `OllamaStatus { installed: boolean; version: string | null; installUrl: string }`
|
|
- Export interface `OllamaModel { name: string; parameterSize: string; quantization: string; sizeBytes: number; family: string; recommended: boolean; recommendationReason: string | null }`
|
|
- Export `async function detectOllama(): Promise<OllamaStatus>` — fetch /api/version with AbortController 3s timeout. On success return installed:true + version. On any error return installed:false + installUrl.
|
|
- Export `async function listOllamaModels(): Promise<OllamaModel[]>` — fetch /api/tags, map response.models to OllamaModel[]. On error return [].
|
|
- Export `function getRecommendedModel(models: OllamaModel[], systemRamBytes: number): OllamaModel[]` — reads catalog JSON, computes usableRamGb = (systemRamBytes / 1024^3) * 0.75, for each model checks if a catalog entry matches by name and fits in RAM. Returns models with `recommended` field set. The highest-quality model within budget gets recommended=true + a recommendationReason string. All others get recommended=false.
|
|
- Anti-pattern: Do NOT poll in a loop. Do NOT hard-code localhost:11434 — always use OLLAMA_BASE_URL.
|
|
|
|
3. Create `server/src/__tests__/ollama-service.test.ts`:
|
|
- Mock global fetch using vi.stubGlobal("fetch", vi.fn())
|
|
- Test detectOllama success case (mock returns { version: "0.5.1" })
|
|
- Test detectOllama failure case (mock rejects with ECONNREFUSED)
|
|
- Test detectOllama timeout case (mock never resolves, verify AbortController fires)
|
|
- Test listOllamaModels success with mock /api/tags response matching OllamaTagsResponse shape from RESEARCH
|
|
- Test listOllamaModels returns [] on fetch error
|
|
- Test getRecommendedModel with 8GB RAM → recommends 7b-class model
|
|
- Test getRecommendedModel with 32GB RAM → recommends 32b-class model
|
|
- Test getRecommendedModel with models not in catalog → recommended=false for all
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus/server && npx vitest run src/__tests__/ollama-service.test.ts</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- grep -q "detectOllama" server/src/services/ollama.ts
|
|
- grep -q "listOllamaModels" server/src/services/ollama.ts
|
|
- grep -q "getRecommendedModel" server/src/services/ollama.ts
|
|
- grep -q "OLLAMA_BASE_URL" server/src/services/ollama.ts
|
|
- grep -q "qwen2.5-coder" server/src/data/ollama-model-catalog.json
|
|
- grep -q "detectOllama" server/src/__tests__/ollama-service.test.ts
|
|
- grep -q "getRecommendedModel" server/src/__tests__/ollama-service.test.ts
|
|
</acceptance_criteria>
|
|
<done>All ollama service tests pass. detectOllama, listOllamaModels, and getRecommendedModel functions exported and tested. Model catalog JSON file exists with 5+ model families.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Create Ollama HTTP routes and mount in app</name>
|
|
<files>server/src/routes/ollama.ts, server/src/routes/index.ts, server/src/app.ts</files>
|
|
<read_first>
|
|
- server/src/routes/agents.ts (lines 1-60 for route pattern, assertCompanyAccess usage)
|
|
- server/src/app.ts (lines 134-170 for route mounting pattern)
|
|
- server/src/routes/index.ts
|
|
</read_first>
|
|
<action>
|
|
1. Create `server/src/routes/ollama.ts`:
|
|
- Import Router from express, import assertCompanyAccess from "./authz.js", import detectOllama/listOllamaModels/getRecommendedModel from "../services/ollama.js"
|
|
- Import `os` for `os.totalmem()`
|
|
- Export function `ollamaRoutes()` returning Router
|
|
- `GET /companies/:companyId/ollama/status`:
|
|
- Call `assertCompanyAccess(req, companyId)`
|
|
- Call `detectOllama()`, return JSON response
|
|
- `GET /companies/:companyId/ollama/models`:
|
|
- Call `assertCompanyAccess(req, companyId)`
|
|
- Call `detectOllama()` first — if not installed, return `{ models: [], ramGb: 0 }`
|
|
- Call `listOllamaModels()`, then `getRecommendedModel(models, os.totalmem())`
|
|
- Return `{ models: enrichedModels, ramGb: Math.round(os.totalmem() / 1073741824) }`
|
|
- Wrap each handler in try/catch, return 500 on unexpected errors
|
|
|
|
2. Add `export { ollamaRoutes } from "./ollama.js"` to `server/src/routes/index.ts`
|
|
|
|
3. In `server/src/app.ts`:
|
|
- Add import: `import { ollamaRoutes } from "./routes/ollama.js"`
|
|
- Add `api.use(ollamaRoutes())` after the `api.use(agentRoutes(db))` line (around line 152)
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus/server && npx tsc --noEmit 2>&1 | head -20</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- grep -q "ollamaRoutes" server/src/routes/ollama.ts
|
|
- grep -q "assertCompanyAccess" server/src/routes/ollama.ts
|
|
- grep -q "/companies/:companyId/ollama/status" server/src/routes/ollama.ts
|
|
- grep -q "/companies/:companyId/ollama/models" server/src/routes/ollama.ts
|
|
- grep -q "ollamaRoutes" server/src/routes/index.ts
|
|
- grep -q "ollamaRoutes" server/src/app.ts
|
|
- grep -q "os.totalmem" server/src/routes/ollama.ts
|
|
</acceptance_criteria>
|
|
<done>Ollama routes mounted at /companies/:companyId/ollama/status and /models. TypeScript compiles without errors. Routes use assertCompanyAccess for auth and os.totalmem() for RAM detection.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `cd /opt/nexus/server && npx vitest run src/__tests__/ollama-service.test.ts` — all tests pass
|
|
- `cd /opt/nexus/server && npx tsc --noEmit` — no type errors
|
|
- Ollama service gracefully returns installed:false when Ollama is not running (no crashes)
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Ollama detection service exists with detectOllama, listOllamaModels, getRecommendedModel
|
|
- Model catalog JSON ships with 5+ model families
|
|
- HTTP routes mounted and accessible at /companies/:companyId/ollama/status and /models
|
|
- Unit tests cover success, failure, timeout, and recommendation scenarios
|
|
- All code compiles without TypeScript errors
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/28-ollama-integration/28-01-SUMMARY.md`
|
|
</output>
|