feat: multi-provider support — Anthropic + Ollama (P2-002) (#74)

Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #74.
This commit is contained in:
2026-03-13 03:10:51 +00:00
committed by jason.woltje
parent aa9ee75a2a
commit 95f95f54cf
9 changed files with 287 additions and 11 deletions

View File

@@ -5,9 +5,17 @@ import {
type AgentSession as PiAgentSession,
type AgentSessionEvent,
} from '@mariozechner/pi-coding-agent';
import { ProviderService } from './provider.service.js';
export interface AgentSessionOptions {
provider?: string;
modelId?: string;
}
export interface AgentSession {
id: string;
provider: string;
modelId: string;
piSession: PiAgentSession;
listeners: Set<(event: AgentSessionEvent) => void>;
unsubscribe: () => void;
@@ -19,33 +27,46 @@ export class AgentService implements OnModuleDestroy {
private readonly sessions = new Map<string, AgentSession>();
private readonly creating = new Map<string, Promise<AgentSession>>();
async createSession(sessionId: string): Promise<AgentSession> {
constructor(private readonly providerService: ProviderService) {}
async createSession(sessionId: string, options?: AgentSessionOptions): Promise<AgentSession> {
const existing = this.sessions.get(sessionId);
if (existing) return existing;
const inflight = this.creating.get(sessionId);
if (inflight) return inflight;
const promise = this.doCreateSession(sessionId).finally(() => {
const promise = this.doCreateSession(sessionId, options).finally(() => {
this.creating.delete(sessionId);
});
this.creating.set(sessionId, promise);
return promise;
}
private async doCreateSession(sessionId: string): Promise<AgentSession> {
this.logger.log(`Creating agent session: ${sessionId}`);
private async doCreateSession(
sessionId: string,
options?: AgentSessionOptions,
): Promise<AgentSession> {
const model = this.resolveModel(options);
const providerName = model?.provider ?? 'default';
const modelId = model?.id ?? 'default';
this.logger.log(
`Creating agent session: ${sessionId} (provider=${providerName}, model=${modelId})`,
);
let piSession: PiAgentSession;
try {
const result = await createAgentSession({
sessionManager: SessionManager.inMemory(),
modelRegistry: this.providerService.getRegistry(),
model: model ?? undefined,
tools: [],
});
piSession = result.session;
} catch (err) {
this.logger.error(
`Failed to create Pi SDK session for ${sessionId}`,
`Failed to create agent session for ${sessionId}`,
err instanceof Error ? err.stack : String(err),
);
throw new Error(`Agent session creation failed for ${sessionId}: ${String(err)}`);
@@ -65,17 +86,43 @@ export class AgentService implements OnModuleDestroy {
const session: AgentSession = {
id: sessionId,
provider: providerName,
modelId,
piSession,
listeners,
unsubscribe,
};
this.sessions.set(sessionId, session);
this.logger.log(`Agent session ${sessionId} ready`);
this.logger.log(`Agent session ${sessionId} ready (${providerName}/${modelId})`);
return session;
}
private resolveModel(options?: AgentSessionOptions) {
if (!options?.provider && !options?.modelId) {
return this.providerService.getDefaultModel() ?? null;
}
if (options.provider && options.modelId) {
const model = this.providerService.findModel(options.provider, options.modelId);
if (!model) {
throw new Error(`Model not found: ${options.provider}/${options.modelId}`);
}
return model;
}
if (options.modelId) {
const available = this.providerService.listAvailableModels();
const match = available.find((m) => m.id === options.modelId);
if (match) {
return this.providerService.findModel(match.provider, match.id) ?? null;
}
}
return this.providerService.getDefaultModel() ?? null;
}
getSession(sessionId: string): AgentSession | undefined {
return this.sessions.get(sessionId);
}
@@ -89,7 +136,7 @@ export class AgentService implements OnModuleDestroy {
await session.piSession.prompt(message);
} catch (err) {
this.logger.error(
`Pi SDK prompt failed for session=${sessionId}, messageLength=${message.length}`,
`Prompt failed for session=${sessionId}, messageLength=${message.length}`,
err instanceof Error ? err.stack : String(err),
);
throw err;
@@ -112,7 +159,7 @@ export class AgentService implements OnModuleDestroy {
try {
session.unsubscribe();
} catch (err) {
this.logger.error(`Failed to unsubscribe Pi session ${sessionId}`, String(err));
this.logger.error(`Failed to unsubscribe session ${sessionId}`, String(err));
}
session.listeners.clear();
this.sessions.delete(sessionId);