koishi-plugin-chatluna-google-gemini-adapter 1.3.8 → 1.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.d.ts CHANGED
@@ -8,11 +8,11 @@ import { ChatLunaPlugin } from 'koishi-plugin-chatluna/services/chat';
8
8
  import { RunnableConfig } from '@langchain/core/runnables';
9
9
  export declare class GeminiClient extends PlatformModelAndEmbeddingsClient<ClientConfig> {
10
10
  private _config;
11
- plugin: ChatLunaPlugin;
11
+ plugin: ChatLunaPlugin<ClientConfig, Config>;
12
12
  platform: string;
13
13
  private _requester;
14
14
  get logger(): import("reggol");
15
- constructor(ctx: Context, _config: Config, plugin: ChatLunaPlugin);
15
+ constructor(ctx: Context, _config: Config, plugin: ChatLunaPlugin<ClientConfig, Config>);
16
16
  refreshModels(config?: RunnableConfig): Promise<ModelInfo[]>;
17
17
  protected _createModel(model: string): ChatLunaChatModel | ChatLunaBaseEmbeddings;
18
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: "模型配置", maxContextRatio: "最大上下文使用比例(0~1),控制可用的模型上下文窗口大小的最大百分比。例如 0.35 表示最多使用模型上下文的 35%。", temperature: "回复的随机性程度,数值越高,回复越随机(范围:0~2)。", googleSearch: "为模型启用谷歌搜索。", thinkingBudget: "思考预算,范围:(-1~24576),设置的数值越大,思考时花费的 Token 越多,-1 为动态思考。目前仅支持 gemini 2.5 系列模型。", groundingContentDisplay: "是否显示谷歌搜索结果。", imageGeneration: "为模型启用图像生成。目前仅支持 `gemini-3.0-pro-image-preview` 和 `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 参数。", useCamelCaseSystemInstruction: "使用大写的 systemInstruction 而不是小写的 system_instruction" }] };
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-*-image-*` 和 `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 参数。", useCamelCaseSystemInstruction: "使用大写的 systemInstruction 而不是小写的 system_instruction" }] };
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)", "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-3.0-pro-image-preview` 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.", useCamelCaseSystemInstruction: "Use camelCase systemInstruction instead of snake_case system_instruction" }] };
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-*-image-*` 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.", useCamelCaseSystemInstruction: "Use camelCase systemInstruction instead of snake_case system_instruction" }] };
34
34
  }
35
35
  });
36
36
 
@@ -46,7 +46,7 @@ __export(index_exports, {
46
46
  });
47
47
  module.exports = __toCommonJS(index_exports);
48
48
  var import_chat = require("koishi-plugin-chatluna/services/chat");
49
- var import_koishi2 = require("koishi");
49
+ var import_koishi = require("koishi");
50
50
 
51
51
  // src/client.ts
52
52
  var import_client = require("koishi-plugin-chatluna/llm-core/platform/client");
@@ -67,14 +67,18 @@ var import_v1_shared_adapter = require("@chatluna/v1-shared-adapter");
67
67
  var import_string = require("koishi-plugin-chatluna/utils/string");
68
68
  var import_types = require("@langchain/core/utils/types");
69
69
  var import_zod_openapi = require("@anatine/zod-openapi");
70
- var import_koishi = require("koishi");
70
+ var import_object = require("koishi-plugin-chatluna/utils/object");
71
71
  async function langchainMessageToGeminiMessage(messages, plugin, model) {
72
72
  return Promise.all(
73
73
  messages.map(async (message) => {
74
74
  const role = messageTypeToGeminiRole(message.getType());
75
75
  const hasFunctionCall = message.tool_calls != null && message.tool_calls.length > 0;
76
76
  if (role === "function" || hasFunctionCall) {
77
- return processFunctionMessage(message);
77
+ return processFunctionMessage(
78
+ message,
79
+ // 如果使用 new api,我们应该去掉 id,,,
80
+ plugin.config.useCamelCaseSystemInstruction
81
+ );
78
82
  }
79
83
  const result = {
80
84
  role,
@@ -134,7 +138,7 @@ function parseJsonArgs(args) {
134
138
  }
135
139
  }
136
140
  __name(parseJsonArgs, "parseJsonArgs");
137
- function processFunctionMessage(message) {
141
+ function processFunctionMessage(message, removeId) {
138
142
  const thoughtData = message.additional_kwargs["thought_data"] ?? {};
139
143
  if (message["tool_calls"]) {
140
144
  message = message;
@@ -142,27 +146,33 @@ function processFunctionMessage(message) {
142
146
  return {
143
147
  role: "model",
144
148
  parts: toolCalls.map((toolCall) => {
149
+ const functionCall = {
150
+ name: toolCall.name,
151
+ args: toolCall.args
152
+ };
153
+ if (!removeId) {
154
+ functionCall.id = toolCall.id;
155
+ }
145
156
  return {
146
- functionCall: {
147
- name: toolCall.name,
148
- args: toolCall.args,
149
- id: toolCall.id
150
- },
157
+ functionCall,
151
158
  ...thoughtData
152
159
  };
153
160
  })
154
161
  };
155
162
  }
156
163
  const finalMessage = message;
164
+ const functionResponse = {
165
+ name: message.name,
166
+ response: parseJsonArgs(message.content)
167
+ };
168
+ if (!removeId) {
169
+ functionResponse.id = finalMessage.tool_call_id;
170
+ }
157
171
  return {
158
172
  role: "user",
159
173
  parts: [
160
174
  {
161
- functionResponse: {
162
- name: message.name,
163
- id: finalMessage.tool_call_id,
164
- response: parseJsonArgs(message.content)
165
- }
175
+ functionResponse
166
176
  }
167
177
  ]
168
178
  };
@@ -324,6 +334,7 @@ function prepareModelConfig(params, pluginConfig) {
324
334
  let model = params.model;
325
335
  let enabledThinking = null;
326
336
  let thinkingLevel = "THINKING_LEVEL_UNSPECIFIED";
337
+ let imageSize;
327
338
  if (model.includes("-thinking") && model.includes("gemini-2.5")) {
328
339
  enabledThinking = !model.includes("-non-thinking");
329
340
  model = model.replace("-non-thinking", "").replace("-thinking", "");
@@ -348,6 +359,11 @@ function prepareModelConfig(params, pluginConfig) {
348
359
  } else {
349
360
  thinkingLevel = void 0;
350
361
  }
362
+ const imageSizeMatch = model.match(/-(2K|4K)$/);
363
+ if (imageSizeMatch) {
364
+ imageSize = imageSizeMatch[1];
365
+ model = model.replace(`-${imageSize}`, "");
366
+ }
351
367
  let imageGeneration = pluginConfig.imageGeneration ?? false;
352
368
  if (imageGeneration) {
353
369
  imageGeneration = params.model.includes("gemini-2.0-flash-exp") || params.model.includes("image");
@@ -357,7 +373,8 @@ function prepareModelConfig(params, pluginConfig) {
357
373
  enabledThinking,
358
374
  thinkingBudget,
359
375
  imageGeneration,
360
- thinkingLevel
376
+ thinkingLevel,
377
+ imageSize
361
378
  };
362
379
  }
363
380
  __name(prepareModelConfig, "prepareModelConfig");
@@ -388,21 +405,25 @@ function createSafetySettings(model) {
388
405
  }
389
406
  __name(createSafetySettings, "createSafetySettings");
390
407
  function createGenerationConfig(params, modelConfig, pluginConfig) {
391
- return {
408
+ const base = {
392
409
  stopSequences: params.stop,
393
410
  temperature: params.temperature,
394
411
  maxOutputTokens: params.model.includes("vision") ? void 0 : params.maxTokens,
395
412
  topP: params.topP,
396
413
  responseModalities: modelConfig.imageGeneration ? ["TEXT", "IMAGE"] : void 0,
397
- thinkingConfig: modelConfig.enabledThinking != null || pluginConfig.includeThoughts ? (0, import_koishi.filterKeys)(
414
+ imageConfig: modelConfig.imageSize ? {
415
+ imageSize: modelConfig.imageSize
416
+ } : void 0,
417
+ thinkingConfig: modelConfig.enabledThinking != null || pluginConfig.includeThoughts ? filterKeys(
398
418
  {
399
419
  thinkingBudget: modelConfig.thinkingBudget,
400
420
  thinkingLevel: modelConfig.thinkingLevel,
401
421
  includeThoughts: pluginConfig.includeThoughts
402
422
  },
403
- (k, v) => v != null
423
+ notNullFn
404
424
  ) : void 0
405
425
  };
426
+ return (0, import_object.deepAssign)({}, base, params.overrideRequestParams ?? {});
406
427
  }
407
428
  __name(createGenerationConfig, "createGenerationConfig");
408
429
  async function createChatGenerationParams(params, plugin, modelConfig, pluginConfig) {
@@ -434,6 +455,16 @@ function isChatResponse(response) {
434
455
  return "candidates" in response;
435
456
  }
436
457
  __name(isChatResponse, "isChatResponse");
458
+ function notNullFn(_, v) {
459
+ return v != null;
460
+ }
461
+ __name(notNullFn, "notNullFn");
462
+ function filterKeys(obj, fn) {
463
+ return Object.fromEntries(
464
+ Object.entries(obj).filter(([k, v]) => fn(k, v))
465
+ );
466
+ }
467
+ __name(filterKeys, "filterKeys");
437
468
 
438
469
  // src/requester.ts
439
470
  var import_string2 = require("koishi-plugin-chatluna/utils/string");
@@ -442,6 +473,7 @@ var GeminiRequester = class extends import_api.ModelRequester {
442
473
  constructor(ctx, _configPool, _pluginConfig, _plugin) {
443
474
  super(ctx, _configPool, _pluginConfig, _plugin);
444
475
  this._pluginConfig = _pluginConfig;
476
+ this._plugin = _plugin;
445
477
  }
446
478
  static {
447
479
  __name(this, "GeminiRequester");
@@ -1009,6 +1041,7 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
1009
1041
  };
1010
1042
  const thinkingModel = ["gemini-2.5-pro", "gemini-2.5-flash"];
1011
1043
  const thinkingLevelModel = ["gemini-3.0-pro"];
1044
+ const imageResolutionModel = ["gemini-3.0-pro-image"];
1012
1045
  if (thinkingModel.some(
1013
1046
  (name2) => model.name.toLowerCase().includes(name2) && !model.name.toLowerCase().includes("image")
1014
1047
  )) {
@@ -1028,6 +1061,14 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
1028
1061
  { ...info, name: model.name + "-low-thinking" },
1029
1062
  info
1030
1063
  );
1064
+ } else if (imageResolutionModel.some(
1065
+ (name2) => model.name.toLowerCase().includes(name2)
1066
+ )) {
1067
+ models.push(
1068
+ { ...info, name: model.name + "-2K" },
1069
+ { ...info, name: model.name + "-4K" },
1070
+ info
1071
+ );
1031
1072
  } else {
1032
1073
  models.push(info);
1033
1074
  }
@@ -1096,33 +1137,33 @@ function apply(ctx, config) {
1096
1137
  });
1097
1138
  }
1098
1139
  __name(apply, "apply");
1099
- var Config4 = import_koishi2.Schema.intersect([
1140
+ var Config4 = import_koishi.Schema.intersect([
1100
1141
  import_chat.ChatLunaPlugin.Config,
1101
- import_koishi2.Schema.object({
1102
- platform: import_koishi2.Schema.string().default("gemini"),
1103
- apiKeys: import_koishi2.Schema.array(
1104
- import_koishi2.Schema.tuple([
1105
- import_koishi2.Schema.string().role("secret").default(""),
1106
- import_koishi2.Schema.string().default(
1142
+ import_koishi.Schema.object({
1143
+ platform: import_koishi.Schema.string().default("gemini"),
1144
+ apiKeys: import_koishi.Schema.array(
1145
+ import_koishi.Schema.tuple([
1146
+ import_koishi.Schema.string().role("secret").default(""),
1147
+ import_koishi.Schema.string().default(
1107
1148
  "https://generativelanguage.googleapis.com/v1beta"
1108
1149
  ),
1109
- import_koishi2.Schema.boolean().default(true)
1150
+ import_koishi.Schema.boolean().default(true)
1110
1151
  ])
1111
1152
  ).default([[]]).role("table")
1112
1153
  }),
1113
- import_koishi2.Schema.object({
1114
- maxContextRatio: import_koishi2.Schema.number().min(0).max(1).step(1e-4).role("slider").default(0.35),
1115
- temperature: import_koishi2.Schema.percent().min(0).max(2).step(0.1).default(1),
1116
- googleSearch: import_koishi2.Schema.boolean().default(false),
1117
- codeExecution: import_koishi2.Schema.boolean().default(false),
1118
- urlContext: import_koishi2.Schema.boolean().default(false),
1119
- thinkingBudget: import_koishi2.Schema.number().min(-1).max(24576).default(-1),
1120
- includeThoughts: import_koishi2.Schema.boolean().default(false),
1121
- nonStreaming: import_koishi2.Schema.boolean().default(false),
1122
- imageGeneration: import_koishi2.Schema.boolean().default(false),
1123
- groundingContentDisplay: import_koishi2.Schema.boolean().default(false),
1124
- searchThreshold: import_koishi2.Schema.number().min(0).max(1).step(0.1).default(0.5),
1125
- useCamelCaseSystemInstruction: import_koishi2.Schema.boolean().default(false)
1154
+ import_koishi.Schema.object({
1155
+ maxContextRatio: import_koishi.Schema.number().min(0).max(1).step(1e-4).role("slider").default(0.35),
1156
+ temperature: import_koishi.Schema.percent().min(0).max(2).step(0.1).default(1),
1157
+ googleSearch: import_koishi.Schema.boolean().default(false),
1158
+ codeExecution: import_koishi.Schema.boolean().default(false),
1159
+ urlContext: import_koishi.Schema.boolean().default(false),
1160
+ thinkingBudget: import_koishi.Schema.number().min(-1).max(24576).default(-1),
1161
+ includeThoughts: import_koishi.Schema.boolean().default(false),
1162
+ nonStreaming: import_koishi.Schema.boolean().default(false),
1163
+ imageGeneration: import_koishi.Schema.boolean().default(false),
1164
+ groundingContentDisplay: import_koishi.Schema.boolean().default(false),
1165
+ searchThreshold: import_koishi.Schema.number().min(0).max(1).step(0.1).default(0.5),
1166
+ useCamelCaseSystemInstruction: import_koishi.Schema.boolean().default(false)
1126
1167
  })
1127
1168
  ]).i18n({
1128
1169
  "zh-CN": require_zh_CN_schema(),
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: "模型配置", maxContextRatio: "最大上下文使用比例(0~1),控制可用的模型上下文窗口大小的最大百分比。例如 0.35 表示最多使用模型上下文的 35%。", temperature: "回复的随机性程度,数值越高,回复越随机(范围:0~2)。", googleSearch: "为模型启用谷歌搜索。", thinkingBudget: "思考预算,范围:(-1~24576),设置的数值越大,思考时花费的 Token 越多,-1 为动态思考。目前仅支持 gemini 2.5 系列模型。", groundingContentDisplay: "是否显示谷歌搜索结果。", imageGeneration: "为模型启用图像生成。目前仅支持 `gemini-3.0-pro-image-preview` 和 `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 参数。", useCamelCaseSystemInstruction: "使用大写的 systemInstruction 而不是小写的 system_instruction" }] };
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-*-image-*` 和 `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 参数。", useCamelCaseSystemInstruction: "使用大写的 systemInstruction 而不是小写的 system_instruction" }] };
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)", "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-3.0-pro-image-preview` 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.", useCamelCaseSystemInstruction: "Use camelCase systemInstruction instead of snake_case system_instruction" }] };
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-*-image-*` 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.", useCamelCaseSystemInstruction: "Use camelCase systemInstruction instead of snake_case system_instruction" }] };
19
19
  }
20
20
  });
21
21
 
@@ -64,14 +64,18 @@ import {
64
64
  } from "koishi-plugin-chatluna/utils/string";
65
65
  import { isZodSchemaV3 } from "@langchain/core/utils/types";
66
66
  import { generateSchema } from "@anatine/zod-openapi";
67
- import { filterKeys } from "koishi";
67
+ import { deepAssign } from "koishi-plugin-chatluna/utils/object";
68
68
  async function langchainMessageToGeminiMessage(messages, plugin, model) {
69
69
  return Promise.all(
70
70
  messages.map(async (message) => {
71
71
  const role = messageTypeToGeminiRole(message.getType());
72
72
  const hasFunctionCall = message.tool_calls != null && message.tool_calls.length > 0;
73
73
  if (role === "function" || hasFunctionCall) {
74
- return processFunctionMessage(message);
74
+ return processFunctionMessage(
75
+ message,
76
+ // 如果使用 new api,我们应该去掉 id,,,
77
+ plugin.config.useCamelCaseSystemInstruction
78
+ );
75
79
  }
76
80
  const result = {
77
81
  role,
@@ -131,7 +135,7 @@ function parseJsonArgs(args) {
131
135
  }
132
136
  }
133
137
  __name(parseJsonArgs, "parseJsonArgs");
134
- function processFunctionMessage(message) {
138
+ function processFunctionMessage(message, removeId) {
135
139
  const thoughtData = message.additional_kwargs["thought_data"] ?? {};
136
140
  if (message["tool_calls"]) {
137
141
  message = message;
@@ -139,27 +143,33 @@ function processFunctionMessage(message) {
139
143
  return {
140
144
  role: "model",
141
145
  parts: toolCalls.map((toolCall) => {
146
+ const functionCall = {
147
+ name: toolCall.name,
148
+ args: toolCall.args
149
+ };
150
+ if (!removeId) {
151
+ functionCall.id = toolCall.id;
152
+ }
142
153
  return {
143
- functionCall: {
144
- name: toolCall.name,
145
- args: toolCall.args,
146
- id: toolCall.id
147
- },
154
+ functionCall,
148
155
  ...thoughtData
149
156
  };
150
157
  })
151
158
  };
152
159
  }
153
160
  const finalMessage = message;
161
+ const functionResponse = {
162
+ name: message.name,
163
+ response: parseJsonArgs(message.content)
164
+ };
165
+ if (!removeId) {
166
+ functionResponse.id = finalMessage.tool_call_id;
167
+ }
154
168
  return {
155
169
  role: "user",
156
170
  parts: [
157
171
  {
158
- functionResponse: {
159
- name: message.name,
160
- id: finalMessage.tool_call_id,
161
- response: parseJsonArgs(message.content)
162
- }
172
+ functionResponse
163
173
  }
164
174
  ]
165
175
  };
@@ -321,6 +331,7 @@ function prepareModelConfig(params, pluginConfig) {
321
331
  let model = params.model;
322
332
  let enabledThinking = null;
323
333
  let thinkingLevel = "THINKING_LEVEL_UNSPECIFIED";
334
+ let imageSize;
324
335
  if (model.includes("-thinking") && model.includes("gemini-2.5")) {
325
336
  enabledThinking = !model.includes("-non-thinking");
326
337
  model = model.replace("-non-thinking", "").replace("-thinking", "");
@@ -345,6 +356,11 @@ function prepareModelConfig(params, pluginConfig) {
345
356
  } else {
346
357
  thinkingLevel = void 0;
347
358
  }
359
+ const imageSizeMatch = model.match(/-(2K|4K)$/);
360
+ if (imageSizeMatch) {
361
+ imageSize = imageSizeMatch[1];
362
+ model = model.replace(`-${imageSize}`, "");
363
+ }
348
364
  let imageGeneration = pluginConfig.imageGeneration ?? false;
349
365
  if (imageGeneration) {
350
366
  imageGeneration = params.model.includes("gemini-2.0-flash-exp") || params.model.includes("image");
@@ -354,7 +370,8 @@ function prepareModelConfig(params, pluginConfig) {
354
370
  enabledThinking,
355
371
  thinkingBudget,
356
372
  imageGeneration,
357
- thinkingLevel
373
+ thinkingLevel,
374
+ imageSize
358
375
  };
359
376
  }
360
377
  __name(prepareModelConfig, "prepareModelConfig");
@@ -385,21 +402,25 @@ function createSafetySettings(model) {
385
402
  }
386
403
  __name(createSafetySettings, "createSafetySettings");
387
404
  function createGenerationConfig(params, modelConfig, pluginConfig) {
388
- return {
405
+ const base = {
389
406
  stopSequences: params.stop,
390
407
  temperature: params.temperature,
391
408
  maxOutputTokens: params.model.includes("vision") ? void 0 : params.maxTokens,
392
409
  topP: params.topP,
393
410
  responseModalities: modelConfig.imageGeneration ? ["TEXT", "IMAGE"] : void 0,
411
+ imageConfig: modelConfig.imageSize ? {
412
+ imageSize: modelConfig.imageSize
413
+ } : void 0,
394
414
  thinkingConfig: modelConfig.enabledThinking != null || pluginConfig.includeThoughts ? filterKeys(
395
415
  {
396
416
  thinkingBudget: modelConfig.thinkingBudget,
397
417
  thinkingLevel: modelConfig.thinkingLevel,
398
418
  includeThoughts: pluginConfig.includeThoughts
399
419
  },
400
- (k, v) => v != null
420
+ notNullFn
401
421
  ) : void 0
402
422
  };
423
+ return deepAssign({}, base, params.overrideRequestParams ?? {});
403
424
  }
404
425
  __name(createGenerationConfig, "createGenerationConfig");
405
426
  async function createChatGenerationParams(params, plugin, modelConfig, pluginConfig) {
@@ -431,6 +452,16 @@ function isChatResponse(response) {
431
452
  return "candidates" in response;
432
453
  }
433
454
  __name(isChatResponse, "isChatResponse");
455
+ function notNullFn(_, v) {
456
+ return v != null;
457
+ }
458
+ __name(notNullFn, "notNullFn");
459
+ function filterKeys(obj, fn) {
460
+ return Object.fromEntries(
461
+ Object.entries(obj).filter(([k, v]) => fn(k, v))
462
+ );
463
+ }
464
+ __name(filterKeys, "filterKeys");
434
465
 
435
466
  // src/requester.ts
436
467
  import { getMessageContent } from "koishi-plugin-chatluna/utils/string";
@@ -439,6 +470,7 @@ var GeminiRequester = class extends ModelRequester {
439
470
  constructor(ctx, _configPool, _pluginConfig, _plugin) {
440
471
  super(ctx, _configPool, _pluginConfig, _plugin);
441
472
  this._pluginConfig = _pluginConfig;
473
+ this._plugin = _plugin;
442
474
  }
443
475
  static {
444
476
  __name(this, "GeminiRequester");
@@ -1006,6 +1038,7 @@ var GeminiClient = class extends PlatformModelAndEmbeddingsClient {
1006
1038
  };
1007
1039
  const thinkingModel = ["gemini-2.5-pro", "gemini-2.5-flash"];
1008
1040
  const thinkingLevelModel = ["gemini-3.0-pro"];
1041
+ const imageResolutionModel = ["gemini-3.0-pro-image"];
1009
1042
  if (thinkingModel.some(
1010
1043
  (name2) => model.name.toLowerCase().includes(name2) && !model.name.toLowerCase().includes("image")
1011
1044
  )) {
@@ -1025,6 +1058,14 @@ var GeminiClient = class extends PlatformModelAndEmbeddingsClient {
1025
1058
  { ...info, name: model.name + "-low-thinking" },
1026
1059
  info
1027
1060
  );
1061
+ } else if (imageResolutionModel.some(
1062
+ (name2) => model.name.toLowerCase().includes(name2)
1063
+ )) {
1064
+ models.push(
1065
+ { ...info, name: model.name + "-2K" },
1066
+ { ...info, name: model.name + "-4K" },
1067
+ info
1068
+ );
1028
1069
  } else {
1029
1070
  models.push(info);
1030
1071
  }
@@ -6,9 +6,10 @@ import { GeminiModelInfo } from './types';
6
6
  import { ChatLunaPlugin } from 'koishi-plugin-chatluna/services/chat';
7
7
  import { Context } from 'koishi';
8
8
  import { RunnableConfig } from '@langchain/core/runnables';
9
- export declare class GeminiRequester extends ModelRequester implements EmbeddingsRequester {
9
+ export declare class GeminiRequester extends ModelRequester<ClientConfig, Config> implements EmbeddingsRequester {
10
10
  _pluginConfig: Config;
11
- constructor(ctx: Context, _configPool: ClientConfigPool<ClientConfig>, _pluginConfig: Config, _plugin: ChatLunaPlugin);
11
+ _plugin: ChatLunaPlugin<ClientConfig, Config>;
12
+ constructor(ctx: Context, _configPool: ClientConfigPool<ClientConfig>, _pluginConfig: Config, _plugin: ChatLunaPlugin<ClientConfig, Config>);
12
13
  completion(params: ModelRequestParams): Promise<ChatGeneration>;
13
14
  completionStreamInternal(params: ModelRequestParams): AsyncGenerator<ChatGenerationChunk>;
14
15
  completionInternal(params: ModelRequestParams): Promise<ChatGeneration>;
package/lib/utils.d.ts CHANGED
@@ -4,7 +4,8 @@ import { ChatCompletionFunction, ChatCompletionResponseMessage, ChatCompletionRe
4
4
  import { Config } from '.';
5
5
  import { ModelRequestParams } from 'koishi-plugin-chatluna/llm-core/platform/api';
6
6
  import { ChatLunaPlugin } from 'koishi-plugin-chatluna/services/chat';
7
- export declare function langchainMessageToGeminiMessage(messages: BaseMessage[], plugin: ChatLunaPlugin, model?: string): Promise<ChatCompletionResponseMessage[]>;
7
+ import { ClientConfig } from 'koishi-plugin-chatluna/llm-core/platform/config';
8
+ export declare function langchainMessageToGeminiMessage(messages: BaseMessage[], plugin: ChatLunaPlugin<ClientConfig, Config>, model?: string): Promise<ChatCompletionResponseMessage[]>;
8
9
  export declare function extractSystemMessages(messages: ChatCompletionResponseMessage[]): [ChatCompletionResponseMessage, ChatCompletionResponseMessage[]];
9
10
  export declare function partAsType<T extends ChatPart>(part: ChatPart): T;
10
11
  export declare function partAsTypeCheck<T extends ChatPart>(part: ChatPart, check: (part: ChatPart & unknown) => boolean): T | undefined;
@@ -17,6 +18,7 @@ export declare function prepareModelConfig(params: ModelRequestParams, pluginCon
17
18
  thinkingBudget: number;
18
19
  imageGeneration: boolean;
19
20
  thinkingLevel: string;
21
+ imageSize: string;
20
22
  };
21
23
  export declare function createSafetySettings(model: string): {
22
24
  category: string;
@@ -28,20 +30,16 @@ export declare function createGenerationConfig(params: ModelRequestParams, model
28
30
  maxOutputTokens: number;
29
31
  topP: number;
30
32
  responseModalities: string[];
31
- thinkingConfig: import("cosmokit").Dict<string | number | boolean, "thinkingBudget" | "thinkingLevel" | "includeThoughts">;
32
- };
33
- export declare function createChatGenerationParams(params: ModelRequestParams, plugin: ChatLunaPlugin, modelConfig: ReturnType<typeof prepareModelConfig>, pluginConfig: Config): Promise<{
34
- [x: string]: {
35
- stopSequences: string | string[];
36
- temperature: number;
37
- maxOutputTokens: number;
38
- topP: number;
39
- responseModalities: string[];
40
- thinkingConfig: import("cosmokit").Dict<string | number | boolean, "thinkingBudget" | "thinkingLevel" | "includeThoughts">;
41
- } | ChatCompletionResponseMessage | ChatCompletionResponseMessage[] | {
33
+ imageConfig: {
34
+ imageSize: string;
35
+ };
36
+ thinkingConfig: Record<"thinkingBudget" | "thinkingLevel" | "includeThoughts", string | number | boolean>;
37
+ } & Record<string, any>;
38
+ export declare function createChatGenerationParams(params: ModelRequestParams, plugin: ChatLunaPlugin<ClientConfig, Config>, modelConfig: ReturnType<typeof prepareModelConfig>, pluginConfig: Config): Promise<{
39
+ [x: string]: Record<string, any> | ChatCompletionResponseMessage | ChatCompletionResponseMessage[] | {
42
40
  category: string;
43
41
  threshold: string;
44
- }[] | Record<string, any>;
42
+ }[];
45
43
  contents: ChatCompletionResponseMessage[];
46
44
  safetySettings: {
47
45
  category: string;
@@ -53,8 +51,11 @@ export declare function createChatGenerationParams(params: ModelRequestParams, p
53
51
  maxOutputTokens: number;
54
52
  topP: number;
55
53
  responseModalities: string[];
56
- thinkingConfig: import("cosmokit").Dict<string | number | boolean, "thinkingBudget" | "thinkingLevel" | "includeThoughts">;
57
- };
54
+ imageConfig: {
55
+ imageSize: string;
56
+ };
57
+ thinkingConfig: Record<"thinkingBudget" | "thinkingLevel" | "includeThoughts", string | number | boolean>;
58
+ } & Record<string, any>;
58
59
  tools: Record<string, any>;
59
60
  }>;
60
61
  export declare function isChatResponse(response: any): response is 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.8",
4
+ "version": "1.3.10",
5
5
  "main": "lib/index.cjs",
6
6
  "module": "lib/index.mjs",
7
7
  "typings": "lib/index.d.ts",
@@ -63,7 +63,7 @@
63
63
  ],
64
64
  "dependencies": {
65
65
  "@anatine/zod-openapi": "^2.2.8",
66
- "@chatluna/v1-shared-adapter": "^1.0.16",
66
+ "@chatluna/v1-shared-adapter": "^1.0.17",
67
67
  "@langchain/core": "0.3.62",
68
68
  "openapi3-ts": "^4.5.0",
69
69
  "zod": "3.25.76",
@@ -75,7 +75,7 @@
75
75
  },
76
76
  "peerDependencies": {
77
77
  "koishi": "^4.18.9",
78
- "koishi-plugin-chatluna": "^1.3.0",
78
+ "koishi-plugin-chatluna": "^1.3.1",
79
79
  "koishi-plugin-chatluna-storage-service": "^0.0.11"
80
80
  },
81
81
  "peerDependenciesMeta": {