feat(M3-001): refactor ProviderService into IProviderAdapter pattern (#306)
Some checks failed
ci/woodpecker/push/ci Pipeline failed
Some checks failed
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #306.
This commit is contained in:
125
apps/gateway/src/agent/adapters/ollama.adapter.ts
Normal file
125
apps/gateway/src/agent/adapters/ollama.adapter.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import type { ModelRegistry } from '@mariozechner/pi-coding-agent';
|
||||
import type {
|
||||
CompletionEvent,
|
||||
CompletionParams,
|
||||
IProviderAdapter,
|
||||
ModelInfo,
|
||||
ProviderHealth,
|
||||
} from '@mosaic/types';
|
||||
|
||||
/**
|
||||
* Ollama provider adapter.
|
||||
*
|
||||
* Registers local Ollama models with the Pi ModelRegistry via the OpenAI-compatible
|
||||
* completions API. Configuration is driven by environment variables:
|
||||
* OLLAMA_BASE_URL or OLLAMA_HOST — base URL of the Ollama instance
|
||||
* OLLAMA_MODELS — comma-separated list of model IDs (default: llama3.2,codellama,mistral)
|
||||
*/
|
||||
export class OllamaAdapter implements IProviderAdapter {
|
||||
readonly name = 'ollama';
|
||||
|
||||
private readonly logger = new Logger(OllamaAdapter.name);
|
||||
private registeredModels: ModelInfo[] = [];
|
||||
|
||||
constructor(private readonly registry: ModelRegistry) {}
|
||||
|
||||
async register(): Promise<void> {
|
||||
const ollamaUrl = process.env['OLLAMA_BASE_URL'] ?? process.env['OLLAMA_HOST'];
|
||||
if (!ollamaUrl) {
|
||||
this.logger.debug('Skipping Ollama provider registration: OLLAMA_BASE_URL not set');
|
||||
return;
|
||||
}
|
||||
|
||||
const modelsEnv = process.env['OLLAMA_MODELS'] ?? 'llama3.2,codellama,mistral';
|
||||
const modelIds = modelsEnv
|
||||
.split(',')
|
||||
.map((id: string) => id.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
this.registry.registerProvider('ollama', {
|
||||
baseUrl: `${ollamaUrl}/v1`,
|
||||
apiKey: 'ollama',
|
||||
api: 'openai-completions' as never,
|
||||
models: modelIds.map((id) => ({
|
||||
id,
|
||||
name: id,
|
||||
reasoning: false,
|
||||
input: ['text'] as ('text' | 'image')[],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 8192,
|
||||
maxTokens: 4096,
|
||||
})),
|
||||
});
|
||||
|
||||
this.registeredModels = modelIds.map((id) => ({
|
||||
id,
|
||||
provider: 'ollama',
|
||||
name: id,
|
||||
reasoning: false,
|
||||
contextWindow: 8192,
|
||||
maxTokens: 4096,
|
||||
inputTypes: ['text'] as ('text' | 'image')[],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
}));
|
||||
|
||||
this.logger.log(
|
||||
`Ollama provider registered at ${ollamaUrl} with models: ${modelIds.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
listModels(): ModelInfo[] {
|
||||
return this.registeredModels;
|
||||
}
|
||||
|
||||
async healthCheck(): Promise<ProviderHealth> {
|
||||
const ollamaUrl = process.env['OLLAMA_BASE_URL'] ?? process.env['OLLAMA_HOST'];
|
||||
if (!ollamaUrl) {
|
||||
return {
|
||||
status: 'down',
|
||||
lastChecked: new Date().toISOString(),
|
||||
error: 'OLLAMA_BASE_URL not configured',
|
||||
};
|
||||
}
|
||||
|
||||
const checkUrl = `${ollamaUrl}/v1/models`;
|
||||
const start = Date.now();
|
||||
|
||||
try {
|
||||
const res = await fetch(checkUrl, {
|
||||
method: 'GET',
|
||||
headers: { Accept: 'application/json' },
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
const latencyMs = Date.now() - start;
|
||||
|
||||
if (!res.ok) {
|
||||
return {
|
||||
status: 'degraded',
|
||||
latencyMs,
|
||||
lastChecked: new Date().toISOString(),
|
||||
error: `HTTP ${res.status}`,
|
||||
};
|
||||
}
|
||||
|
||||
return { status: 'healthy', latencyMs, lastChecked: new Date().toISOString() };
|
||||
} catch (err) {
|
||||
const latencyMs = Date.now() - start;
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
return { status: 'down', latencyMs, lastChecked: new Date().toISOString(), error };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* createCompletion is reserved for future direct-completion use.
|
||||
* The current integration routes completions through Pi SDK's ModelRegistry/AgentSession.
|
||||
*/
|
||||
async *createCompletion(_params: CompletionParams): AsyncIterable<CompletionEvent> {
|
||||
throw new Error(
|
||||
'OllamaAdapter.createCompletion is not yet implemented. ' +
|
||||
'Use Pi SDK AgentSession for completions.',
|
||||
);
|
||||
// Satisfy the AsyncGenerator return type — unreachable but required for TypeScript.
|
||||
yield undefined as never;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user