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:
@@ -34,9 +34,9 @@ describe('ProviderService', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('skips API-key providers when env vars are missing (no models become available)', () => {
|
||||
it('skips API-key providers when env vars are missing (no models become available)', async () => {
|
||||
const service = new ProviderService();
|
||||
service.onModuleInit();
|
||||
await service.onModuleInit();
|
||||
|
||||
// Pi's built-in registry may include model definitions for all providers, but
|
||||
// without API keys none of them should be available (usable).
|
||||
@@ -54,11 +54,11 @@ describe('ProviderService', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('registers Anthropic provider with correct models when ANTHROPIC_API_KEY is set', () => {
|
||||
it('registers Anthropic provider with correct models when ANTHROPIC_API_KEY is set', async () => {
|
||||
process.env['ANTHROPIC_API_KEY'] = 'test-anthropic';
|
||||
|
||||
const service = new ProviderService();
|
||||
service.onModuleInit();
|
||||
await service.onModuleInit();
|
||||
|
||||
const providers = service.listProviders();
|
||||
const anthropic = providers.find((p) => p.id === 'anthropic');
|
||||
@@ -77,11 +77,11 @@ describe('ProviderService', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('registers OpenAI provider with correct models when OPENAI_API_KEY is set', () => {
|
||||
it('registers OpenAI provider with correct models when OPENAI_API_KEY is set', async () => {
|
||||
process.env['OPENAI_API_KEY'] = 'test-openai';
|
||||
|
||||
const service = new ProviderService();
|
||||
service.onModuleInit();
|
||||
await service.onModuleInit();
|
||||
|
||||
const providers = service.listProviders();
|
||||
const openai = providers.find((p) => p.id === 'openai');
|
||||
@@ -90,11 +90,11 @@ describe('ProviderService', () => {
|
||||
expect(openai!.models.map((m) => m.id)).toEqual(['gpt-4o', 'gpt-4o-mini', 'o3-mini']);
|
||||
});
|
||||
|
||||
it('registers Z.ai provider with correct models when ZAI_API_KEY is set', () => {
|
||||
it('registers Z.ai provider with correct models when ZAI_API_KEY is set', async () => {
|
||||
process.env['ZAI_API_KEY'] = 'test-zai';
|
||||
|
||||
const service = new ProviderService();
|
||||
service.onModuleInit();
|
||||
await service.onModuleInit();
|
||||
|
||||
const providers = service.listProviders();
|
||||
const zai = providers.find((p) => p.id === 'zai');
|
||||
@@ -103,13 +103,13 @@ describe('ProviderService', () => {
|
||||
expect(zai!.models.map((m) => m.id)).toEqual(['glm-4.5', 'glm-4.5-air', 'glm-4.5-flash']);
|
||||
});
|
||||
|
||||
it('registers all three providers when all keys are set', () => {
|
||||
it('registers all three providers when all keys are set', async () => {
|
||||
process.env['ANTHROPIC_API_KEY'] = 'test-anthropic';
|
||||
process.env['OPENAI_API_KEY'] = 'test-openai';
|
||||
process.env['ZAI_API_KEY'] = 'test-zai';
|
||||
|
||||
const service = new ProviderService();
|
||||
service.onModuleInit();
|
||||
await service.onModuleInit();
|
||||
|
||||
const providerIds = service.listProviders().map((p) => p.id);
|
||||
expect(providerIds).toContain('anthropic');
|
||||
@@ -117,11 +117,11 @@ describe('ProviderService', () => {
|
||||
expect(providerIds).toContain('zai');
|
||||
});
|
||||
|
||||
it('can find registered Anthropic models by provider+id', () => {
|
||||
it('can find registered Anthropic models by provider+id', async () => {
|
||||
process.env['ANTHROPIC_API_KEY'] = 'test-anthropic';
|
||||
|
||||
const service = new ProviderService();
|
||||
service.onModuleInit();
|
||||
await service.onModuleInit();
|
||||
|
||||
const sonnet = service.findModel('anthropic', 'claude-sonnet-4-6');
|
||||
expect(sonnet).toBeDefined();
|
||||
@@ -129,11 +129,11 @@ describe('ProviderService', () => {
|
||||
expect(sonnet!.id).toBe('claude-sonnet-4-6');
|
||||
});
|
||||
|
||||
it('can find registered Z.ai models by provider+id', () => {
|
||||
it('can find registered Z.ai models by provider+id', async () => {
|
||||
process.env['ZAI_API_KEY'] = 'test-zai';
|
||||
|
||||
const service = new ProviderService();
|
||||
service.onModuleInit();
|
||||
await service.onModuleInit();
|
||||
|
||||
const glm = service.findModel('zai', 'glm-4.5');
|
||||
expect(glm).toBeDefined();
|
||||
|
||||
1
apps/gateway/src/agent/adapters/index.ts
Normal file
1
apps/gateway/src/agent/adapters/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { OllamaAdapter } from './ollama.adapter.js';
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,43 @@
|
||||
import { Injectable, Logger, type OnModuleInit } from '@nestjs/common';
|
||||
import { ModelRegistry, AuthStorage } from '@mariozechner/pi-coding-agent';
|
||||
import { getModel, type Model, type Api } from '@mariozechner/pi-ai';
|
||||
import type { ModelInfo, ProviderInfo, CustomProviderConfig } from '@mosaic/types';
|
||||
import type {
|
||||
CustomProviderConfig,
|
||||
IProviderAdapter,
|
||||
ModelInfo,
|
||||
ProviderHealth,
|
||||
ProviderInfo,
|
||||
} from '@mosaic/types';
|
||||
import { OllamaAdapter } from './adapters/index.js';
|
||||
import type { TestConnectionResultDto } from './provider.dto.js';
|
||||
|
||||
/** DI injection token for the provider adapter array. */
|
||||
export const PROVIDER_ADAPTERS = Symbol('PROVIDER_ADAPTERS');
|
||||
|
||||
@Injectable()
|
||||
export class ProviderService implements OnModuleInit {
|
||||
private readonly logger = new Logger(ProviderService.name);
|
||||
private registry!: ModelRegistry;
|
||||
|
||||
onModuleInit(): void {
|
||||
/**
|
||||
* Adapters registered with this service.
|
||||
* Built-in adapters (Ollama) are always present; additional adapters can be
|
||||
* supplied via the PROVIDER_ADAPTERS injection token in the future.
|
||||
*/
|
||||
private adapters: IProviderAdapter[] = [];
|
||||
|
||||
async onModuleInit(): Promise<void> {
|
||||
const authStorage = AuthStorage.inMemory();
|
||||
this.registry = new ModelRegistry(authStorage);
|
||||
|
||||
this.registerOllamaProvider();
|
||||
// Build the default set of adapters that rely on the registry
|
||||
this.adapters = [new OllamaAdapter(this.registry)];
|
||||
|
||||
// Run all adapter registrations first (Ollama, and any future adapters)
|
||||
await this.registerAll();
|
||||
|
||||
// Register API-key providers directly (Anthropic, OpenAI, Z.ai, custom)
|
||||
// These do not yet have dedicated adapter classes (M3-002 through M3-005).
|
||||
this.registerAnthropicProvider();
|
||||
this.registerOpenAIProvider();
|
||||
this.registerZaiProvider();
|
||||
@@ -23,6 +47,59 @@ export class ProviderService implements OnModuleInit {
|
||||
this.logger.log(`Providers initialized: ${available.length} models available`);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Adapter-pattern API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Call register() on each adapter in order.
|
||||
* Errors from individual adapters are logged and do not abort the others.
|
||||
*/
|
||||
async registerAll(): Promise<void> {
|
||||
for (const adapter of this.adapters) {
|
||||
try {
|
||||
await adapter.register();
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
`Adapter "${adapter.name}" registration failed`,
|
||||
err instanceof Error ? err.stack : String(err),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the adapter registered under the given provider name, or undefined.
|
||||
*/
|
||||
getAdapter(providerName: string): IProviderAdapter | undefined {
|
||||
return this.adapters.find((a) => a.name === providerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run healthCheck() on all adapters and return results keyed by provider name.
|
||||
*/
|
||||
async healthCheckAll(): Promise<Record<string, ProviderHealth>> {
|
||||
const results: Record<string, ProviderHealth> = {};
|
||||
await Promise.all(
|
||||
this.adapters.map(async (adapter) => {
|
||||
try {
|
||||
results[adapter.name] = await adapter.healthCheck();
|
||||
} catch (err) {
|
||||
results[adapter.name] = {
|
||||
status: 'down',
|
||||
lastChecked: new Date().toISOString(),
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
};
|
||||
}
|
||||
}),
|
||||
);
|
||||
return results;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Legacy / Pi-SDK-facing API (preserved for AgentService and RoutingService)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
getRegistry(): ModelRegistry {
|
||||
return this.registry;
|
||||
}
|
||||
@@ -69,6 +146,18 @@ export class ProviderService implements OnModuleInit {
|
||||
}
|
||||
|
||||
async testConnection(providerId: string, baseUrl?: string): Promise<TestConnectionResultDto> {
|
||||
// Delegate to the adapter when one exists and no URL override is given
|
||||
const adapter = this.getAdapter(providerId);
|
||||
if (adapter && !baseUrl) {
|
||||
const health = await adapter.healthCheck();
|
||||
return {
|
||||
providerId,
|
||||
reachable: health.status !== 'down',
|
||||
latencyMs: health.latencyMs,
|
||||
error: health.error,
|
||||
};
|
||||
}
|
||||
|
||||
// Resolve baseUrl: explicit override > registered provider > ollama env
|
||||
let resolvedUrl = baseUrl;
|
||||
|
||||
@@ -143,6 +232,11 @@ export class ProviderService implements OnModuleInit {
|
||||
this.logger.log(`Registered custom provider: ${config.id} (${config.models.length} models)`);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private helpers — direct registry registration for providers without adapters yet
|
||||
// (Anthropic, OpenAI, Z.ai will move to adapters in M3-002 through M3-005)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
private registerAnthropicProvider(): void {
|
||||
const apiKey = process.env['ANTHROPIC_API_KEY'];
|
||||
if (!apiKey) {
|
||||
@@ -203,36 +297,6 @@ export class ProviderService implements OnModuleInit {
|
||||
this.logger.log('Z.ai provider registered with 3 models');
|
||||
}
|
||||
|
||||
private registerOllamaProvider(): void {
|
||||
const ollamaUrl = process.env['OLLAMA_BASE_URL'] ?? process.env['OLLAMA_HOST'];
|
||||
if (!ollamaUrl) return;
|
||||
|
||||
const modelsEnv = process.env['OLLAMA_MODELS'] ?? 'llama3.2,codellama,mistral';
|
||||
const modelIds = modelsEnv
|
||||
.split(',')
|
||||
.map((modelId: string) => modelId.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.logger.log(
|
||||
`Ollama provider registered at ${ollamaUrl} with models: ${modelIds.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
private registerCustomProviders(): void {
|
||||
const customJson = process.env['MOSAIC_CUSTOM_PROVIDERS'];
|
||||
if (!customJson) return;
|
||||
|
||||
55
docs/scratchpads/m3-001-provider-adapter.md
Normal file
55
docs/scratchpads/m3-001-provider-adapter.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# M3-001 Provider Adapter Pattern — Scratchpad
|
||||
|
||||
## Objective
|
||||
|
||||
Refactor ProviderService into an IProviderAdapter pattern without breaking existing Ollama flow.
|
||||
|
||||
## Plan
|
||||
|
||||
1. Add `IProviderAdapter` interface and supporting types to `@mosaic/types` provider package
|
||||
2. Create `apps/gateway/src/agent/adapters/` directory with:
|
||||
- `provider-adapter.interface.ts` — IProviderAdapter + ProviderHealth + CompletionParams + CompletionEvent
|
||||
- `ollama.adapter.ts` — extract existing Ollama logic
|
||||
3. Refactor ProviderService:
|
||||
- Accept `IProviderAdapter[]` (injected via DI token)
|
||||
- `registerAll()` / `listModels()` aggregates from all adapters
|
||||
- `getAdapter(name)` — lookup by name
|
||||
- `healthCheckAll()` — check all adapters
|
||||
- Keep Pi ModelRegistry wiring (required by AgentService)
|
||||
4. Wire up in AgentModule
|
||||
|
||||
## Key Findings
|
||||
|
||||
### Pi SDK Compatibility
|
||||
|
||||
- Pi SDK uses `ModelRegistry` as central registry; ProviderService wraps it
|
||||
- `ModelRegistry.registerProvider()` is the integration point — adapters call this
|
||||
- Pi doesn't have a native "IProviderAdapter" concept — adapters are a Mosaic abstraction on top
|
||||
- The `createAgentSession()` call in AgentService uses `modelRegistry: this.providerService.getRegistry()`
|
||||
- OllamaAdapter should call `registry.registerProvider('ollama', {...})` same as today
|
||||
- CompletionParams/CompletionEvent: Pi SDK streams via `AgentSession.prompt()`, not raw completion
|
||||
— IProviderAdapter.createCompletion() is for future direct use; for now stub or leave as interface-only
|
||||
— ASSUMPTION: createCompletion is reserved for future M3+ work; Pi SDK owns the actual streaming
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- ESM: use `.js` extensions in all imports
|
||||
- NestJS: use `@Inject()` explicitly
|
||||
- Keep RoutingService working — it only uses `providerService.listAvailableModels()`
|
||||
- Keep AgentService working — it uses `providerService.getRegistry()`, `findModel()`, `getDefaultModel()`, `listAvailableModels()`
|
||||
|
||||
## Progress
|
||||
|
||||
- [ ] Add types to @mosaic/types
|
||||
- [ ] Create adapters/ directory
|
||||
- [ ] Create IProviderAdapter interface file
|
||||
- [ ] Create OllamaAdapter
|
||||
- [ ] Refactor ProviderService
|
||||
- [ ] Update AgentModule
|
||||
- [ ] Run tests
|
||||
- [ ] Run quality gates
|
||||
|
||||
## Risks
|
||||
|
||||
- Pi SDK doesn't natively support IProviderAdapter — adapters are a layer on top
|
||||
- createCompletion() is architecturally sound but requires Pi session bypass (future work)
|
||||
@@ -52,3 +52,100 @@ export interface CustomProviderConfig {
|
||||
maxTokens?: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IProviderAdapter pattern — M3-001
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Health status of a provider */
|
||||
export type ProviderHealthStatus = 'healthy' | 'degraded' | 'down';
|
||||
|
||||
/** Result of a provider health check */
|
||||
export interface ProviderHealth {
|
||||
status: ProviderHealthStatus;
|
||||
/** Round-trip latency in milliseconds (undefined when provider is down) */
|
||||
latencyMs?: number;
|
||||
/** ISO-8601 timestamp of the check */
|
||||
lastChecked: string;
|
||||
/** Human-readable error message (defined when status is not healthy) */
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/** A single message in a completion request */
|
||||
export interface CompletionMessage {
|
||||
role: 'system' | 'user' | 'assistant';
|
||||
content: string;
|
||||
}
|
||||
|
||||
/** Tool definition for completion requests */
|
||||
export interface CompletionTool {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** Parameters for a completion request */
|
||||
export interface CompletionParams {
|
||||
model: string;
|
||||
messages: CompletionMessage[];
|
||||
tools?: CompletionTool[];
|
||||
temperature?: number;
|
||||
maxTokens?: number;
|
||||
stream?: boolean;
|
||||
}
|
||||
|
||||
/** Usage statistics for a completion event */
|
||||
export interface CompletionUsage {
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
}
|
||||
|
||||
/** A streamed completion event */
|
||||
export type CompletionEvent =
|
||||
| { type: 'text_delta'; content: string }
|
||||
| { type: 'tool_call'; name: string; arguments: string }
|
||||
| { type: 'done'; usage?: CompletionUsage };
|
||||
|
||||
/**
|
||||
* Pluggable provider adapter interface.
|
||||
*
|
||||
* Each LLM provider (Anthropic, OpenAI, Ollama, etc.) implements this interface
|
||||
* to integrate with Mosaic's provider layer. The ProviderService aggregates all
|
||||
* registered adapters and routes requests accordingly.
|
||||
*
|
||||
* Note on createCompletion: this method is part of the interface for future
|
||||
* direct-completion use cases. The current Pi SDK integration routes completions
|
||||
* through the Pi session/ModelRegistry layer rather than calling adapters directly.
|
||||
* Adapters MUST still implement register() and healthCheck() correctly — those are
|
||||
* used by ProviderService today.
|
||||
*/
|
||||
export interface IProviderAdapter {
|
||||
/** Unique provider identifier (e.g. 'anthropic', 'openai', 'ollama') */
|
||||
readonly name: string;
|
||||
|
||||
/**
|
||||
* Initialize the provider — connect, discover models, register with the
|
||||
* Pi ModelRegistry. Called once at module startup by ProviderService.registerAll().
|
||||
*/
|
||||
register(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Return the list of models this adapter makes available.
|
||||
* Returns an empty array when the provider is not configured.
|
||||
*/
|
||||
listModels(): ModelInfo[];
|
||||
|
||||
/**
|
||||
* Check whether the provider endpoint is reachable and responsive.
|
||||
*/
|
||||
healthCheck(): Promise<ProviderHealth>;
|
||||
|
||||
/**
|
||||
* Stream a completion from the provider.
|
||||
*
|
||||
* Note: Currently reserved for future use. The Pi SDK integration routes
|
||||
* completions through ModelRegistry / AgentSession rather than this method.
|
||||
* Implementations may throw NotImplementedError until M3+ tasks wire this up.
|
||||
*/
|
||||
createCompletion(params: CompletionParams): AsyncIterable<CompletionEvent>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user