feat(providers): support custom API protocols

This commit is contained in:
Bohdan Triapitsyn
2026-08-20 18:39:13 +03:00
parent d80a011cbd
commit 647a949369
16 changed files with 180 additions and 20 deletions
@@ -16,6 +16,7 @@ const t = (key: string) => key;
const baseForm = (overrides: Partial<CustomProviderFormState> = {}): CustomProviderFormState => ({
providerID: 'custom-provider',
name: 'Custom Provider',
protocol: 'openai-chat',
baseURL: 'https://api.example.com/v1',
apiKey: 'sk-test',
models: [{ row: 'm0', id: 'model-a', name: 'Model A' }],
@@ -96,6 +97,16 @@ describe('validateCustomProvider', () => {
expect(result.result?.config.env).toEqual(['CUSTOM_PROVIDER_KEY']);
});
test('uses the selected OpenCode provider adapter', () => {
const result = validateCustomProvider({
form: baseForm({ protocol: 'openai-responses' }),
t,
existingProviderIDs: new Set(),
});
expect(result.result?.config.npm).toBe('@ai-sdk/openai');
});
test('rejects missing credentials', () => {
const result = validateCustomProvider({
form: baseForm({ apiKey: ' ' }),
@@ -300,10 +311,21 @@ describe('provider edit helpers', () => {
expect(state.name).toBe('Campus LLM');
expect(state.baseURL).toBe('https://llm.example.edu/v1');
expect(state.apiKey).toBe('{env:CAMPUS_KEY}');
expect(state.protocol).toBe('openai-chat');
expect(state.models[0]).toEqual({ row: state.models[0].row, id: 'fast', name: 'Fast' });
expect(state.headers[0]).toEqual({ row: state.headers[0].row, key: 'X-Campus', value: '1' });
});
test('prefills the protocol from a custom provider model', () => {
const state = providerToCustomFormState({
id: 'responses-api',
options: { baseURL: 'https://api.example.com/v1' },
models: [{ id: 'gpt', name: 'GPT', api: { npm: '@ai-sdk/openai' } }],
});
expect(state.protocol).toBe('openai-responses');
});
test('requires a config-layer source before treating a provider as editable custom', () => {
const catalogLike = {
id: 'openai',