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.
Files changed (44) hide show
  1. package/LICENSE +198 -0
  2. package/README.md +871 -0
  3. package/dist/index.d.mts +1317 -0
  4. package/dist/index.d.ts +1317 -0
  5. package/dist/index.js +2764 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +2734 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +70 -0
  10. package/src/__tests__/caching.test.ts +97 -0
  11. package/src/__tests__/chat.test.ts +386 -0
  12. package/src/__tests__/code-integration.test.ts +562 -0
  13. package/src/__tests__/code-provider.test.ts +289 -0
  14. package/src/__tests__/code.test.ts +427 -0
  15. package/src/__tests__/core.test.ts +172 -0
  16. package/src/__tests__/files.test.ts +185 -0
  17. package/src/__tests__/integration.test.ts +457 -0
  18. package/src/__tests__/provider.test.ts +188 -0
  19. package/src/__tests__/tools.test.ts +519 -0
  20. package/src/chat/index.ts +42 -0
  21. package/src/chat/kimi-chat-language-model.ts +829 -0
  22. package/src/chat/kimi-chat-messages.ts +297 -0
  23. package/src/chat/kimi-chat-response.ts +84 -0
  24. package/src/chat/kimi-chat-settings.ts +216 -0
  25. package/src/code/index.ts +66 -0
  26. package/src/code/kimi-code-language-model.ts +669 -0
  27. package/src/code/kimi-code-messages.ts +303 -0
  28. package/src/code/kimi-code-provider.ts +239 -0
  29. package/src/code/kimi-code-settings.ts +193 -0
  30. package/src/code/kimi-code-types.ts +354 -0
  31. package/src/core/errors.ts +140 -0
  32. package/src/core/index.ts +36 -0
  33. package/src/core/types.ts +148 -0
  34. package/src/core/utils.ts +210 -0
  35. package/src/files/attachment-processor.ts +276 -0
  36. package/src/files/file-utils.ts +257 -0
  37. package/src/files/index.ts +24 -0
  38. package/src/files/kimi-file-client.ts +292 -0
  39. package/src/index.ts +122 -0
  40. package/src/kimi-provider.ts +263 -0
  41. package/src/tools/builtin-tools.ts +273 -0
  42. package/src/tools/index.ts +33 -0
  43. package/src/tools/prepare-tools.ts +306 -0
  44. package/src/version.ts +4 -0
@@ -0,0 +1,562 @@
1
+ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
2
+ import { createKimiCode } from '../code';
3
+
4
+ // Mock fetch for API testing
5
+ const mockFetch = vi.fn();
6
+
7
+ describe('KimiCodeLanguageModel Integration', () => {
8
+ const originalEnv = process.env;
9
+
10
+ beforeEach(() => {
11
+ vi.resetAllMocks();
12
+ process.env = { ...originalEnv };
13
+ process.env.KIMI_CODE_API_KEY = 'sk-test-key';
14
+ });
15
+
16
+ afterEach(() => {
17
+ process.env = originalEnv;
18
+ });
19
+
20
+ describe('doGenerate', () => {
21
+ it('should make correct API request', async () => {
22
+ const mockResponse = {
23
+ id: 'msg_123',
24
+ type: 'message',
25
+ model: 'kimi-for-coding',
26
+ content: [{ type: 'text', text: 'Hello!' }],
27
+ stop_reason: 'end_turn',
28
+ usage: {
29
+ input_tokens: 10,
30
+ output_tokens: 5
31
+ }
32
+ };
33
+
34
+ mockFetch.mockResolvedValueOnce({
35
+ ok: true,
36
+ status: 200,
37
+ headers: new Headers({
38
+ 'content-type': 'application/json',
39
+ 'x-request-id': 'req-123'
40
+ }),
41
+ json: async () => mockResponse,
42
+ text: async () => JSON.stringify(mockResponse)
43
+ });
44
+
45
+ const provider = createKimiCode({
46
+ apiKey: 'sk-test-key',
47
+ fetch: mockFetch
48
+ });
49
+ const model = provider('kimi-for-coding');
50
+
51
+ const result = await model.doGenerate({
52
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Hello' }] }]
53
+ });
54
+
55
+ expect(mockFetch).toHaveBeenCalledTimes(1);
56
+ const [url, options] = mockFetch.mock.calls[0];
57
+
58
+ expect(url).toBe('https://api.kimi.com/coding/v1/messages');
59
+ expect(options.method).toBe('POST');
60
+ expect(options.headers['x-api-key']).toBe('sk-test-key');
61
+ expect(options.headers['anthropic-version']).toBe('2023-06-01');
62
+
63
+ const body = JSON.parse(options.body);
64
+ expect(body.model).toBe('kimi-for-coding');
65
+ expect(body.messages).toEqual([{ role: 'user', content: 'Hello' }]);
66
+
67
+ expect(result.content).toEqual([{ type: 'text', text: 'Hello!' }]);
68
+ expect(result.finishReason.unified).toBe('stop');
69
+ expect(result.usage.inputTokens.total).toBe(10);
70
+ expect(result.usage.outputTokens.total).toBe(5);
71
+ });
72
+
73
+ it('should handle thinking blocks in response', async () => {
74
+ const mockResponse = {
75
+ id: 'msg_123',
76
+ type: 'message',
77
+ model: 'kimi-k2-thinking',
78
+ content: [
79
+ { type: 'thinking', thinking: 'Let me think about this...' },
80
+ { type: 'text', text: 'The answer is 42.' }
81
+ ],
82
+ stop_reason: 'end_turn',
83
+ usage: { input_tokens: 20, output_tokens: 30 }
84
+ };
85
+
86
+ mockFetch.mockResolvedValueOnce({
87
+ ok: true,
88
+ status: 200,
89
+ headers: new Headers({ 'content-type': 'application/json' }),
90
+ json: async () => mockResponse,
91
+ text: async () => JSON.stringify(mockResponse)
92
+ });
93
+
94
+ const provider = createKimiCode({
95
+ apiKey: 'sk-test-key',
96
+ fetch: mockFetch
97
+ });
98
+ const model = provider('kimi-k2-thinking', {
99
+ extendedThinking: { enabled: true, effort: 'medium' }
100
+ });
101
+
102
+ const result = await model.doGenerate({
103
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'What is the meaning of life?' }] }]
104
+ });
105
+
106
+ expect(result.content).toHaveLength(2);
107
+ expect(result.content[0]).toEqual({
108
+ type: 'reasoning',
109
+ text: 'Let me think about this...'
110
+ });
111
+ expect(result.content[1]).toEqual({
112
+ type: 'text',
113
+ text: 'The answer is 42.'
114
+ });
115
+ });
116
+
117
+ it('should handle tool use in response', async () => {
118
+ const mockResponse = {
119
+ id: 'msg_123',
120
+ type: 'message',
121
+ model: 'kimi-for-coding',
122
+ content: [
123
+ {
124
+ type: 'tool_use',
125
+ id: 'tool_123',
126
+ name: 'read_file',
127
+ input: { path: '/src/index.ts' }
128
+ }
129
+ ],
130
+ stop_reason: 'tool_use',
131
+ usage: { input_tokens: 15, output_tokens: 25 }
132
+ };
133
+
134
+ mockFetch.mockResolvedValueOnce({
135
+ ok: true,
136
+ status: 200,
137
+ headers: new Headers({ 'content-type': 'application/json' }),
138
+ json: async () => mockResponse,
139
+ text: async () => JSON.stringify(mockResponse)
140
+ });
141
+
142
+ const provider = createKimiCode({
143
+ apiKey: 'sk-test-key',
144
+ fetch: mockFetch
145
+ });
146
+ const model = provider('kimi-for-coding');
147
+
148
+ const result = await model.doGenerate({
149
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Read the index file' }] }],
150
+
151
+ tools: [
152
+ {
153
+ type: 'function',
154
+ name: 'read_file',
155
+ description: 'Read a file',
156
+ inputSchema: {
157
+ type: 'object',
158
+ properties: { path: { type: 'string' } }
159
+ }
160
+ }
161
+ ]
162
+ });
163
+
164
+ expect(result.content).toHaveLength(1);
165
+ expect(result.content[0]).toEqual({
166
+ type: 'tool-call',
167
+ toolCallId: 'tool_123',
168
+ toolName: 'read_file',
169
+ input: JSON.stringify({ path: '/src/index.ts' })
170
+ });
171
+ expect(result.finishReason.unified).toBe('tool-calls');
172
+ });
173
+
174
+ it('should send thinking parameter when enabled', async () => {
175
+ const mockResponse = {
176
+ id: 'msg_123',
177
+ type: 'message',
178
+ model: 'kimi-for-coding',
179
+ content: [{ type: 'text', text: 'Done!' }],
180
+ stop_reason: 'end_turn',
181
+ usage: { input_tokens: 10, output_tokens: 5 }
182
+ };
183
+
184
+ mockFetch.mockResolvedValueOnce({
185
+ ok: true,
186
+ status: 200,
187
+ headers: new Headers({ 'content-type': 'application/json' }),
188
+ json: async () => mockResponse,
189
+ text: async () => JSON.stringify(mockResponse)
190
+ });
191
+
192
+ const provider = createKimiCode({
193
+ apiKey: 'sk-test-key',
194
+ fetch: mockFetch
195
+ });
196
+ const model = provider('kimi-for-coding', {
197
+ extendedThinking: { enabled: true, effort: 'high' }
198
+ });
199
+
200
+ await model.doGenerate({
201
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Think hard' }] }],
202
+
203
+ });
204
+
205
+ const body = JSON.parse(mockFetch.mock.calls[0][1].body);
206
+ expect(body.thinking).toEqual({
207
+ type: 'enabled',
208
+ budget_tokens: 16384
209
+ });
210
+ });
211
+
212
+ it('should handle cache token usage', async () => {
213
+ const mockResponse = {
214
+ id: 'msg_123',
215
+ type: 'message',
216
+ model: 'kimi-for-coding',
217
+ content: [{ type: 'text', text: 'Cached response!' }],
218
+ stop_reason: 'end_turn',
219
+ usage: {
220
+ input_tokens: 100,
221
+ output_tokens: 20,
222
+ cache_read_input_tokens: 80,
223
+ cache_creation_input_tokens: 10
224
+ }
225
+ };
226
+
227
+ mockFetch.mockResolvedValueOnce({
228
+ ok: true,
229
+ status: 200,
230
+ headers: new Headers({ 'content-type': 'application/json' }),
231
+ json: async () => mockResponse,
232
+ text: async () => JSON.stringify(mockResponse)
233
+ });
234
+
235
+ const provider = createKimiCode({
236
+ apiKey: 'sk-test-key',
237
+ fetch: mockFetch
238
+ });
239
+ const model = provider('kimi-for-coding');
240
+
241
+ const result = await model.doGenerate({
242
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Test' }] }],
243
+
244
+ });
245
+
246
+ expect(result.usage.inputTokens.total).toBe(100);
247
+ expect(result.usage.inputTokens.cacheRead).toBe(80);
248
+ expect(result.usage.inputTokens.cacheWrite).toBe(10);
249
+ expect(result.usage.inputTokens.noCache).toBe(20);
250
+ });
251
+
252
+ it('should handle API errors', async () => {
253
+ const errorResponse = {
254
+ error: {
255
+ type: 'invalid_request_error',
256
+ message: 'Invalid API key'
257
+ }
258
+ };
259
+
260
+ mockFetch.mockResolvedValueOnce({
261
+ ok: false,
262
+ status: 401,
263
+ headers: new Headers({ 'content-type': 'application/json' }),
264
+ json: async () => errorResponse,
265
+ text: async () => JSON.stringify(errorResponse)
266
+ });
267
+
268
+ const provider = createKimiCode({
269
+ apiKey: 'sk-invalid-key',
270
+ fetch: mockFetch
271
+ });
272
+ const model = provider('kimi-for-coding');
273
+
274
+ await expect(
275
+ model.doGenerate({
276
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Test' }] }],
277
+
278
+ })
279
+ ).rejects.toThrow('Invalid API key');
280
+ });
281
+
282
+ it('should handle max_tokens stop reason', async () => {
283
+ const mockResponse = {
284
+ id: 'msg_123',
285
+ type: 'message',
286
+ model: 'kimi-for-coding',
287
+ content: [{ type: 'text', text: 'Truncated...' }],
288
+ stop_reason: 'max_tokens',
289
+ usage: { input_tokens: 10, output_tokens: 100 }
290
+ };
291
+
292
+ mockFetch.mockResolvedValueOnce({
293
+ ok: true,
294
+ status: 200,
295
+ headers: new Headers({ 'content-type': 'application/json' }),
296
+ json: async () => mockResponse,
297
+ text: async () => JSON.stringify(mockResponse)
298
+ });
299
+
300
+ const provider = createKimiCode({
301
+ apiKey: 'sk-test-key',
302
+ fetch: mockFetch
303
+ });
304
+ const model = provider('kimi-for-coding');
305
+
306
+ const result = await model.doGenerate({
307
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Write a long story' }] }],
308
+
309
+ maxOutputTokens: 100
310
+ });
311
+
312
+ expect(result.finishReason.unified).toBe('length');
313
+ });
314
+ });
315
+
316
+ describe('doStream', () => {
317
+ it('should handle streaming response', async () => {
318
+ // Create a mock SSE stream
319
+ const events = [
320
+ 'event: message_start\ndata: {"type":"message_start","message":{"id":"msg_123","type":"message","model":"kimi-for-coding","content":[],"usage":{"input_tokens":10}}}\n\n',
321
+ 'event: content_block_start\ndata: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}\n\n',
322
+ 'event: content_block_delta\ndata: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}\n\n',
323
+ 'event: content_block_delta\ndata: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" World!"}}\n\n',
324
+ 'event: content_block_stop\ndata: {"type":"content_block_stop","index":0}\n\n',
325
+ 'event: message_delta\ndata: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":5}}\n\n',
326
+ 'event: message_stop\ndata: {"type":"message_stop"}\n\n'
327
+ ];
328
+
329
+ const encoder = new TextEncoder();
330
+ const stream = new ReadableStream({
331
+ start(controller) {
332
+ for (const event of events) {
333
+ controller.enqueue(encoder.encode(event));
334
+ }
335
+ controller.close();
336
+ }
337
+ });
338
+
339
+ mockFetch.mockResolvedValueOnce({
340
+ ok: true,
341
+ status: 200,
342
+ headers: new Headers({ 'content-type': 'text/event-stream' }),
343
+ body: stream
344
+ });
345
+
346
+ const provider = createKimiCode({
347
+ apiKey: 'sk-test-key',
348
+ fetch: mockFetch
349
+ });
350
+ const model = provider('kimi-for-coding');
351
+
352
+ const result = await model.doStream({
353
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Say hello' }] }],
354
+
355
+ });
356
+
357
+ const parts: any[] = [];
358
+ for await (const part of result.stream) {
359
+ parts.push(part);
360
+ }
361
+
362
+ // Verify streaming worked
363
+ expect(parts.length).toBeGreaterThan(0);
364
+
365
+ // Check for expected part types
366
+ const partTypes = parts.map((p) => p.type);
367
+ expect(partTypes).toContain('stream-start');
368
+ expect(partTypes).toContain('response-metadata');
369
+ expect(partTypes).toContain('text-start');
370
+ expect(partTypes).toContain('text-delta');
371
+ expect(partTypes).toContain('text-end');
372
+ expect(partTypes).toContain('finish');
373
+ });
374
+
375
+ it('should handle thinking blocks in stream', async () => {
376
+ const events = [
377
+ 'event: message_start\ndata: {"type":"message_start","message":{"id":"msg_123","type":"message","model":"kimi-k2-thinking","content":[],"usage":{"input_tokens":10}}}\n\n',
378
+ 'event: content_block_start\ndata: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":""}}\n\n',
379
+ 'event: content_block_delta\ndata: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"Thinking..."}}\n\n',
380
+ 'event: content_block_stop\ndata: {"type":"content_block_stop","index":0}\n\n',
381
+ 'event: content_block_start\ndata: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""}}\n\n',
382
+ 'event: content_block_delta\ndata: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Answer!"}}\n\n',
383
+ 'event: content_block_stop\ndata: {"type":"content_block_stop","index":1}\n\n',
384
+ 'event: message_delta\ndata: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":10}}\n\n',
385
+ 'event: message_stop\ndata: {"type":"message_stop"}\n\n'
386
+ ];
387
+
388
+ const encoder = new TextEncoder();
389
+ const stream = new ReadableStream({
390
+ start(controller) {
391
+ for (const event of events) {
392
+ controller.enqueue(encoder.encode(event));
393
+ }
394
+ controller.close();
395
+ }
396
+ });
397
+
398
+ mockFetch.mockResolvedValueOnce({
399
+ ok: true,
400
+ status: 200,
401
+ headers: new Headers({ 'content-type': 'text/event-stream' }),
402
+ body: stream
403
+ });
404
+
405
+ const provider = createKimiCode({
406
+ apiKey: 'sk-test-key',
407
+ fetch: mockFetch
408
+ });
409
+ const model = provider('kimi-k2-thinking');
410
+
411
+ const result = await model.doStream({
412
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Think about this' }] }],
413
+
414
+ });
415
+
416
+ const parts: any[] = [];
417
+ for await (const part of result.stream) {
418
+ parts.push(part);
419
+ }
420
+
421
+ const partTypes = parts.map((p) => p.type);
422
+ expect(partTypes).toContain('reasoning-start');
423
+ expect(partTypes).toContain('reasoning-delta');
424
+ expect(partTypes).toContain('reasoning-end');
425
+ expect(partTypes).toContain('text-start');
426
+ expect(partTypes).toContain('text-delta');
427
+ expect(partTypes).toContain('text-end');
428
+ });
429
+
430
+ it('should handle tool calls in stream', async () => {
431
+ const events = [
432
+ 'event: message_start\ndata: {"type":"message_start","message":{"id":"msg_123","type":"message","model":"kimi-for-coding","content":[],"usage":{"input_tokens":10}}}\n\n',
433
+ 'event: content_block_start\ndata: {"type":"content_block_start","index":0,"content_block":{"type":"tool_use","id":"tool_123","name":"read_file"}}\n\n',
434
+ 'event: content_block_delta\ndata: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"{\\"path\\":"}}\n\n',
435
+ 'event: content_block_delta\ndata: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"\\"/src/index.ts\\"}"}}\n\n',
436
+ 'event: content_block_stop\ndata: {"type":"content_block_stop","index":0}\n\n',
437
+ 'event: message_delta\ndata: {"type":"message_delta","delta":{"stop_reason":"tool_use"},"usage":{"output_tokens":15}}\n\n',
438
+ 'event: message_stop\ndata: {"type":"message_stop"}\n\n'
439
+ ];
440
+
441
+ const encoder = new TextEncoder();
442
+ const stream = new ReadableStream({
443
+ start(controller) {
444
+ for (const event of events) {
445
+ controller.enqueue(encoder.encode(event));
446
+ }
447
+ controller.close();
448
+ }
449
+ });
450
+
451
+ mockFetch.mockResolvedValueOnce({
452
+ ok: true,
453
+ status: 200,
454
+ headers: new Headers({ 'content-type': 'text/event-stream' }),
455
+ body: stream
456
+ });
457
+
458
+ const provider = createKimiCode({
459
+ apiKey: 'sk-test-key',
460
+ fetch: mockFetch
461
+ });
462
+ const model = provider('kimi-for-coding');
463
+
464
+ const result = await model.doStream({
465
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Read the file' }] }],
466
+
467
+ tools: [
468
+ {
469
+ type: 'function',
470
+ name: 'read_file',
471
+ description: 'Read a file',
472
+ inputSchema: { type: 'object', properties: { path: { type: 'string' } } }
473
+ }
474
+ ]
475
+ });
476
+
477
+ const parts: any[] = [];
478
+ for await (const part of result.stream) {
479
+ parts.push(part);
480
+ }
481
+
482
+ const partTypes = parts.map((p) => p.type);
483
+ expect(partTypes).toContain('tool-input-start');
484
+ expect(partTypes).toContain('tool-input-delta');
485
+ expect(partTypes).toContain('tool-input-end');
486
+ expect(partTypes).toContain('tool-call');
487
+
488
+ const toolCallPart = parts.find((p) => p.type === 'tool-call');
489
+ expect(toolCallPart.toolName).toBe('read_file');
490
+ expect(toolCallPart.input).toContain('/src/index.ts');
491
+ });
492
+ });
493
+
494
+ describe('request headers', () => {
495
+ it('should include Anthropic version header', async () => {
496
+ const mockResponse = {
497
+ id: 'msg_123',
498
+ type: 'message',
499
+ model: 'kimi-for-coding',
500
+ content: [{ type: 'text', text: 'OK' }],
501
+ stop_reason: 'end_turn',
502
+ usage: { input_tokens: 5, output_tokens: 2 }
503
+ };
504
+
505
+ mockFetch.mockResolvedValueOnce({
506
+ ok: true,
507
+ status: 200,
508
+ headers: new Headers({ 'content-type': 'application/json' }),
509
+ json: async () => mockResponse,
510
+ text: async () => JSON.stringify(mockResponse)
511
+ });
512
+
513
+ const provider = createKimiCode({
514
+ apiKey: 'sk-test-key',
515
+ fetch: mockFetch
516
+ });
517
+ const model = provider('kimi-for-coding');
518
+
519
+ await model.doGenerate({
520
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Test' }] }],
521
+
522
+ });
523
+
524
+ const headers = mockFetch.mock.calls[0][1].headers;
525
+ expect(headers['anthropic-version']).toBe('2023-06-01');
526
+ });
527
+
528
+ it('should include custom headers', async () => {
529
+ const mockResponse = {
530
+ id: 'msg_123',
531
+ type: 'message',
532
+ model: 'kimi-for-coding',
533
+ content: [{ type: 'text', text: 'OK' }],
534
+ stop_reason: 'end_turn',
535
+ usage: { input_tokens: 5, output_tokens: 2 }
536
+ };
537
+
538
+ mockFetch.mockResolvedValueOnce({
539
+ ok: true,
540
+ status: 200,
541
+ headers: new Headers({ 'content-type': 'application/json' }),
542
+ json: async () => mockResponse,
543
+ text: async () => JSON.stringify(mockResponse)
544
+ });
545
+
546
+ const provider = createKimiCode({
547
+ apiKey: 'sk-test-key',
548
+ headers: { 'x-custom-header': 'custom-value' },
549
+ fetch: mockFetch
550
+ });
551
+ const model = provider('kimi-for-coding');
552
+
553
+ await model.doGenerate({
554
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Test' }] }]
555
+ });
556
+
557
+ const headers = mockFetch.mock.calls[0][1].headers;
558
+ // Custom headers are passed through provider headers option
559
+ expect(headers['x-custom-header']).toBe('custom-value');
560
+ });
561
+ });
562
+ });