Skip to content
Merged

mmm #68

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
13 changes: 13 additions & 0 deletions apps/api/dev/testOpenAISdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ async function runSdkCompatibilityTest() {
const apiKey = process.env.TEST_API_KEY || DEFAULT_TEST_API_KEY;
const model = 'gpt-3.5-turbo';
const responsesModel = 'gpt-5.4';
const genericNativeAutoModel = 'text-pro-1';
const rawBaseUrl = resolveRawApiBaseUrl();
const baseURL = resolveClientBaseUrl();
const nativeResponsesBaseUrl = `${rawBaseUrl}/native/auto/v1`;
Expand Down Expand Up @@ -332,6 +333,18 @@ async function runSdkCompatibilityTest() {
assertNativeProxyResponseId(nativeResponseObject.id);
console.log('[SDK-TEST] ✅ Native responses.create rewrites response ids to proxy-owned ids.');

const genericAutoCompletion = await nativeResponsesClient.chat.completions.create({
model: genericNativeAutoModel,
messages: [{ role: 'user', content: 'Say hello from a generic auto-routed model.' }],
});

assert.equal(genericAutoCompletion.object, 'chat.completion');
assert.equal(
genericAutoCompletion.choices[0]?.message?.content,
'Hello! I am a mock AI provider. How can I help you today?'
);
console.log('[SDK-TEST] ✅ Native auto chat routing supports generic OpenAI-compatible model ids.');

const nativeFollowupInput = 'Say hello again from native responses.';
const expectedNativeFollowupText = getExpectedMockResponsesText(nativeFollowupInput);
const nativeFollowup = await nativeResponsesClient.responses.create({
Expand Down
46 changes: 45 additions & 1 deletion apps/api/dev/testSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export function setupMockProviderConfig(mode: 'openai' | 'anthropic' = 'openai')
const providerId = mode === 'anthropic' ? 'claude-mock' : 'openai-mock';
const primaryModelId = mode === 'anthropic' ? 'claude-3-5-sonnet' : 'gpt-3.5-turbo';
const secondaryModelId = mode === 'anthropic' ? 'claude-3-7-sonnet' : 'gpt-5.4';
const genericOpenAiCompatibleProviderId = 'acme-compatible-mock';
const genericOpenAiCompatibleModelId = 'text-pro-1';
const providerUrl = mode === 'anthropic'
? `http://localhost:${mockPort}/v1/messages`
: `http://localhost:${mockPort}/v1/chat/completions`;
Expand Down Expand Up @@ -75,6 +77,35 @@ export function setupMockProviderConfig(mode: 'openai' | 'anthropic' = 'openai')
disabled: false
};

const additionalProviders = mode === 'openai'
? [
{
id: genericOpenAiCompatibleProviderId,
apiKey: 'mock-api-key-for-testing',
provider_url: providerUrl,
native_protocol: 'openai',
streamingCompatible: true,
models: {
[genericOpenAiCompatibleModelId]: {
id: genericOpenAiCompatibleModelId,
token_generation_speed: 50,
response_times: [],
errors: 0,
consecutive_errors: 0,
avg_response_time: null,
avg_provider_latency: null,
avg_token_speed: null
}
},
avg_response_time: null,
avg_provider_latency: null,
errors: 0,
provider_score: 90,
disabled: false
}
]
: [];

const testUserKey = {
userId: 'test-user',
tokenUsage: 0,
Expand Down Expand Up @@ -105,6 +136,19 @@ export function setupMockProviderConfig(mode: 'openai' | 'anthropic' = 'openai')
throughput: 50,
capabilities: ['text', 'tool_calling'],
},
...(mode === 'openai'
? [
{
id: genericOpenAiCompatibleModelId,
object: 'model',
created,
owned_by: genericOpenAiCompatibleProviderId,
providers: 1,
throughput: 50,
capabilities: ['text', 'tool_calling'],
},
]
: []),
],
};

Expand All @@ -129,7 +173,7 @@ export function setupMockProviderConfig(mode: 'openai' | 'anthropic' = 'openai')
}

// Write mock provider configuration
fs.writeFileSync(providersFilePath, JSON.stringify([mockProvider], null, 2));
fs.writeFileSync(providersFilePath, JSON.stringify([mockProvider, ...additionalProviders], null, 2));
console.log('[TEST-SETUP] Created mock provider configuration');

fs.writeFileSync(modelsFilePath, JSON.stringify(testModels, null, 2));
Expand Down
4 changes: 4 additions & 0 deletions apps/api/modules/dataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ export interface LoadedProviderData {
apiKey: string | null; // Make consistent with Provider interface
provider_url: string; // Make required, consistent with Provider interface
provider_urls?: { [modelId: string]: string };
provider?: string;
type?: string;
native_family?: string;
native_protocol?: string;
streamingCompatible?: boolean;
models: { [key: string]: ProviderModelData };
disabled: boolean; // Make required with default false
Expand Down
4 changes: 2 additions & 2 deletions apps/api/routes/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

function applyResponseHeaders(response: Response, headers: Record<string, string> | undefined): void {
export function applyResponseHeaders(response: Response, headers: Record<string, string> | undefined): void {
if (!headers) return;
for (const [name, value] of Object.entries(headers)) {
response.setHeader(name, value);
Expand All @@ -50,7 +50,7 @@ function extractRequestApiKey(request: any): string | null {
);
}

async function buildModelsPayload(request: any): Promise<PreparedModelsPayload> {
export async function buildModelsPayload(request: any): Promise<PreparedModelsPayload> {
const [modelsSnapshot, providersData] = await Promise.all([
dataManager.load<ModelsFileStructure>('models'),
dataManager.load<LoadedProviders>('providers'),
Expand Down
Loading
Loading