@chatluna/v1-shared-adapter 1.0.27 → 1.0.28

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/lib/index.cjs CHANGED
@@ -28,6 +28,7 @@ __export(index_exports, {
28
28
  createEmbeddings: () => createEmbeddings,
29
29
  createRequestContext: () => createRequestContext,
30
30
  expandReasoningEffortModelVariants: () => expandReasoningEffortModelVariants,
31
+ fetchFileLikeUrl: () => fetchFileLikeUrl,
31
32
  fetchImageUrl: () => fetchImageUrl,
32
33
  formatToolToOpenAITool: () => formatToolToOpenAITool,
33
34
  formatToolsToOpenAITools: () => formatToolsToOpenAITools,
@@ -190,7 +191,7 @@ var import_messages = require("@langchain/core/messages");
190
191
  var import_zod_to_json_schema = require("zod-to-json-schema");
191
192
  var import_string = require("koishi-plugin-chatluna/utils/string");
192
193
  var import_types = require("@langchain/core/utils/types");
193
- async function langchainMessageToOpenAIMessage(messages, plugin, model, supportImageInput2, removeSystemMessage) {
194
+ async function langchainMessageToOpenAIMessage(messages, plugin, model, supportImageInputType, removeSystemMessage) {
194
195
  const result = [];
195
196
  const normalizedModel = model ? normalizeOpenAIModelName(model) : model;
196
197
  const isDeepseekThinkModel = normalizedModel?.includes("deepseek-reasoner");
@@ -224,7 +225,7 @@ async function langchainMessageToOpenAIMessage(messages, plugin, model, supportI
224
225
  }
225
226
  const images = rawMessage.additional_kwargs.images;
226
227
  const lowerModel = normalizedModel?.toLowerCase() ?? "";
227
- if ((lowerModel?.includes("vision") || lowerModel?.includes("gpt-4o") || lowerModel?.includes("claude") || lowerModel?.includes("gemini") || lowerModel?.includes("qwen-vl") || lowerModel?.includes("omni") || lowerModel?.includes("qwen2.5-vl") || lowerModel?.includes("qwen2.5-omni") || lowerModel?.includes("qwen-omni") || lowerModel?.includes("qwen2-vl") || lowerModel?.includes("qwen3.5") || lowerModel?.includes("qvq") || normalizedModel?.includes("o1") || normalizedModel?.includes("o4") || normalizedModel?.includes("o3") || normalizedModel?.includes("gpt-4.1") || normalizedModel?.includes("gpt-5") || supportImageInput2) && images != null) {
228
+ if (images != null && (supportImageInput(lowerModel) || supportImageInputType)) {
228
229
  msg.content = [
229
230
  {
230
231
  type: "text",
@@ -376,15 +377,73 @@ async function fetchImageUrl(plugin, content) {
376
377
  }
377
378
  const ext = url.match(/\.([^.?#]+)(?:[?#]|$)/)?.[1]?.toLowerCase();
378
379
  const imageType = (0, import_string.getImageMimeType)(ext);
379
- const buffer = await plugin.fetch(url).then((res) => {
380
- if (!res.ok) {
381
- throw new Error(`Failed to fetch image: ${res.status}`);
382
- }
383
- return res.arrayBuffer();
384
- }).then(Buffer.from);
380
+ const controller = new AbortController();
381
+ const timeout = setTimeout(() => controller.abort(), 6e4);
382
+ const response = await plugin.fetch(url, {
383
+ signal: controller.signal
384
+ }).finally(() => {
385
+ clearTimeout(timeout);
386
+ });
387
+ if (!response.ok) {
388
+ throw new Error(`Failed to fetch image: ${response.status}`);
389
+ }
390
+ const buffer = Buffer.from(await response.arrayBuffer());
385
391
  return `data:${imageType};base64,${buffer.toString("base64")}`;
386
392
  }
387
393
  __name(fetchImageUrl, "fetchImageUrl");
394
+ function getFileLikeUrlInfo(content) {
395
+ switch (content.type) {
396
+ case "file_url": {
397
+ const raw = content.file_url;
398
+ return {
399
+ url: typeof raw === "string" ? raw : raw.url,
400
+ mimeType: typeof raw === "string" ? void 0 : raw.mimeType
401
+ };
402
+ }
403
+ case "audio_url": {
404
+ const raw = content.audio_url;
405
+ return {
406
+ url: typeof raw === "string" ? raw : raw.url,
407
+ mimeType: typeof raw === "string" ? void 0 : raw.mimeType
408
+ };
409
+ }
410
+ case "video_url": {
411
+ const raw = content.video_url;
412
+ return {
413
+ url: typeof raw === "string" ? raw : raw.url,
414
+ mimeType: typeof raw === "string" ? void 0 : raw.mimeType
415
+ };
416
+ }
417
+ }
418
+ }
419
+ __name(getFileLikeUrlInfo, "getFileLikeUrlInfo");
420
+ async function fetchFileLikeUrl(plugin, content) {
421
+ const { url, mimeType } = getFileLikeUrlInfo(content);
422
+ const dataUrlMatch = url.match(/^data:([^;,]+);base64,(.+)$/i);
423
+ if (dataUrlMatch) {
424
+ return {
425
+ buffer: Buffer.from(dataUrlMatch[2], "base64"),
426
+ mimeType: dataUrlMatch[1] || mimeType || "application/octet-stream"
427
+ };
428
+ }
429
+ const controller = new AbortController();
430
+ const timeout = setTimeout(() => controller.abort(), 6e4);
431
+ const response = await plugin.fetch(url, {
432
+ signal: controller.signal
433
+ }).finally(() => {
434
+ clearTimeout(timeout);
435
+ });
436
+ if (!response.ok) {
437
+ throw new Error(`Failed to fetch file: ${response.status}`);
438
+ }
439
+ const buffer = Buffer.from(await response.arrayBuffer());
440
+ const fetchedMimeType = response.headers.get("content-type")?.split(";")[0]?.trim();
441
+ return {
442
+ buffer,
443
+ mimeType: mimeType ?? fetchedMimeType ?? (0, import_string.getMimeTypeFromSource)(url) ?? "application/octet-stream"
444
+ };
445
+ }
446
+ __name(fetchFileLikeUrl, "fetchFileLikeUrl");
388
447
  function messageTypeToOpenAIRole(type) {
389
448
  switch (type) {
390
449
  case "system":
@@ -899,6 +958,7 @@ __name(createRequestContext, "createRequestContext");
899
958
  createEmbeddings,
900
959
  createRequestContext,
901
960
  expandReasoningEffortModelVariants,
961
+ fetchFileLikeUrl,
902
962
  fetchImageUrl,
903
963
  formatToolToOpenAITool,
904
964
  formatToolsToOpenAITools,
package/lib/index.mjs CHANGED
@@ -151,10 +151,11 @@ import {
151
151
  import { zodToJsonSchema } from "zod-to-json-schema";
152
152
  import {
153
153
  getImageMimeType,
154
+ getMimeTypeFromSource,
154
155
  isMessageContentImageUrl
155
156
  } from "koishi-plugin-chatluna/utils/string";
156
157
  import { isZodSchemaV3 } from "@langchain/core/utils/types";
157
- async function langchainMessageToOpenAIMessage(messages, plugin, model, supportImageInput2, removeSystemMessage) {
158
+ async function langchainMessageToOpenAIMessage(messages, plugin, model, supportImageInputType, removeSystemMessage) {
158
159
  const result = [];
159
160
  const normalizedModel = model ? normalizeOpenAIModelName(model) : model;
160
161
  const isDeepseekThinkModel = normalizedModel?.includes("deepseek-reasoner");
@@ -188,7 +189,7 @@ async function langchainMessageToOpenAIMessage(messages, plugin, model, supportI
188
189
  }
189
190
  const images = rawMessage.additional_kwargs.images;
190
191
  const lowerModel = normalizedModel?.toLowerCase() ?? "";
191
- if ((lowerModel?.includes("vision") || lowerModel?.includes("gpt-4o") || lowerModel?.includes("claude") || lowerModel?.includes("gemini") || lowerModel?.includes("qwen-vl") || lowerModel?.includes("omni") || lowerModel?.includes("qwen2.5-vl") || lowerModel?.includes("qwen2.5-omni") || lowerModel?.includes("qwen-omni") || lowerModel?.includes("qwen2-vl") || lowerModel?.includes("qwen3.5") || lowerModel?.includes("qvq") || normalizedModel?.includes("o1") || normalizedModel?.includes("o4") || normalizedModel?.includes("o3") || normalizedModel?.includes("gpt-4.1") || normalizedModel?.includes("gpt-5") || supportImageInput2) && images != null) {
192
+ if (images != null && (supportImageInput(lowerModel) || supportImageInputType)) {
192
193
  msg.content = [
193
194
  {
194
195
  type: "text",
@@ -340,15 +341,73 @@ async function fetchImageUrl(plugin, content) {
340
341
  }
341
342
  const ext = url.match(/\.([^.?#]+)(?:[?#]|$)/)?.[1]?.toLowerCase();
342
343
  const imageType = getImageMimeType(ext);
343
- const buffer = await plugin.fetch(url).then((res) => {
344
- if (!res.ok) {
345
- throw new Error(`Failed to fetch image: ${res.status}`);
346
- }
347
- return res.arrayBuffer();
348
- }).then(Buffer.from);
344
+ const controller = new AbortController();
345
+ const timeout = setTimeout(() => controller.abort(), 6e4);
346
+ const response = await plugin.fetch(url, {
347
+ signal: controller.signal
348
+ }).finally(() => {
349
+ clearTimeout(timeout);
350
+ });
351
+ if (!response.ok) {
352
+ throw new Error(`Failed to fetch image: ${response.status}`);
353
+ }
354
+ const buffer = Buffer.from(await response.arrayBuffer());
349
355
  return `data:${imageType};base64,${buffer.toString("base64")}`;
350
356
  }
351
357
  __name(fetchImageUrl, "fetchImageUrl");
358
+ function getFileLikeUrlInfo(content) {
359
+ switch (content.type) {
360
+ case "file_url": {
361
+ const raw = content.file_url;
362
+ return {
363
+ url: typeof raw === "string" ? raw : raw.url,
364
+ mimeType: typeof raw === "string" ? void 0 : raw.mimeType
365
+ };
366
+ }
367
+ case "audio_url": {
368
+ const raw = content.audio_url;
369
+ return {
370
+ url: typeof raw === "string" ? raw : raw.url,
371
+ mimeType: typeof raw === "string" ? void 0 : raw.mimeType
372
+ };
373
+ }
374
+ case "video_url": {
375
+ const raw = content.video_url;
376
+ return {
377
+ url: typeof raw === "string" ? raw : raw.url,
378
+ mimeType: typeof raw === "string" ? void 0 : raw.mimeType
379
+ };
380
+ }
381
+ }
382
+ }
383
+ __name(getFileLikeUrlInfo, "getFileLikeUrlInfo");
384
+ async function fetchFileLikeUrl(plugin, content) {
385
+ const { url, mimeType } = getFileLikeUrlInfo(content);
386
+ const dataUrlMatch = url.match(/^data:([^;,]+);base64,(.+)$/i);
387
+ if (dataUrlMatch) {
388
+ return {
389
+ buffer: Buffer.from(dataUrlMatch[2], "base64"),
390
+ mimeType: dataUrlMatch[1] || mimeType || "application/octet-stream"
391
+ };
392
+ }
393
+ const controller = new AbortController();
394
+ const timeout = setTimeout(() => controller.abort(), 6e4);
395
+ const response = await plugin.fetch(url, {
396
+ signal: controller.signal
397
+ }).finally(() => {
398
+ clearTimeout(timeout);
399
+ });
400
+ if (!response.ok) {
401
+ throw new Error(`Failed to fetch file: ${response.status}`);
402
+ }
403
+ const buffer = Buffer.from(await response.arrayBuffer());
404
+ const fetchedMimeType = response.headers.get("content-type")?.split(";")[0]?.trim();
405
+ return {
406
+ buffer,
407
+ mimeType: mimeType ?? fetchedMimeType ?? getMimeTypeFromSource(url) ?? "application/octet-stream"
408
+ };
409
+ }
410
+ __name(fetchFileLikeUrl, "fetchFileLikeUrl");
352
411
  function messageTypeToOpenAIRole(type) {
353
412
  switch (type) {
354
413
  case "system":
@@ -862,6 +921,7 @@ export {
862
921
  createEmbeddings,
863
922
  createRequestContext,
864
923
  expandReasoningEffortModelVariants,
924
+ fetchFileLikeUrl,
865
925
  fetchImageUrl,
866
926
  formatToolToOpenAITool,
867
927
  formatToolsToOpenAITools,
package/lib/utils.d.ts CHANGED
@@ -1,15 +1,43 @@
1
- import { AIMessageChunk, BaseMessage, ChatMessageChunk, FunctionMessageChunk, HumanMessageChunk, MessageContentImageUrl, MessageType, SystemMessageChunk, ToolMessageChunk } from '@langchain/core/messages';
1
+ import { AIMessageChunk, BaseMessage, ChatMessageChunk, FunctionMessageChunk, HumanMessageChunk, MessageContentComplex, MessageContentImageUrl, MessageType, SystemMessageChunk, ToolMessageChunk } from '@langchain/core/messages';
2
2
  import { StructuredTool } from '@langchain/core/tools';
3
3
  import { JsonSchema7Type } from 'zod-to-json-schema';
4
4
  import { ChatCompletionResponseMessage, ChatCompletionResponseMessageRoleEnum, ChatCompletionTool } from './types';
5
5
  import { ChatLunaPlugin } from 'koishi-plugin-chatluna/services/chat';
6
- export declare function langchainMessageToOpenAIMessage(messages: BaseMessage[], plugin: ChatLunaPlugin, model?: string, supportImageInput?: boolean, removeSystemMessage?: boolean): Promise<ChatCompletionResponseMessage[]>;
6
+ export declare function langchainMessageToOpenAIMessage(messages: BaseMessage[], plugin: ChatLunaPlugin, model?: string, supportImageInputType?: boolean, removeSystemMessage?: boolean): Promise<ChatCompletionResponseMessage[]>;
7
7
  export declare function processDeepSeekThinkMessages(convertedMessages: ChatCompletionResponseMessage[], originalMessages: BaseMessage[]): ChatCompletionResponseMessage[];
8
8
  export declare function transformSystemMessages(messages: ChatCompletionResponseMessage[]): ChatCompletionResponseMessage[];
9
9
  export declare function fetchImageUrl(plugin: ChatLunaPlugin, content: MessageContentImageUrl): Promise<string>;
10
+ type MessageContentFileLike = MessageContentComplex & ({
11
+ type: 'file_url';
12
+ file_url: string | {
13
+ url: string;
14
+ mimeType?: string;
15
+ };
16
+ } | {
17
+ type: 'audio_url';
18
+ audio_url: string | {
19
+ url: string;
20
+ mimeType?: string;
21
+ };
22
+ } | {
23
+ type: 'video_url';
24
+ video_url: string | {
25
+ url: string;
26
+ mimeType?: string;
27
+ };
28
+ });
29
+ /**
30
+ * Fetch file/audio/video content and return decoded bytes.
31
+ * If the source is a base64 data URL, it is decoded directly.
32
+ */
33
+ export declare function fetchFileLikeUrl(plugin: ChatLunaPlugin, content: MessageContentFileLike): Promise<{
34
+ buffer: Buffer<ArrayBuffer>;
35
+ mimeType: string;
36
+ }>;
10
37
  export declare function messageTypeToOpenAIRole(type: MessageType): ChatCompletionResponseMessageRoleEnum;
11
38
  export declare function formatToolsToOpenAITools(tools: StructuredTool[], includeGoogleSearch: boolean): ChatCompletionTool[];
12
39
  export declare function formatToolToOpenAITool(tool: StructuredTool): ChatCompletionTool;
13
40
  export declare function removeAdditionalProperties(schema: JsonSchema7Type): JsonSchema7Type;
14
41
  export declare function convertMessageToMessageChunk(message: ChatCompletionResponseMessage): HumanMessageChunk | AIMessageChunk | SystemMessageChunk | FunctionMessageChunk | ToolMessageChunk | ChatMessageChunk;
15
42
  export declare function convertDeltaToMessageChunk(delta: Record<string, any>, defaultRole?: ChatCompletionResponseMessageRoleEnum): HumanMessageChunk | AIMessageChunk | SystemMessageChunk | FunctionMessageChunk | ToolMessageChunk | ChatMessageChunk;
43
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chatluna/v1-shared-adapter",
3
3
  "description": "chatluna shared adapter",
4
- "version": "1.0.27",
4
+ "version": "1.0.28",
5
5
  "main": "lib/index.cjs",
6
6
  "module": "lib/index.mjs",
7
7
  "typings": "lib/index.d.ts",
@@ -70,6 +70,6 @@
70
70
  },
71
71
  "peerDependencies": {
72
72
  "koishi": "^4.18.9",
73
- "koishi-plugin-chatluna": "^1.3.22"
73
+ "koishi-plugin-chatluna": "^1.3.23"
74
74
  }
75
75
  }