@chatluna/v1-shared-adapter 1.0.26 → 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,11 +191,10 @@ 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");
197
- console.log(messages);
198
198
  for (const rawMessage of messages) {
199
199
  const role = messageTypeToOpenAIRole(rawMessage.getType());
200
200
  const msg = {
@@ -225,7 +225,7 @@ async function langchainMessageToOpenAIMessage(messages, plugin, model, supportI
225
225
  }
226
226
  const images = rawMessage.additional_kwargs.images;
227
227
  const lowerModel = normalizedModel?.toLowerCase() ?? "";
228
- 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)) {
229
229
  msg.content = [
230
230
  {
231
231
  type: "text",
@@ -377,15 +377,73 @@ async function fetchImageUrl(plugin, content) {
377
377
  }
378
378
  const ext = url.match(/\.([^.?#]+)(?:[?#]|$)/)?.[1]?.toLowerCase();
379
379
  const imageType = (0, import_string.getImageMimeType)(ext);
380
- const buffer = await plugin.fetch(url).then((res) => {
381
- if (!res.ok) {
382
- throw new Error(`Failed to fetch image: ${res.status}`);
383
- }
384
- return res.arrayBuffer();
385
- }).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());
386
391
  return `data:${imageType};base64,${buffer.toString("base64")}`;
387
392
  }
388
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");
389
447
  function messageTypeToOpenAIRole(type) {
390
448
  switch (type) {
391
449
  case "system":
@@ -900,6 +958,7 @@ __name(createRequestContext, "createRequestContext");
900
958
  createEmbeddings,
901
959
  createRequestContext,
902
960
  expandReasoningEffortModelVariants,
961
+ fetchFileLikeUrl,
903
962
  fetchImageUrl,
904
963
  formatToolToOpenAITool,
905
964
  formatToolsToOpenAITools,
package/lib/index.mjs CHANGED
@@ -151,14 +151,14 @@ 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");
161
- console.log(messages);
162
162
  for (const rawMessage of messages) {
163
163
  const role = messageTypeToOpenAIRole(rawMessage.getType());
164
164
  const msg = {
@@ -189,7 +189,7 @@ async function langchainMessageToOpenAIMessage(messages, plugin, model, supportI
189
189
  }
190
190
  const images = rawMessage.additional_kwargs.images;
191
191
  const lowerModel = normalizedModel?.toLowerCase() ?? "";
192
- 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)) {
193
193
  msg.content = [
194
194
  {
195
195
  type: "text",
@@ -341,15 +341,73 @@ async function fetchImageUrl(plugin, content) {
341
341
  }
342
342
  const ext = url.match(/\.([^.?#]+)(?:[?#]|$)/)?.[1]?.toLowerCase();
343
343
  const imageType = getImageMimeType(ext);
344
- const buffer = await plugin.fetch(url).then((res) => {
345
- if (!res.ok) {
346
- throw new Error(`Failed to fetch image: ${res.status}`);
347
- }
348
- return res.arrayBuffer();
349
- }).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());
350
355
  return `data:${imageType};base64,${buffer.toString("base64")}`;
351
356
  }
352
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");
353
411
  function messageTypeToOpenAIRole(type) {
354
412
  switch (type) {
355
413
  case "system":
@@ -863,6 +921,7 @@ export {
863
921
  createEmbeddings,
864
922
  createRequestContext,
865
923
  expandReasoningEffortModelVariants,
924
+ fetchFileLikeUrl,
866
925
  fetchImageUrl,
867
926
  formatToolToOpenAITool,
868
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.26",
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.21"
73
+ "koishi-plugin-chatluna": "^1.3.23"
74
74
  }
75
75
  }