kimi-vercel-ai-sdk-provider 0.2.0 → 0.4.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/dist/index.mjs CHANGED
@@ -31,7 +31,7 @@ var kimiErrorSchema = z.union([
31
31
  error: z.object({
32
32
  message: z.string(),
33
33
  type: z.string().nullish(),
34
- param: z.any().nullish(),
34
+ param: z.string().nullish(),
35
35
  code: z.union([z.string(), z.number()]).nullish(),
36
36
  request_id: z.string().nullish()
37
37
  })
@@ -98,6 +98,9 @@ var KimiContextLengthError = class extends KimiError {
98
98
  };
99
99
 
100
100
  // src/core/types.ts
101
+ var THINKING_MODEL_TEMPERATURE = 1;
102
+ var THINKING_MODEL_DEFAULT_MAX_TOKENS = 32768;
103
+ var STANDARD_MODEL_DEFAULT_MAX_TOKENS = 4096;
101
104
  function inferModelCapabilities(modelId) {
102
105
  const isThinkingModel = modelId.includes("-thinking");
103
106
  const isK25Model = modelId.includes("k2.5") || modelId.includes("k2-5");
@@ -112,7 +115,12 @@ function inferModelCapabilities(modelId) {
112
115
  // 256k context window
113
116
  toolCalling: true,
114
117
  jsonMode: true,
115
- structuredOutputs: true
118
+ structuredOutputs: true,
119
+ // Thinking models require temperature=1.0 for optimal reasoning
120
+ defaultTemperature: isThinkingModel ? THINKING_MODEL_TEMPERATURE : void 0,
121
+ temperatureLocked: isThinkingModel,
122
+ // Thinking models need higher token limits to avoid truncated reasoning
123
+ defaultMaxOutputTokens: isThinkingModel ? THINKING_MODEL_DEFAULT_MAX_TOKENS : STANDARD_MODEL_DEFAULT_MAX_TOKENS
116
124
  };
117
125
  }
118
126
 
@@ -260,24 +268,25 @@ function isBuiltinToolName(toolName) {
260
268
  }
261
269
  var kimiTools = {
262
270
  /**
263
- * Create a web search tool for use with Kimi models.
264
- *
265
- * @param config - Optional configuration
266
- * @returns A provider tool definition
267
- *
268
- * @example
269
- * ```ts
270
- * import { kimi, kimiTools } from 'ai-sdk-provider-kimi';
271
- *
272
- * const result = await generateText({
273
- * model: kimi('kimi-k2.5'),
274
- * tools: {
275
- * webSearch: kimiTools.webSearch(),
276
- * },
277
- * prompt: 'What are the latest AI news?',
278
- * });
279
- * ```
280
- */
271
+ * Create a web search tool for use with Kimi models.
272
+ *
273
+ * @param config - Optional configuration
274
+ * @returns A provider tool definition
275
+ *
276
+ * @example
277
+ * ```ts
278
+ * import { kimi, kimiTools } from 'kimi-vercel-ai-sdk-provider
279
+ ';
280
+ *
281
+ * const result = await generateText({
282
+ * model: kimi('kimi-k2.5'),
283
+ * tools: {
284
+ * webSearch: kimiTools.webSearch(),
285
+ * },
286
+ * prompt: 'What are the latest AI news?',
287
+ * });
288
+ * ```
289
+ */
281
290
  webSearch: (config) => {
282
291
  return {
283
292
  type: "provider",
@@ -286,24 +295,25 @@ var kimiTools = {
286
295
  };
287
296
  },
288
297
  /**
289
- * Create a code interpreter tool for use with Kimi models.
290
- *
291
- * @param config - Optional configuration
292
- * @returns A provider tool definition
293
- *
294
- * @example
295
- * ```ts
296
- * import { kimi, kimiTools } from 'ai-sdk-provider-kimi';
297
- *
298
- * const result = await generateText({
299
- * model: kimi('kimi-k2.5'),
300
- * tools: {
301
- * codeInterpreter: kimiTools.codeInterpreter(),
302
- * },
303
- * prompt: 'Calculate the factorial of 10',
304
- * });
305
- * ```
306
- */
298
+ * Create a code interpreter tool for use with Kimi models.
299
+ *
300
+ * @param config - Optional configuration
301
+ * @returns A provider tool definition
302
+ *
303
+ * @example
304
+ * ```ts
305
+ * import { kimi, kimiTools } from 'kimi-vercel-ai-sdk-provider
306
+ ';
307
+ *
308
+ * const result = await generateText({
309
+ * model: kimi('kimi-k2.5'),
310
+ * tools: {
311
+ * codeInterpreter: kimiTools.codeInterpreter(),
312
+ * },
313
+ * prompt: 'Calculate the factorial of 10',
314
+ * });
315
+ * ```
316
+ */
307
317
  codeInterpreter: (config) => {
308
318
  return {
309
319
  type: "provider",
@@ -349,13 +359,15 @@ function prepareKimiTools({
349
359
  });
350
360
  continue;
351
361
  }
362
+ const sanitizedSchema = sanitizeToolSchema(tool.inputSchema);
352
363
  kimiTools2.push({
353
364
  type: "function",
354
365
  function: {
355
366
  name: tool.name,
356
367
  description: tool.description,
357
- parameters: tool.inputSchema,
358
- ...tool.strict != null ? { strict: tool.strict } : {}
368
+ parameters: sanitizedSchema
369
+ // Don't pass strict mode to Kimi - it may cause issues
370
+ // ...(tool.strict != null ? { strict: tool.strict } : {})
359
371
  }
360
372
  });
361
373
  }
@@ -429,6 +441,61 @@ function generateRequiredToolMessage(toolNames) {
429
441
  function generateSpecificToolMessage(toolName) {
430
442
  return `IMPORTANT INSTRUCTION: You MUST use the "${toolName}" tool to respond to this request. Do NOT use any other tool or provide a direct text response. Call the "${toolName}" tool with appropriate parameters.`;
431
443
  }
444
+ var UNSUPPORTED_SCHEMA_KEYWORDS = [
445
+ "$schema",
446
+ "$id",
447
+ "$ref",
448
+ "$defs",
449
+ "definitions",
450
+ "if",
451
+ "then",
452
+ "else",
453
+ "allOf",
454
+ "anyOf",
455
+ "oneOf",
456
+ "not",
457
+ "patternProperties",
458
+ "additionalItems",
459
+ "contains",
460
+ "propertyNames",
461
+ "const",
462
+ "contentMediaType",
463
+ "contentEncoding",
464
+ "examples",
465
+ "$comment"
466
+ ];
467
+ function sanitizeToolSchema(schema) {
468
+ if (schema === null || schema === void 0) {
469
+ return schema;
470
+ }
471
+ if (Array.isArray(schema)) {
472
+ return schema.map(sanitizeToolSchema);
473
+ }
474
+ if (typeof schema !== "object") {
475
+ return schema;
476
+ }
477
+ const sanitized = {};
478
+ const schemaObj = schema;
479
+ for (const [key, value] of Object.entries(schemaObj)) {
480
+ if (UNSUPPORTED_SCHEMA_KEYWORDS.includes(key)) {
481
+ continue;
482
+ }
483
+ if (key === "properties" && typeof value === "object" && value !== null) {
484
+ const props = {};
485
+ for (const [propKey, propValue] of Object.entries(value)) {
486
+ props[propKey] = sanitizeToolSchema(propValue);
487
+ }
488
+ sanitized[key] = props;
489
+ } else if (key === "items" && typeof value === "object") {
490
+ sanitized[key] = sanitizeToolSchema(value);
491
+ } else if (key === "additionalProperties" && typeof value === "object") {
492
+ sanitized[key] = sanitizeToolSchema(value);
493
+ } else {
494
+ sanitized[key] = value;
495
+ }
496
+ }
497
+ return sanitized;
498
+ }
432
499
  function tryConvertToKimiBuiltinTool(tool) {
433
500
  if (!tool.id.startsWith("kimi.")) {
434
501
  return void 0;
@@ -832,11 +899,24 @@ var KimiChatLanguageModel = class {
832
899
  if (toolChoiceSystemMessage) {
833
900
  messages.unshift({ role: "system", content: toolChoiceSystemMessage });
834
901
  }
902
+ const caps = this.capabilities;
903
+ let resolvedTemperature = temperature;
904
+ if (caps.temperatureLocked && caps.defaultTemperature !== void 0) {
905
+ if (temperature !== void 0 && temperature !== caps.defaultTemperature) {
906
+ warnings.push({
907
+ type: "compatibility",
908
+ feature: "temperature",
909
+ details: `Thinking models require temperature=${caps.defaultTemperature}. Your value (${temperature}) will be overridden.`
910
+ });
911
+ }
912
+ resolvedTemperature = caps.defaultTemperature;
913
+ }
914
+ const resolvedMaxTokens = maxOutputTokens ?? caps.defaultMaxOutputTokens;
835
915
  const body = removeUndefinedEntries({
836
916
  model: this.modelId,
837
917
  messages,
838
- max_tokens: maxOutputTokens,
839
- temperature,
918
+ max_tokens: resolvedMaxTokens,
919
+ temperature: resolvedTemperature,
840
920
  top_p: topP,
841
921
  frequency_penalty: frequencyPenalty,
842
922
  presence_penalty: presencePenalty,
@@ -1251,6 +1331,21 @@ var kimiTokenUsageSchema = z3.object({
1251
1331
  reasoning_tokens: z3.number().nullish()
1252
1332
  }).nullish()
1253
1333
  }).nullish();
1334
+ var kimiContentPartSchema = z3.union([
1335
+ z3.object({
1336
+ type: z3.literal("text"),
1337
+ text: z3.string()
1338
+ }),
1339
+ z3.object({
1340
+ type: z3.literal("image_url"),
1341
+ image_url: z3.object({
1342
+ url: z3.string()
1343
+ })
1344
+ }),
1345
+ z3.looseObject({
1346
+ type: z3.string()
1347
+ })
1348
+ ]);
1254
1349
  var kimiChatResponseSchema = z3.looseObject({
1255
1350
  id: z3.string().nullish(),
1256
1351
  created: z3.number().nullish(),
@@ -1259,7 +1354,7 @@ var kimiChatResponseSchema = z3.looseObject({
1259
1354
  z3.object({
1260
1355
  message: z3.object({
1261
1356
  role: z3.string().nullish(),
1262
- content: z3.union([z3.string(), z3.array(z3.any())]).nullish(),
1357
+ content: z3.union([z3.string(), z3.array(kimiContentPartSchema)]).nullish(),
1263
1358
  reasoning_content: z3.string().nullish(),
1264
1359
  reasoning: z3.string().nullish(),
1265
1360
  tool_calls: z3.array(
@@ -1306,6 +1401,115 @@ var kimiChatChunkBaseSchema = z3.looseObject({
1306
1401
  });
1307
1402
  var kimiChatChunkSchema = z3.union([kimiChatChunkBaseSchema, kimiErrorSchema]);
1308
1403
 
1404
+ // src/files/file-cache.ts
1405
+ var FileCache = class {
1406
+ constructor(options = {}) {
1407
+ this.maxSize = options.maxSize ?? 100;
1408
+ this.ttlMs = options.ttlMs ?? 36e5;
1409
+ this.cache = /* @__PURE__ */ new Map();
1410
+ }
1411
+ /**
1412
+ * Get a cached entry by content hash.
1413
+ * Returns undefined if not found or expired.
1414
+ * Moves the entry to the end (most recently used).
1415
+ */
1416
+ get(contentHash) {
1417
+ const entry = this.cache.get(contentHash);
1418
+ if (!entry) {
1419
+ return void 0;
1420
+ }
1421
+ if (this.isExpired(entry)) {
1422
+ this.cache.delete(contentHash);
1423
+ return void 0;
1424
+ }
1425
+ this.cache.delete(contentHash);
1426
+ this.cache.set(contentHash, entry);
1427
+ return entry;
1428
+ }
1429
+ /**
1430
+ * Set a cache entry.
1431
+ * Evicts the least recently used entry if cache is full.
1432
+ */
1433
+ set(contentHash, entry) {
1434
+ this.cache.delete(contentHash);
1435
+ while (this.cache.size >= this.maxSize) {
1436
+ const oldestKey = this.cache.keys().next().value;
1437
+ if (oldestKey !== void 0) {
1438
+ this.cache.delete(oldestKey);
1439
+ } else {
1440
+ break;
1441
+ }
1442
+ }
1443
+ this.cache.set(contentHash, entry);
1444
+ }
1445
+ /**
1446
+ * Check if an entry exists and is not expired.
1447
+ */
1448
+ has(contentHash) {
1449
+ return this.get(contentHash) !== void 0;
1450
+ }
1451
+ /**
1452
+ * Delete a specific entry.
1453
+ */
1454
+ delete(contentHash) {
1455
+ return this.cache.delete(contentHash);
1456
+ }
1457
+ /**
1458
+ * Clear all entries.
1459
+ */
1460
+ clear() {
1461
+ this.cache.clear();
1462
+ }
1463
+ /**
1464
+ * Get the current cache size.
1465
+ */
1466
+ get size() {
1467
+ return this.cache.size;
1468
+ }
1469
+ /**
1470
+ * Remove all expired entries.
1471
+ */
1472
+ prune() {
1473
+ let pruned = 0;
1474
+ for (const [key, entry] of this.cache) {
1475
+ if (this.isExpired(entry)) {
1476
+ this.cache.delete(key);
1477
+ pruned++;
1478
+ }
1479
+ }
1480
+ return pruned;
1481
+ }
1482
+ /**
1483
+ * Check if an entry is expired.
1484
+ */
1485
+ isExpired(entry) {
1486
+ return Date.now() - entry.createdAt > this.ttlMs;
1487
+ }
1488
+ };
1489
+ function generateContentHash(data) {
1490
+ const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
1491
+ let hash = 2166136261;
1492
+ for (let i = 0; i < bytes.length; i++) {
1493
+ hash ^= bytes[i];
1494
+ hash = Math.imul(hash, 16777619);
1495
+ }
1496
+ hash ^= bytes.length;
1497
+ return (hash >>> 0).toString(16).padStart(8, "0");
1498
+ }
1499
+ function generateCacheKey(data, filename) {
1500
+ const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
1501
+ const contentHash = generateContentHash(data);
1502
+ const normalizedFilename = filename.toLowerCase().replace(/[^a-z0-9.]/g, "_");
1503
+ return `${contentHash}_${bytes.length}_${normalizedFilename}`;
1504
+ }
1505
+ var defaultCache = null;
1506
+ function getDefaultFileCache() {
1507
+ if (!defaultCache) {
1508
+ defaultCache = new FileCache();
1509
+ }
1510
+ return defaultCache;
1511
+ }
1512
+
1309
1513
  // src/files/file-utils.ts
1310
1514
  var SUPPORTED_FILE_EXTENSIONS = [
1311
1515
  // Documents
@@ -1661,8 +1865,10 @@ async function processAttachments(options) {
1661
1865
  clientConfig,
1662
1866
  autoUploadDocuments = true,
1663
1867
  uploadImages = false,
1664
- cleanupAfterExtract = false
1868
+ cleanupAfterExtract = false,
1869
+ cache = false
1665
1870
  } = options;
1871
+ const cacheInstance = cache === true ? getDefaultFileCache() : cache === false ? null : cache;
1666
1872
  const results = [];
1667
1873
  const client = new KimiFileClient(clientConfig);
1668
1874
  for (const attachment of attachments) {
@@ -1670,7 +1876,8 @@ async function processAttachments(options) {
1670
1876
  const processed = await processAttachment(attachment, client, {
1671
1877
  autoUploadDocuments,
1672
1878
  uploadImages,
1673
- cleanupAfterExtract
1879
+ cleanupAfterExtract,
1880
+ cache: cacheInstance
1674
1881
  });
1675
1882
  results.push(processed);
1676
1883
  } catch (error) {
@@ -1730,12 +1937,35 @@ async function processAttachment(attachment, client, options) {
1730
1937
  error: "No content or URL provided for document attachment"
1731
1938
  };
1732
1939
  }
1940
+ const filename = attachment.name ?? guessFilename(attachment, contentType);
1941
+ if (options.cache) {
1942
+ const cacheKey = generateCacheKey(data, filename);
1943
+ const cached = options.cache.get(cacheKey);
1944
+ if (cached) {
1945
+ return {
1946
+ original: attachment,
1947
+ type: "text-inject",
1948
+ textContent: cached.content,
1949
+ fileId: cached.fileId
1950
+ };
1951
+ }
1952
+ }
1733
1953
  const result = await client.uploadAndExtract({
1734
1954
  data,
1735
- filename: attachment.name ?? guessFilename(attachment, contentType),
1955
+ filename,
1736
1956
  mediaType: contentType,
1737
1957
  purpose: "file-extract"
1738
1958
  });
1959
+ if (options.cache && result.content) {
1960
+ const cacheKey = generateCacheKey(data, filename);
1961
+ const cacheEntry = {
1962
+ fileId: result.file.id,
1963
+ content: result.content,
1964
+ createdAt: Date.now(),
1965
+ purpose: "file-extract"
1966
+ };
1967
+ options.cache.set(cacheKey, cacheEntry);
1968
+ }
1739
1969
  if (options.cleanupAfterExtract && result.file.id) {
1740
1970
  try {
1741
1971
  await client.deleteFile(result.file.id);
@@ -1776,7 +2006,7 @@ function guessFilename(attachment, contentType) {
1776
2006
  const urlPath = attachment.url.split("?")[0];
1777
2007
  const segments = urlPath.split("/");
1778
2008
  const lastSegment = segments[segments.length - 1];
1779
- if (lastSegment && lastSegment.includes(".")) {
2009
+ if (lastSegment.includes(".")) {
1780
2010
  return lastSegment;
1781
2011
  }
1782
2012
  }
@@ -1792,7 +2022,7 @@ function guessFilename(attachment, contentType) {
1792
2022
  }
1793
2023
 
1794
2024
  // src/version.ts
1795
- var VERSION = "0.2.0".length > 0 ? "0.2.0" : "0.0.0";
2025
+ var VERSION = "0.4.0".length > 0 ? "0.4.0" : "0.0.0";
1796
2026
 
1797
2027
  // src/kimi-provider.ts
1798
2028
  var GLOBAL_BASE_URL = "https://api.moonshot.ai/v1";