Skip to content

Commit 239b654

Browse files
authored
fix(model-provider): handle unknown providers by defaulting to openai-compatible (#1823)
1 parent f3278ee commit 239b654

4 files changed

Lines changed: 75 additions & 26 deletions

File tree

.secretlintrc.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,20 @@
2828
"pattern": "/\\b(?<key>(?:password|pass|secret|token|apiKey)(?:[_-]\\w+)?)\\b\\s*[:=]\\s*(?<value>(?!['\"]?\\s*['\"]?$)(?!\\d+\\.\\d+(?:\\.\\d+)?(?:\\s|$))\\S.*)/i"
2929
}
3030
],
31-
"allows": ["your_api_key", "YOUR_API_KEY"]
31+
"allows": [
32+
"your_api_key",
33+
"YOUR_API_KEY",
34+
"undefined",
35+
"test-key",
36+
"original-key",
37+
"custom-key",
38+
"deepseek-key",
39+
"azure-key",
40+
"kimi-api-key",
41+
"ollama",
42+
"/agentModel\\?\\.apiKey/",
43+
"/defaultConfig\\.apiKey/"
44+
]
3245
}
3346
},
3447
{ "id": "@secretlint/secretlint-rule-privatekey" }

multimodal/tarko/model-provider/src/model-resolver.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,32 @@ import { AgentModel, ModelProviderName, BaseModelProviderName } from './types';
77
import { HIGH_LEVEL_MODEL_PROVIDER_CONFIGS } from './constants';
88
import { addClaudeHeadersIfNeeded } from './claude-headers';
99
import { addAzureClaudeParamsIfNeeded } from './azure-claude-params';
10+
import { models } from '@tarko/llm-client';
11+
12+
/**
13+
* Known base model providers from llm-client
14+
*/
15+
const KNOWN_BASE_PROVIDERS = new Set(Object.keys(models));
1016

1117
/**
1218
* Get the actual provider implementation name
19+
* For unknown providers (like 'kimi'), defaults to 'openai-compatible'
1320
*/
1421
function getActualProvider(providerName: ModelProviderName): BaseModelProviderName {
22+
// First check if there's a high-level config that extends a base provider
1523
const config = HIGH_LEVEL_MODEL_PROVIDER_CONFIGS.find((c) => c.name === providerName);
16-
return (config?.extends || providerName) as BaseModelProviderName;
24+
if (config?.extends) {
25+
return config.extends;
26+
}
27+
28+
// If the provider is a known base provider, use it directly
29+
if (KNOWN_BASE_PROVIDERS.has(providerName)) {
30+
return providerName as BaseModelProviderName;
31+
}
32+
33+
// For unknown providers, default to 'openai-compatible'
34+
// This handles custom providers like 'kimi' that use OpenAI-compatible APIs
35+
return 'openai-compatible';
1736
}
1837

1938
/**

multimodal/tarko/model-provider/tests/integration.test.ts

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -111,25 +111,17 @@ describe('Integration Tests', () => {
111111
describe('Claude Headers Integration', () => {
112112
it('should automatically add Claude headers when resolving Claude models', () => {
113113
// Test with Claude model
114-
const claudeModel = resolveModel(
115-
undefined,
116-
'claude-3-sonnet',
117-
'anthropic'
118-
);
119-
114+
const claudeModel = resolveModel(undefined, 'claude-3-sonnet', 'anthropic');
115+
120116
expect(claudeModel.headers?.['anthropic-beta']).toBe(
121-
'fine-grained-tool-streaming-2025-05-14,token-efficient-tools-2025-02-19'
117+
'fine-grained-tool-streaming-2025-05-14,token-efficient-tools-2025-02-19',
122118
);
123119
});
124120

125121
it('should not add Claude headers for non-Claude models', () => {
126122
// Test with non-Claude model
127-
const openaiModel = resolveModel(
128-
undefined,
129-
'gpt-4',
130-
'openai'
131-
);
132-
123+
const openaiModel = resolveModel(undefined, 'gpt-4', 'openai');
124+
133125
expect(openaiModel.headers?.['anthropic-beta']).toBeUndefined();
134126
});
135127

@@ -139,14 +131,14 @@ describe('Integration Tests', () => {
139131
provider: 'anthropic',
140132
headers: {
141133
'X-Custom': 'value',
142-
'Authorization': 'Bearer token'
143-
}
134+
Authorization: 'Bearer token',
135+
},
144136
});
145-
137+
146138
expect(customModel.headers?.['X-Custom']).toBe('value');
147139
expect(customModel.headers?.['Authorization']).toBe('Bearer token');
148140
expect(customModel.headers?.['anthropic-beta']).toBe(
149-
'fine-grained-tool-streaming-2025-05-14,token-efficient-tools-2025-02-19'
141+
'fine-grained-tool-streaming-2025-05-14,token-efficient-tools-2025-02-19',
150142
);
151143
});
152144

@@ -155,13 +147,13 @@ describe('Integration Tests', () => {
155147
'claude-3-sonnet',
156148
'claude-3-5-sonnet-20241022',
157149
'claude-3-haiku',
158-
'anthropic/claude-3-opus'
150+
'anthropic/claude-3-opus',
159151
];
160-
161-
models.forEach(modelId => {
152+
153+
models.forEach((modelId) => {
162154
const model = resolveModel(undefined, modelId, 'anthropic');
163155
expect(model.headers?.['anthropic-beta']).toBe(
164-
'fine-grained-tool-streaming-2025-05-14,token-efficient-tools-2025-02-19'
156+
'fine-grained-tool-streaming-2025-05-14,token-efficient-tools-2025-02-19',
165157
);
166158
});
167159
});
@@ -173,16 +165,17 @@ describe('Integration Tests', () => {
173165
});
174166

175167
describe('Error handling', () => {
176-
it('should handle invalid provider gracefully', () => {
177-
// TypeScript should prevent this, but test runtime behavior
168+
it('should handle unknown provider by falling back to openai-compatible', () => {
169+
// Unknown providers (like 'kimi') should default to openai-compatible
170+
// to support custom OpenAI-compatible APIs
178171
const resolved = resolveModel(
179172
undefined,
180173
'test-model',
181174
'invalid-provider' as ModelProviderName,
182175
);
183176

184177
expect(resolved.provider).toBe('invalid-provider');
185-
expect(resolved.baseProvider).toBe('invalid-provider'); // Falls back to same name
178+
expect(resolved.baseProvider).toBe('openai-compatible'); // Falls back to openai-compatible for unknown providers
186179
expect(resolved.baseURL).toBeUndefined();
187180
expect(resolved.apiKey).toBeUndefined();
188181
});

multimodal/tarko/model-provider/tests/model-resolver.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,28 @@ describe('resolveModel', () => {
164164
baseProvider: 'azure-openai',
165165
});
166166
});
167+
168+
it('should handle custom OpenAI-compatible providers like kimi', () => {
169+
// Test case for issue #1822: custom providers should default to openai-compatible
170+
const agentModel: AgentModel = {
171+
provider: 'kimi' as any, // Custom provider not in predefined list
172+
id: 'kimi-k2.5',
173+
displayName: 'kimi k2.5',
174+
apiKey: 'kimi-api-key',
175+
baseURL: 'https://api.moonshot.cn/v1',
176+
};
177+
178+
const result = resolveModel(agentModel);
179+
180+
expect(result).toEqual({
181+
provider: 'kimi',
182+
id: 'kimi-k2.5',
183+
displayName: 'kimi k2.5',
184+
baseURL: 'https://api.moonshot.cn/v1',
185+
apiKey: 'kimi-api-key',
186+
headers: {},
187+
params: undefined,
188+
baseProvider: 'openai-compatible', // Should default to openai-compatible for unknown providers
189+
});
190+
});
167191
});

0 commit comments

Comments
 (0)