Skip to content
Closed
4 changes: 4 additions & 0 deletions src/agents/orchestrator.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { config } from '../config.js'
import { AGENTS, getAgentById } from './registry.js'
import { getProvider, listProviders } from '../providers/index.js'

Check warning on line 3 in src/agents/orchestrator.js

View workflow job for this annotation

GitHub Actions / lint

'listProviders' is defined but never used

Check warning on line 3 in src/agents/orchestrator.js

View workflow job for this annotation

GitHub Actions / lint

'getProvider' is defined but never used

// Legacy imports — kept for backward compatibility
// New code should use getProvider() from the provider layer
import {
runResearch,
runSummary,
Expand Down
4 changes: 4 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export const config = {
100,
toNumberOr(process.env.ANTHROPIC_RETRY_BASE_DELAY_MS, 500)
),

// Provider abstraction for multi-LLM support
provider: (process.env.PROVIDER || 'anthropic').toLowerCase(),

logFormat: (process.env.LOG_FORMAT || 'json').toLowerCase() === 'pretty' ? 'pretty' : 'json',
// Rate limiting (defaults are intentionally permissive for demos)
rateLimit: {
Expand Down
36 changes: 36 additions & 0 deletions src/providers/anthropic-provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Anthropic Provider — default implementation. Behavior unchanged from services.js.
*/
import Anthropic from '@anthropic-ai/sdk'
import { config } from '../config.js'

export const name = 'anthropic'
// eslint-disable-next-line no-unused-vars
const _anthropicClient = new Anthropic({ apiKey: config.anthropicApiKey })
let available = true

export const capabilities = {
research: { model: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5' },
summary: { model: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5' },
analysis: { model: 'claude-sonnet-4-5-20250929', fallback: 'claude-haiku-4-5-20251001', label: 'Claude Sonnet 4.5' },

Check failure on line 15 in src/providers/anthropic-provider.js

View workflow job for this annotation

GitHub Actions / lint

Replace `·model:·'claude-sonnet-4-5-20250929',·fallback:·'claude-haiku-4-5-20251001',·label:·'Claude·Sonnet·4.5'` with `⏎····model:·'claude-sonnet-4-5-20250929',⏎····fallback:·'claude-haiku-4-5-20251001',⏎····label:·'Claude·Sonnet·4.5',⏎·`
code: { model: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5' },
}

export function setApiKey(newKey) {
_anthropicClient = new Anthropic({ apiKey: newKey })
config.anthropicApiKey = newKey
available = true
}

export const isAvailable = () => available

import { runResearch, runSummary, runAnalysis, runCode, createAnthropicMessage } from '../agents/services.js'

Check failure on line 27 in src/providers/anthropic-provider.js

View workflow job for this annotation

GitHub Actions / lint

Replace `·runResearch,·runSummary,·runAnalysis,·runCode,·createAnthropicMessage·` with `⏎··runResearch,⏎··runSummary,⏎··runAnalysis,⏎··runCode,⏎··createAnthropicMessage,⏎`
export const research = runResearch
export const summary = runSummary
export const analysis = runAnalysis
export const code = runCode
export const createMessage = createAnthropicMessage

export function getProvider() {
return { name, capabilities, research, summary, analysis, code, createMessage, setApiKey, isAvailable }

Check failure on line 35 in src/providers/anthropic-provider.js

View workflow job for this annotation

GitHub Actions / lint

Replace `·name,·capabilities,·research,·summary,·analysis,·code,·createMessage,·setApiKey,·isAvailable` with `⏎····name,⏎····capabilities,⏎····research,⏎····summary,⏎····analysis,⏎····code,⏎····createMessage,⏎····setApiKey,⏎····isAvailable,⏎·`
}
45 changes: 45 additions & 0 deletions src/providers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Provider Factory — selects and caches the configured LLM provider.
* Usage: import { getProvider } from './providers/index.js'
*/
import { config } from '../config.js'
import { validateProvider } from './interface.js'
import { getProvider as getAnthropic } from './anthropic-provider.js'
import { getProvider as getOpenAI } from './openai-provider.js'

const REGISTRY = { anthropic: getAnthropic, openai: getOpenAI }
const NAMES = Object.keys(REGISTRY)
let _cached = null

export function getProvider() {
if (_cached) return _cached
const name = (config.provider || 'anthropic').toLowerCase()
if (!REGISTRY[name]) {
console.warn(`[provider] Unknown "${name}", using anthropic. Available: ${NAMES.join(', ')}`)
_cached = REGISTRY.anthropic()
return _cached
}
_cached = REGISTRY[name]()
const v = validateProvider(_cached)
if (!v.valid) {
console.error(`[provider] "${name}" incomplete: ${v.missing.join(', ')}. Using anthropic.`)
_cached = REGISTRY.anthropic()
}
return _cached
}

export function listProviders() {
return NAMES.map((n) => {
const p = REGISTRY[n]()
return {
name: n,
available: p.isAvailable ? p.isAvailable() : true,
capabilities: p.capabilities,
}
})
}

export function resetProvider() {
_cached = null
}
export { validateProvider, PROVIDER_MODES } from './interface.js'
10 changes: 10 additions & 0 deletions src/providers/interface.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Provider Interface — contract for LLM provider implementations.
* Required: name, capabilities, research(), summary(), analysis(), code()
*/
export function validateProvider(provider) {
const required = ['name', 'research', 'summary', 'analysis', 'code', 'capabilities']
const missing = required.filter((key) => typeof provider[key] === 'undefined')
return { valid: missing.length === 0, missing }
}
export const PROVIDER_MODES = ['research', 'summary', 'analysis', 'code']
26 changes: 26 additions & 0 deletions src/providers/openai-provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* OpenAI Provider — PLACEHOLDER. Set PROVIDER=openai to activate when implemented.
*/
export const name = 'openai'
export const capabilities = {
research: { model: 'gpt-4o-mini', label: 'GPT-4o Mini' },
summary: { model: 'gpt-4o-mini', label: 'GPT-4o Mini' },
analysis: { model: 'gpt-4o', fallback: 'gpt-4o-mini', label: 'GPT-4o' },
code: { model: 'gpt-4o-mini', label: 'GPT-4o Mini' },
}
function notYet(mode) {
return async () => {
throw new Error(

Check failure on line 13 in src/providers/openai-provider.js

View workflow job for this annotation

GitHub Actions / lint

Replace `⏎······`OpenAI·${mode}·not·implemented.·Use·PROVIDER=anthropic.`⏎····` with ``OpenAI·${mode}·not·implemented.·Use·PROVIDER=anthropic.``
`OpenAI ${mode} not implemented. Use PROVIDER=anthropic.`
)
}
}
export const research = notYet('research')
export const summary = notYet('summary')
export const analysis = notYet('analysis')
export const code = notYet('code')
export const isAvailable = () => false
export function setApiKey(_k) {}
export function getProvider() {
return { name, capabilities, research, summary, analysis, code, setApiKey, isAvailable }
}
Loading