koishi-plugin-chatluna-google-gemini-adapter 1.3.0-alpha.9 → 1.3.1

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/README.md CHANGED
@@ -1,7 +1,7 @@
1
- ## chatluna-google-gemini-adapter
2
-
3
- ## [![npm](https://img.shields.io/npm/v/koishi-plugin-chatluna-google-gemini-adapter)](https://www.npmjs.com/package/koishi-plugin-chatluna-google-gemini) [![npm](https://img.shields.io/npm/dm/koishi-plugin-chatluna-google-gemini-adapter)](https://www.npmjs.com/package//koishi-plugin-chatluna-google-gemini-adapter)
4
-
5
- > 为 ChatLuna 提供 Google Gemini 支持的适配器
6
-
7
- [Gemini 适配器文档](https://chatluna.chat/guide/configure-model-platform/google-gemini.html)
1
+ ## chatluna-google-gemini-adapter
2
+
3
+ ## [![npm](https://img.shields.io/npm/v/koishi-plugin-chatluna-google-gemini-adapter)](https://www.npmjs.com/package/koishi-plugin-chatluna-google-gemini) [![npm](https://img.shields.io/npm/dm/koishi-plugin-chatluna-google-gemini-adapter)](https://www.npmjs.com/package//koishi-plugin-chatluna-google-gemini-adapter)
4
+
5
+ > 为 ChatLuna 提供 Google Gemini 支持的适配器
6
+
7
+ [Gemini 适配器文档](https://chatluna.chat/guide/configure-model-platform/google-gemini.html)
package/lib/index.cjs CHANGED
@@ -23,14 +23,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
23
23
  // src/locales/zh-CN.schema.yml
24
24
  var require_zh_CN_schema = __commonJS({
25
25
  "src/locales/zh-CN.schema.yml"(exports2, module2) {
26
- module2.exports = { $inner: [{}, { $desc: "请求选项", platform: "适配器的平台名。(不懂请不要修改)", apiKeys: { $inner: ["Gemini API Key", "Gemini API 的请求地址"], $desc: "Gemini 的 API Key 和请求地址列表。" } }, { $desc: "模型配置", maxTokens: "输入的最大上下文 Token(16~2097000,必须是 16 的倍数)。注意:仅当您使用的模型最大 Token 为 8000 及以上时,才建议设置超过 2000 token。", temperature: "回复的随机性程度,数值越高,回复越随机(范围:0~2)。", googleSearch: "为模型启用谷歌搜索。", thinkingBudget: "思考预算,范围:(-1~24576),设置的数值越大,思考时花费的 Token 越多,-1 为动态思考。目前仅支持 gemini 2.5 系列模型。", groundingContentDisplay: "是否显示谷歌搜索结果。", imageGeneration: "为模型启用图像生成。目前仅支持 `gemini-2.0-flash-exp` 和 `gemini-2.5-flash-image-preview` 模型。", searchThreshold: "搜索的[置信度阈值](https://ai.google.dev/gemini-api/docs/grounding?lang=rest#dynamic-retrieval),范围:0~1,设置的数值越低,则越倾向于使用谷歌搜索。(仅支持 `gemini-1.5` 系列模型。gemini 2.0 模型起使用动态的工具调用)", includeThoughts: "是否获取模型的思考内容。", codeExecution: "为模型启用代码执行工具。", urlContext: "为模型启用 URL 内容获取工具。", nonStreaming: "强制不启用流式返回。开启后,将总是以非流式发起请求,即便配置了 stream 参数。" }] };
26
+ module2.exports = { $inner: [{}, { $desc: "请求选项", platform: "适配器的平台名。(不懂请不要修改)", apiKeys: { $inner: ["Gemini API Key", "Gemini API 请求地址", "是否启用此配置"], $desc: "Gemini 的 API Key 和请求地址列表。" } }, { $desc: "模型配置", maxContextRatio: "最大上下文使用比例(0~1),控制可用的模型上下文窗口大小的最大百分比。例如 0.35 表示最多使用模型上下文的 35%。", temperature: "回复的随机性程度,数值越高,回复越随机(范围:0~2)。", googleSearch: "为模型启用谷歌搜索。", thinkingBudget: "思考预算,范围:(-1~24576),设置的数值越大,思考时花费的 Token 越多,-1 为动态思考。目前仅支持 gemini 2.5 系列模型。", groundingContentDisplay: "是否显示谷歌搜索结果。", imageGeneration: "为模型启用图像生成。目前仅支持 `gemini-2.0-flash-exp` 和 `gemini-2.5-flash-image-preview` 模型。", searchThreshold: "搜索的[置信度阈值](https://ai.google.dev/gemini-api/docs/grounding?lang=rest#dynamic-retrieval),范围:0~1,设置的数值越低,则越倾向于使用谷歌搜索。(仅支持 `gemini-1.5` 系列模型。gemini 2.0 模型起使用动态的工具调用)", includeThoughts: "是否获取模型的思考内容。", codeExecution: "为模型启用代码执行工具。", urlContext: "为模型启用 URL 内容获取工具。", nonStreaming: "强制不启用流式返回。开启后,将总是以非流式发起请求,即便配置了 stream 参数。" }] };
27
27
  }
28
28
  });
29
29
 
30
30
  // src/locales/en-US.schema.yml
31
31
  var require_en_US_schema = __commonJS({
32
32
  "src/locales/en-US.schema.yml"(exports2, module2) {
33
- module2.exports = { $inner: [{}, { $desc: "API Configuration", platform: "Adapter platform name. (Do not modify if you do not understand)", apiKeys: { $inner: ["Gemini API Key", "Gemini API Endpoint (optional)"], $desc: "Gemini API access credentials" } }, { $desc: "Model Parameters", maxTokens: "Max output tokens (16-2097000, multiple of 16). >2000 for 8k+ models", temperature: "Sampling temperature (0-2). Higher: more random, Lower: more deterministic", googleSearch: "Enable Google search", thinkingBudget: "Thinking budget (-1-24576). (0: dynamic thinking) Higher: more tokens spent on thinking. Currently only supports `gemini-2.5` series models.", groundingContentDisplay: "Enable display of search results", imageGeneration: "Enable image generation (only for `gemini-2.0-flash-exp` and `gemini-2.5-flash-image-preview` model)", searchThreshold: "Search confidence [threshold](https://ai.google.dev/gemini-api/docs/grounding?lang=rest#dynamic-retrieval) (0-1). Lower: more likely to use Google search", includeThoughts: "Enable retrieval of model thoughts", codeExecution: "Enable code execution tool", urlContext: "Enable URL context retrieval tool", nonStreaming: "Force disable streaming response. When enabled, requests will always be made in non-streaming mode, even if the stream parameter is configured." }] };
33
+ module2.exports = { $inner: [{}, { $desc: "API Configuration", platform: "Adapter platform name. (Do not modify if you do not understand)", apiKeys: { $inner: ["Gemini API Key", "Gemini API Endpoint (optional)", "Enabled"], $desc: "Gemini API access credentials" } }, { $desc: "Model Parameters", maxContextRatio: "Maximum context usage ratio (0-1). Controls the maximum percentage of model context window available for use. For example, 0.35 means at most 35% of the model context can be used.", temperature: "Sampling temperature (0-2). Higher: more random, Lower: more deterministic", googleSearch: "Enable Google search", thinkingBudget: "Thinking budget (-1-24576). (0: dynamic thinking) Higher: more tokens spent on thinking. Currently only supports `gemini-2.5` series models.", groundingContentDisplay: "Enable display of search results", imageGeneration: "Enable image generation (only for `gemini-2.0-flash-exp` and `gemini-2.5-flash-image-preview` model)", searchThreshold: "Search confidence [threshold](https://ai.google.dev/gemini-api/docs/grounding?lang=rest#dynamic-retrieval) (0-1). Lower: more likely to use Google search", includeThoughts: "Enable retrieval of model thoughts", codeExecution: "Enable code execution tool", urlContext: "Enable URL context retrieval tool", nonStreaming: "Force disable streaming response. When enabled, requests will always be made in non-streaming mode, even if the stream parameter is configured." }] };
34
34
  }
35
35
  });
36
36
 
@@ -63,10 +63,10 @@ var import_sse = require("koishi-plugin-chatluna/utils/sse");
63
63
  var import_stream = require("koishi-plugin-chatluna/utils/stream");
64
64
 
65
65
  // src/utils.ts
66
- var import_zod_to_json_schema = require("zod-to-json-schema");
67
66
  var import_v1_shared_adapter = require("@chatluna/v1-shared-adapter");
68
67
  var import_string = require("koishi-plugin-chatluna/utils/string");
69
68
  var import_types = require("@langchain/core/utils/types");
69
+ var import_zod_openapi = require("@anatine/zod-openapi");
70
70
  async function langchainMessageToGeminiMessage(messages, plugin, model) {
71
71
  return Promise.all(
72
72
  messages.map(async (message) => {
@@ -79,7 +79,12 @@ async function langchainMessageToGeminiMessage(messages, plugin, model) {
79
79
  role,
80
80
  parts: []
81
81
  };
82
- result.parts = typeof message.content === "string" ? [{ text: message.content }] : await processGeminiContentParts(plugin, message.content);
82
+ const thoughtData = message.additional_kwargs["thought_data"] ?? {};
83
+ result.parts = typeof message.content === "string" ? [{ text: message.content, ...thoughtData }] : await processGeminiContentParts(
84
+ plugin,
85
+ message.content,
86
+ thoughtData
87
+ );
83
88
  const images = message.additional_kwargs.images;
84
89
  if (images) {
85
90
  processImageParts(result, images, model);
@@ -129,6 +134,7 @@ function parseJsonArgs(args) {
129
134
  }
130
135
  __name(parseJsonArgs, "parseJsonArgs");
131
136
  function processFunctionMessage(message) {
137
+ const thoughtData = message.additional_kwargs["thought_data"] ?? {};
132
138
  if (message["tool_calls"]) {
133
139
  message = message;
134
140
  const toolCalls = message.tool_calls;
@@ -140,7 +146,8 @@ function processFunctionMessage(message) {
140
146
  name: toolCall.name,
141
147
  args: toolCall.args,
142
148
  id: toolCall.id
143
- }
149
+ },
150
+ ...thoughtData
144
151
  };
145
152
  })
146
153
  };
@@ -195,11 +202,11 @@ async function processGeminiImageContent(plugin, part) {
195
202
  };
196
203
  }
197
204
  __name(processGeminiImageContent, "processGeminiImageContent");
198
- async function processGeminiContentParts(plugin, content) {
205
+ async function processGeminiContentParts(plugin, content, thoughtData) {
199
206
  return Promise.all(
200
207
  content.map(async (part) => {
201
208
  if ((0, import_string.isMessageContentText)(part)) {
202
- return { text: part.text };
209
+ return { text: part.text, ...thoughtData };
203
210
  }
204
211
  if ((0, import_string.isMessageContentImageUrl)(part)) {
205
212
  return await processGeminiImageContent(plugin, part);
@@ -287,9 +294,7 @@ function formatToolsToGeminiAITools(tools, config, model) {
287
294
  __name(formatToolsToGeminiAITools, "formatToolsToGeminiAITools");
288
295
  function formatToolToGeminiAITool(tool) {
289
296
  const parameters = (0, import_v1_shared_adapter.removeAdditionalProperties)(
290
- (0, import_types.isZodSchemaV3)(tool.schema) ? (0, import_zod_to_json_schema.zodToJsonSchema)(tool.schema, {
291
- allowedAdditionalProperties: void 0
292
- }) : tool.schema
297
+ (0, import_types.isZodSchemaV3)(tool.schema) ? (0, import_zod_openapi.generateSchema)(tool.schema, true, "3.0") : tool.schema
293
298
  );
294
299
  return {
295
300
  name: tool.name,
@@ -405,6 +410,7 @@ __name(isChatResponse, "isChatResponse");
405
410
 
406
411
  // src/requester.ts
407
412
  var import_string2 = require("koishi-plugin-chatluna/utils/string");
413
+ var import_logger = require("koishi-plugin-chatluna/utils/logger");
408
414
  var GeminiRequester = class extends import_api.ModelRequester {
409
415
  constructor(ctx, _configPool, _pluginConfig, _plugin) {
410
416
  super(ctx, _configPool, _pluginConfig, _plugin);
@@ -448,6 +454,13 @@ var GeminiRequester = class extends import_api.ModelRequester {
448
454
  await (0, import_sse.checkResponse)(response);
449
455
  yield* this._processResponseStream(response);
450
456
  } catch (e) {
457
+ if (this.ctx.chatluna.config.isLog) {
458
+ await (0, import_logger.trackLogToLocal)(
459
+ "Request",
460
+ JSON.stringify(chatGenerationParams),
461
+ logger
462
+ );
463
+ }
451
464
  if (e instanceof import_error.ChatLunaError) {
452
465
  throw e;
453
466
  } else {
@@ -457,15 +470,16 @@ var GeminiRequester = class extends import_api.ModelRequester {
457
470
  }
458
471
  async completionInternal(params) {
459
472
  const modelConfig = prepareModelConfig(params, this._pluginConfig);
473
+ const chatGenerationParams = await createChatGenerationParams(
474
+ params,
475
+ this._plugin,
476
+ modelConfig,
477
+ this._pluginConfig
478
+ );
460
479
  try {
461
480
  const response = await this._post(
462
481
  `models/${modelConfig.model}:generateContent`,
463
- await createChatGenerationParams(
464
- params,
465
- this._plugin,
466
- modelConfig,
467
- this._pluginConfig
468
- ),
482
+ chatGenerationParams,
469
483
  {
470
484
  signal: params.signal
471
485
  }
@@ -473,6 +487,13 @@ var GeminiRequester = class extends import_api.ModelRequester {
473
487
  await (0, import_sse.checkResponse)(response);
474
488
  return await this._processResponse(response);
475
489
  } catch (e) {
490
+ if (this.ctx.chatluna.config.isLog) {
491
+ await (0, import_logger.trackLogToLocal)(
492
+ "Request",
493
+ JSON.stringify(chatGenerationParams),
494
+ logger
495
+ );
496
+ }
476
497
  if (e instanceof import_error.ChatLunaError) {
477
498
  throw e;
478
499
  } else {
@@ -669,6 +690,18 @@ var GeminiRequester = class extends import_api.ModelRequester {
669
690
  return;
670
691
  }
671
692
  const transformValue = typeof chunk === "string" ? JSON.parse(chunk) : chunk;
693
+ if (transformValue.usageMetadata) {
694
+ const promptTokens = transformValue.usageMetadata.promptTokenCount;
695
+ const totalTokens = transformValue.usageMetadata.totalTokenCount;
696
+ const completionTokens = transformValue.usageMetadata.candidatesTokenCount ?? totalTokens - promptTokens;
697
+ controller.enqueue({
698
+ usage: {
699
+ promptTokens,
700
+ completionTokens,
701
+ totalTokens
702
+ }
703
+ });
704
+ }
672
705
  if (!transformValue?.candidates) {
673
706
  return;
674
707
  }
@@ -707,6 +740,22 @@ var GeminiRequester = class extends import_api.ModelRequester {
707
740
  let errorCount = 0;
708
741
  let functionIndex = 0;
709
742
  for await (const chunk of iterable) {
743
+ let parsedChunk;
744
+ if (parsedChunk = partAsTypeCheck(
745
+ chunk,
746
+ (chunk2) => chunk2["usage"] != null
747
+ )) {
748
+ const generationChunk = new import_outputs.ChatGenerationChunk({
749
+ message: new import_messages.AIMessageChunk({
750
+ content: "",
751
+ response_metadata: {
752
+ tokenUsage: parsedChunk.usage
753
+ }
754
+ }),
755
+ text: ""
756
+ });
757
+ yield { type: "generation", generation: generationChunk };
758
+ }
710
759
  try {
711
760
  const { updatedContent, updatedReasoning, updatedToolCalling } = await this._processChunk(
712
761
  chunk,
@@ -722,10 +771,7 @@ var GeminiRequester = class extends import_api.ModelRequester {
722
771
  const messageChunk = this._createMessageChunk(
723
772
  updatedContent,
724
773
  updatedToolCalling,
725
- this.ctx.chatluna_storage != null ? void 0 : partAsTypeCheck(
726
- chunk,
727
- (part) => part["inlineData"] != null
728
- )
774
+ chunk
729
775
  );
730
776
  const generationChunk = new import_outputs.ChatGenerationChunk({
731
777
  message: messageChunk,
@@ -826,7 +872,11 @@ ${groundingContent}`
826
872
  }
827
873
  }
828
874
  }
829
- _createMessageChunk(content, functionCall, imagePart) {
875
+ _createMessageChunk(content, functionCall, chunk) {
876
+ const imagePart = this.ctx.chatluna_storage != null ? void 0 : partAsTypeCheck(
877
+ chunk,
878
+ (part) => part["inlineData"] != null
879
+ );
830
880
  const messageChunk = new import_messages.AIMessageChunk({
831
881
  content: content ?? "",
832
882
  tool_call_chunks: [functionCall].filter(Boolean)
@@ -834,7 +884,10 @@ ${groundingContent}`
834
884
  messageChunk.additional_kwargs = {
835
885
  images: imagePart ? [
836
886
  `data:${imagePart.inlineData.mimeType ?? "image/png"};base64,${imagePart.inlineData.data}`
837
- ] : void 0
887
+ ] : void 0,
888
+ thought_data: {
889
+ thoughtSignature: chunk["thoughtSignature"]
890
+ }
838
891
  };
839
892
  return messageChunk;
840
893
  }
@@ -927,7 +980,10 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
927
980
  import_types2.ModelCapabilities.ToolCall
928
981
  ]
929
982
  };
930
- if (model.name.includes("gemini-2.5") && !model.name.includes("pro") && !model.name.includes("image")) {
983
+ const thinkingModel = ["gemini-2.5-pro", "gemini-3.0-pro"];
984
+ if (thinkingModel.some(
985
+ (name2) => name2.includes(model.name.toLowerCase())
986
+ )) {
931
987
  if (!model.name.includes("-thinking")) {
932
988
  models.push(
933
989
  { ...info, name: model.name + "-non-thinking" },
@@ -960,7 +1016,9 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
960
1016
  requester: this._requester,
961
1017
  model,
962
1018
  modelMaxContextSize: info.maxTokens,
963
- maxTokenLimit: this._config.maxTokens,
1019
+ maxTokenLimit: Math.floor(
1020
+ (info.maxTokens || 1e5) * this._config.maxContextRatio
1021
+ ),
964
1022
  timeout: this._config.timeout,
965
1023
  temperature: this._config.temperature,
966
1024
  maxRetries: this._config.maxRetries,
@@ -976,15 +1034,17 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
976
1034
  };
977
1035
 
978
1036
  // src/index.ts
979
- var import_logger = require("koishi-plugin-chatluna/utils/logger");
1037
+ var import_logger2 = require("koishi-plugin-chatluna/utils/logger");
980
1038
  var logger;
981
1039
  var reusable = true;
982
1040
  function apply(ctx, config) {
983
- const plugin = new import_chat.ChatLunaPlugin(ctx, config, config.platform);
984
- logger = (0, import_logger.createLogger)(ctx, "chatluna-gemini-adapter");
1041
+ logger = (0, import_logger2.createLogger)(ctx, "chatluna-gemini-adapter");
985
1042
  ctx.on("ready", async () => {
1043
+ const plugin = new import_chat.ChatLunaPlugin(ctx, config, config.platform);
986
1044
  plugin.parseConfig((config2) => {
987
- return config2.apiKeys.map(([apiKey, apiEndpoint]) => {
1045
+ return config2.apiKeys.filter(([apiKey, _, enabled]) => {
1046
+ return apiKey.length > 0 && enabled;
1047
+ }).map(([apiKey, apiEndpoint]) => {
988
1048
  return {
989
1049
  apiKey,
990
1050
  apiEndpoint,
@@ -996,8 +1056,8 @@ function apply(ctx, config) {
996
1056
  };
997
1057
  });
998
1058
  });
999
- plugin.registerClient((ctx2) => new GeminiClient(ctx2, config, plugin));
1000
- await plugin.initClients();
1059
+ plugin.registerClient(() => new GeminiClient(ctx, config, plugin));
1060
+ await plugin.initClient();
1001
1061
  });
1002
1062
  }
1003
1063
  __name(apply, "apply");
@@ -1007,15 +1067,16 @@ var Config4 = import_koishi.Schema.intersect([
1007
1067
  platform: import_koishi.Schema.string().default("gemini"),
1008
1068
  apiKeys: import_koishi.Schema.array(
1009
1069
  import_koishi.Schema.tuple([
1010
- import_koishi.Schema.string().role("secret"),
1070
+ import_koishi.Schema.string().role("secret").default(""),
1011
1071
  import_koishi.Schema.string().default(
1012
1072
  "https://generativelanguage.googleapis.com/v1beta"
1013
- )
1073
+ ),
1074
+ import_koishi.Schema.boolean().default(true)
1014
1075
  ])
1015
- ).default([["", "https://generativelanguage.googleapis.com/v1beta"]])
1076
+ ).default([[]]).role("table")
1016
1077
  }),
1017
1078
  import_koishi.Schema.object({
1018
- maxTokens: import_koishi.Schema.number().min(16).max(2097e3).step(16).default(8064),
1079
+ maxContextRatio: import_koishi.Schema.number().min(0).max(1).step(1e-4).role("slider").default(0.35),
1019
1080
  temperature: import_koishi.Schema.percent().min(0).max(2).step(0.1).default(1),
1020
1081
  googleSearch: import_koishi.Schema.boolean().default(false),
1021
1082
  codeExecution: import_koishi.Schema.boolean().default(false),
package/lib/index.d.ts CHANGED
@@ -4,8 +4,8 @@ export declare let logger: Logger;
4
4
  export declare const reusable = true;
5
5
  export declare function apply(ctx: Context, config: Config): void;
6
6
  export interface Config extends ChatLunaPlugin.Config {
7
- apiKeys: [string, string][];
8
- maxTokens: number;
7
+ apiKeys: [string, string, boolean][];
8
+ maxContextRatio: number;
9
9
  platform: string;
10
10
  temperature: number;
11
11
  googleSearch: boolean;
package/lib/index.mjs CHANGED
@@ -8,14 +8,14 @@ var __commonJS = (cb, mod) => function __require() {
8
8
  // src/locales/zh-CN.schema.yml
9
9
  var require_zh_CN_schema = __commonJS({
10
10
  "src/locales/zh-CN.schema.yml"(exports, module) {
11
- module.exports = { $inner: [{}, { $desc: "请求选项", platform: "适配器的平台名。(不懂请不要修改)", apiKeys: { $inner: ["Gemini API Key", "Gemini API 的请求地址"], $desc: "Gemini 的 API Key 和请求地址列表。" } }, { $desc: "模型配置", maxTokens: "输入的最大上下文 Token(16~2097000,必须是 16 的倍数)。注意:仅当您使用的模型最大 Token 为 8000 及以上时,才建议设置超过 2000 token。", temperature: "回复的随机性程度,数值越高,回复越随机(范围:0~2)。", googleSearch: "为模型启用谷歌搜索。", thinkingBudget: "思考预算,范围:(-1~24576),设置的数值越大,思考时花费的 Token 越多,-1 为动态思考。目前仅支持 gemini 2.5 系列模型。", groundingContentDisplay: "是否显示谷歌搜索结果。", imageGeneration: "为模型启用图像生成。目前仅支持 `gemini-2.0-flash-exp` 和 `gemini-2.5-flash-image-preview` 模型。", searchThreshold: "搜索的[置信度阈值](https://ai.google.dev/gemini-api/docs/grounding?lang=rest#dynamic-retrieval),范围:0~1,设置的数值越低,则越倾向于使用谷歌搜索。(仅支持 `gemini-1.5` 系列模型。gemini 2.0 模型起使用动态的工具调用)", includeThoughts: "是否获取模型的思考内容。", codeExecution: "为模型启用代码执行工具。", urlContext: "为模型启用 URL 内容获取工具。", nonStreaming: "强制不启用流式返回。开启后,将总是以非流式发起请求,即便配置了 stream 参数。" }] };
11
+ module.exports = { $inner: [{}, { $desc: "请求选项", platform: "适配器的平台名。(不懂请不要修改)", apiKeys: { $inner: ["Gemini API Key", "Gemini API 请求地址", "是否启用此配置"], $desc: "Gemini 的 API Key 和请求地址列表。" } }, { $desc: "模型配置", maxContextRatio: "最大上下文使用比例(0~1),控制可用的模型上下文窗口大小的最大百分比。例如 0.35 表示最多使用模型上下文的 35%。", temperature: "回复的随机性程度,数值越高,回复越随机(范围:0~2)。", googleSearch: "为模型启用谷歌搜索。", thinkingBudget: "思考预算,范围:(-1~24576),设置的数值越大,思考时花费的 Token 越多,-1 为动态思考。目前仅支持 gemini 2.5 系列模型。", groundingContentDisplay: "是否显示谷歌搜索结果。", imageGeneration: "为模型启用图像生成。目前仅支持 `gemini-2.0-flash-exp` 和 `gemini-2.5-flash-image-preview` 模型。", searchThreshold: "搜索的[置信度阈值](https://ai.google.dev/gemini-api/docs/grounding?lang=rest#dynamic-retrieval),范围:0~1,设置的数值越低,则越倾向于使用谷歌搜索。(仅支持 `gemini-1.5` 系列模型。gemini 2.0 模型起使用动态的工具调用)", includeThoughts: "是否获取模型的思考内容。", codeExecution: "为模型启用代码执行工具。", urlContext: "为模型启用 URL 内容获取工具。", nonStreaming: "强制不启用流式返回。开启后,将总是以非流式发起请求,即便配置了 stream 参数。" }] };
12
12
  }
13
13
  });
14
14
 
15
15
  // src/locales/en-US.schema.yml
16
16
  var require_en_US_schema = __commonJS({
17
17
  "src/locales/en-US.schema.yml"(exports, module) {
18
- module.exports = { $inner: [{}, { $desc: "API Configuration", platform: "Adapter platform name. (Do not modify if you do not understand)", apiKeys: { $inner: ["Gemini API Key", "Gemini API Endpoint (optional)"], $desc: "Gemini API access credentials" } }, { $desc: "Model Parameters", maxTokens: "Max output tokens (16-2097000, multiple of 16). >2000 for 8k+ models", temperature: "Sampling temperature (0-2). Higher: more random, Lower: more deterministic", googleSearch: "Enable Google search", thinkingBudget: "Thinking budget (-1-24576). (0: dynamic thinking) Higher: more tokens spent on thinking. Currently only supports `gemini-2.5` series models.", groundingContentDisplay: "Enable display of search results", imageGeneration: "Enable image generation (only for `gemini-2.0-flash-exp` and `gemini-2.5-flash-image-preview` model)", searchThreshold: "Search confidence [threshold](https://ai.google.dev/gemini-api/docs/grounding?lang=rest#dynamic-retrieval) (0-1). Lower: more likely to use Google search", includeThoughts: "Enable retrieval of model thoughts", codeExecution: "Enable code execution tool", urlContext: "Enable URL context retrieval tool", nonStreaming: "Force disable streaming response. When enabled, requests will always be made in non-streaming mode, even if the stream parameter is configured." }] };
18
+ module.exports = { $inner: [{}, { $desc: "API Configuration", platform: "Adapter platform name. (Do not modify if you do not understand)", apiKeys: { $inner: ["Gemini API Key", "Gemini API Endpoint (optional)", "Enabled"], $desc: "Gemini API access credentials" } }, { $desc: "Model Parameters", maxContextRatio: "Maximum context usage ratio (0-1). Controls the maximum percentage of model context window available for use. For example, 0.35 means at most 35% of the model context can be used.", temperature: "Sampling temperature (0-2). Higher: more random, Lower: more deterministic", googleSearch: "Enable Google search", thinkingBudget: "Thinking budget (-1-24576). (0: dynamic thinking) Higher: more tokens spent on thinking. Currently only supports `gemini-2.5` series models.", groundingContentDisplay: "Enable display of search results", imageGeneration: "Enable image generation (only for `gemini-2.0-flash-exp` and `gemini-2.5-flash-image-preview` model)", searchThreshold: "Search confidence [threshold](https://ai.google.dev/gemini-api/docs/grounding?lang=rest#dynamic-retrieval) (0-1). Lower: more likely to use Google search", includeThoughts: "Enable retrieval of model thoughts", codeExecution: "Enable code execution tool", urlContext: "Enable URL context retrieval tool", nonStreaming: "Force disable streaming response. When enabled, requests will always be made in non-streaming mode, even if the stream parameter is configured." }] };
19
19
  }
20
20
  });
21
21
 
@@ -54,7 +54,6 @@ import { checkResponse, sseIterable } from "koishi-plugin-chatluna/utils/sse";
54
54
  import { readableStreamToAsyncIterable } from "koishi-plugin-chatluna/utils/stream";
55
55
 
56
56
  // src/utils.ts
57
- import { zodToJsonSchema } from "zod-to-json-schema";
58
57
  import {
59
58
  fetchImageUrl,
60
59
  removeAdditionalProperties
@@ -64,6 +63,7 @@ import {
64
63
  isMessageContentText
65
64
  } from "koishi-plugin-chatluna/utils/string";
66
65
  import { isZodSchemaV3 } from "@langchain/core/utils/types";
66
+ import { generateSchema } from "@anatine/zod-openapi";
67
67
  async function langchainMessageToGeminiMessage(messages, plugin, model) {
68
68
  return Promise.all(
69
69
  messages.map(async (message) => {
@@ -76,7 +76,12 @@ async function langchainMessageToGeminiMessage(messages, plugin, model) {
76
76
  role,
77
77
  parts: []
78
78
  };
79
- result.parts = typeof message.content === "string" ? [{ text: message.content }] : await processGeminiContentParts(plugin, message.content);
79
+ const thoughtData = message.additional_kwargs["thought_data"] ?? {};
80
+ result.parts = typeof message.content === "string" ? [{ text: message.content, ...thoughtData }] : await processGeminiContentParts(
81
+ plugin,
82
+ message.content,
83
+ thoughtData
84
+ );
80
85
  const images = message.additional_kwargs.images;
81
86
  if (images) {
82
87
  processImageParts(result, images, model);
@@ -126,6 +131,7 @@ function parseJsonArgs(args) {
126
131
  }
127
132
  __name(parseJsonArgs, "parseJsonArgs");
128
133
  function processFunctionMessage(message) {
134
+ const thoughtData = message.additional_kwargs["thought_data"] ?? {};
129
135
  if (message["tool_calls"]) {
130
136
  message = message;
131
137
  const toolCalls = message.tool_calls;
@@ -137,7 +143,8 @@ function processFunctionMessage(message) {
137
143
  name: toolCall.name,
138
144
  args: toolCall.args,
139
145
  id: toolCall.id
140
- }
146
+ },
147
+ ...thoughtData
141
148
  };
142
149
  })
143
150
  };
@@ -192,11 +199,11 @@ async function processGeminiImageContent(plugin, part) {
192
199
  };
193
200
  }
194
201
  __name(processGeminiImageContent, "processGeminiImageContent");
195
- async function processGeminiContentParts(plugin, content) {
202
+ async function processGeminiContentParts(plugin, content, thoughtData) {
196
203
  return Promise.all(
197
204
  content.map(async (part) => {
198
205
  if (isMessageContentText(part)) {
199
- return { text: part.text };
206
+ return { text: part.text, ...thoughtData };
200
207
  }
201
208
  if (isMessageContentImageUrl(part)) {
202
209
  return await processGeminiImageContent(plugin, part);
@@ -284,9 +291,7 @@ function formatToolsToGeminiAITools(tools, config, model) {
284
291
  __name(formatToolsToGeminiAITools, "formatToolsToGeminiAITools");
285
292
  function formatToolToGeminiAITool(tool) {
286
293
  const parameters = removeAdditionalProperties(
287
- isZodSchemaV3(tool.schema) ? zodToJsonSchema(tool.schema, {
288
- allowedAdditionalProperties: void 0
289
- }) : tool.schema
294
+ isZodSchemaV3(tool.schema) ? generateSchema(tool.schema, true, "3.0") : tool.schema
290
295
  );
291
296
  return {
292
297
  name: tool.name,
@@ -402,6 +407,7 @@ __name(isChatResponse, "isChatResponse");
402
407
 
403
408
  // src/requester.ts
404
409
  import { getMessageContent } from "koishi-plugin-chatluna/utils/string";
410
+ import { trackLogToLocal } from "koishi-plugin-chatluna/utils/logger";
405
411
  var GeminiRequester = class extends ModelRequester {
406
412
  constructor(ctx, _configPool, _pluginConfig, _plugin) {
407
413
  super(ctx, _configPool, _pluginConfig, _plugin);
@@ -445,6 +451,13 @@ var GeminiRequester = class extends ModelRequester {
445
451
  await checkResponse(response);
446
452
  yield* this._processResponseStream(response);
447
453
  } catch (e) {
454
+ if (this.ctx.chatluna.config.isLog) {
455
+ await trackLogToLocal(
456
+ "Request",
457
+ JSON.stringify(chatGenerationParams),
458
+ logger
459
+ );
460
+ }
448
461
  if (e instanceof ChatLunaError) {
449
462
  throw e;
450
463
  } else {
@@ -454,15 +467,16 @@ var GeminiRequester = class extends ModelRequester {
454
467
  }
455
468
  async completionInternal(params) {
456
469
  const modelConfig = prepareModelConfig(params, this._pluginConfig);
470
+ const chatGenerationParams = await createChatGenerationParams(
471
+ params,
472
+ this._plugin,
473
+ modelConfig,
474
+ this._pluginConfig
475
+ );
457
476
  try {
458
477
  const response = await this._post(
459
478
  `models/${modelConfig.model}:generateContent`,
460
- await createChatGenerationParams(
461
- params,
462
- this._plugin,
463
- modelConfig,
464
- this._pluginConfig
465
- ),
479
+ chatGenerationParams,
466
480
  {
467
481
  signal: params.signal
468
482
  }
@@ -470,6 +484,13 @@ var GeminiRequester = class extends ModelRequester {
470
484
  await checkResponse(response);
471
485
  return await this._processResponse(response);
472
486
  } catch (e) {
487
+ if (this.ctx.chatluna.config.isLog) {
488
+ await trackLogToLocal(
489
+ "Request",
490
+ JSON.stringify(chatGenerationParams),
491
+ logger
492
+ );
493
+ }
473
494
  if (e instanceof ChatLunaError) {
474
495
  throw e;
475
496
  } else {
@@ -666,6 +687,18 @@ var GeminiRequester = class extends ModelRequester {
666
687
  return;
667
688
  }
668
689
  const transformValue = typeof chunk === "string" ? JSON.parse(chunk) : chunk;
690
+ if (transformValue.usageMetadata) {
691
+ const promptTokens = transformValue.usageMetadata.promptTokenCount;
692
+ const totalTokens = transformValue.usageMetadata.totalTokenCount;
693
+ const completionTokens = transformValue.usageMetadata.candidatesTokenCount ?? totalTokens - promptTokens;
694
+ controller.enqueue({
695
+ usage: {
696
+ promptTokens,
697
+ completionTokens,
698
+ totalTokens
699
+ }
700
+ });
701
+ }
669
702
  if (!transformValue?.candidates) {
670
703
  return;
671
704
  }
@@ -704,6 +737,22 @@ var GeminiRequester = class extends ModelRequester {
704
737
  let errorCount = 0;
705
738
  let functionIndex = 0;
706
739
  for await (const chunk of iterable) {
740
+ let parsedChunk;
741
+ if (parsedChunk = partAsTypeCheck(
742
+ chunk,
743
+ (chunk2) => chunk2["usage"] != null
744
+ )) {
745
+ const generationChunk = new ChatGenerationChunk({
746
+ message: new AIMessageChunk({
747
+ content: "",
748
+ response_metadata: {
749
+ tokenUsage: parsedChunk.usage
750
+ }
751
+ }),
752
+ text: ""
753
+ });
754
+ yield { type: "generation", generation: generationChunk };
755
+ }
707
756
  try {
708
757
  const { updatedContent, updatedReasoning, updatedToolCalling } = await this._processChunk(
709
758
  chunk,
@@ -719,10 +768,7 @@ var GeminiRequester = class extends ModelRequester {
719
768
  const messageChunk = this._createMessageChunk(
720
769
  updatedContent,
721
770
  updatedToolCalling,
722
- this.ctx.chatluna_storage != null ? void 0 : partAsTypeCheck(
723
- chunk,
724
- (part) => part["inlineData"] != null
725
- )
771
+ chunk
726
772
  );
727
773
  const generationChunk = new ChatGenerationChunk({
728
774
  message: messageChunk,
@@ -823,7 +869,11 @@ ${groundingContent}`
823
869
  }
824
870
  }
825
871
  }
826
- _createMessageChunk(content, functionCall, imagePart) {
872
+ _createMessageChunk(content, functionCall, chunk) {
873
+ const imagePart = this.ctx.chatluna_storage != null ? void 0 : partAsTypeCheck(
874
+ chunk,
875
+ (part) => part["inlineData"] != null
876
+ );
827
877
  const messageChunk = new AIMessageChunk({
828
878
  content: content ?? "",
829
879
  tool_call_chunks: [functionCall].filter(Boolean)
@@ -831,7 +881,10 @@ ${groundingContent}`
831
881
  messageChunk.additional_kwargs = {
832
882
  images: imagePart ? [
833
883
  `data:${imagePart.inlineData.mimeType ?? "image/png"};base64,${imagePart.inlineData.data}`
834
- ] : void 0
884
+ ] : void 0,
885
+ thought_data: {
886
+ thoughtSignature: chunk["thoughtSignature"]
887
+ }
835
888
  };
836
889
  return messageChunk;
837
890
  }
@@ -924,7 +977,10 @@ var GeminiClient = class extends PlatformModelAndEmbeddingsClient {
924
977
  ModelCapabilities.ToolCall
925
978
  ]
926
979
  };
927
- if (model.name.includes("gemini-2.5") && !model.name.includes("pro") && !model.name.includes("image")) {
980
+ const thinkingModel = ["gemini-2.5-pro", "gemini-3.0-pro"];
981
+ if (thinkingModel.some(
982
+ (name2) => name2.includes(model.name.toLowerCase())
983
+ )) {
928
984
  if (!model.name.includes("-thinking")) {
929
985
  models.push(
930
986
  { ...info, name: model.name + "-non-thinking" },
@@ -957,7 +1013,9 @@ var GeminiClient = class extends PlatformModelAndEmbeddingsClient {
957
1013
  requester: this._requester,
958
1014
  model,
959
1015
  modelMaxContextSize: info.maxTokens,
960
- maxTokenLimit: this._config.maxTokens,
1016
+ maxTokenLimit: Math.floor(
1017
+ (info.maxTokens || 1e5) * this._config.maxContextRatio
1018
+ ),
961
1019
  timeout: this._config.timeout,
962
1020
  temperature: this._config.temperature,
963
1021
  maxRetries: this._config.maxRetries,
@@ -977,11 +1035,13 @@ import { createLogger } from "koishi-plugin-chatluna/utils/logger";
977
1035
  var logger;
978
1036
  var reusable = true;
979
1037
  function apply(ctx, config) {
980
- const plugin = new ChatLunaPlugin(ctx, config, config.platform);
981
1038
  logger = createLogger(ctx, "chatluna-gemini-adapter");
982
1039
  ctx.on("ready", async () => {
1040
+ const plugin = new ChatLunaPlugin(ctx, config, config.platform);
983
1041
  plugin.parseConfig((config2) => {
984
- return config2.apiKeys.map(([apiKey, apiEndpoint]) => {
1042
+ return config2.apiKeys.filter(([apiKey, _, enabled]) => {
1043
+ return apiKey.length > 0 && enabled;
1044
+ }).map(([apiKey, apiEndpoint]) => {
985
1045
  return {
986
1046
  apiKey,
987
1047
  apiEndpoint,
@@ -993,8 +1053,8 @@ function apply(ctx, config) {
993
1053
  };
994
1054
  });
995
1055
  });
996
- plugin.registerClient((ctx2) => new GeminiClient(ctx2, config, plugin));
997
- await plugin.initClients();
1056
+ plugin.registerClient(() => new GeminiClient(ctx, config, plugin));
1057
+ await plugin.initClient();
998
1058
  });
999
1059
  }
1000
1060
  __name(apply, "apply");
@@ -1004,15 +1064,16 @@ var Config4 = Schema.intersect([
1004
1064
  platform: Schema.string().default("gemini"),
1005
1065
  apiKeys: Schema.array(
1006
1066
  Schema.tuple([
1007
- Schema.string().role("secret"),
1067
+ Schema.string().role("secret").default(""),
1008
1068
  Schema.string().default(
1009
1069
  "https://generativelanguage.googleapis.com/v1beta"
1010
- )
1070
+ ),
1071
+ Schema.boolean().default(true)
1011
1072
  ])
1012
- ).default([["", "https://generativelanguage.googleapis.com/v1beta"]])
1073
+ ).default([[]]).role("table")
1013
1074
  }),
1014
1075
  Schema.object({
1015
- maxTokens: Schema.number().min(16).max(2097e3).step(16).default(8064),
1076
+ maxContextRatio: Schema.number().min(0).max(1).step(1e-4).role("slider").default(0.35),
1016
1077
  temperature: Schema.percent().min(0).max(2).step(0.1).default(1),
1017
1078
  googleSearch: Schema.boolean().default(false),
1018
1079
  codeExecution: Schema.boolean().default(false),
package/lib/types.d.ts CHANGED
@@ -2,11 +2,21 @@ export interface ChatCompletionResponseMessage {
2
2
  role: string;
3
3
  parts?: ChatPart[];
4
4
  }
5
- export type ChatPart = ChatMessagePart | ChatInlineDataPart | ChatFunctionCallingPart | ChatFunctionResponsePart | ChatUploadDataPart;
5
+ export type BaseChatPart = {
6
+ thoughtSignature?: string;
7
+ };
8
+ export type ChatPart = (ChatMessagePart & BaseChatPart) | ChatInlineDataPart | (ChatFunctionCallingPart & BaseChatPart) | ChatFunctionResponsePart | ChatUploadDataPart | ChatUsageMetadataPart;
6
9
  export type ChatMessagePart = {
7
10
  text: string;
8
11
  thought?: boolean;
9
12
  };
13
+ export type ChatUsageMetadataPart = {
14
+ usage: {
15
+ promptTokens: number;
16
+ completionTokens: number;
17
+ totalTokens: number;
18
+ };
19
+ };
10
20
  export type ChatInlineDataPart = {
11
21
  inlineData: {
12
22
  mimeType: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chatluna-google-gemini-adapter",
3
3
  "description": "google-gemini adapter for chatluna",
4
- "version": "1.3.0-alpha.9",
4
+ "version": "1.3.1",
5
5
  "main": "lib/index.cjs",
6
6
  "module": "lib/index.mjs",
7
7
  "typings": "lib/index.d.ts",
@@ -22,13 +22,13 @@
22
22
  "repository": {
23
23
  "type": "git",
24
24
  "url": "https://github.com/ChatLunaLab/chatluna.git",
25
- "directory": "packages/google-gemini-adapter"
25
+ "directory": "packages/adapter-gemini"
26
26
  },
27
27
  "license": "AGPL-3.0",
28
28
  "bugs": {
29
29
  "url": "https://github.com/ChatLunaLab/chatluna/issues"
30
30
  },
31
- "homepage": "https://github.com/ChatLunaLab/chatluna/tree/v1-dev/packages/google-gemini-adapter#readme",
31
+ "homepage": "https://github.com/ChatLunaLab/chatluna/tree/v1-dev/packages/adapter-gemini#readme",
32
32
  "scripts": {
33
33
  "build": "atsc -b"
34
34
  },
@@ -62,19 +62,21 @@
62
62
  "adapter"
63
63
  ],
64
64
  "dependencies": {
65
- "@chatluna/v1-shared-adapter": "^1.0.9",
65
+ "@anatine/zod-openapi": "^2.2.8",
66
+ "@chatluna/v1-shared-adapter": "^1.0.16",
66
67
  "@langchain/core": "0.3.62",
68
+ "openapi3-ts": "^4.5.0",
67
69
  "zod": "3.25.76",
68
- "zod-to-json-schema": "^3.24.5"
70
+ "zod-to-json-schema": "^3.24.6"
69
71
  },
70
72
  "devDependencies": {
71
73
  "atsc": "^2.1.0",
72
- "koishi": "^4.18.7"
74
+ "koishi": "^4.18.9"
73
75
  },
74
76
  "peerDependencies": {
75
- "koishi": "^4.18.7",
76
- "koishi-plugin-chatluna": "^1.3.0-alpha.42",
77
- "koishi-plugin-chatluna-storage-service": "^0.0.9"
77
+ "koishi": "^4.18.9",
78
+ "koishi-plugin-chatluna": "^1.3.0",
79
+ "koishi-plugin-chatluna-storage-service": "^0.0.11"
78
80
  },
79
81
  "peerDependenciesMeta": {
80
82
  "koishi-plugin-chatluna-storage-service": {