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

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
@@ -63,15 +63,15 @@ 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
- var import_zod = require("zod");
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) => {
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,9 +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, {
298
- allowedAdditionalProperties: void 0
299
- }) : tool.schema
290
+ (0, import_types.isZodSchemaV3)(tool.schema) ? (0, import_zod_openapi.generateSchema)(tool.schema, true, "3.0") : tool.schema
300
291
  );
301
292
  return {
302
293
  name: tool.name,
@@ -314,7 +305,7 @@ function messageTypeToGeminiRole(type) {
314
305
  return "model";
315
306
  case "human":
316
307
  return "user";
317
- case "function":
308
+ case "tool":
318
309
  return "function";
319
310
  default:
320
311
  throw new Error(`Unknown message type: ${type}`);
@@ -326,7 +317,7 @@ function prepareModelConfig(params, pluginConfig) {
326
317
  let enabledThinking = null;
327
318
  if (model.includes("-thinking") && model.includes("gemini-2.5")) {
328
319
  enabledThinking = !model.includes("-non-thinking");
329
- model = model.replace("-nom-thinking", "").replace("-thinking", "");
320
+ model = model.replace("-non-thinking", "").replace("-thinking", "");
330
321
  }
331
322
  let thinkingBudget = pluginConfig.thinkingBudget ?? -1;
332
323
  if (!enabledThinking && !model.includes("2.5-pro")) {
@@ -412,6 +403,7 @@ __name(isChatResponse, "isChatResponse");
412
403
 
413
404
  // src/requester.ts
414
405
  var import_string2 = require("koishi-plugin-chatluna/utils/string");
406
+ var import_logger = require("koishi-plugin-chatluna/utils/logger");
415
407
  var GeminiRequester = class extends import_api.ModelRequester {
416
408
  constructor(ctx, _configPool, _pluginConfig, _plugin) {
417
409
  super(ctx, _configPool, _pluginConfig, _plugin);
@@ -455,6 +447,13 @@ var GeminiRequester = class extends import_api.ModelRequester {
455
447
  await (0, import_sse.checkResponse)(response);
456
448
  yield* this._processResponseStream(response);
457
449
  } catch (e) {
450
+ if (this.ctx.chatluna.config.isLog) {
451
+ await (0, import_logger.trackLogToLocal)(
452
+ "Request",
453
+ JSON.stringify(chatGenerationParams),
454
+ logger
455
+ );
456
+ }
458
457
  if (e instanceof import_error.ChatLunaError) {
459
458
  throw e;
460
459
  } else {
@@ -464,15 +463,16 @@ var GeminiRequester = class extends import_api.ModelRequester {
464
463
  }
465
464
  async completionInternal(params) {
466
465
  const modelConfig = prepareModelConfig(params, this._pluginConfig);
466
+ const chatGenerationParams = await createChatGenerationParams(
467
+ params,
468
+ this._plugin,
469
+ modelConfig,
470
+ this._pluginConfig
471
+ );
467
472
  try {
468
473
  const response = await this._post(
469
474
  `models/${modelConfig.model}:generateContent`,
470
- await createChatGenerationParams(
471
- params,
472
- this._plugin,
473
- modelConfig,
474
- this._pluginConfig
475
- ),
475
+ chatGenerationParams,
476
476
  {
477
477
  signal: params.signal
478
478
  }
@@ -480,6 +480,13 @@ var GeminiRequester = class extends import_api.ModelRequester {
480
480
  await (0, import_sse.checkResponse)(response);
481
481
  return await this._processResponse(response);
482
482
  } catch (e) {
483
+ if (this.ctx.chatluna.config.isLog) {
484
+ await (0, import_logger.trackLogToLocal)(
485
+ "Request",
486
+ JSON.stringify(chatGenerationParams),
487
+ logger
488
+ );
489
+ }
483
490
  if (e instanceof import_error.ChatLunaError) {
484
491
  throw e;
485
492
  } else {
@@ -529,12 +536,17 @@ var GeminiRequester = class extends import_api.ModelRequester {
529
536
  "error when calling gemini embeddings, Result: " + JSON.stringify(data)
530
537
  );
531
538
  }
532
- async getModels() {
539
+ async getModels(config) {
533
540
  try {
534
- const response = await this._get("models");
541
+ const response = await this._get("models", {
542
+ signal: config?.signal
543
+ });
535
544
  const data = await this._parseModelsResponse(response);
536
545
  return this._filterAndTransformModels(data.models);
537
546
  } catch (e) {
547
+ if (e instanceof import_error.ChatLunaError) {
548
+ throw e;
549
+ }
538
550
  const error = new Error(
539
551
  "error when listing gemini models, Error: " + e.message
540
552
  );
@@ -671,6 +683,18 @@ var GeminiRequester = class extends import_api.ModelRequester {
671
683
  return;
672
684
  }
673
685
  const transformValue = typeof chunk === "string" ? JSON.parse(chunk) : chunk;
686
+ if (transformValue.usageMetadata) {
687
+ const promptTokens = transformValue.usageMetadata.promptTokenCount;
688
+ const totalTokens = transformValue.usageMetadata.totalTokenCount;
689
+ const completionTokens = transformValue.usageMetadata.candidatesTokenCount ?? totalTokens - promptTokens;
690
+ controller.enqueue({
691
+ usage: {
692
+ promptTokens,
693
+ completionTokens,
694
+ totalTokens
695
+ }
696
+ });
697
+ }
674
698
  if (!transformValue?.candidates) {
675
699
  return;
676
700
  }
@@ -707,27 +731,37 @@ var GeminiRequester = class extends import_api.ModelRequester {
707
731
  async *_processChunks(iterable) {
708
732
  let reasoningContent = "";
709
733
  let errorCount = 0;
710
- const functionCall = {
711
- name: "",
712
- args: "",
713
- arguments: ""
714
- };
734
+ let functionIndex = 0;
715
735
  for await (const chunk of iterable) {
736
+ let parsedChunk;
737
+ if (parsedChunk = partAsTypeCheck(
738
+ chunk,
739
+ (chunk2) => chunk2["usage"] != null
740
+ )) {
741
+ const generationChunk = new import_outputs.ChatGenerationChunk({
742
+ message: new import_messages.AIMessageChunk(""),
743
+ text: "",
744
+ generationInfo: {
745
+ tokenUsage: parsedChunk.usage
746
+ }
747
+ });
748
+ yield { type: "generation", generation: generationChunk };
749
+ }
716
750
  try {
717
- const { updatedContent, updatedReasoning } = await this._processChunk(
751
+ const { updatedContent, updatedReasoning, updatedToolCalling } = await this._processChunk(
718
752
  chunk,
719
753
  reasoningContent,
720
- functionCall
754
+ functionIndex
721
755
  );
722
756
  if (updatedReasoning !== reasoningContent) {
723
757
  reasoningContent = updatedReasoning;
724
758
  yield { type: "reasoning", content: reasoningContent };
725
759
  continue;
726
760
  }
727
- if (updatedContent || functionCall.name) {
761
+ if (updatedContent || updatedToolCalling) {
728
762
  const messageChunk = this._createMessageChunk(
729
763
  updatedContent,
730
- functionCall,
764
+ updatedToolCalling,
731
765
  this.ctx.chatluna_storage != null ? void 0 : partAsTypeCheck(
732
766
  chunk,
733
767
  (part) => part["inlineData"] != null
@@ -739,6 +773,9 @@ var GeminiRequester = class extends import_api.ModelRequester {
739
773
  });
740
774
  yield { type: "generation", generation: generationChunk };
741
775
  }
776
+ if (updatedToolCalling) {
777
+ functionIndex++;
778
+ }
742
779
  } catch (e) {
743
780
  if (errorCount > 5) {
744
781
  logger.error("error with chunk", chunk);
@@ -753,7 +790,7 @@ var GeminiRequester = class extends import_api.ModelRequester {
753
790
  }
754
791
  }
755
792
  }
756
- async _processChunk(chunk, reasoningContent, functionCall) {
793
+ async _processChunk(chunk, reasoningContent, functionIndex) {
757
794
  const messagePart = partAsType(chunk);
758
795
  const chatFunctionCallingPart = partAsType(chunk);
759
796
  const imagePart = partAsTypeCheck(
@@ -790,30 +827,25 @@ var GeminiRequester = class extends import_api.ModelRequester {
790
827
  }
791
828
  }
792
829
  const deltaFunctionCall = chatFunctionCallingPart?.functionCall;
830
+ let updatedToolCalling;
793
831
  if (deltaFunctionCall) {
794
- this._updateFunctionCall(functionCall, deltaFunctionCall);
832
+ updatedToolCalling = this._createToolCallChunk(
833
+ deltaFunctionCall,
834
+ functionIndex
835
+ );
795
836
  }
796
837
  return {
797
838
  updatedContent: messageContent,
798
- updatedReasoning: reasoningContent
839
+ updatedReasoning: reasoningContent,
840
+ updatedToolCalling
799
841
  };
800
842
  }
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;
843
+ _createToolCallChunk(deltaFunctionCall, functionIndex) {
844
+ return {
845
+ name: deltaFunctionCall?.name,
846
+ args: JSON.stringify(deltaFunctionCall.args),
847
+ id: deltaFunctionCall.id ?? `function_call_${functionIndex}`
848
+ };
817
849
  }
818
850
  _handleFinalContent(reasoningContent, groundingContent) {
819
851
  if (reasoningContent.length > 0) {
@@ -836,15 +868,10 @@ ${groundingContent}`
836
868
  }
837
869
  _createMessageChunk(content, functionCall, imagePart) {
838
870
  const messageChunk = new import_messages.AIMessageChunk({
839
- content: content ?? ""
871
+ content: content ?? "",
872
+ tool_call_chunks: [functionCall].filter(Boolean)
840
873
  });
841
874
  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
875
  images: imagePart ? [
849
876
  `data:${imagePart.inlineData.mimeType ?? "image/png"};base64,${imagePart.inlineData.data}`
850
877
  ] : void 0
@@ -867,11 +894,12 @@ ${groundingContent}`
867
894
  ...params
868
895
  });
869
896
  }
870
- _get(url) {
897
+ _get(url, params = {}) {
871
898
  const requestUrl = this._concatUrl(url);
872
899
  return this._plugin.fetch(requestUrl, {
873
900
  method: "GET",
874
- headers: this._buildHeaders()
901
+ headers: this._buildHeaders(),
902
+ ...params
875
903
  });
876
904
  }
877
905
  _concatUrl(url) {
@@ -919,9 +947,9 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
919
947
  get logger() {
920
948
  return logger;
921
949
  }
922
- async refreshModels() {
950
+ async refreshModels(config) {
923
951
  try {
924
- const rawModels = await this._requester.getModels();
952
+ const rawModels = await this._requester.getModels(config);
925
953
  if (!rawModels.length) {
926
954
  throw new import_error2.ChatLunaError(
927
955
  import_error2.ChatLunaErrorCode.MODEL_INIT_ERROR,
@@ -933,16 +961,16 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
933
961
  const info = {
934
962
  name: model.name,
935
963
  maxTokens: model.inputTokenLimit,
936
- type: model.name.includes("embedding") ? import_types.ModelType.embeddings : import_types.ModelType.llm,
964
+ type: model.name.includes("embedding") ? import_types2.ModelType.embeddings : import_types2.ModelType.llm,
937
965
  capabilities: [
938
- import_types.ModelCapabilities.ImageInput,
939
- import_types.ModelCapabilities.ToolCall
966
+ import_types2.ModelCapabilities.ImageInput,
967
+ import_types2.ModelCapabilities.ToolCall
940
968
  ]
941
969
  };
942
970
  if (model.name.includes("gemini-2.5") && !model.name.includes("pro") && !model.name.includes("image")) {
943
971
  if (!model.name.includes("-thinking")) {
944
972
  models.push(
945
- { ...info, name: model.name + "-nonthinking" },
973
+ { ...info, name: model.name + "-non-thinking" },
946
974
  { ...info, name: model.name + "-thinking" },
947
975
  info
948
976
  );
@@ -955,6 +983,9 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
955
983
  }
956
984
  return models;
957
985
  } catch (e) {
986
+ if (e instanceof import_error2.ChatLunaError) {
987
+ throw e;
988
+ }
958
989
  throw new import_error2.ChatLunaError(import_error2.ChatLunaErrorCode.MODEL_INIT_ERROR, e);
959
990
  }
960
991
  }
@@ -963,13 +994,15 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
963
994
  if (info == null) {
964
995
  throw new import_error2.ChatLunaError(import_error2.ChatLunaErrorCode.MODEL_NOT_FOUND);
965
996
  }
966
- if (info.type === import_types.ModelType.llm) {
997
+ if (info.type === import_types2.ModelType.llm) {
967
998
  return new import_model.ChatLunaChatModel({
968
999
  modelInfo: info,
969
1000
  requester: this._requester,
970
1001
  model,
971
1002
  modelMaxContextSize: info.maxTokens,
972
- maxTokenLimit: this._config.maxTokens,
1003
+ maxTokenLimit: Math.floor(
1004
+ (info.maxTokens || 1e5) * this._config.maxContextRatio
1005
+ ),
973
1006
  timeout: this._config.timeout,
974
1007
  temperature: this._config.temperature,
975
1008
  maxRetries: this._config.maxRetries,
@@ -985,16 +1018,17 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
985
1018
  };
986
1019
 
987
1020
  // src/index.ts
988
- var import_logger = require("koishi-plugin-chatluna/utils/logger");
1021
+ var import_logger2 = require("koishi-plugin-chatluna/utils/logger");
989
1022
  var logger;
990
1023
  var reusable = true;
991
1024
  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");
1025
+ logger = (0, import_logger2.createLogger)(ctx, "chatluna-gemini-adapter");
994
1026
  ctx.on("ready", async () => {
995
- plugin.registerToService();
996
- await plugin.parseConfig((config2) => {
997
- return config2.apiKeys.map(([apiKey, apiEndpoint]) => {
1027
+ const plugin = new import_chat.ChatLunaPlugin(ctx, config, config.platform);
1028
+ plugin.parseConfig((config2) => {
1029
+ return config2.apiKeys.filter(([apiKey, _, enabled]) => {
1030
+ return apiKey.length > 0 && enabled;
1031
+ }).map(([apiKey, apiEndpoint]) => {
998
1032
  return {
999
1033
  apiKey,
1000
1034
  apiEndpoint,
@@ -1006,8 +1040,8 @@ function apply(ctx, config) {
1006
1040
  };
1007
1041
  });
1008
1042
  });
1009
- plugin.registerClient((ctx2) => new GeminiClient(ctx2, config, plugin));
1010
- await plugin.initClients();
1043
+ plugin.registerClient(() => new GeminiClient(ctx, config, plugin));
1044
+ await plugin.initClient();
1011
1045
  });
1012
1046
  }
1013
1047
  __name(apply, "apply");
@@ -1017,15 +1051,16 @@ var Config4 = import_koishi.Schema.intersect([
1017
1051
  platform: import_koishi.Schema.string().default("gemini"),
1018
1052
  apiKeys: import_koishi.Schema.array(
1019
1053
  import_koishi.Schema.tuple([
1020
- import_koishi.Schema.string().role("secret"),
1054
+ import_koishi.Schema.string().role("secret").default(""),
1021
1055
  import_koishi.Schema.string().default(
1022
1056
  "https://generativelanguage.googleapis.com/v1beta"
1023
- )
1057
+ ),
1058
+ import_koishi.Schema.boolean().default(true)
1024
1059
  ])
1025
- ).default([["", "https://generativelanguage.googleapis.com/v1beta"]])
1060
+ ).default([[]]).role("table")
1026
1061
  }),
1027
1062
  import_koishi.Schema.object({
1028
- maxTokens: import_koishi.Schema.number().min(16).max(2097e3).step(16).default(8064),
1063
+ maxContextRatio: import_koishi.Schema.number().min(0).max(1).step(1e-4).role("slider").default(0.35),
1029
1064
  temperature: import_koishi.Schema.percent().min(0).max(2).step(0.1).default(1),
1030
1065
  googleSearch: import_koishi.Schema.boolean().default(false),
1031
1066
  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
@@ -63,12 +62,13 @@ import {
63
62
  isMessageContentImageUrl,
64
63
  isMessageContentText
65
64
  } from "koishi-plugin-chatluna/utils/string";
66
- import { ZodSchema } from "zod";
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) => {
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,9 +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, {
295
- allowedAdditionalProperties: void 0
296
- }) : tool.schema
287
+ isZodSchemaV3(tool.schema) ? generateSchema(tool.schema, true, "3.0") : tool.schema
297
288
  );
298
289
  return {
299
290
  name: tool.name,
@@ -311,7 +302,7 @@ function messageTypeToGeminiRole(type) {
311
302
  return "model";
312
303
  case "human":
313
304
  return "user";
314
- case "function":
305
+ case "tool":
315
306
  return "function";
316
307
  default:
317
308
  throw new Error(`Unknown message type: ${type}`);
@@ -323,7 +314,7 @@ function prepareModelConfig(params, pluginConfig) {
323
314
  let enabledThinking = null;
324
315
  if (model.includes("-thinking") && model.includes("gemini-2.5")) {
325
316
  enabledThinking = !model.includes("-non-thinking");
326
- model = model.replace("-nom-thinking", "").replace("-thinking", "");
317
+ model = model.replace("-non-thinking", "").replace("-thinking", "");
327
318
  }
328
319
  let thinkingBudget = pluginConfig.thinkingBudget ?? -1;
329
320
  if (!enabledThinking && !model.includes("2.5-pro")) {
@@ -409,6 +400,7 @@ __name(isChatResponse, "isChatResponse");
409
400
 
410
401
  // src/requester.ts
411
402
  import { getMessageContent } from "koishi-plugin-chatluna/utils/string";
403
+ import { trackLogToLocal } from "koishi-plugin-chatluna/utils/logger";
412
404
  var GeminiRequester = class extends ModelRequester {
413
405
  constructor(ctx, _configPool, _pluginConfig, _plugin) {
414
406
  super(ctx, _configPool, _pluginConfig, _plugin);
@@ -452,6 +444,13 @@ var GeminiRequester = class extends ModelRequester {
452
444
  await checkResponse(response);
453
445
  yield* this._processResponseStream(response);
454
446
  } catch (e) {
447
+ if (this.ctx.chatluna.config.isLog) {
448
+ await trackLogToLocal(
449
+ "Request",
450
+ JSON.stringify(chatGenerationParams),
451
+ logger
452
+ );
453
+ }
455
454
  if (e instanceof ChatLunaError) {
456
455
  throw e;
457
456
  } else {
@@ -461,15 +460,16 @@ var GeminiRequester = class extends ModelRequester {
461
460
  }
462
461
  async completionInternal(params) {
463
462
  const modelConfig = prepareModelConfig(params, this._pluginConfig);
463
+ const chatGenerationParams = await createChatGenerationParams(
464
+ params,
465
+ this._plugin,
466
+ modelConfig,
467
+ this._pluginConfig
468
+ );
464
469
  try {
465
470
  const response = await this._post(
466
471
  `models/${modelConfig.model}:generateContent`,
467
- await createChatGenerationParams(
468
- params,
469
- this._plugin,
470
- modelConfig,
471
- this._pluginConfig
472
- ),
472
+ chatGenerationParams,
473
473
  {
474
474
  signal: params.signal
475
475
  }
@@ -477,6 +477,13 @@ var GeminiRequester = class extends ModelRequester {
477
477
  await checkResponse(response);
478
478
  return await this._processResponse(response);
479
479
  } catch (e) {
480
+ if (this.ctx.chatluna.config.isLog) {
481
+ await trackLogToLocal(
482
+ "Request",
483
+ JSON.stringify(chatGenerationParams),
484
+ logger
485
+ );
486
+ }
480
487
  if (e instanceof ChatLunaError) {
481
488
  throw e;
482
489
  } else {
@@ -526,12 +533,17 @@ var GeminiRequester = class extends ModelRequester {
526
533
  "error when calling gemini embeddings, Result: " + JSON.stringify(data)
527
534
  );
528
535
  }
529
- async getModels() {
536
+ async getModels(config) {
530
537
  try {
531
- const response = await this._get("models");
538
+ const response = await this._get("models", {
539
+ signal: config?.signal
540
+ });
532
541
  const data = await this._parseModelsResponse(response);
533
542
  return this._filterAndTransformModels(data.models);
534
543
  } catch (e) {
544
+ if (e instanceof ChatLunaError) {
545
+ throw e;
546
+ }
535
547
  const error = new Error(
536
548
  "error when listing gemini models, Error: " + e.message
537
549
  );
@@ -668,6 +680,18 @@ var GeminiRequester = class extends ModelRequester {
668
680
  return;
669
681
  }
670
682
  const transformValue = typeof chunk === "string" ? JSON.parse(chunk) : chunk;
683
+ if (transformValue.usageMetadata) {
684
+ const promptTokens = transformValue.usageMetadata.promptTokenCount;
685
+ const totalTokens = transformValue.usageMetadata.totalTokenCount;
686
+ const completionTokens = transformValue.usageMetadata.candidatesTokenCount ?? totalTokens - promptTokens;
687
+ controller.enqueue({
688
+ usage: {
689
+ promptTokens,
690
+ completionTokens,
691
+ totalTokens
692
+ }
693
+ });
694
+ }
671
695
  if (!transformValue?.candidates) {
672
696
  return;
673
697
  }
@@ -704,27 +728,37 @@ var GeminiRequester = class extends ModelRequester {
704
728
  async *_processChunks(iterable) {
705
729
  let reasoningContent = "";
706
730
  let errorCount = 0;
707
- const functionCall = {
708
- name: "",
709
- args: "",
710
- arguments: ""
711
- };
731
+ let functionIndex = 0;
712
732
  for await (const chunk of iterable) {
733
+ let parsedChunk;
734
+ if (parsedChunk = partAsTypeCheck(
735
+ chunk,
736
+ (chunk2) => chunk2["usage"] != null
737
+ )) {
738
+ const generationChunk = new ChatGenerationChunk({
739
+ message: new AIMessageChunk(""),
740
+ text: "",
741
+ generationInfo: {
742
+ tokenUsage: parsedChunk.usage
743
+ }
744
+ });
745
+ yield { type: "generation", generation: generationChunk };
746
+ }
713
747
  try {
714
- const { updatedContent, updatedReasoning } = await this._processChunk(
748
+ const { updatedContent, updatedReasoning, updatedToolCalling } = await this._processChunk(
715
749
  chunk,
716
750
  reasoningContent,
717
- functionCall
751
+ functionIndex
718
752
  );
719
753
  if (updatedReasoning !== reasoningContent) {
720
754
  reasoningContent = updatedReasoning;
721
755
  yield { type: "reasoning", content: reasoningContent };
722
756
  continue;
723
757
  }
724
- if (updatedContent || functionCall.name) {
758
+ if (updatedContent || updatedToolCalling) {
725
759
  const messageChunk = this._createMessageChunk(
726
760
  updatedContent,
727
- functionCall,
761
+ updatedToolCalling,
728
762
  this.ctx.chatluna_storage != null ? void 0 : partAsTypeCheck(
729
763
  chunk,
730
764
  (part) => part["inlineData"] != null
@@ -736,6 +770,9 @@ var GeminiRequester = class extends ModelRequester {
736
770
  });
737
771
  yield { type: "generation", generation: generationChunk };
738
772
  }
773
+ if (updatedToolCalling) {
774
+ functionIndex++;
775
+ }
739
776
  } catch (e) {
740
777
  if (errorCount > 5) {
741
778
  logger.error("error with chunk", chunk);
@@ -750,7 +787,7 @@ var GeminiRequester = class extends ModelRequester {
750
787
  }
751
788
  }
752
789
  }
753
- async _processChunk(chunk, reasoningContent, functionCall) {
790
+ async _processChunk(chunk, reasoningContent, functionIndex) {
754
791
  const messagePart = partAsType(chunk);
755
792
  const chatFunctionCallingPart = partAsType(chunk);
756
793
  const imagePart = partAsTypeCheck(
@@ -787,30 +824,25 @@ var GeminiRequester = class extends ModelRequester {
787
824
  }
788
825
  }
789
826
  const deltaFunctionCall = chatFunctionCallingPart?.functionCall;
827
+ let updatedToolCalling;
790
828
  if (deltaFunctionCall) {
791
- this._updateFunctionCall(functionCall, deltaFunctionCall);
829
+ updatedToolCalling = this._createToolCallChunk(
830
+ deltaFunctionCall,
831
+ functionIndex
832
+ );
792
833
  }
793
834
  return {
794
835
  updatedContent: messageContent,
795
- updatedReasoning: reasoningContent
836
+ updatedReasoning: reasoningContent,
837
+ updatedToolCalling
796
838
  };
797
839
  }
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;
840
+ _createToolCallChunk(deltaFunctionCall, functionIndex) {
841
+ return {
842
+ name: deltaFunctionCall?.name,
843
+ args: JSON.stringify(deltaFunctionCall.args),
844
+ id: deltaFunctionCall.id ?? `function_call_${functionIndex}`
845
+ };
814
846
  }
815
847
  _handleFinalContent(reasoningContent, groundingContent) {
816
848
  if (reasoningContent.length > 0) {
@@ -833,15 +865,10 @@ ${groundingContent}`
833
865
  }
834
866
  _createMessageChunk(content, functionCall, imagePart) {
835
867
  const messageChunk = new AIMessageChunk({
836
- content: content ?? ""
868
+ content: content ?? "",
869
+ tool_call_chunks: [functionCall].filter(Boolean)
837
870
  });
838
871
  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
872
  images: imagePart ? [
846
873
  `data:${imagePart.inlineData.mimeType ?? "image/png"};base64,${imagePart.inlineData.data}`
847
874
  ] : void 0
@@ -864,11 +891,12 @@ ${groundingContent}`
864
891
  ...params
865
892
  });
866
893
  }
867
- _get(url) {
894
+ _get(url, params = {}) {
868
895
  const requestUrl = this._concatUrl(url);
869
896
  return this._plugin.fetch(requestUrl, {
870
897
  method: "GET",
871
- headers: this._buildHeaders()
898
+ headers: this._buildHeaders(),
899
+ ...params
872
900
  });
873
901
  }
874
902
  _concatUrl(url) {
@@ -916,9 +944,9 @@ var GeminiClient = class extends PlatformModelAndEmbeddingsClient {
916
944
  get logger() {
917
945
  return logger;
918
946
  }
919
- async refreshModels() {
947
+ async refreshModels(config) {
920
948
  try {
921
- const rawModels = await this._requester.getModels();
949
+ const rawModels = await this._requester.getModels(config);
922
950
  if (!rawModels.length) {
923
951
  throw new ChatLunaError2(
924
952
  ChatLunaErrorCode2.MODEL_INIT_ERROR,
@@ -939,7 +967,7 @@ var GeminiClient = class extends PlatformModelAndEmbeddingsClient {
939
967
  if (model.name.includes("gemini-2.5") && !model.name.includes("pro") && !model.name.includes("image")) {
940
968
  if (!model.name.includes("-thinking")) {
941
969
  models.push(
942
- { ...info, name: model.name + "-nonthinking" },
970
+ { ...info, name: model.name + "-non-thinking" },
943
971
  { ...info, name: model.name + "-thinking" },
944
972
  info
945
973
  );
@@ -952,6 +980,9 @@ var GeminiClient = class extends PlatformModelAndEmbeddingsClient {
952
980
  }
953
981
  return models;
954
982
  } catch (e) {
983
+ if (e instanceof ChatLunaError2) {
984
+ throw e;
985
+ }
955
986
  throw new ChatLunaError2(ChatLunaErrorCode2.MODEL_INIT_ERROR, e);
956
987
  }
957
988
  }
@@ -966,7 +997,9 @@ var GeminiClient = class extends PlatformModelAndEmbeddingsClient {
966
997
  requester: this._requester,
967
998
  model,
968
999
  modelMaxContextSize: info.maxTokens,
969
- maxTokenLimit: this._config.maxTokens,
1000
+ maxTokenLimit: Math.floor(
1001
+ (info.maxTokens || 1e5) * this._config.maxContextRatio
1002
+ ),
970
1003
  timeout: this._config.timeout,
971
1004
  temperature: this._config.temperature,
972
1005
  maxRetries: this._config.maxRetries,
@@ -986,12 +1019,13 @@ import { createLogger } from "koishi-plugin-chatluna/utils/logger";
986
1019
  var logger;
987
1020
  var reusable = true;
988
1021
  function apply(ctx, config) {
989
- const plugin = new ChatLunaPlugin(ctx, config, config.platform);
990
1022
  logger = createLogger(ctx, "chatluna-gemini-adapter");
991
1023
  ctx.on("ready", async () => {
992
- plugin.registerToService();
993
- await plugin.parseConfig((config2) => {
994
- return config2.apiKeys.map(([apiKey, apiEndpoint]) => {
1024
+ const plugin = new ChatLunaPlugin(ctx, config, config.platform);
1025
+ plugin.parseConfig((config2) => {
1026
+ return config2.apiKeys.filter(([apiKey, _, enabled]) => {
1027
+ return apiKey.length > 0 && enabled;
1028
+ }).map(([apiKey, apiEndpoint]) => {
995
1029
  return {
996
1030
  apiKey,
997
1031
  apiEndpoint,
@@ -1003,8 +1037,8 @@ function apply(ctx, config) {
1003
1037
  };
1004
1038
  });
1005
1039
  });
1006
- plugin.registerClient((ctx2) => new GeminiClient(ctx2, config, plugin));
1007
- await plugin.initClients();
1040
+ plugin.registerClient(() => new GeminiClient(ctx, config, plugin));
1041
+ await plugin.initClient();
1008
1042
  });
1009
1043
  }
1010
1044
  __name(apply, "apply");
@@ -1014,15 +1048,16 @@ var Config4 = Schema.intersect([
1014
1048
  platform: Schema.string().default("gemini"),
1015
1049
  apiKeys: Schema.array(
1016
1050
  Schema.tuple([
1017
- Schema.string().role("secret"),
1051
+ Schema.string().role("secret").default(""),
1018
1052
  Schema.string().default(
1019
1053
  "https://generativelanguage.googleapis.com/v1beta"
1020
- )
1054
+ ),
1055
+ Schema.boolean().default(true)
1021
1056
  ])
1022
- ).default([["", "https://generativelanguage.googleapis.com/v1beta"]])
1057
+ ).default([[]]).role("table")
1023
1058
  }),
1024
1059
  Schema.object({
1025
- maxTokens: Schema.number().min(16).max(2097e3).step(16).default(8064),
1060
+ maxContextRatio: Schema.number().min(0).max(1).step(1e-4).role("slider").default(0.35),
1026
1061
  temperature: Schema.percent().min(0).max(2).step(0.1).default(1),
1027
1062
  googleSearch: Schema.boolean().default(false),
1028
1063
  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.21",
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,21 @@
62
62
  "adapter"
63
63
  ],
64
64
  "dependencies": {
65
- "@chatluna/v1-shared-adapter": "^1.0.7",
65
+ "@anatine/zod-openapi": "^2.2.8",
66
+ "@chatluna/v1-shared-adapter": "^1.0.14",
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.28",
77
- "koishi-plugin-chatluna-storage-service": "^0.0.8"
77
+ "koishi": "^4.18.9",
78
+ "koishi-plugin-chatluna": "^1.3.0-alpha.73",
79
+ "koishi-plugin-chatluna-storage-service": "^0.0.11"
78
80
  },
79
81
  "peerDependenciesMeta": {
80
82
  "koishi-plugin-chatluna-storage-service": {