koishi-plugin-chatluna-google-gemini-adapter 1.3.0-alpha.2 → 1.3.0-alpha.20

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/client.d.ts CHANGED
@@ -5,6 +5,7 @@ import { ChatLunaBaseEmbeddings, ChatLunaChatModel } from 'koishi-plugin-chatlun
5
5
  import { ModelInfo } from 'koishi-plugin-chatluna/llm-core/platform/types';
6
6
  import { Config } from '.';
7
7
  import { ChatLunaPlugin } from 'koishi-plugin-chatluna/services/chat';
8
+ import { RunnableConfig } from '@langchain/core/runnables';
8
9
  export declare class GeminiClient extends PlatformModelAndEmbeddingsClient<ClientConfig> {
9
10
  private _config;
10
11
  plugin: ChatLunaPlugin;
@@ -12,6 +13,6 @@ export declare class GeminiClient extends PlatformModelAndEmbeddingsClient<Clien
12
13
  private _requester;
13
14
  get logger(): import("reggol");
14
15
  constructor(ctx: Context, _config: Config, plugin: ChatLunaPlugin);
15
- refreshModels(): Promise<ModelInfo[]>;
16
+ refreshModels(config?: RunnableConfig): Promise<ModelInfo[]>;
16
17
  protected _createModel(model: string): ChatLunaChatModel | ChatLunaBaseEmbeddings;
17
18
  }
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
 
@@ -51,7 +51,7 @@ var import_koishi = require("koishi");
51
51
  // src/client.ts
52
52
  var import_client = require("koishi-plugin-chatluna/llm-core/platform/client");
53
53
  var import_model = require("koishi-plugin-chatluna/llm-core/platform/model");
54
- var import_types = require("koishi-plugin-chatluna/llm-core/platform/types");
54
+ var import_types2 = require("koishi-plugin-chatluna/llm-core/platform/types");
55
55
  var import_error2 = require("koishi-plugin-chatluna/utils/error");
56
56
 
57
57
  // src/requester.ts
@@ -66,12 +66,12 @@ var import_stream = require("koishi-plugin-chatluna/utils/stream");
66
66
  var import_zod_to_json_schema = require("zod-to-json-schema");
67
67
  var import_v1_shared_adapter = require("@chatluna/v1-shared-adapter");
68
68
  var import_string = require("koishi-plugin-chatluna/utils/string");
69
- var import_zod = require("zod");
69
+ var import_types = require("@langchain/core/utils/types");
70
70
  async function langchainMessageToGeminiMessage(messages, plugin, model) {
71
71
  return Promise.all(
72
72
  messages.map(async (message) => {
73
73
  const role = messageTypeToGeminiRole(message.getType());
74
- const hasFunctionCall = message.additional_kwargs?.function_call != null;
74
+ const hasFunctionCall = message.tool_calls != null && message.tool_calls.length > 0;
75
75
  if (role === "function" || hasFunctionCall) {
76
76
  return processFunctionMessage(message);
77
77
  }
@@ -117,50 +117,43 @@ function extractSystemMessages(messages) {
117
117
  ];
118
118
  }
119
119
  __name(extractSystemMessages, "extractSystemMessages");
120
- function parseJsonSafely(content) {
121
- try {
122
- const result = JSON.parse(content);
123
- return typeof result === "string" ? { response: result } : result;
124
- } catch {
125
- return { response: content };
126
- }
127
- }
128
- __name(parseJsonSafely, "parseJsonSafely");
129
120
  function parseJsonArgs(args) {
130
121
  try {
131
122
  const result = JSON.parse(args);
132
- return typeof result === "string" ? { input: result } : result;
123
+ if (typeof result === "string") return { response: result };
124
+ if (Array.isArray(result)) return { response: result };
125
+ return result;
133
126
  } catch {
134
- return { input: args };
127
+ return { response: args };
135
128
  }
136
129
  }
137
130
  __name(parseJsonArgs, "parseJsonArgs");
138
131
  function processFunctionMessage(message) {
139
- const hasFunctionCall = message.additional_kwargs?.function_call != null;
140
- if (hasFunctionCall) {
141
- const functionCall = message.additional_kwargs.function_call;
132
+ if (message["tool_calls"]) {
133
+ message = message;
134
+ const toolCalls = message.tool_calls;
142
135
  return {
143
- role: "function",
144
- parts: [
145
- {
136
+ role: "model",
137
+ parts: toolCalls.map((toolCall) => {
138
+ return {
146
139
  functionCall: {
147
- name: functionCall.name,
148
- args: parseJsonArgs(functionCall.arguments)
140
+ name: toolCall.name,
141
+ args: toolCall.args,
142
+ id: toolCall.id
149
143
  }
150
- }
151
- ]
144
+ };
145
+ })
152
146
  };
153
147
  }
148
+ const finalMessage = message;
154
149
  return {
155
- role: "function",
150
+ role: "user",
156
151
  parts: [
157
152
  {
158
153
  functionResponse: {
159
154
  name: message.name,
160
- response: {
161
- name: message.name,
162
- content: parseJsonSafely(message.content)
163
- }
155
+ id: finalMessage.tool_call_id,
156
+ response: parseJsonArgs(message.content)
164
157
  }
165
158
  }
166
159
  ]
@@ -294,7 +287,7 @@ function formatToolsToGeminiAITools(tools, config, model) {
294
287
  __name(formatToolsToGeminiAITools, "formatToolsToGeminiAITools");
295
288
  function formatToolToGeminiAITool(tool) {
296
289
  const parameters = (0, import_v1_shared_adapter.removeAdditionalProperties)(
297
- tool.schema instanceof import_zod.ZodSchema ? (0, import_zod_to_json_schema.zodToJsonSchema)(tool.schema, {
290
+ (0, import_types.isZodSchemaV3)(tool.schema) ? (0, import_zod_to_json_schema.zodToJsonSchema)(tool.schema, {
298
291
  allowedAdditionalProperties: void 0
299
292
  }) : tool.schema
300
293
  );
@@ -314,7 +307,7 @@ function messageTypeToGeminiRole(type) {
314
307
  return "model";
315
308
  case "human":
316
309
  return "user";
317
- case "function":
310
+ case "tool":
318
311
  return "function";
319
312
  default:
320
313
  throw new Error(`Unknown message type: ${type}`);
@@ -326,7 +319,7 @@ function prepareModelConfig(params, pluginConfig) {
326
319
  let enabledThinking = null;
327
320
  if (model.includes("-thinking") && model.includes("gemini-2.5")) {
328
321
  enabledThinking = !model.includes("-non-thinking");
329
- model = model.replace("-nom-thinking", "").replace("-thinking", "");
322
+ model = model.replace("-non-thinking", "").replace("-thinking", "");
330
323
  }
331
324
  let thinkingBudget = pluginConfig.thinkingBudget ?? -1;
332
325
  if (!enabledThinking && !model.includes("2.5-pro")) {
@@ -412,6 +405,7 @@ __name(isChatResponse, "isChatResponse");
412
405
 
413
406
  // src/requester.ts
414
407
  var import_string2 = require("koishi-plugin-chatluna/utils/string");
408
+ var import_logger = require("koishi-plugin-chatluna/utils/logger");
415
409
  var GeminiRequester = class extends import_api.ModelRequester {
416
410
  constructor(ctx, _configPool, _pluginConfig, _plugin) {
417
411
  super(ctx, _configPool, _pluginConfig, _plugin);
@@ -455,6 +449,13 @@ var GeminiRequester = class extends import_api.ModelRequester {
455
449
  await (0, import_sse.checkResponse)(response);
456
450
  yield* this._processResponseStream(response);
457
451
  } catch (e) {
452
+ if (this.ctx.chatluna.config.isLog) {
453
+ await (0, import_logger.trackLogToLocal)(
454
+ "Request",
455
+ JSON.stringify(chatGenerationParams),
456
+ logger
457
+ );
458
+ }
458
459
  if (e instanceof import_error.ChatLunaError) {
459
460
  throw e;
460
461
  } else {
@@ -464,15 +465,16 @@ var GeminiRequester = class extends import_api.ModelRequester {
464
465
  }
465
466
  async completionInternal(params) {
466
467
  const modelConfig = prepareModelConfig(params, this._pluginConfig);
468
+ const chatGenerationParams = await createChatGenerationParams(
469
+ params,
470
+ this._plugin,
471
+ modelConfig,
472
+ this._pluginConfig
473
+ );
467
474
  try {
468
475
  const response = await this._post(
469
476
  `models/${modelConfig.model}:generateContent`,
470
- await createChatGenerationParams(
471
- params,
472
- this._plugin,
473
- modelConfig,
474
- this._pluginConfig
475
- ),
477
+ chatGenerationParams,
476
478
  {
477
479
  signal: params.signal
478
480
  }
@@ -480,6 +482,13 @@ var GeminiRequester = class extends import_api.ModelRequester {
480
482
  await (0, import_sse.checkResponse)(response);
481
483
  return await this._processResponse(response);
482
484
  } catch (e) {
485
+ if (this.ctx.chatluna.config.isLog) {
486
+ await (0, import_logger.trackLogToLocal)(
487
+ "Request",
488
+ JSON.stringify(chatGenerationParams),
489
+ logger
490
+ );
491
+ }
483
492
  if (e instanceof import_error.ChatLunaError) {
484
493
  throw e;
485
494
  } else {
@@ -529,12 +538,17 @@ var GeminiRequester = class extends import_api.ModelRequester {
529
538
  "error when calling gemini embeddings, Result: " + JSON.stringify(data)
530
539
  );
531
540
  }
532
- async getModels() {
541
+ async getModels(config) {
533
542
  try {
534
- const response = await this._get("models");
543
+ const response = await this._get("models", {
544
+ signal: config?.signal
545
+ });
535
546
  const data = await this._parseModelsResponse(response);
536
547
  return this._filterAndTransformModels(data.models);
537
548
  } catch (e) {
549
+ if (e instanceof import_error.ChatLunaError) {
550
+ throw e;
551
+ }
538
552
  const error = new Error(
539
553
  "error when listing gemini models, Error: " + e.message
540
554
  );
@@ -671,6 +685,18 @@ var GeminiRequester = class extends import_api.ModelRequester {
671
685
  return;
672
686
  }
673
687
  const transformValue = typeof chunk === "string" ? JSON.parse(chunk) : chunk;
688
+ if (transformValue.usageMetadata) {
689
+ const promptTokens = transformValue.usageMetadata.promptTokenCount;
690
+ const totalTokens = transformValue.usageMetadata.totalTokenCount;
691
+ const completionTokens = transformValue.usageMetadata.candidatesTokenCount ?? totalTokens - promptTokens;
692
+ controller.enqueue({
693
+ usage: {
694
+ promptTokens,
695
+ completionTokens,
696
+ totalTokens
697
+ }
698
+ });
699
+ }
674
700
  if (!transformValue?.candidates) {
675
701
  return;
676
702
  }
@@ -707,27 +733,37 @@ var GeminiRequester = class extends import_api.ModelRequester {
707
733
  async *_processChunks(iterable) {
708
734
  let reasoningContent = "";
709
735
  let errorCount = 0;
710
- const functionCall = {
711
- name: "",
712
- args: "",
713
- arguments: ""
714
- };
736
+ let functionIndex = 0;
715
737
  for await (const chunk of iterable) {
738
+ let parsedChunk;
739
+ if (parsedChunk = partAsTypeCheck(
740
+ chunk,
741
+ (chunk2) => chunk2["usage"] != null
742
+ )) {
743
+ const generationChunk = new import_outputs.ChatGenerationChunk({
744
+ message: new import_messages.AIMessageChunk(""),
745
+ text: "",
746
+ generationInfo: {
747
+ tokenUsage: parsedChunk.usage
748
+ }
749
+ });
750
+ yield { type: "generation", generation: generationChunk };
751
+ }
716
752
  try {
717
- const { updatedContent, updatedReasoning } = await this._processChunk(
753
+ const { updatedContent, updatedReasoning, updatedToolCalling } = await this._processChunk(
718
754
  chunk,
719
755
  reasoningContent,
720
- functionCall
756
+ functionIndex
721
757
  );
722
758
  if (updatedReasoning !== reasoningContent) {
723
759
  reasoningContent = updatedReasoning;
724
760
  yield { type: "reasoning", content: reasoningContent };
725
761
  continue;
726
762
  }
727
- if (updatedContent || functionCall.name) {
763
+ if (updatedContent || updatedToolCalling) {
728
764
  const messageChunk = this._createMessageChunk(
729
765
  updatedContent,
730
- functionCall,
766
+ updatedToolCalling,
731
767
  this.ctx.chatluna_storage != null ? void 0 : partAsTypeCheck(
732
768
  chunk,
733
769
  (part) => part["inlineData"] != null
@@ -739,6 +775,9 @@ var GeminiRequester = class extends import_api.ModelRequester {
739
775
  });
740
776
  yield { type: "generation", generation: generationChunk };
741
777
  }
778
+ if (updatedToolCalling) {
779
+ functionIndex++;
780
+ }
742
781
  } catch (e) {
743
782
  if (errorCount > 5) {
744
783
  logger.error("error with chunk", chunk);
@@ -753,7 +792,7 @@ var GeminiRequester = class extends import_api.ModelRequester {
753
792
  }
754
793
  }
755
794
  }
756
- async _processChunk(chunk, reasoningContent, functionCall) {
795
+ async _processChunk(chunk, reasoningContent, functionIndex) {
757
796
  const messagePart = partAsType(chunk);
758
797
  const chatFunctionCallingPart = partAsType(chunk);
759
798
  const imagePart = partAsTypeCheck(
@@ -790,30 +829,25 @@ var GeminiRequester = class extends import_api.ModelRequester {
790
829
  }
791
830
  }
792
831
  const deltaFunctionCall = chatFunctionCallingPart?.functionCall;
832
+ let updatedToolCalling;
793
833
  if (deltaFunctionCall) {
794
- this._updateFunctionCall(functionCall, deltaFunctionCall);
834
+ updatedToolCalling = this._createToolCallChunk(
835
+ deltaFunctionCall,
836
+ functionIndex
837
+ );
795
838
  }
796
839
  return {
797
840
  updatedContent: messageContent,
798
- updatedReasoning: reasoningContent
841
+ updatedReasoning: reasoningContent,
842
+ updatedToolCalling
799
843
  };
800
844
  }
801
- _updateFunctionCall(functionCall, deltaFunctionCall) {
802
- let args = deltaFunctionCall.args;
803
- try {
804
- let parsedArgs = JSON.parse(args);
805
- if (typeof parsedArgs !== "string") {
806
- args = parsedArgs;
807
- }
808
- parsedArgs = JSON.parse(args);
809
- if (typeof parsedArgs !== "string") {
810
- args = parsedArgs;
811
- }
812
- } catch (e) {
813
- }
814
- functionCall.args = JSON.stringify(args);
815
- functionCall.name = deltaFunctionCall.name;
816
- functionCall.arguments = deltaFunctionCall.args;
845
+ _createToolCallChunk(deltaFunctionCall, functionIndex) {
846
+ return {
847
+ name: deltaFunctionCall?.name,
848
+ args: JSON.stringify(deltaFunctionCall.args),
849
+ id: deltaFunctionCall.id ?? `function_call_${functionIndex}`
850
+ };
817
851
  }
818
852
  _handleFinalContent(reasoningContent, groundingContent) {
819
853
  if (reasoningContent.length > 0) {
@@ -836,15 +870,10 @@ ${groundingContent}`
836
870
  }
837
871
  _createMessageChunk(content, functionCall, imagePart) {
838
872
  const messageChunk = new import_messages.AIMessageChunk({
839
- content: content ?? ""
873
+ content: content ?? "",
874
+ tool_call_chunks: [functionCall].filter(Boolean)
840
875
  });
841
876
  messageChunk.additional_kwargs = {
842
- function_call: functionCall.name.length > 0 ? {
843
- name: functionCall.name,
844
- arguments: functionCall.args,
845
- args: functionCall.arguments
846
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
847
- } : void 0,
848
877
  images: imagePart ? [
849
878
  `data:${imagePart.inlineData.mimeType ?? "image/png"};base64,${imagePart.inlineData.data}`
850
879
  ] : void 0
@@ -867,11 +896,12 @@ ${groundingContent}`
867
896
  ...params
868
897
  });
869
898
  }
870
- _get(url) {
899
+ _get(url, params = {}) {
871
900
  const requestUrl = this._concatUrl(url);
872
901
  return this._plugin.fetch(requestUrl, {
873
902
  method: "GET",
874
- headers: this._buildHeaders()
903
+ headers: this._buildHeaders(),
904
+ ...params
875
905
  });
876
906
  }
877
907
  _concatUrl(url) {
@@ -919,9 +949,9 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
919
949
  get logger() {
920
950
  return logger;
921
951
  }
922
- async refreshModels() {
952
+ async refreshModels(config) {
923
953
  try {
924
- const rawModels = await this._requester.getModels();
954
+ const rawModels = await this._requester.getModels(config);
925
955
  if (!rawModels.length) {
926
956
  throw new import_error2.ChatLunaError(
927
957
  import_error2.ChatLunaErrorCode.MODEL_INIT_ERROR,
@@ -933,16 +963,16 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
933
963
  const info = {
934
964
  name: model.name,
935
965
  maxTokens: model.inputTokenLimit,
936
- type: model.name.includes("embedding") ? import_types.ModelType.embeddings : import_types.ModelType.llm,
966
+ type: model.name.includes("embedding") ? import_types2.ModelType.embeddings : import_types2.ModelType.llm,
937
967
  capabilities: [
938
- import_types.ModelCapabilities.ImageInput,
939
- import_types.ModelCapabilities.ToolCall
968
+ import_types2.ModelCapabilities.ImageInput,
969
+ import_types2.ModelCapabilities.ToolCall
940
970
  ]
941
971
  };
942
972
  if (model.name.includes("gemini-2.5") && !model.name.includes("pro") && !model.name.includes("image")) {
943
973
  if (!model.name.includes("-thinking")) {
944
974
  models.push(
945
- { ...info, name: model.name + "-nonthinking" },
975
+ { ...info, name: model.name + "-non-thinking" },
946
976
  { ...info, name: model.name + "-thinking" },
947
977
  info
948
978
  );
@@ -955,6 +985,9 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
955
985
  }
956
986
  return models;
957
987
  } catch (e) {
988
+ if (e instanceof import_error2.ChatLunaError) {
989
+ throw e;
990
+ }
958
991
  throw new import_error2.ChatLunaError(import_error2.ChatLunaErrorCode.MODEL_INIT_ERROR, e);
959
992
  }
960
993
  }
@@ -963,13 +996,15 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
963
996
  if (info == null) {
964
997
  throw new import_error2.ChatLunaError(import_error2.ChatLunaErrorCode.MODEL_NOT_FOUND);
965
998
  }
966
- if (info.type === import_types.ModelType.llm) {
999
+ if (info.type === import_types2.ModelType.llm) {
967
1000
  return new import_model.ChatLunaChatModel({
968
1001
  modelInfo: info,
969
1002
  requester: this._requester,
970
1003
  model,
971
1004
  modelMaxContextSize: info.maxTokens,
972
- maxTokenLimit: this._config.maxTokens,
1005
+ maxTokenLimit: Math.floor(
1006
+ (info.maxTokens || 1e5) * this._config.maxContextRatio
1007
+ ),
973
1008
  timeout: this._config.timeout,
974
1009
  temperature: this._config.temperature,
975
1010
  maxRetries: this._config.maxRetries,
@@ -985,16 +1020,17 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
985
1020
  };
986
1021
 
987
1022
  // src/index.ts
988
- var import_logger = require("koishi-plugin-chatluna/utils/logger");
1023
+ var import_logger2 = require("koishi-plugin-chatluna/utils/logger");
989
1024
  var logger;
990
1025
  var reusable = true;
991
1026
  function apply(ctx, config) {
992
- const plugin = new import_chat.ChatLunaPlugin(ctx, config, config.platform);
993
- logger = (0, import_logger.createLogger)(ctx, "chatluna-gemini-adapter");
1027
+ logger = (0, import_logger2.createLogger)(ctx, "chatluna-gemini-adapter");
994
1028
  ctx.on("ready", async () => {
995
- plugin.registerToService();
996
- await plugin.parseConfig((config2) => {
997
- return config2.apiKeys.map(([apiKey, apiEndpoint]) => {
1029
+ const plugin = new import_chat.ChatLunaPlugin(ctx, config, config.platform);
1030
+ plugin.parseConfig((config2) => {
1031
+ return config2.apiKeys.filter(([apiKey, _, enabled]) => {
1032
+ return apiKey.length > 0 && enabled;
1033
+ }).map(([apiKey, apiEndpoint]) => {
998
1034
  return {
999
1035
  apiKey,
1000
1036
  apiEndpoint,
@@ -1006,8 +1042,8 @@ function apply(ctx, config) {
1006
1042
  };
1007
1043
  });
1008
1044
  });
1009
- plugin.registerClient((ctx2) => new GeminiClient(ctx2, config, plugin));
1010
- await plugin.initClients();
1045
+ plugin.registerClient(() => new GeminiClient(ctx, config, plugin));
1046
+ await plugin.initClient();
1011
1047
  });
1012
1048
  }
1013
1049
  __name(apply, "apply");
@@ -1017,15 +1053,16 @@ var Config4 = import_koishi.Schema.intersect([
1017
1053
  platform: import_koishi.Schema.string().default("gemini"),
1018
1054
  apiKeys: import_koishi.Schema.array(
1019
1055
  import_koishi.Schema.tuple([
1020
- import_koishi.Schema.string().role("secret"),
1056
+ import_koishi.Schema.string().role("secret").default(""),
1021
1057
  import_koishi.Schema.string().default(
1022
1058
  "https://generativelanguage.googleapis.com/v1beta"
1023
- )
1059
+ ),
1060
+ import_koishi.Schema.boolean().default(true)
1024
1061
  ])
1025
- ).default([["", "https://generativelanguage.googleapis.com/v1beta"]])
1062
+ ).default([[]]).role("table")
1026
1063
  }),
1027
1064
  import_koishi.Schema.object({
1028
- maxTokens: import_koishi.Schema.number().min(16).max(2097e3).step(16).default(8064),
1065
+ maxContextRatio: import_koishi.Schema.number().min(0).max(1).step(1e-4).role("slider").default(0.35),
1029
1066
  temperature: import_koishi.Schema.percent().min(0).max(2).step(0.1).default(1),
1030
1067
  googleSearch: import_koishi.Schema.boolean().default(false),
1031
1068
  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
 
@@ -63,12 +63,12 @@ import {
63
63
  isMessageContentImageUrl,
64
64
  isMessageContentText
65
65
  } from "koishi-plugin-chatluna/utils/string";
66
- import { ZodSchema } from "zod";
66
+ import { isZodSchemaV3 } from "@langchain/core/utils/types";
67
67
  async function langchainMessageToGeminiMessage(messages, plugin, model) {
68
68
  return Promise.all(
69
69
  messages.map(async (message) => {
70
70
  const role = messageTypeToGeminiRole(message.getType());
71
- const hasFunctionCall = message.additional_kwargs?.function_call != null;
71
+ const hasFunctionCall = message.tool_calls != null && message.tool_calls.length > 0;
72
72
  if (role === "function" || hasFunctionCall) {
73
73
  return processFunctionMessage(message);
74
74
  }
@@ -114,50 +114,43 @@ function extractSystemMessages(messages) {
114
114
  ];
115
115
  }
116
116
  __name(extractSystemMessages, "extractSystemMessages");
117
- function parseJsonSafely(content) {
118
- try {
119
- const result = JSON.parse(content);
120
- return typeof result === "string" ? { response: result } : result;
121
- } catch {
122
- return { response: content };
123
- }
124
- }
125
- __name(parseJsonSafely, "parseJsonSafely");
126
117
  function parseJsonArgs(args) {
127
118
  try {
128
119
  const result = JSON.parse(args);
129
- return typeof result === "string" ? { input: result } : result;
120
+ if (typeof result === "string") return { response: result };
121
+ if (Array.isArray(result)) return { response: result };
122
+ return result;
130
123
  } catch {
131
- return { input: args };
124
+ return { response: args };
132
125
  }
133
126
  }
134
127
  __name(parseJsonArgs, "parseJsonArgs");
135
128
  function processFunctionMessage(message) {
136
- const hasFunctionCall = message.additional_kwargs?.function_call != null;
137
- if (hasFunctionCall) {
138
- const functionCall = message.additional_kwargs.function_call;
129
+ if (message["tool_calls"]) {
130
+ message = message;
131
+ const toolCalls = message.tool_calls;
139
132
  return {
140
- role: "function",
141
- parts: [
142
- {
133
+ role: "model",
134
+ parts: toolCalls.map((toolCall) => {
135
+ return {
143
136
  functionCall: {
144
- name: functionCall.name,
145
- args: parseJsonArgs(functionCall.arguments)
137
+ name: toolCall.name,
138
+ args: toolCall.args,
139
+ id: toolCall.id
146
140
  }
147
- }
148
- ]
141
+ };
142
+ })
149
143
  };
150
144
  }
145
+ const finalMessage = message;
151
146
  return {
152
- role: "function",
147
+ role: "user",
153
148
  parts: [
154
149
  {
155
150
  functionResponse: {
156
151
  name: message.name,
157
- response: {
158
- name: message.name,
159
- content: parseJsonSafely(message.content)
160
- }
152
+ id: finalMessage.tool_call_id,
153
+ response: parseJsonArgs(message.content)
161
154
  }
162
155
  }
163
156
  ]
@@ -291,7 +284,7 @@ function formatToolsToGeminiAITools(tools, config, model) {
291
284
  __name(formatToolsToGeminiAITools, "formatToolsToGeminiAITools");
292
285
  function formatToolToGeminiAITool(tool) {
293
286
  const parameters = removeAdditionalProperties(
294
- tool.schema instanceof ZodSchema ? zodToJsonSchema(tool.schema, {
287
+ isZodSchemaV3(tool.schema) ? zodToJsonSchema(tool.schema, {
295
288
  allowedAdditionalProperties: void 0
296
289
  }) : tool.schema
297
290
  );
@@ -311,7 +304,7 @@ function messageTypeToGeminiRole(type) {
311
304
  return "model";
312
305
  case "human":
313
306
  return "user";
314
- case "function":
307
+ case "tool":
315
308
  return "function";
316
309
  default:
317
310
  throw new Error(`Unknown message type: ${type}`);
@@ -323,7 +316,7 @@ function prepareModelConfig(params, pluginConfig) {
323
316
  let enabledThinking = null;
324
317
  if (model.includes("-thinking") && model.includes("gemini-2.5")) {
325
318
  enabledThinking = !model.includes("-non-thinking");
326
- model = model.replace("-nom-thinking", "").replace("-thinking", "");
319
+ model = model.replace("-non-thinking", "").replace("-thinking", "");
327
320
  }
328
321
  let thinkingBudget = pluginConfig.thinkingBudget ?? -1;
329
322
  if (!enabledThinking && !model.includes("2.5-pro")) {
@@ -409,6 +402,7 @@ __name(isChatResponse, "isChatResponse");
409
402
 
410
403
  // src/requester.ts
411
404
  import { getMessageContent } from "koishi-plugin-chatluna/utils/string";
405
+ import { trackLogToLocal } from "koishi-plugin-chatluna/utils/logger";
412
406
  var GeminiRequester = class extends ModelRequester {
413
407
  constructor(ctx, _configPool, _pluginConfig, _plugin) {
414
408
  super(ctx, _configPool, _pluginConfig, _plugin);
@@ -452,6 +446,13 @@ var GeminiRequester = class extends ModelRequester {
452
446
  await checkResponse(response);
453
447
  yield* this._processResponseStream(response);
454
448
  } catch (e) {
449
+ if (this.ctx.chatluna.config.isLog) {
450
+ await trackLogToLocal(
451
+ "Request",
452
+ JSON.stringify(chatGenerationParams),
453
+ logger
454
+ );
455
+ }
455
456
  if (e instanceof ChatLunaError) {
456
457
  throw e;
457
458
  } else {
@@ -461,15 +462,16 @@ var GeminiRequester = class extends ModelRequester {
461
462
  }
462
463
  async completionInternal(params) {
463
464
  const modelConfig = prepareModelConfig(params, this._pluginConfig);
465
+ const chatGenerationParams = await createChatGenerationParams(
466
+ params,
467
+ this._plugin,
468
+ modelConfig,
469
+ this._pluginConfig
470
+ );
464
471
  try {
465
472
  const response = await this._post(
466
473
  `models/${modelConfig.model}:generateContent`,
467
- await createChatGenerationParams(
468
- params,
469
- this._plugin,
470
- modelConfig,
471
- this._pluginConfig
472
- ),
474
+ chatGenerationParams,
473
475
  {
474
476
  signal: params.signal
475
477
  }
@@ -477,6 +479,13 @@ var GeminiRequester = class extends ModelRequester {
477
479
  await checkResponse(response);
478
480
  return await this._processResponse(response);
479
481
  } catch (e) {
482
+ if (this.ctx.chatluna.config.isLog) {
483
+ await trackLogToLocal(
484
+ "Request",
485
+ JSON.stringify(chatGenerationParams),
486
+ logger
487
+ );
488
+ }
480
489
  if (e instanceof ChatLunaError) {
481
490
  throw e;
482
491
  } else {
@@ -526,12 +535,17 @@ var GeminiRequester = class extends ModelRequester {
526
535
  "error when calling gemini embeddings, Result: " + JSON.stringify(data)
527
536
  );
528
537
  }
529
- async getModels() {
538
+ async getModels(config) {
530
539
  try {
531
- const response = await this._get("models");
540
+ const response = await this._get("models", {
541
+ signal: config?.signal
542
+ });
532
543
  const data = await this._parseModelsResponse(response);
533
544
  return this._filterAndTransformModels(data.models);
534
545
  } catch (e) {
546
+ if (e instanceof ChatLunaError) {
547
+ throw e;
548
+ }
535
549
  const error = new Error(
536
550
  "error when listing gemini models, Error: " + e.message
537
551
  );
@@ -668,6 +682,18 @@ var GeminiRequester = class extends ModelRequester {
668
682
  return;
669
683
  }
670
684
  const transformValue = typeof chunk === "string" ? JSON.parse(chunk) : chunk;
685
+ if (transformValue.usageMetadata) {
686
+ const promptTokens = transformValue.usageMetadata.promptTokenCount;
687
+ const totalTokens = transformValue.usageMetadata.totalTokenCount;
688
+ const completionTokens = transformValue.usageMetadata.candidatesTokenCount ?? totalTokens - promptTokens;
689
+ controller.enqueue({
690
+ usage: {
691
+ promptTokens,
692
+ completionTokens,
693
+ totalTokens
694
+ }
695
+ });
696
+ }
671
697
  if (!transformValue?.candidates) {
672
698
  return;
673
699
  }
@@ -704,27 +730,37 @@ var GeminiRequester = class extends ModelRequester {
704
730
  async *_processChunks(iterable) {
705
731
  let reasoningContent = "";
706
732
  let errorCount = 0;
707
- const functionCall = {
708
- name: "",
709
- args: "",
710
- arguments: ""
711
- };
733
+ let functionIndex = 0;
712
734
  for await (const chunk of iterable) {
735
+ let parsedChunk;
736
+ if (parsedChunk = partAsTypeCheck(
737
+ chunk,
738
+ (chunk2) => chunk2["usage"] != null
739
+ )) {
740
+ const generationChunk = new ChatGenerationChunk({
741
+ message: new AIMessageChunk(""),
742
+ text: "",
743
+ generationInfo: {
744
+ tokenUsage: parsedChunk.usage
745
+ }
746
+ });
747
+ yield { type: "generation", generation: generationChunk };
748
+ }
713
749
  try {
714
- const { updatedContent, updatedReasoning } = await this._processChunk(
750
+ const { updatedContent, updatedReasoning, updatedToolCalling } = await this._processChunk(
715
751
  chunk,
716
752
  reasoningContent,
717
- functionCall
753
+ functionIndex
718
754
  );
719
755
  if (updatedReasoning !== reasoningContent) {
720
756
  reasoningContent = updatedReasoning;
721
757
  yield { type: "reasoning", content: reasoningContent };
722
758
  continue;
723
759
  }
724
- if (updatedContent || functionCall.name) {
760
+ if (updatedContent || updatedToolCalling) {
725
761
  const messageChunk = this._createMessageChunk(
726
762
  updatedContent,
727
- functionCall,
763
+ updatedToolCalling,
728
764
  this.ctx.chatluna_storage != null ? void 0 : partAsTypeCheck(
729
765
  chunk,
730
766
  (part) => part["inlineData"] != null
@@ -736,6 +772,9 @@ var GeminiRequester = class extends ModelRequester {
736
772
  });
737
773
  yield { type: "generation", generation: generationChunk };
738
774
  }
775
+ if (updatedToolCalling) {
776
+ functionIndex++;
777
+ }
739
778
  } catch (e) {
740
779
  if (errorCount > 5) {
741
780
  logger.error("error with chunk", chunk);
@@ -750,7 +789,7 @@ var GeminiRequester = class extends ModelRequester {
750
789
  }
751
790
  }
752
791
  }
753
- async _processChunk(chunk, reasoningContent, functionCall) {
792
+ async _processChunk(chunk, reasoningContent, functionIndex) {
754
793
  const messagePart = partAsType(chunk);
755
794
  const chatFunctionCallingPart = partAsType(chunk);
756
795
  const imagePart = partAsTypeCheck(
@@ -787,30 +826,25 @@ var GeminiRequester = class extends ModelRequester {
787
826
  }
788
827
  }
789
828
  const deltaFunctionCall = chatFunctionCallingPart?.functionCall;
829
+ let updatedToolCalling;
790
830
  if (deltaFunctionCall) {
791
- this._updateFunctionCall(functionCall, deltaFunctionCall);
831
+ updatedToolCalling = this._createToolCallChunk(
832
+ deltaFunctionCall,
833
+ functionIndex
834
+ );
792
835
  }
793
836
  return {
794
837
  updatedContent: messageContent,
795
- updatedReasoning: reasoningContent
838
+ updatedReasoning: reasoningContent,
839
+ updatedToolCalling
796
840
  };
797
841
  }
798
- _updateFunctionCall(functionCall, deltaFunctionCall) {
799
- let args = deltaFunctionCall.args;
800
- try {
801
- let parsedArgs = JSON.parse(args);
802
- if (typeof parsedArgs !== "string") {
803
- args = parsedArgs;
804
- }
805
- parsedArgs = JSON.parse(args);
806
- if (typeof parsedArgs !== "string") {
807
- args = parsedArgs;
808
- }
809
- } catch (e) {
810
- }
811
- functionCall.args = JSON.stringify(args);
812
- functionCall.name = deltaFunctionCall.name;
813
- functionCall.arguments = deltaFunctionCall.args;
842
+ _createToolCallChunk(deltaFunctionCall, functionIndex) {
843
+ return {
844
+ name: deltaFunctionCall?.name,
845
+ args: JSON.stringify(deltaFunctionCall.args),
846
+ id: deltaFunctionCall.id ?? `function_call_${functionIndex}`
847
+ };
814
848
  }
815
849
  _handleFinalContent(reasoningContent, groundingContent) {
816
850
  if (reasoningContent.length > 0) {
@@ -833,15 +867,10 @@ ${groundingContent}`
833
867
  }
834
868
  _createMessageChunk(content, functionCall, imagePart) {
835
869
  const messageChunk = new AIMessageChunk({
836
- content: content ?? ""
870
+ content: content ?? "",
871
+ tool_call_chunks: [functionCall].filter(Boolean)
837
872
  });
838
873
  messageChunk.additional_kwargs = {
839
- function_call: functionCall.name.length > 0 ? {
840
- name: functionCall.name,
841
- arguments: functionCall.args,
842
- args: functionCall.arguments
843
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
844
- } : void 0,
845
874
  images: imagePart ? [
846
875
  `data:${imagePart.inlineData.mimeType ?? "image/png"};base64,${imagePart.inlineData.data}`
847
876
  ] : void 0
@@ -864,11 +893,12 @@ ${groundingContent}`
864
893
  ...params
865
894
  });
866
895
  }
867
- _get(url) {
896
+ _get(url, params = {}) {
868
897
  const requestUrl = this._concatUrl(url);
869
898
  return this._plugin.fetch(requestUrl, {
870
899
  method: "GET",
871
- headers: this._buildHeaders()
900
+ headers: this._buildHeaders(),
901
+ ...params
872
902
  });
873
903
  }
874
904
  _concatUrl(url) {
@@ -916,9 +946,9 @@ var GeminiClient = class extends PlatformModelAndEmbeddingsClient {
916
946
  get logger() {
917
947
  return logger;
918
948
  }
919
- async refreshModels() {
949
+ async refreshModels(config) {
920
950
  try {
921
- const rawModels = await this._requester.getModels();
951
+ const rawModels = await this._requester.getModels(config);
922
952
  if (!rawModels.length) {
923
953
  throw new ChatLunaError2(
924
954
  ChatLunaErrorCode2.MODEL_INIT_ERROR,
@@ -939,7 +969,7 @@ var GeminiClient = class extends PlatformModelAndEmbeddingsClient {
939
969
  if (model.name.includes("gemini-2.5") && !model.name.includes("pro") && !model.name.includes("image")) {
940
970
  if (!model.name.includes("-thinking")) {
941
971
  models.push(
942
- { ...info, name: model.name + "-nonthinking" },
972
+ { ...info, name: model.name + "-non-thinking" },
943
973
  { ...info, name: model.name + "-thinking" },
944
974
  info
945
975
  );
@@ -952,6 +982,9 @@ var GeminiClient = class extends PlatformModelAndEmbeddingsClient {
952
982
  }
953
983
  return models;
954
984
  } catch (e) {
985
+ if (e instanceof ChatLunaError2) {
986
+ throw e;
987
+ }
955
988
  throw new ChatLunaError2(ChatLunaErrorCode2.MODEL_INIT_ERROR, e);
956
989
  }
957
990
  }
@@ -966,7 +999,9 @@ var GeminiClient = class extends PlatformModelAndEmbeddingsClient {
966
999
  requester: this._requester,
967
1000
  model,
968
1001
  modelMaxContextSize: info.maxTokens,
969
- maxTokenLimit: this._config.maxTokens,
1002
+ maxTokenLimit: Math.floor(
1003
+ (info.maxTokens || 1e5) * this._config.maxContextRatio
1004
+ ),
970
1005
  timeout: this._config.timeout,
971
1006
  temperature: this._config.temperature,
972
1007
  maxRetries: this._config.maxRetries,
@@ -986,12 +1021,13 @@ import { createLogger } from "koishi-plugin-chatluna/utils/logger";
986
1021
  var logger;
987
1022
  var reusable = true;
988
1023
  function apply(ctx, config) {
989
- const plugin = new ChatLunaPlugin(ctx, config, config.platform);
990
1024
  logger = createLogger(ctx, "chatluna-gemini-adapter");
991
1025
  ctx.on("ready", async () => {
992
- plugin.registerToService();
993
- await plugin.parseConfig((config2) => {
994
- return config2.apiKeys.map(([apiKey, apiEndpoint]) => {
1026
+ const plugin = new ChatLunaPlugin(ctx, config, config.platform);
1027
+ plugin.parseConfig((config2) => {
1028
+ return config2.apiKeys.filter(([apiKey, _, enabled]) => {
1029
+ return apiKey.length > 0 && enabled;
1030
+ }).map(([apiKey, apiEndpoint]) => {
995
1031
  return {
996
1032
  apiKey,
997
1033
  apiEndpoint,
@@ -1003,8 +1039,8 @@ function apply(ctx, config) {
1003
1039
  };
1004
1040
  });
1005
1041
  });
1006
- plugin.registerClient((ctx2) => new GeminiClient(ctx2, config, plugin));
1007
- await plugin.initClients();
1042
+ plugin.registerClient(() => new GeminiClient(ctx, config, plugin));
1043
+ await plugin.initClient();
1008
1044
  });
1009
1045
  }
1010
1046
  __name(apply, "apply");
@@ -1014,15 +1050,16 @@ var Config4 = Schema.intersect([
1014
1050
  platform: Schema.string().default("gemini"),
1015
1051
  apiKeys: Schema.array(
1016
1052
  Schema.tuple([
1017
- Schema.string().role("secret"),
1053
+ Schema.string().role("secret").default(""),
1018
1054
  Schema.string().default(
1019
1055
  "https://generativelanguage.googleapis.com/v1beta"
1020
- )
1056
+ ),
1057
+ Schema.boolean().default(true)
1021
1058
  ])
1022
- ).default([["", "https://generativelanguage.googleapis.com/v1beta"]])
1059
+ ).default([[]]).role("table")
1023
1060
  }),
1024
1061
  Schema.object({
1025
- maxTokens: Schema.number().min(16).max(2097e3).step(16).default(8064),
1062
+ maxContextRatio: Schema.number().min(0).max(1).step(1e-4).role("slider").default(0.35),
1026
1063
  temperature: Schema.percent().min(0).max(2).step(0.1).default(1),
1027
1064
  googleSearch: Schema.boolean().default(false),
1028
1065
  codeExecution: Schema.boolean().default(false),
@@ -5,6 +5,7 @@ import { Config } from '.';
5
5
  import { GeminiModelInfo } from './types';
6
6
  import { ChatLunaPlugin } from 'koishi-plugin-chatluna/services/chat';
7
7
  import { Context } from 'koishi';
8
+ import { RunnableConfig } from '@langchain/core/runnables';
8
9
  export declare class GeminiRequester extends ModelRequester implements EmbeddingsRequester {
9
10
  _pluginConfig: Config;
10
11
  constructor(ctx: Context, _configPool: ClientConfigPool<ClientConfig>, _pluginConfig: Config, _plugin: ChatLunaPlugin);
@@ -15,7 +16,7 @@ export declare class GeminiRequester extends ModelRequester implements Embedding
15
16
  private _prepareEmbeddingsInput;
16
17
  private _createEmbeddingsRequest;
17
18
  private _processEmbeddingsResponse;
18
- getModels(): Promise<GeminiModelInfo[]>;
19
+ getModels(config?: RunnableConfig): Promise<GeminiModelInfo[]>;
19
20
  private _parseModelsResponse;
20
21
  private _filterAndTransformModels;
21
22
  private _processResponse;
@@ -26,7 +27,7 @@ export declare class GeminiRequester extends ModelRequester implements Embedding
26
27
  private _processCandidateChunk;
27
28
  private _processChunks;
28
29
  private _processChunk;
29
- private _updateFunctionCall;
30
+ private _createToolCallChunk;
30
31
  private _handleFinalContent;
31
32
  private _createMessageChunk;
32
33
  private _post;
package/lib/types.d.ts CHANGED
@@ -2,11 +2,18 @@ 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 ChatPart = ChatMessagePart | ChatInlineDataPart | ChatFunctionCallingPart | ChatFunctionResponsePart | ChatUploadDataPart | ChatUsageMetadataPart;
6
6
  export type ChatMessagePart = {
7
7
  text: string;
8
8
  thought?: boolean;
9
9
  };
10
+ export type ChatUsageMetadataPart = {
11
+ usage: {
12
+ promptTokens: number;
13
+ completionTokens: number;
14
+ totalTokens: number;
15
+ };
16
+ };
10
17
  export type ChatInlineDataPart = {
11
18
  inlineData: {
12
19
  mimeType: string;
@@ -23,12 +30,14 @@ export type ChatFunctionCallingPart = {
23
30
  functionCall: {
24
31
  name: string;
25
32
  args?: any;
33
+ id?: string;
26
34
  };
27
35
  };
28
36
  export type ChatFunctionResponsePart = {
29
37
  functionResponse: {
30
38
  name: string;
31
39
  response: any;
40
+ id?: string;
32
41
  };
33
42
  };
34
43
  export interface ChatResponse {
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.2",
4
+ "version": "1.3.0-alpha.20",
5
5
  "main": "lib/index.cjs",
6
6
  "module": "lib/index.mjs",
7
7
  "typings": "lib/index.d.ts",
@@ -22,28 +22,28 @@
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
  },
35
35
  "resolutions": {
36
36
  "@langchain/core": "0.3.62",
37
- "js-tiktoken": "npm:@dingyi222666/js-tiktoken@^1.0.19"
37
+ "js-tiktoken": "npm:@dingyi222666/js-tiktoken@^1.0.21"
38
38
  },
39
39
  "overrides": {
40
40
  "@langchain/core": "0.3.62",
41
- "js-tiktoken": "npm:@dingyi222666/js-tiktoken@^1.0.19"
41
+ "js-tiktoken": "npm:@dingyi222666/js-tiktoken@^1.0.21"
42
42
  },
43
43
  "pnpm": {
44
44
  "overrides": {
45
45
  "@langchain/core": "0.3.62",
46
- "js-tiktoken": "npm:@dingyi222666/js-tiktoken@^1.0.19"
46
+ "js-tiktoken": "npm:@dingyi222666/js-tiktoken@^1.0.21"
47
47
  }
48
48
  },
49
49
  "engines": {
@@ -62,19 +62,19 @@
62
62
  "adapter"
63
63
  ],
64
64
  "dependencies": {
65
- "@chatluna/v1-shared-adapter": "^1.0.7",
65
+ "@chatluna/v1-shared-adapter": "^1.0.14",
66
66
  "@langchain/core": "0.3.62",
67
67
  "zod": "3.25.76",
68
- "zod-to-json-schema": "^3.24.5"
68
+ "zod-to-json-schema": "^3.24.6"
69
69
  },
70
70
  "devDependencies": {
71
71
  "atsc": "^2.1.0",
72
- "koishi": "^4.18.7"
72
+ "koishi": "^4.18.9"
73
73
  },
74
74
  "peerDependencies": {
75
- "koishi": "^4.18.7",
76
- "koishi-plugin-chatluna": "^1.3.0-alpha.28",
77
- "koishi-plugin-chatluna-storage-service": "^0.0.8"
75
+ "koishi": "^4.18.9",
76
+ "koishi-plugin-chatluna": "^1.3.0-alpha.68",
77
+ "koishi-plugin-chatluna-storage-service": "^0.0.11"
78
78
  },
79
79
  "peerDependenciesMeta": {
80
80
  "koishi-plugin-chatluna-storage-service": {