Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions v3/@claude-flow/providers/src/__tests__/novita-provider.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import { NovitaProvider } from '../novita-provider.js';
import { consoleLogger } from '../base-provider.js';

describe('NovitaProvider', () => {
afterEach(() => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
});

it('uses Novita OpenAI-compatible endpoint and returns novita provider label', async () => {
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
expect(String(input)).toBe('https://api.novita.ai/openai/chat/completions');
return new Response(
JSON.stringify({
id: 'cmpl-test',
object: 'chat.completion',
created: Date.now(),
model: 'gpt-4o-mini',
choices: [
{
index: 0,
message: { role: 'assistant', content: 'hello from novita' },
finish_reason: 'stop',
},
],
usage: {
prompt_tokens: 5,
completion_tokens: 3,
total_tokens: 8,
},
}),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
);
});

vi.stubGlobal('fetch', fetchMock);

const provider = new NovitaProvider({
config: {
provider: 'novita',
apiKey: 'test-key',
model: 'gpt-4o-mini',
},
logger: consoleLogger,
});

await provider.initialize();
const response = await provider.complete({
messages: [{ role: 'user', content: 'hello' }],
});

expect(response.provider).toBe('novita');
expect(response.content).toBe('hello from novita');
expect(fetchMock).toHaveBeenCalledTimes(1);

provider.destroy();
});
});
1 change: 1 addition & 0 deletions v3/@claude-flow/providers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export type { BaseProviderOptions, ILogger } from './base-provider.js';
// Export providers
export { AnthropicProvider } from './anthropic-provider.js';
export { OpenAIProvider } from './openai-provider.js';
export { NovitaProvider } from './novita-provider.js';
export { GoogleProvider } from './google-provider.js';
export { CohereProvider } from './cohere-provider.js';
export { OllamaProvider } from './ollama-provider.js';
Expand Down
25 changes: 25 additions & 0 deletions v3/@claude-flow/providers/src/novita-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* V3 Novita Provider
*
* OpenAI-compatible provider configured for Novita endpoint.
*
* @module @claude-flow/providers/novita-provider
*/

import { BaseProviderOptions } from './base-provider.js';
import { OpenAIProvider } from './openai-provider.js';

const NOVITA_API_URL = 'https://api.novita.ai/openai';

export class NovitaProvider extends OpenAIProvider {
constructor(options: BaseProviderOptions) {
super({
...options,
config: {
...options.config,
provider: 'novita',
apiUrl: options.config.apiUrl || NOVITA_API_URL,
},
});
}
}
17 changes: 9 additions & 8 deletions v3/@claude-flow/providers/src/openai-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ interface OpenAIResponse {
}

export class OpenAIProvider extends BaseProvider {
readonly name: LLMProvider = 'openai';
readonly name: LLMProvider;
readonly capabilities: ProviderCapabilities = {
supportedModels: [
'gpt-4o',
Expand Down Expand Up @@ -168,16 +168,17 @@ export class OpenAIProvider extends BaseProvider {
},
};

private baseUrl: string = 'https://api.openai.com/v1';
protected baseUrl: string = 'https://api.openai.com/v1';
private headers: Record<string, string> = {};

constructor(options: BaseProviderOptions) {
super(options);
this.name = options.config.provider === 'novita' ? 'novita' : 'openai';
}

protected async doInitialize(): Promise<void> {
if (!this.config.apiKey) {
throw new AuthenticationError('OpenAI API key is required', 'openai');
throw new AuthenticationError(`${this.name} API key is required`, this.name);
}

this.baseUrl = this.config.apiUrl || 'https://api.openai.com/v1';
Expand Down Expand Up @@ -433,7 +434,7 @@ export class OpenAIProvider extends BaseProvider {
return {
id: data.id,
model: model as LLMModel,
provider: 'openai',
provider: this.name,
content: choice.message.content || '',
toolCalls: choice.message.tool_calls,
usage: {
Expand Down Expand Up @@ -465,22 +466,22 @@ export class OpenAIProvider extends BaseProvider {

switch (response.status) {
case 401:
throw new AuthenticationError(message, 'openai', errorData);
throw new AuthenticationError(message, this.name, errorData);
case 429:
const retryAfter = response.headers.get('retry-after');
throw new RateLimitError(
message,
'openai',
this.name,
retryAfter ? parseInt(retryAfter) : undefined,
errorData
);
case 404:
throw new ModelNotFoundError(this.config.model, 'openai', errorData);
throw new ModelNotFoundError(this.config.model, this.name, errorData);
default:
throw new LLMProviderError(
message,
`OPENAI_${response.status}`,
'openai',
this.name,
response.status,
response.status >= 500,
errorData
Expand Down
3 changes: 3 additions & 0 deletions v3/@claude-flow/providers/src/provider-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
import { BaseProviderOptions, ILogger, consoleLogger } from './base-provider.js';
import { AnthropicProvider } from './anthropic-provider.js';
import { OpenAIProvider } from './openai-provider.js';
import { NovitaProvider } from './novita-provider.js';
import { GoogleProvider } from './google-provider.js';
import { CohereProvider } from './cohere-provider.js';
import { OllamaProvider } from './ollama-provider.js';
Expand Down Expand Up @@ -119,6 +120,8 @@ export class ProviderManager extends EventEmitter {
return new AnthropicProvider(options);
case 'openai':
return new OpenAIProvider(options);
case 'novita':
return new NovitaProvider(options);
case 'google':
return new GoogleProvider(options);
case 'cohere':
Expand Down
1 change: 1 addition & 0 deletions v3/@claude-flow/providers/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { EventEmitter } from 'events';
export type LLMProvider =
| 'anthropic'
| 'openai'
| 'novita'
| 'google'
| 'cohere'
| 'ollama'
Expand Down