Merge remote-tracking branch 'upstream/main' into pr-70
# Conflicts: # src/config/index.ts # src/llm/index.ts # src/server.ts # src/setup.ts # src/types/index.ts
This commit is contained in:
75 files changed
+7788
-586
No files matched your search
+42
-4
@@ -4,6 +4,7 @@
|
||||
* Multi-provider LLM integration supporting:
|
||||
* - OpenRouter (recommended - access to Claude, GPT-4, Llama, etc.)
|
||||
* - Venice (OpenAI-compatible, privacy-focused / uncensored models)
|
||||
* - LiteLLM (OpenAI-compatible AI gateway — 100+ providers via unified proxy)
|
||||
* - Anthropic (direct Claude access)
|
||||
* - OpenAI (GPT models)
|
||||
* - Mock (for testing)
|
||||
@@ -360,6 +361,28 @@ class VeniceAdapter extends OpenRouterAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// LITELLM ADAPTER
|
||||
// =============================================================================
|
||||
// LiteLLM is an AI gateway proxy that routes to 100+ LLM providers through a
|
||||
// single OpenAI-compatible API. Same wire shape as OpenRouter (Bearer auth,
|
||||
// POST /chat/completions, identical request/response + tool-calling + SSE
|
||||
// stream), so it reuses the entire OpenRouter adapter and only differs in its
|
||||
// default base URL and the key-required error message.
|
||||
class LiteLLMAdapter extends OpenRouterAdapter {
|
||||
name = 'litellm';
|
||||
|
||||
validateConfig(): { valid: boolean; error?: string } {
|
||||
if (!this.config.baseUrl) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'LiteLLM proxy base URL is required. Set LITELLM_BASE_URL or configure via tempest setup. Docs: https://docs.litellm.ai/docs/proxy/quick_start',
|
||||
};
|
||||
}
|
||||
return { valid: true };
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ANTHROPIC ADAPTER
|
||||
// =============================================================================
|
||||
@@ -744,9 +767,12 @@ class LocalAdapter implements LLMProviderAdapter {
|
||||
+ (contract
|
||||
? ' You drive a ReAct loop: REQUEST tools, the harness runs them and returns results, you reason until the surface is exhausted.' + contract
|
||||
: ' Operate in planning and analysis mode; when JSON is requested, return ONLY the requested block — no preamble.');
|
||||
const out: { role: string; content: string }[] = [{ role: 'system', content: preamble }];
|
||||
const systemParts = [preamble];
|
||||
const out: { role: string; content: string }[] = [];
|
||||
for (const m of messages) {
|
||||
if (m.role === 'assistant' && m.toolCalls?.length) {
|
||||
if (m.role === 'system') {
|
||||
if (m.content.trim()) systemParts.push(m.content);
|
||||
} else if (m.role === 'assistant' && m.toolCalls?.length) {
|
||||
out.push({ role: 'assistant', content: `[requested tools: ${m.toolCalls.map(t => t.name).join(', ')}]` });
|
||||
} else if (m.role === 'tool') {
|
||||
out.push({ role: 'user', content: `TOOL RESULT (${m.name || 'tool'}):\n${m.content}` });
|
||||
@@ -754,7 +780,7 @@ class LocalAdapter implements LLMProviderAdapter {
|
||||
out.push({ role: m.role, content: m.content });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
return [{ role: 'system', content: systemParts.join('\n\n') }, ...out];
|
||||
}
|
||||
|
||||
async chat(messages: LLMMessage[], options?: ChatOptions): Promise<LLMResponse> {
|
||||
@@ -1201,6 +1227,8 @@ export class LLMBackbone extends EventEmitter<LLMEvents> {
|
||||
return new OpenRouterAdapter(config);
|
||||
case 'venice':
|
||||
return new VeniceAdapter(config);
|
||||
case 'litellm':
|
||||
return new LiteLLMAdapter(config);
|
||||
case 'anthropic':
|
||||
return new AnthropicAdapter(config);
|
||||
case 'openai':
|
||||
@@ -1491,6 +1519,13 @@ export function createDeepSeekBackbone(apiKey?: string, model?: string): LLMBack
|
||||
return new LLMBackbone(llmConfig);
|
||||
}
|
||||
|
||||
export function createLiteLLMBackbone(apiKey?: string, model?: string, baseUrl?: string): LLMBackbone {
|
||||
const llmConfig = config.getLLMConfig('litellm', model);
|
||||
if (apiKey) llmConfig.apiKey = apiKey;
|
||||
if (baseUrl) llmConfig.baseUrl = baseUrl;
|
||||
return new LLMBackbone(llmConfig);
|
||||
}
|
||||
|
||||
export function createMockBackbone(): LLMBackbone {
|
||||
return new LLMBackbone({
|
||||
provider: 'mock',
|
||||
@@ -1514,7 +1549,7 @@ export function createLocalBackbone(model?: string, baseUrl?: string): LLMBackbo
|
||||
* Create the best available backbone based on configured API keys
|
||||
*/
|
||||
export function createBestAvailableBackbone(): LLMBackbone {
|
||||
// Priority: OpenRouter > Venice > Anthropic > OpenAI > Local > Mock
|
||||
// Priority: OpenRouter > Venice > LiteLLM > Anthropic > OpenAI > DeepSeek > Local > Mock
|
||||
const providers = config.getConfiguredProviders();
|
||||
|
||||
if (providers.includes('openrouter')) {
|
||||
@@ -1523,6 +1558,9 @@ export function createBestAvailableBackbone(): LLMBackbone {
|
||||
if (providers.includes('venice')) {
|
||||
return createVeniceBackbone();
|
||||
}
|
||||
if (providers.includes('litellm')) {
|
||||
return createLiteLLMBackbone();
|
||||
}
|
||||
if (providers.includes('anthropic')) {
|
||||
return createAnthropicBackbone();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user