kimi-vercel-ai-sdk-provider 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +198 -0
- package/README.md +871 -0
- package/dist/index.d.mts +1317 -0
- package/dist/index.d.ts +1317 -0
- package/dist/index.js +2764 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2734 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +70 -0
- package/src/__tests__/caching.test.ts +97 -0
- package/src/__tests__/chat.test.ts +386 -0
- package/src/__tests__/code-integration.test.ts +562 -0
- package/src/__tests__/code-provider.test.ts +289 -0
- package/src/__tests__/code.test.ts +427 -0
- package/src/__tests__/core.test.ts +172 -0
- package/src/__tests__/files.test.ts +185 -0
- package/src/__tests__/integration.test.ts +457 -0
- package/src/__tests__/provider.test.ts +188 -0
- package/src/__tests__/tools.test.ts +519 -0
- package/src/chat/index.ts +42 -0
- package/src/chat/kimi-chat-language-model.ts +829 -0
- package/src/chat/kimi-chat-messages.ts +297 -0
- package/src/chat/kimi-chat-response.ts +84 -0
- package/src/chat/kimi-chat-settings.ts +216 -0
- package/src/code/index.ts +66 -0
- package/src/code/kimi-code-language-model.ts +669 -0
- package/src/code/kimi-code-messages.ts +303 -0
- package/src/code/kimi-code-provider.ts +239 -0
- package/src/code/kimi-code-settings.ts +193 -0
- package/src/code/kimi-code-types.ts +354 -0
- package/src/core/errors.ts +140 -0
- package/src/core/index.ts +36 -0
- package/src/core/types.ts +148 -0
- package/src/core/utils.ts +210 -0
- package/src/files/attachment-processor.ts +276 -0
- package/src/files/file-utils.ts +257 -0
- package/src/files/index.ts +24 -0
- package/src/files/kimi-file-client.ts +292 -0
- package/src/index.ts +122 -0
- package/src/kimi-provider.ts +263 -0
- package/src/tools/builtin-tools.ts +273 -0
- package/src/tools/index.ts +33 -0
- package/src/tools/prepare-tools.ts +306 -0
- package/src/version.ts +4 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
KimiAuthenticationError,
|
|
4
|
+
KimiContentFilterError,
|
|
5
|
+
KimiContextLengthError,
|
|
6
|
+
KimiError,
|
|
7
|
+
KimiModelNotFoundError,
|
|
8
|
+
KimiRateLimitError,
|
|
9
|
+
KimiValidationError,
|
|
10
|
+
kimiErrorSchema
|
|
11
|
+
} from '../core';
|
|
12
|
+
|
|
13
|
+
describe('KimiError', () => {
|
|
14
|
+
it('should create a base error', () => {
|
|
15
|
+
const error = new KimiError('Test error', 'test_code', 500);
|
|
16
|
+
expect(error.message).toBe('Test error');
|
|
17
|
+
expect(error.code).toBe('test_code');
|
|
18
|
+
expect(error.statusCode).toBe(500);
|
|
19
|
+
expect(error.name).toBe('KimiError');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should work without status code', () => {
|
|
23
|
+
const error = new KimiError('Test error', 'test_code');
|
|
24
|
+
expect(error.statusCode).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('KimiAuthenticationError', () => {
|
|
29
|
+
it('should create authentication error with default message', () => {
|
|
30
|
+
const error = new KimiAuthenticationError();
|
|
31
|
+
expect(error.message).toBe('Invalid API key or authentication failed');
|
|
32
|
+
expect(error.code).toBe('authentication_error');
|
|
33
|
+
expect(error.statusCode).toBe(401);
|
|
34
|
+
expect(error.name).toBe('KimiAuthenticationError');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should create authentication error with custom message', () => {
|
|
38
|
+
const error = new KimiAuthenticationError('Custom auth error');
|
|
39
|
+
expect(error.message).toBe('Custom auth error');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('KimiRateLimitError', () => {
|
|
44
|
+
it('should create rate limit error with default message', () => {
|
|
45
|
+
const error = new KimiRateLimitError();
|
|
46
|
+
expect(error.message).toBe('Rate limit exceeded');
|
|
47
|
+
expect(error.code).toBe('rate_limit_error');
|
|
48
|
+
expect(error.statusCode).toBe(429);
|
|
49
|
+
expect(error.name).toBe('KimiRateLimitError');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should include retry after', () => {
|
|
53
|
+
const error = new KimiRateLimitError('Too many requests', 30);
|
|
54
|
+
expect(error.retryAfter).toBe(30);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('KimiValidationError', () => {
|
|
59
|
+
it('should create validation error', () => {
|
|
60
|
+
const error = new KimiValidationError('Invalid parameter');
|
|
61
|
+
expect(error.message).toBe('Invalid parameter');
|
|
62
|
+
expect(error.code).toBe('validation_error');
|
|
63
|
+
expect(error.statusCode).toBe(400);
|
|
64
|
+
expect(error.name).toBe('KimiValidationError');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should include param in message', () => {
|
|
68
|
+
const error = new KimiValidationError('Invalid value', 'temperature');
|
|
69
|
+
expect(error.message).toBe('Invalid value (param: temperature)');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('KimiModelNotFoundError', () => {
|
|
74
|
+
it('should create model not found error', () => {
|
|
75
|
+
const error = new KimiModelNotFoundError('kimi-invalid');
|
|
76
|
+
expect(error.message).toBe("Model 'kimi-invalid' not found");
|
|
77
|
+
expect(error.code).toBe('model_not_found');
|
|
78
|
+
expect(error.statusCode).toBe(404);
|
|
79
|
+
expect(error.modelId).toBe('kimi-invalid');
|
|
80
|
+
expect(error.name).toBe('KimiModelNotFoundError');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('KimiContentFilterError', () => {
|
|
85
|
+
it('should create content filter error with default message', () => {
|
|
86
|
+
const error = new KimiContentFilterError();
|
|
87
|
+
expect(error.message).toBe('Content was filtered due to policy violation');
|
|
88
|
+
expect(error.code).toBe('content_filter');
|
|
89
|
+
expect(error.statusCode).toBe(400);
|
|
90
|
+
expect(error.name).toBe('KimiContentFilterError');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('KimiContextLengthError', () => {
|
|
95
|
+
it('should create context length error with default message', () => {
|
|
96
|
+
const error = new KimiContextLengthError();
|
|
97
|
+
expect(error.message).toBe('Context length exceeded');
|
|
98
|
+
expect(error.code).toBe('context_length_exceeded');
|
|
99
|
+
expect(error.statusCode).toBe(400);
|
|
100
|
+
expect(error.name).toBe('KimiContextLengthError');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should create with custom message', () => {
|
|
104
|
+
const error = new KimiContextLengthError('Max 128k tokens exceeded');
|
|
105
|
+
expect(error.message).toBe('Max 128k tokens exceeded');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('kimiErrorSchema', () => {
|
|
110
|
+
it('should parse standard error response', () => {
|
|
111
|
+
const result = kimiErrorSchema.safeParse({
|
|
112
|
+
error: {
|
|
113
|
+
message: 'Invalid API key',
|
|
114
|
+
type: 'authentication_error',
|
|
115
|
+
code: 'invalid_api_key'
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(result.success).toBe(true);
|
|
120
|
+
if (result.success && 'error' in result.data) {
|
|
121
|
+
expect(result.data.error.message).toBe('Invalid API key');
|
|
122
|
+
expect(result.data.error.type).toBe('authentication_error');
|
|
123
|
+
expect(result.data.error.code).toBe('invalid_api_key');
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should parse error with minimal fields', () => {
|
|
128
|
+
const result = kimiErrorSchema.safeParse({
|
|
129
|
+
error: {
|
|
130
|
+
message: 'Something went wrong'
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(result.success).toBe(true);
|
|
135
|
+
if (result.success && 'error' in result.data) {
|
|
136
|
+
expect(result.data.error.message).toBe('Something went wrong');
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should parse simple message error format', () => {
|
|
141
|
+
const result = kimiErrorSchema.safeParse({
|
|
142
|
+
message: 'Simple error message'
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(result.success).toBe(true);
|
|
146
|
+
if (result.success && 'message' in result.data) {
|
|
147
|
+
expect(result.data.message).toBe('Simple error message');
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should parse error with numeric code', () => {
|
|
152
|
+
const result = kimiErrorSchema.safeParse({
|
|
153
|
+
error: {
|
|
154
|
+
message: 'Rate limit exceeded',
|
|
155
|
+
code: 429
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(result.success).toBe(true);
|
|
160
|
+
if (result.success && 'error' in result.data) {
|
|
161
|
+
expect(result.data.error.code).toBe(429);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should fail for completely invalid structure', () => {
|
|
166
|
+
const result = kimiErrorSchema.safeParse({
|
|
167
|
+
invalid: 'structure'
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(result.success).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for file handling utilities.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
SUPPORTED_FILE_EXTENSIONS,
|
|
8
|
+
SUPPORTED_MIME_TYPES,
|
|
9
|
+
getMediaTypeFromExtension,
|
|
10
|
+
getPurposeFromMediaType,
|
|
11
|
+
isDocumentMediaType,
|
|
12
|
+
isFileExtractMediaType,
|
|
13
|
+
isImageMediaType,
|
|
14
|
+
isVideoMediaType
|
|
15
|
+
} from '../files/file-utils';
|
|
16
|
+
|
|
17
|
+
describe('file-utils', () => {
|
|
18
|
+
describe('SUPPORTED_FILE_EXTENSIONS', () => {
|
|
19
|
+
it('should include common document extensions', () => {
|
|
20
|
+
expect(SUPPORTED_FILE_EXTENSIONS).toContain('.pdf');
|
|
21
|
+
expect(SUPPORTED_FILE_EXTENSIONS).toContain('.doc');
|
|
22
|
+
expect(SUPPORTED_FILE_EXTENSIONS).toContain('.docx');
|
|
23
|
+
expect(SUPPORTED_FILE_EXTENSIONS).toContain('.txt');
|
|
24
|
+
expect(SUPPORTED_FILE_EXTENSIONS).toContain('.md');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should include common image extensions', () => {
|
|
28
|
+
expect(SUPPORTED_FILE_EXTENSIONS).toContain('.png');
|
|
29
|
+
expect(SUPPORTED_FILE_EXTENSIONS).toContain('.jpg');
|
|
30
|
+
expect(SUPPORTED_FILE_EXTENSIONS).toContain('.jpeg');
|
|
31
|
+
expect(SUPPORTED_FILE_EXTENSIONS).toContain('.gif');
|
|
32
|
+
expect(SUPPORTED_FILE_EXTENSIONS).toContain('.webp');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should include common code file extensions', () => {
|
|
36
|
+
expect(SUPPORTED_FILE_EXTENSIONS).toContain('.py');
|
|
37
|
+
expect(SUPPORTED_FILE_EXTENSIONS).toContain('.js');
|
|
38
|
+
expect(SUPPORTED_FILE_EXTENSIONS).toContain('.ts');
|
|
39
|
+
expect(SUPPORTED_FILE_EXTENSIONS).toContain('.tsx');
|
|
40
|
+
expect(SUPPORTED_FILE_EXTENSIONS).toContain('.java');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('SUPPORTED_MIME_TYPES', () => {
|
|
45
|
+
it('should map PDF to file-extract', () => {
|
|
46
|
+
expect(SUPPORTED_MIME_TYPES['application/pdf']).toBe('file-extract');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should map images to image purpose', () => {
|
|
50
|
+
expect(SUPPORTED_MIME_TYPES['image/jpeg']).toBe('image');
|
|
51
|
+
expect(SUPPORTED_MIME_TYPES['image/png']).toBe('image');
|
|
52
|
+
expect(SUPPORTED_MIME_TYPES['image/gif']).toBe('image');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should map videos to video purpose', () => {
|
|
56
|
+
expect(SUPPORTED_MIME_TYPES['video/mp4']).toBe('video');
|
|
57
|
+
expect(SUPPORTED_MIME_TYPES['video/webm']).toBe('video');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('isImageMediaType', () => {
|
|
62
|
+
it('should return true for image MIME types', () => {
|
|
63
|
+
expect(isImageMediaType('image/jpeg')).toBe(true);
|
|
64
|
+
expect(isImageMediaType('image/png')).toBe(true);
|
|
65
|
+
expect(isImageMediaType('image/gif')).toBe(true);
|
|
66
|
+
expect(isImageMediaType('image/webp')).toBe(true);
|
|
67
|
+
expect(isImageMediaType('image/svg+xml')).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should return false for non-image MIME types', () => {
|
|
71
|
+
expect(isImageMediaType('application/pdf')).toBe(false);
|
|
72
|
+
expect(isImageMediaType('video/mp4')).toBe(false);
|
|
73
|
+
expect(isImageMediaType('text/plain')).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('isVideoMediaType', () => {
|
|
78
|
+
it('should return true for video MIME types', () => {
|
|
79
|
+
expect(isVideoMediaType('video/mp4')).toBe(true);
|
|
80
|
+
expect(isVideoMediaType('video/webm')).toBe(true);
|
|
81
|
+
expect(isVideoMediaType('video/ogg')).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should return false for non-video MIME types', () => {
|
|
85
|
+
expect(isVideoMediaType('image/jpeg')).toBe(false);
|
|
86
|
+
expect(isVideoMediaType('application/pdf')).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('isFileExtractMediaType', () => {
|
|
91
|
+
it('should return true for text MIME types', () => {
|
|
92
|
+
expect(isFileExtractMediaType('text/plain')).toBe(true);
|
|
93
|
+
expect(isFileExtractMediaType('text/html')).toBe(true);
|
|
94
|
+
expect(isFileExtractMediaType('text/markdown')).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should return true for PDF', () => {
|
|
98
|
+
expect(isFileExtractMediaType('application/pdf')).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should return false for images', () => {
|
|
102
|
+
expect(isFileExtractMediaType('image/jpeg')).toBe(false);
|
|
103
|
+
expect(isFileExtractMediaType('image/png')).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should return false for videos', () => {
|
|
107
|
+
expect(isFileExtractMediaType('video/mp4')).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('isDocumentMediaType', () => {
|
|
112
|
+
it('should return true for document MIME types', () => {
|
|
113
|
+
expect(isDocumentMediaType('application/pdf')).toBe(true);
|
|
114
|
+
expect(isDocumentMediaType('application/msword')).toBe(true);
|
|
115
|
+
expect(isDocumentMediaType('application/vnd.openxmlformats-officedocument.wordprocessingml.document')).toBe(true);
|
|
116
|
+
expect(isDocumentMediaType('application/vnd.ms-excel')).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should return false for non-document types', () => {
|
|
120
|
+
expect(isDocumentMediaType('text/plain')).toBe(false);
|
|
121
|
+
expect(isDocumentMediaType('image/jpeg')).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('getPurposeFromMediaType', () => {
|
|
126
|
+
it('should return image for image MIME types', () => {
|
|
127
|
+
expect(getPurposeFromMediaType('image/jpeg')).toBe('image');
|
|
128
|
+
expect(getPurposeFromMediaType('image/png')).toBe('image');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should return video for video MIME types', () => {
|
|
132
|
+
expect(getPurposeFromMediaType('video/mp4')).toBe('video');
|
|
133
|
+
expect(getPurposeFromMediaType('video/webm')).toBe('video');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should return file-extract for other types', () => {
|
|
137
|
+
expect(getPurposeFromMediaType('application/pdf')).toBe('file-extract');
|
|
138
|
+
expect(getPurposeFromMediaType('text/plain')).toBe('file-extract');
|
|
139
|
+
expect(getPurposeFromMediaType('application/json')).toBe('file-extract');
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('getMediaTypeFromExtension', () => {
|
|
144
|
+
it('should return correct MIME type for document extensions', () => {
|
|
145
|
+
expect(getMediaTypeFromExtension('.pdf')).toBe('application/pdf');
|
|
146
|
+
expect(getMediaTypeFromExtension('.txt')).toBe('text/plain');
|
|
147
|
+
expect(getMediaTypeFromExtension('.md')).toBe('text/markdown');
|
|
148
|
+
expect(getMediaTypeFromExtension('.json')).toBe('application/json');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should return correct MIME type for image extensions', () => {
|
|
152
|
+
expect(getMediaTypeFromExtension('.jpg')).toBe('image/jpeg');
|
|
153
|
+
expect(getMediaTypeFromExtension('.jpeg')).toBe('image/jpeg');
|
|
154
|
+
expect(getMediaTypeFromExtension('.png')).toBe('image/png');
|
|
155
|
+
expect(getMediaTypeFromExtension('.gif')).toBe('image/gif');
|
|
156
|
+
expect(getMediaTypeFromExtension('.webp')).toBe('image/webp');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should return correct MIME type for video extensions', () => {
|
|
160
|
+
expect(getMediaTypeFromExtension('.mp4')).toBe('video/mp4');
|
|
161
|
+
expect(getMediaTypeFromExtension('.webm')).toBe('video/webm');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should return correct MIME type for code extensions', () => {
|
|
165
|
+
expect(getMediaTypeFromExtension('.js')).toBe('text/javascript');
|
|
166
|
+
expect(getMediaTypeFromExtension('.ts')).toBe('text/typescript');
|
|
167
|
+
expect(getMediaTypeFromExtension('.py')).toBe('text/x-python');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should handle extensions without leading dot', () => {
|
|
171
|
+
expect(getMediaTypeFromExtension('pdf')).toBe('application/pdf');
|
|
172
|
+
expect(getMediaTypeFromExtension('jpg')).toBe('image/jpeg');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should handle uppercase extensions', () => {
|
|
176
|
+
expect(getMediaTypeFromExtension('.PDF')).toBe('application/pdf');
|
|
177
|
+
expect(getMediaTypeFromExtension('.JPG')).toBe('image/jpeg');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should return octet-stream for unknown extensions', () => {
|
|
181
|
+
expect(getMediaTypeFromExtension('.xyz')).toBe('application/octet-stream');
|
|
182
|
+
expect(getMediaTypeFromExtension('.unknown')).toBe('application/octet-stream');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|