koishi-plugin-chatluna-google-gemini-adapter 1.2.18 → 1.2.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/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` 模型。", 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 内容获取工具。" }] };
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 参数。" }] };
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` 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" }] };
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." }] };
34
34
  }
35
35
  });
36
36
 
@@ -316,8 +316,96 @@ function messageTypeToGeminiRole(type) {
316
316
  }
317
317
  }
318
318
  __name(messageTypeToGeminiRole, "messageTypeToGeminiRole");
319
+ function prepareModelConfig(params, pluginConfig) {
320
+ let model = params.model;
321
+ let enabledThinking = null;
322
+ if (model.includes("-thinking") && model.includes("gemini-2.5")) {
323
+ enabledThinking = !model.includes("-non-thinking");
324
+ model = model.replace("-nom-thinking", "").replace("-thinking", "");
325
+ }
326
+ let thinkingBudget = pluginConfig.thinkingBudget ?? -1;
327
+ if (!enabledThinking && !model.includes("2.5-pro")) {
328
+ thinkingBudget = 0;
329
+ } else if (thinkingBudget >= 0 && thinkingBudget < 128) {
330
+ thinkingBudget = 128;
331
+ }
332
+ let imageGeneration = pluginConfig.imageGeneration ?? false;
333
+ if (imageGeneration) {
334
+ imageGeneration = params.model.includes("gemini-2.0-flash-exp") || params.model.includes("gemini-2.5-flash-image");
335
+ }
336
+ return { model, enabledThinking, thinkingBudget, imageGeneration };
337
+ }
338
+ __name(prepareModelConfig, "prepareModelConfig");
339
+ function createSafetySettings(model) {
340
+ const isGemini2 = model.includes("gemini-2");
341
+ return [
342
+ {
343
+ category: "HARM_CATEGORY_HARASSMENT",
344
+ threshold: isGemini2 ? "OFF" : "BLOCK_NONE"
345
+ },
346
+ {
347
+ category: "HARM_CATEGORY_HATE_SPEECH",
348
+ threshold: isGemini2 ? "OFF" : "BLOCK_NONE"
349
+ },
350
+ {
351
+ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
352
+ threshold: isGemini2 ? "OFF" : "BLOCK_NONE"
353
+ },
354
+ {
355
+ category: "HARM_CATEGORY_DANGEROUS_CONTENT",
356
+ threshold: isGemini2 ? "OFF" : "BLOCK_NONE"
357
+ },
358
+ {
359
+ category: "HARM_CATEGORY_CIVIC_INTEGRITY",
360
+ threshold: isGemini2 ? "OFF" : "BLOCK_NONE"
361
+ }
362
+ ];
363
+ }
364
+ __name(createSafetySettings, "createSafetySettings");
365
+ function createGenerationConfig(params, modelConfig, pluginConfig) {
366
+ return {
367
+ stopSequences: params.stop,
368
+ temperature: params.temperature,
369
+ maxOutputTokens: params.model.includes("vision") ? void 0 : params.maxTokens,
370
+ topP: params.topP,
371
+ responseModalities: modelConfig.imageGeneration ? ["TEXT", "IMAGE"] : void 0,
372
+ thinkingConfig: modelConfig.enabledThinking != null || pluginConfig.includeThoughts ? {
373
+ thinkingBudget: modelConfig.thinkingBudget,
374
+ includeThoughts: pluginConfig.includeThoughts
375
+ } : void 0
376
+ };
377
+ }
378
+ __name(createGenerationConfig, "createGenerationConfig");
379
+ async function createChatGenerationParams(params, modelConfig, pluginConfig) {
380
+ const geminiMessages = await langchainMessageToGeminiMessage(
381
+ params.input,
382
+ modelConfig.model
383
+ );
384
+ const [systemInstruction, modelMessages] = extractSystemMessages(geminiMessages);
385
+ return {
386
+ contents: modelMessages,
387
+ safetySettings: createSafetySettings(params.model),
388
+ generationConfig: createGenerationConfig(
389
+ params,
390
+ modelConfig,
391
+ pluginConfig
392
+ ),
393
+ system_instruction: systemInstruction != null ? systemInstruction : void 0,
394
+ tools: params.tools != null || pluginConfig.googleSearch || pluginConfig.codeExecution || pluginConfig.urlContext ? formatToolsToGeminiAITools(
395
+ params.tools ?? [],
396
+ pluginConfig,
397
+ params.model
398
+ ) : void 0
399
+ };
400
+ }
401
+ __name(createChatGenerationParams, "createChatGenerationParams");
402
+ function isChatResponse(response) {
403
+ return "candidates" in response;
404
+ }
405
+ __name(isChatResponse, "isChatResponse");
319
406
 
320
407
  // src/requester.ts
408
+ var import_string = require("koishi-plugin-chatluna/utils/string");
321
409
  var GeminiRequester = class extends import_api.ModelRequester {
322
410
  constructor(ctx, _configPool, _pluginConfig, _plugin) {
323
411
  super(ctx, _configPool, _pluginConfig, _plugin);
@@ -326,211 +414,38 @@ var GeminiRequester = class extends import_api.ModelRequester {
326
414
  static {
327
415
  __name(this, "GeminiRequester");
328
416
  }
417
+ async completion(params) {
418
+ if (!this._pluginConfig.nonStreaming) {
419
+ return super.completion(params);
420
+ }
421
+ return await this.completionInternal(params);
422
+ }
329
423
  async *completionStreamInternal(params) {
424
+ if (this._pluginConfig.nonStreaming) {
425
+ const generation = await this.completion(params);
426
+ yield new import_outputs.ChatGenerationChunk({
427
+ generationInfo: generation.generationInfo,
428
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
429
+ message: generation.message,
430
+ text: generation.text
431
+ });
432
+ return;
433
+ }
434
+ const modelConfig = prepareModelConfig(params, this._pluginConfig);
330
435
  try {
331
- let model = params.model;
332
- let enabledThinking = null;
333
- if (model.includes("-thinking") && model.includes("gemini-2.5")) {
334
- enabledThinking = !model.includes("-non-thinking");
335
- model = model.replace("-nom-thinking", "").replace("-thinking", "");
336
- }
337
- const geminiMessages = await langchainMessageToGeminiMessage(
338
- params.input,
339
- model
340
- );
341
- const [systemInstruction, modelMessages] = extractSystemMessages(geminiMessages);
342
- let thinkingBudget = this._pluginConfig.thinkingBudget ?? -1;
343
- if (!enabledThinking && !model.includes("2.5-pro")) {
344
- thinkingBudget = 0;
345
- } else if (thinkingBudget >= 0 && thinkingBudget < 128) {
346
- thinkingBudget = 128;
347
- }
348
- let imageGeneration = this._pluginConfig.imageGeneration ?? false;
349
- if (imageGeneration) {
350
- imageGeneration = params.model.includes("gemini-2.0-flash-exp") || params.model.includes("gemini-2.5-flash-image");
351
- }
352
436
  const response = await this._post(
353
- `models/${model}:streamGenerateContent?alt=sse`,
354
- {
355
- contents: modelMessages,
356
- safetySettings: [
357
- {
358
- category: "HARM_CATEGORY_HARASSMENT",
359
- threshold: params.model.includes("gemini-2") ? "OFF" : "BLOCK_NONE"
360
- },
361
- {
362
- category: "HARM_CATEGORY_HATE_SPEECH",
363
- threshold: params.model.includes("gemini-2") ? "OFF" : "BLOCK_NONE"
364
- },
365
- {
366
- category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
367
- threshold: params.model.includes("gemini-2") ? "OFF" : "BLOCK_NONE"
368
- },
369
- {
370
- category: "HARM_CATEGORY_DANGEROUS_CONTENT",
371
- threshold: params.model.includes("gemini-2") ? "OFF" : "BLOCK_NONE"
372
- },
373
- {
374
- category: "HARM_CATEGORY_CIVIC_INTEGRITY",
375
- threshold: params.model.includes("gemini-2.0") ? "OFF" : "BLOCK_NONE"
376
- }
377
- ],
378
- generationConfig: {
379
- stopSequences: params.stop,
380
- temperature: params.temperature,
381
- maxOutputTokens: params.model.includes("vision") ? void 0 : params.maxTokens,
382
- topP: params.topP,
383
- responseModalities: imageGeneration ? ["TEXT", "IMAGE"] : void 0,
384
- thinkingConfig: enabledThinking != null || this._pluginConfig.includeThoughts ? {
385
- thinkingBudget,
386
- includeThoughts: this._pluginConfig.includeThoughts
387
- } : void 0
388
- },
389
- system_instruction: systemInstruction != null ? systemInstruction : void 0,
390
- tools: params.tools != null || this._pluginConfig.googleSearch || this._pluginConfig.codeExecution || this._pluginConfig.urlContext ? formatToolsToGeminiAITools(
391
- params.tools ?? [],
392
- this._pluginConfig,
393
- params.model
394
- ) : void 0
395
- },
437
+ `models/${modelConfig.model}:streamGenerateContent?alt=sse`,
438
+ await createChatGenerationParams(
439
+ params,
440
+ modelConfig,
441
+ this._pluginConfig
442
+ ),
396
443
  {
397
444
  signal: params.signal
398
445
  }
399
446
  );
400
- let errorCount = 0;
401
- let groundingContent = "";
402
- let currentGroundingIndex = 0;
403
447
  await (0, import_sse.checkResponse)(response);
404
- const readableStream = new ReadableStream({
405
- async start(controller) {
406
- for await (const chunk of (0, import_sse.sseIterable)(response)) {
407
- controller.enqueue(chunk.data);
408
- }
409
- controller.close();
410
- }
411
- });
412
- const transformToChatPartStream = new TransformStream({
413
- async transform(chunk, controller) {
414
- if (chunk === "undefined") {
415
- return;
416
- }
417
- const parsedValue = JSON.parse(chunk);
418
- const transformValue = parsedValue;
419
- if (!transformValue.candidates) {
420
- return;
421
- }
422
- for (const candidate of transformValue.candidates) {
423
- const parts = candidate.content?.parts;
424
- if ((parts == null || parts.length < 1) && candidate.finishReason !== "STOP") {
425
- throw new Error(chunk);
426
- } else if (candidate.finishReason === "STOP" && parts == null) {
427
- continue;
428
- }
429
- for (const part of parts) {
430
- controller.enqueue(part);
431
- }
432
- for (const source of candidate.groundingMetadata?.groundingChunks ?? []) {
433
- groundingContent += `[^${currentGroundingIndex++}]: [${source.web.title}](${source.web.uri})
434
- `;
435
- }
436
- }
437
- }
438
- });
439
- const iterable = (0, import_stream.readableStreamToAsyncIterable)(
440
- readableStream.pipeThrough(transformToChatPartStream)
441
- );
442
- let reasoningContent = "";
443
- let content = "";
444
- const functionCall = {
445
- name: "",
446
- args: "",
447
- arguments: ""
448
- };
449
- for await (const chunk of iterable) {
450
- const messagePart = partAsType(chunk);
451
- const chatFunctionCallingPart = partAsType(chunk);
452
- const imagePart = partAsTypeCheck(
453
- chunk,
454
- (part) => part["inlineData"] != null
455
- );
456
- if (messagePart.text) {
457
- if (messagePart.thought) {
458
- reasoningContent += messagePart.text;
459
- continue;
460
- }
461
- content = messagePart.text;
462
- } else if (imagePart) {
463
- messagePart.text = `![image](data:${imagePart.inlineData.mimeType ?? "image/png"};base64,${imagePart.inlineData.data})`;
464
- content = messagePart.text;
465
- }
466
- const deltaFunctionCall = chatFunctionCallingPart?.functionCall;
467
- if (deltaFunctionCall) {
468
- let args = deltaFunctionCall.args;
469
- try {
470
- let parsedArgs = JSON.parse(args);
471
- if (typeof parsedArgs !== "string") {
472
- args = parsedArgs;
473
- }
474
- parsedArgs = JSON.parse(args);
475
- if (typeof parsedArgs !== "string") {
476
- args = parsedArgs;
477
- }
478
- } catch (e) {
479
- }
480
- functionCall.args = JSON.stringify(args);
481
- functionCall.name = deltaFunctionCall.name;
482
- functionCall.arguments = deltaFunctionCall.args;
483
- }
484
- try {
485
- const messageChunk = new import_messages2.AIMessageChunk(content);
486
- messageChunk.additional_kwargs = {
487
- function_call: functionCall.name.length > 0 ? {
488
- name: functionCall.name,
489
- arguments: functionCall.args,
490
- args: functionCall.arguments
491
- } : void 0,
492
- images: imagePart ? [
493
- `data:${imagePart.inlineData.mimeType ?? "image/png"};base64,${imagePart.inlineData.data}`
494
- ] : void 0
495
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
496
- };
497
- messageChunk.content = content;
498
- const generationChunk = new import_outputs.ChatGenerationChunk({
499
- message: messageChunk,
500
- text: messageChunk.content
501
- });
502
- yield generationChunk;
503
- content = messageChunk.content;
504
- } catch (e) {
505
- if (errorCount > 5) {
506
- logger.error("error with chunk", chunk);
507
- throw new import_error.ChatLunaError(
508
- import_error.ChatLunaErrorCode.API_REQUEST_FAILED,
509
- e
510
- );
511
- } else {
512
- errorCount++;
513
- continue;
514
- }
515
- }
516
- }
517
- if (reasoningContent.length > 0) {
518
- logger.debug(`reasoning content: ${reasoningContent}`);
519
- }
520
- if (groundingContent.length > 0) {
521
- logger.debug(`grounding content: ${groundingContent}`);
522
- if (this._pluginConfig.groundingContentDisplay) {
523
- const groundingMessage = new import_messages2.AIMessageChunk(
524
- `
525
- ${groundingContent}`
526
- );
527
- const generationChunk = new import_outputs.ChatGenerationChunk({
528
- message: groundingMessage,
529
- text: "\n" + groundingContent
530
- });
531
- yield generationChunk;
532
- }
533
- }
448
+ yield* this._processResponseStream(response);
534
449
  } catch (e) {
535
450
  if (e instanceof import_error.ChatLunaError) {
536
451
  throw e;
@@ -539,42 +454,41 @@ ${groundingContent}`
539
454
  }
540
455
  }
541
456
  }
542
- async embeddings(params) {
543
- let data;
544
- if (typeof params.input === "string") {
545
- params.input = [params.input];
546
- }
457
+ async completionInternal(params) {
458
+ const modelConfig = prepareModelConfig(params, this._pluginConfig);
547
459
  try {
548
460
  const response = await this._post(
549
- `models/${params.model}:batchEmbedContents`,
461
+ `models/${modelConfig.model}:generateContent?alt=sse`,
462
+ await createChatGenerationParams(
463
+ params,
464
+ modelConfig,
465
+ this._pluginConfig
466
+ ),
550
467
  {
551
- requests: params.input.map((input) => {
552
- return {
553
- model: `models/${params.model}`,
554
- content: {
555
- parts: [
556
- {
557
- text: input
558
- }
559
- ]
560
- }
561
- };
562
- })
468
+ signal: params.signal
563
469
  }
564
470
  );
565
- data = await response.text();
566
- data = JSON.parse(data);
567
- if (data.embeddings && data.embeddings.length > 0) {
568
- return data.embeddings.map((embedding) => {
569
- return embedding.values;
570
- });
471
+ await (0, import_sse.checkResponse)(response);
472
+ return await this._processResponse(response);
473
+ } catch (e) {
474
+ if (e instanceof import_error.ChatLunaError) {
475
+ throw e;
476
+ } else {
477
+ throw new import_error.ChatLunaError(import_error.ChatLunaErrorCode.API_REQUEST_FAILED, e);
571
478
  }
572
- throw new Error(
573
- "error when calling gemini embeddings, Result: " + JSON.stringify(data)
479
+ }
480
+ }
481
+ async embeddings(params) {
482
+ const input = this._prepareEmbeddingsInput(params.input);
483
+ try {
484
+ const response = await this._post(
485
+ `models/${params.model}:batchEmbedContents`,
486
+ this._createEmbeddingsRequest(params.model, input)
574
487
  );
488
+ return await this._processEmbeddingsResponse(response);
575
489
  } catch (e) {
576
490
  const error = new Error(
577
- "error when calling gemini embeddings, Result: " + JSON.stringify(data)
491
+ "error when calling gemini embeddings, Error: " + e.message
578
492
  );
579
493
  error.stack = e.stack;
580
494
  error.cause = e.cause;
@@ -582,34 +496,333 @@ ${groundingContent}`
582
496
  throw new import_error.ChatLunaError(import_error.ChatLunaErrorCode.API_REQUEST_FAILED, error);
583
497
  }
584
498
  }
499
+ _prepareEmbeddingsInput(input) {
500
+ return typeof input === "string" ? [input] : input;
501
+ }
502
+ _createEmbeddingsRequest(model, input) {
503
+ return {
504
+ requests: input.map((text) => ({
505
+ model: `models/${model}`,
506
+ content: {
507
+ parts: [{ text }]
508
+ }
509
+ }))
510
+ };
511
+ }
512
+ async _processEmbeddingsResponse(response) {
513
+ const data = JSON.parse(
514
+ await response.text()
515
+ );
516
+ if (data.embeddings?.length > 0) {
517
+ return data.embeddings.map((embedding) => embedding.values);
518
+ }
519
+ throw new Error(
520
+ "error when calling gemini embeddings, Result: " + JSON.stringify(data)
521
+ );
522
+ }
585
523
  async getModels() {
586
- let data;
587
524
  try {
588
525
  const response = await this._get("models");
589
- data = await response.text();
590
- data = JSON.parse(data);
591
- if (!data.models || !data.models.length) {
592
- throw new Error(
593
- "error when listing gemini models, Result:" + JSON.stringify(data)
594
- );
595
- }
596
- return data.models.filter(
597
- (model) => model.name.includes("gemini") || model.name.includes("gemma") || model.name.includes("embedding")
598
- ).map((model) => {
599
- return {
600
- ...model,
601
- name: model.name.replace("models/", "")
602
- };
603
- });
526
+ const data = await this._parseModelsResponse(response);
527
+ return this._filterAndTransformModels(data.models);
604
528
  } catch (e) {
605
529
  const error = new Error(
606
- "error when listing gemini models, Result: " + JSON.stringify(data)
530
+ "error when listing gemini models, Error: " + e.message
607
531
  );
608
532
  error.stack = e.stack;
609
533
  error.cause = e.cause;
610
534
  throw error;
611
535
  }
612
536
  }
537
+ async _parseModelsResponse(response) {
538
+ const text = await response.text();
539
+ const data = JSON.parse(text);
540
+ if (!data.models?.length) {
541
+ throw new Error(
542
+ "error when listing gemini models, Result:" + JSON.stringify(data)
543
+ );
544
+ }
545
+ return data;
546
+ }
547
+ _filterAndTransformModels(models) {
548
+ return models.filter(
549
+ (model) => ["gemini", "gemma", "embedding"].some(
550
+ (keyword) => model.name.includes(keyword)
551
+ )
552
+ ).map((model) => ({
553
+ ...model,
554
+ name: model.name.replace("models/", "")
555
+ }));
556
+ }
557
+ async _processResponse(response) {
558
+ const { groundingContent, currentGroundingIndex } = this._createStreamContext();
559
+ const responseText = await response.text();
560
+ let parsedResponse;
561
+ try {
562
+ parsedResponse = JSON.parse(responseText);
563
+ if (!parsedResponse.candidates) {
564
+ throw new import_error.ChatLunaError(
565
+ import_error.ChatLunaErrorCode.API_REQUEST_FAILED,
566
+ new Error(
567
+ "error when calling gemini, Result: " + responseText
568
+ )
569
+ );
570
+ }
571
+ } catch (e) {
572
+ if (e instanceof import_error.ChatLunaError) {
573
+ throw e;
574
+ } else {
575
+ throw new import_error.ChatLunaError(
576
+ import_error.ChatLunaErrorCode.API_REQUEST_FAILED,
577
+ new Error(
578
+ "error when calling gemini, Result: " + responseText
579
+ )
580
+ );
581
+ }
582
+ }
583
+ const iterable = this._setupStreamTransform(
584
+ parsedResponse,
585
+ groundingContent,
586
+ currentGroundingIndex
587
+ );
588
+ let result;
589
+ let reasoningContent = "";
590
+ for await (const chunk of this._processChunks(iterable)) {
591
+ if (chunk.type === "reasoning") {
592
+ reasoningContent = chunk.content;
593
+ } else {
594
+ result = result != null ? result.concat(chunk.generation) : chunk.generation;
595
+ }
596
+ }
597
+ const finalChunk = this._handleFinalContent(
598
+ reasoningContent,
599
+ groundingContent.value
600
+ );
601
+ if (finalChunk != null) {
602
+ result = result.concat(finalChunk);
603
+ }
604
+ return result;
605
+ }
606
+ async *_processResponseStream(response) {
607
+ const { groundingContent, currentGroundingIndex } = this._createStreamContext();
608
+ const iterable = this._setupStreamTransform(
609
+ response,
610
+ groundingContent,
611
+ currentGroundingIndex
612
+ );
613
+ let reasoningContent = "";
614
+ for await (const chunk of this._processChunks(iterable)) {
615
+ if (chunk.type === "reasoning") {
616
+ reasoningContent = chunk.content;
617
+ } else {
618
+ yield chunk.generation;
619
+ }
620
+ }
621
+ const finalContent = this._handleFinalContent(reasoningContent, groundingContent.value);
622
+ if (finalContent != null) {
623
+ yield finalContent;
624
+ }
625
+ }
626
+ _createStreamContext() {
627
+ return {
628
+ groundingContent: { value: "" },
629
+ currentGroundingIndex: { value: 0 }
630
+ };
631
+ }
632
+ _setupStreamTransform(response, groundingContent, currentGroundingIndex) {
633
+ const transformToChatPartStream = this._createTransformStream(
634
+ groundingContent,
635
+ currentGroundingIndex
636
+ );
637
+ const readableStream = new ReadableStream({
638
+ async start(controller) {
639
+ if (isChatResponse(response)) {
640
+ controller.enqueue(response);
641
+ controller.close();
642
+ return;
643
+ }
644
+ for await (const chunk of (0, import_sse.sseIterable)(response)) {
645
+ controller.enqueue(chunk.data);
646
+ }
647
+ controller.close();
648
+ }
649
+ });
650
+ return (0, import_stream.readableStreamToAsyncIterable)(
651
+ readableStream.pipeThrough(transformToChatPartStream)
652
+ );
653
+ }
654
+ _createTransformStream(groundingContent, currentGroundingIndex) {
655
+ const that = this;
656
+ return new TransformStream({
657
+ async transform(chunk, controller) {
658
+ if (chunk === "undefined") {
659
+ return;
660
+ }
661
+ const transformValue = typeof chunk === "string" ? JSON.parse(chunk) : chunk;
662
+ if (!transformValue?.candidates) {
663
+ return;
664
+ }
665
+ for (const candidate of transformValue.candidates) {
666
+ that._processCandidateChunk(
667
+ candidate,
668
+ controller,
669
+ JSON.stringify(transformValue),
670
+ groundingContent,
671
+ currentGroundingIndex
672
+ );
673
+ }
674
+ }
675
+ });
676
+ }
677
+ _processCandidateChunk(candidate, controller, chunk, groundingContent, currentGroundingIndex) {
678
+ const parts = candidate.content?.parts;
679
+ if ((parts == null || parts.length < 1) && candidate.finishReason !== "STOP" && candidate.content === null) {
680
+ throw new Error(chunk);
681
+ } else if (candidate.finishReason === "STOP" && parts == null) {
682
+ return;
683
+ }
684
+ if (parts == null) {
685
+ return;
686
+ }
687
+ for (const part of parts) {
688
+ controller.enqueue(part);
689
+ }
690
+ for (const source of candidate.groundingMetadata?.groundingChunks ?? []) {
691
+ groundingContent.value += `[^${currentGroundingIndex.value++}]: [${source.web.title}](${source.web.uri})
692
+ `;
693
+ }
694
+ }
695
+ async *_processChunks(iterable) {
696
+ let reasoningContent = "";
697
+ let errorCount = 0;
698
+ const functionCall = {
699
+ name: "",
700
+ args: "",
701
+ arguments: ""
702
+ };
703
+ for await (const chunk of iterable) {
704
+ try {
705
+ const { updatedContent, updatedReasoning } = this._processChunk(
706
+ chunk,
707
+ reasoningContent,
708
+ functionCall
709
+ );
710
+ if (updatedReasoning !== reasoningContent) {
711
+ reasoningContent = updatedReasoning;
712
+ yield { type: "reasoning", content: reasoningContent };
713
+ continue;
714
+ }
715
+ if (updatedContent || functionCall.name) {
716
+ const messageChunk = this._createMessageChunk(
717
+ updatedContent,
718
+ functionCall,
719
+ partAsTypeCheck(
720
+ chunk,
721
+ (part) => part["inlineData"] != null
722
+ )
723
+ );
724
+ const generationChunk = new import_outputs.ChatGenerationChunk({
725
+ message: messageChunk,
726
+ text: (0, import_string.getMessageContent)(messageChunk.content) ?? ""
727
+ });
728
+ yield { type: "generation", generation: generationChunk };
729
+ }
730
+ } catch (e) {
731
+ if (errorCount > 5) {
732
+ logger.error("error with chunk", chunk);
733
+ throw new import_error.ChatLunaError(
734
+ import_error.ChatLunaErrorCode.API_REQUEST_FAILED,
735
+ e
736
+ );
737
+ } else {
738
+ errorCount++;
739
+ continue;
740
+ }
741
+ }
742
+ }
743
+ }
744
+ _processChunk(chunk, reasoningContent, functionCall) {
745
+ const messagePart = partAsType(chunk);
746
+ const chatFunctionCallingPart = partAsType(chunk);
747
+ const imagePart = partAsTypeCheck(
748
+ chunk,
749
+ (part) => part["inlineData"] != null
750
+ );
751
+ let messageContent;
752
+ if (messagePart.text) {
753
+ if (messagePart.thought) {
754
+ return {
755
+ updatedContent: messageContent,
756
+ updatedReasoning: reasoningContent + messagePart.text
757
+ };
758
+ }
759
+ messageContent = messagePart.text;
760
+ } else if (imagePart) {
761
+ messagePart.text = `![image](data:${imagePart.inlineData.mimeType ?? "image/png"};base64,${imagePart.inlineData.data})`;
762
+ messageContent = messagePart.text;
763
+ }
764
+ const deltaFunctionCall = chatFunctionCallingPart?.functionCall;
765
+ if (deltaFunctionCall) {
766
+ this._updateFunctionCall(functionCall, deltaFunctionCall);
767
+ }
768
+ return {
769
+ updatedContent: messageContent,
770
+ updatedReasoning: reasoningContent
771
+ };
772
+ }
773
+ _updateFunctionCall(functionCall, deltaFunctionCall) {
774
+ let args = deltaFunctionCall.args;
775
+ try {
776
+ let parsedArgs = JSON.parse(args);
777
+ if (typeof parsedArgs !== "string") {
778
+ args = parsedArgs;
779
+ }
780
+ parsedArgs = JSON.parse(args);
781
+ if (typeof parsedArgs !== "string") {
782
+ args = parsedArgs;
783
+ }
784
+ } catch (e) {
785
+ }
786
+ functionCall.args = JSON.stringify(args);
787
+ functionCall.name = deltaFunctionCall.name;
788
+ functionCall.arguments = deltaFunctionCall.args;
789
+ }
790
+ _handleFinalContent(reasoningContent, groundingContent) {
791
+ if (reasoningContent.length > 0) {
792
+ logger.debug(`reasoning content: ${reasoningContent}`);
793
+ }
794
+ if (groundingContent.length > 0) {
795
+ logger.debug(`grounding content: ${groundingContent}`);
796
+ if (this._pluginConfig.groundingContentDisplay) {
797
+ const groundingMessage = new import_messages2.AIMessageChunk(
798
+ `
799
+ ${groundingContent}`
800
+ );
801
+ const generationChunk = new import_outputs.ChatGenerationChunk({
802
+ message: groundingMessage,
803
+ text: "\n" + groundingContent
804
+ });
805
+ return generationChunk;
806
+ }
807
+ }
808
+ }
809
+ _createMessageChunk(content, functionCall, imagePart) {
810
+ const messageChunk = new import_messages2.AIMessageChunk({
811
+ content
812
+ });
813
+ messageChunk.additional_kwargs = {
814
+ function_call: functionCall.name.length > 0 ? {
815
+ name: functionCall.name,
816
+ arguments: functionCall.args,
817
+ args: functionCall.arguments
818
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
819
+ } : void 0,
820
+ images: imagePart ? [
821
+ `data:${imagePart.inlineData.mimeType ?? "image/png"};base64,${imagePart.inlineData.data}`
822
+ ] : void 0
823
+ };
824
+ return messageChunk;
825
+ }
613
826
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
614
827
  _post(url, data, params = {}) {
615
828
  const requestUrl = this._concatUrl(url);
@@ -789,6 +1002,7 @@ var Config4 = import_koishi.Schema.intersect([
789
1002
  urlContext: import_koishi.Schema.boolean().default(false),
790
1003
  thinkingBudget: import_koishi.Schema.number().min(-1).max(24576).default(-1),
791
1004
  includeThoughts: import_koishi.Schema.boolean().default(false),
1005
+ nonStreaming: import_koishi.Schema.boolean().default(false),
792
1006
  imageGeneration: import_koishi.Schema.boolean().default(false),
793
1007
  groundingContentDisplay: import_koishi.Schema.boolean().default(false),
794
1008
  searchThreshold: import_koishi.Schema.number().min(0).max(1).step(0.1).default(0.5)