koishi-plugin-chatluna-google-gemini-adapter 1.2.19 → 1.2.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/lib/index.cjs +464 -248
- package/lib/index.d.ts +1 -0
- package/lib/index.mjs +468 -249
- package/lib/requester.d.ts +19 -1
- package/lib/types.d.ts +8 -0
- package/lib/utils.d.ts +44 -1
- package/package.json +3 -3
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,214 +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
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
|
|
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" && candidate.content === null) {
|
|
425
|
-
throw new Error(chunk);
|
|
426
|
-
} else if (candidate.finishReason === "STOP" && parts == null) {
|
|
427
|
-
continue;
|
|
428
|
-
}
|
|
429
|
-
if (parts == null) {
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
for (const part of parts) {
|
|
433
|
-
controller.enqueue(part);
|
|
434
|
-
}
|
|
435
|
-
for (const source of candidate.groundingMetadata?.groundingChunks ?? []) {
|
|
436
|
-
groundingContent += `[^${currentGroundingIndex++}]: [${source.web.title}](${source.web.uri})
|
|
437
|
-
`;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
});
|
|
442
|
-
const iterable = (0, import_stream.readableStreamToAsyncIterable)(
|
|
443
|
-
readableStream.pipeThrough(transformToChatPartStream)
|
|
444
|
-
);
|
|
445
|
-
let reasoningContent = "";
|
|
446
|
-
let content = "";
|
|
447
|
-
const functionCall = {
|
|
448
|
-
name: "",
|
|
449
|
-
args: "",
|
|
450
|
-
arguments: ""
|
|
451
|
-
};
|
|
452
|
-
for await (const chunk of iterable) {
|
|
453
|
-
const messagePart = partAsType(chunk);
|
|
454
|
-
const chatFunctionCallingPart = partAsType(chunk);
|
|
455
|
-
const imagePart = partAsTypeCheck(
|
|
456
|
-
chunk,
|
|
457
|
-
(part) => part["inlineData"] != null
|
|
458
|
-
);
|
|
459
|
-
if (messagePart.text) {
|
|
460
|
-
if (messagePart.thought) {
|
|
461
|
-
reasoningContent += messagePart.text;
|
|
462
|
-
continue;
|
|
463
|
-
}
|
|
464
|
-
content = messagePart.text;
|
|
465
|
-
} else if (imagePart) {
|
|
466
|
-
messagePart.text = ``;
|
|
467
|
-
content = messagePart.text;
|
|
468
|
-
}
|
|
469
|
-
const deltaFunctionCall = chatFunctionCallingPart?.functionCall;
|
|
470
|
-
if (deltaFunctionCall) {
|
|
471
|
-
let args = deltaFunctionCall.args;
|
|
472
|
-
try {
|
|
473
|
-
let parsedArgs = JSON.parse(args);
|
|
474
|
-
if (typeof parsedArgs !== "string") {
|
|
475
|
-
args = parsedArgs;
|
|
476
|
-
}
|
|
477
|
-
parsedArgs = JSON.parse(args);
|
|
478
|
-
if (typeof parsedArgs !== "string") {
|
|
479
|
-
args = parsedArgs;
|
|
480
|
-
}
|
|
481
|
-
} catch (e) {
|
|
482
|
-
}
|
|
483
|
-
functionCall.args = JSON.stringify(args);
|
|
484
|
-
functionCall.name = deltaFunctionCall.name;
|
|
485
|
-
functionCall.arguments = deltaFunctionCall.args;
|
|
486
|
-
}
|
|
487
|
-
try {
|
|
488
|
-
const messageChunk = new import_messages2.AIMessageChunk(content);
|
|
489
|
-
messageChunk.additional_kwargs = {
|
|
490
|
-
function_call: functionCall.name.length > 0 ? {
|
|
491
|
-
name: functionCall.name,
|
|
492
|
-
arguments: functionCall.args,
|
|
493
|
-
args: functionCall.arguments
|
|
494
|
-
} : void 0,
|
|
495
|
-
images: imagePart ? [
|
|
496
|
-
`data:${imagePart.inlineData.mimeType ?? "image/png"};base64,${imagePart.inlineData.data}`
|
|
497
|
-
] : void 0
|
|
498
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
499
|
-
};
|
|
500
|
-
messageChunk.content = content;
|
|
501
|
-
const generationChunk = new import_outputs.ChatGenerationChunk({
|
|
502
|
-
message: messageChunk,
|
|
503
|
-
text: messageChunk.content
|
|
504
|
-
});
|
|
505
|
-
yield generationChunk;
|
|
506
|
-
content = messageChunk.content;
|
|
507
|
-
} catch (e) {
|
|
508
|
-
if (errorCount > 5) {
|
|
509
|
-
logger.error("error with chunk", chunk);
|
|
510
|
-
throw new import_error.ChatLunaError(
|
|
511
|
-
import_error.ChatLunaErrorCode.API_REQUEST_FAILED,
|
|
512
|
-
e
|
|
513
|
-
);
|
|
514
|
-
} else {
|
|
515
|
-
errorCount++;
|
|
516
|
-
continue;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
if (reasoningContent.length > 0) {
|
|
521
|
-
logger.debug(`reasoning content: ${reasoningContent}`);
|
|
522
|
-
}
|
|
523
|
-
if (groundingContent.length > 0) {
|
|
524
|
-
logger.debug(`grounding content: ${groundingContent}`);
|
|
525
|
-
if (this._pluginConfig.groundingContentDisplay) {
|
|
526
|
-
const groundingMessage = new import_messages2.AIMessageChunk(
|
|
527
|
-
`
|
|
528
|
-
${groundingContent}`
|
|
529
|
-
);
|
|
530
|
-
const generationChunk = new import_outputs.ChatGenerationChunk({
|
|
531
|
-
message: groundingMessage,
|
|
532
|
-
text: "\n" + groundingContent
|
|
533
|
-
});
|
|
534
|
-
yield generationChunk;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
448
|
+
yield* this._processResponseStream(response);
|
|
537
449
|
} catch (e) {
|
|
538
450
|
if (e instanceof import_error.ChatLunaError) {
|
|
539
451
|
throw e;
|
|
@@ -542,42 +454,41 @@ ${groundingContent}`
|
|
|
542
454
|
}
|
|
543
455
|
}
|
|
544
456
|
}
|
|
545
|
-
async
|
|
546
|
-
|
|
547
|
-
if (typeof params.input === "string") {
|
|
548
|
-
params.input = [params.input];
|
|
549
|
-
}
|
|
457
|
+
async completionInternal(params) {
|
|
458
|
+
const modelConfig = prepareModelConfig(params, this._pluginConfig);
|
|
550
459
|
try {
|
|
551
460
|
const response = await this._post(
|
|
552
|
-
`models/${
|
|
461
|
+
`models/${modelConfig.model}:generateContent?alt=sse`,
|
|
462
|
+
await createChatGenerationParams(
|
|
463
|
+
params,
|
|
464
|
+
modelConfig,
|
|
465
|
+
this._pluginConfig
|
|
466
|
+
),
|
|
553
467
|
{
|
|
554
|
-
|
|
555
|
-
return {
|
|
556
|
-
model: `models/${params.model}`,
|
|
557
|
-
content: {
|
|
558
|
-
parts: [
|
|
559
|
-
{
|
|
560
|
-
text: input
|
|
561
|
-
}
|
|
562
|
-
]
|
|
563
|
-
}
|
|
564
|
-
};
|
|
565
|
-
})
|
|
468
|
+
signal: params.signal
|
|
566
469
|
}
|
|
567
470
|
);
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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);
|
|
574
478
|
}
|
|
575
|
-
|
|
576
|
-
|
|
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)
|
|
577
487
|
);
|
|
488
|
+
return await this._processEmbeddingsResponse(response);
|
|
578
489
|
} catch (e) {
|
|
579
490
|
const error = new Error(
|
|
580
|
-
"error when calling gemini embeddings,
|
|
491
|
+
"error when calling gemini embeddings, Error: " + e.message
|
|
581
492
|
);
|
|
582
493
|
error.stack = e.stack;
|
|
583
494
|
error.cause = e.cause;
|
|
@@ -585,34 +496,336 @@ ${groundingContent}`
|
|
|
585
496
|
throw new import_error.ChatLunaError(import_error.ChatLunaErrorCode.API_REQUEST_FAILED, error);
|
|
586
497
|
}
|
|
587
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
|
+
}
|
|
588
523
|
async getModels() {
|
|
589
|
-
let data;
|
|
590
524
|
try {
|
|
591
525
|
const response = await this._get("models");
|
|
592
|
-
data = await
|
|
593
|
-
|
|
594
|
-
if (!data.models || !data.models.length) {
|
|
595
|
-
throw new Error(
|
|
596
|
-
"error when listing gemini models, Result:" + JSON.stringify(data)
|
|
597
|
-
);
|
|
598
|
-
}
|
|
599
|
-
return data.models.filter(
|
|
600
|
-
(model) => model.name.includes("gemini") || model.name.includes("gemma") || model.name.includes("embedding")
|
|
601
|
-
).map((model) => {
|
|
602
|
-
return {
|
|
603
|
-
...model,
|
|
604
|
-
name: model.name.replace("models/", "")
|
|
605
|
-
};
|
|
606
|
-
});
|
|
526
|
+
const data = await this._parseModelsResponse(response);
|
|
527
|
+
return this._filterAndTransformModels(data.models);
|
|
607
528
|
} catch (e) {
|
|
608
529
|
const error = new Error(
|
|
609
|
-
"error when listing gemini models,
|
|
530
|
+
"error when listing gemini models, Error: " + e.message
|
|
610
531
|
);
|
|
611
532
|
error.stack = e.stack;
|
|
612
533
|
error.cause = e.cause;
|
|
613
534
|
throw error;
|
|
614
535
|
}
|
|
615
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(
|
|
622
|
+
reasoningContent,
|
|
623
|
+
groundingContent.value
|
|
624
|
+
);
|
|
625
|
+
if (finalContent != null) {
|
|
626
|
+
yield finalContent;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
_createStreamContext() {
|
|
630
|
+
return {
|
|
631
|
+
groundingContent: { value: "" },
|
|
632
|
+
currentGroundingIndex: { value: 0 }
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
_setupStreamTransform(response, groundingContent, currentGroundingIndex) {
|
|
636
|
+
const transformToChatPartStream = this._createTransformStream(
|
|
637
|
+
groundingContent,
|
|
638
|
+
currentGroundingIndex
|
|
639
|
+
);
|
|
640
|
+
const readableStream = new ReadableStream({
|
|
641
|
+
async start(controller) {
|
|
642
|
+
if (isChatResponse(response)) {
|
|
643
|
+
controller.enqueue(response);
|
|
644
|
+
controller.close();
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
for await (const chunk of (0, import_sse.sseIterable)(response)) {
|
|
648
|
+
controller.enqueue(chunk.data);
|
|
649
|
+
}
|
|
650
|
+
controller.close();
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
return (0, import_stream.readableStreamToAsyncIterable)(
|
|
654
|
+
readableStream.pipeThrough(transformToChatPartStream)
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
_createTransformStream(groundingContent, currentGroundingIndex) {
|
|
658
|
+
const that = this;
|
|
659
|
+
return new TransformStream({
|
|
660
|
+
async transform(chunk, controller) {
|
|
661
|
+
if (chunk === "undefined") {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const transformValue = typeof chunk === "string" ? JSON.parse(chunk) : chunk;
|
|
665
|
+
if (!transformValue?.candidates) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
for (const candidate of transformValue.candidates) {
|
|
669
|
+
that._processCandidateChunk(
|
|
670
|
+
candidate,
|
|
671
|
+
controller,
|
|
672
|
+
JSON.stringify(transformValue),
|
|
673
|
+
groundingContent,
|
|
674
|
+
currentGroundingIndex
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
_processCandidateChunk(candidate, controller, chunk, groundingContent, currentGroundingIndex) {
|
|
681
|
+
const parts = candidate.content?.parts;
|
|
682
|
+
if ((parts == null || parts.length < 1) && candidate.finishReason !== "STOP" && candidate.content === null) {
|
|
683
|
+
throw new Error(chunk);
|
|
684
|
+
} else if (candidate.finishReason === "STOP" && parts == null) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (parts == null) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
for (const part of parts) {
|
|
691
|
+
controller.enqueue(part);
|
|
692
|
+
}
|
|
693
|
+
for (const source of candidate.groundingMetadata?.groundingChunks ?? []) {
|
|
694
|
+
groundingContent.value += `[^${currentGroundingIndex.value++}]: [${source.web.title}](${source.web.uri})
|
|
695
|
+
`;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
async *_processChunks(iterable) {
|
|
699
|
+
let reasoningContent = "";
|
|
700
|
+
let errorCount = 0;
|
|
701
|
+
const functionCall = {
|
|
702
|
+
name: "",
|
|
703
|
+
args: "",
|
|
704
|
+
arguments: ""
|
|
705
|
+
};
|
|
706
|
+
for await (const chunk of iterable) {
|
|
707
|
+
try {
|
|
708
|
+
const { updatedContent, updatedReasoning } = this._processChunk(
|
|
709
|
+
chunk,
|
|
710
|
+
reasoningContent,
|
|
711
|
+
functionCall
|
|
712
|
+
);
|
|
713
|
+
if (updatedReasoning !== reasoningContent) {
|
|
714
|
+
reasoningContent = updatedReasoning;
|
|
715
|
+
yield { type: "reasoning", content: reasoningContent };
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
if (updatedContent || functionCall.name) {
|
|
719
|
+
const messageChunk = this._createMessageChunk(
|
|
720
|
+
updatedContent,
|
|
721
|
+
functionCall,
|
|
722
|
+
partAsTypeCheck(
|
|
723
|
+
chunk,
|
|
724
|
+
(part) => part["inlineData"] != null
|
|
725
|
+
)
|
|
726
|
+
);
|
|
727
|
+
const generationChunk = new import_outputs.ChatGenerationChunk({
|
|
728
|
+
message: messageChunk,
|
|
729
|
+
text: (0, import_string.getMessageContent)(messageChunk.content) ?? ""
|
|
730
|
+
});
|
|
731
|
+
yield { type: "generation", generation: generationChunk };
|
|
732
|
+
}
|
|
733
|
+
} catch (e) {
|
|
734
|
+
if (errorCount > 5) {
|
|
735
|
+
logger.error("error with chunk", chunk);
|
|
736
|
+
throw new import_error.ChatLunaError(
|
|
737
|
+
import_error.ChatLunaErrorCode.API_REQUEST_FAILED,
|
|
738
|
+
e
|
|
739
|
+
);
|
|
740
|
+
} else {
|
|
741
|
+
errorCount++;
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
_processChunk(chunk, reasoningContent, functionCall) {
|
|
748
|
+
const messagePart = partAsType(chunk);
|
|
749
|
+
const chatFunctionCallingPart = partAsType(chunk);
|
|
750
|
+
const imagePart = partAsTypeCheck(
|
|
751
|
+
chunk,
|
|
752
|
+
(part) => part["inlineData"] != null
|
|
753
|
+
);
|
|
754
|
+
let messageContent;
|
|
755
|
+
if (messagePart.text) {
|
|
756
|
+
if (messagePart.thought) {
|
|
757
|
+
return {
|
|
758
|
+
updatedContent: messageContent,
|
|
759
|
+
updatedReasoning: reasoningContent + messagePart.text
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
messageContent = messagePart.text;
|
|
763
|
+
} else if (imagePart) {
|
|
764
|
+
messagePart.text = ``;
|
|
765
|
+
messageContent = messagePart.text;
|
|
766
|
+
}
|
|
767
|
+
const deltaFunctionCall = chatFunctionCallingPart?.functionCall;
|
|
768
|
+
if (deltaFunctionCall) {
|
|
769
|
+
this._updateFunctionCall(functionCall, deltaFunctionCall);
|
|
770
|
+
}
|
|
771
|
+
return {
|
|
772
|
+
updatedContent: messageContent,
|
|
773
|
+
updatedReasoning: reasoningContent
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
_updateFunctionCall(functionCall, deltaFunctionCall) {
|
|
777
|
+
let args = deltaFunctionCall.args;
|
|
778
|
+
try {
|
|
779
|
+
let parsedArgs = JSON.parse(args);
|
|
780
|
+
if (typeof parsedArgs !== "string") {
|
|
781
|
+
args = parsedArgs;
|
|
782
|
+
}
|
|
783
|
+
parsedArgs = JSON.parse(args);
|
|
784
|
+
if (typeof parsedArgs !== "string") {
|
|
785
|
+
args = parsedArgs;
|
|
786
|
+
}
|
|
787
|
+
} catch (e) {
|
|
788
|
+
}
|
|
789
|
+
functionCall.args = JSON.stringify(args);
|
|
790
|
+
functionCall.name = deltaFunctionCall.name;
|
|
791
|
+
functionCall.arguments = deltaFunctionCall.args;
|
|
792
|
+
}
|
|
793
|
+
_handleFinalContent(reasoningContent, groundingContent) {
|
|
794
|
+
if (reasoningContent.length > 0) {
|
|
795
|
+
logger.debug(`reasoning content: ${reasoningContent}`);
|
|
796
|
+
}
|
|
797
|
+
if (groundingContent.length > 0) {
|
|
798
|
+
logger.debug(`grounding content: ${groundingContent}`);
|
|
799
|
+
if (this._pluginConfig.groundingContentDisplay) {
|
|
800
|
+
const groundingMessage = new import_messages2.AIMessageChunk(
|
|
801
|
+
`
|
|
802
|
+
${groundingContent}`
|
|
803
|
+
);
|
|
804
|
+
const generationChunk = new import_outputs.ChatGenerationChunk({
|
|
805
|
+
message: groundingMessage,
|
|
806
|
+
text: "\n" + groundingContent
|
|
807
|
+
});
|
|
808
|
+
return generationChunk;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
_createMessageChunk(content, functionCall, imagePart) {
|
|
813
|
+
const messageChunk = new import_messages2.AIMessageChunk({
|
|
814
|
+
content
|
|
815
|
+
});
|
|
816
|
+
messageChunk.additional_kwargs = {
|
|
817
|
+
function_call: functionCall.name.length > 0 ? {
|
|
818
|
+
name: functionCall.name,
|
|
819
|
+
arguments: functionCall.args,
|
|
820
|
+
args: functionCall.arguments
|
|
821
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
822
|
+
} : void 0,
|
|
823
|
+
images: imagePart ? [
|
|
824
|
+
`data:${imagePart.inlineData.mimeType ?? "image/png"};base64,${imagePart.inlineData.data}`
|
|
825
|
+
] : void 0
|
|
826
|
+
};
|
|
827
|
+
return messageChunk;
|
|
828
|
+
}
|
|
616
829
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
617
830
|
_post(url, data, params = {}) {
|
|
618
831
|
const requestUrl = this._concatUrl(url);
|
|
@@ -696,8 +909,10 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
|
|
|
696
909
|
name: model.name,
|
|
697
910
|
maxTokens: model.inputTokenLimit,
|
|
698
911
|
type: model.name.includes("embedding") ? import_types.ModelType.embeddings : import_types.ModelType.llm,
|
|
699
|
-
|
|
700
|
-
|
|
912
|
+
capabilities: [
|
|
913
|
+
import_types.ModelCapabilities.ImageInput,
|
|
914
|
+
import_types.ModelCapabilities.ToolCall
|
|
915
|
+
]
|
|
701
916
|
};
|
|
702
917
|
if (model.name.includes("gemini-2.5") && !model.name.includes("pro") && !model.name.includes("image")) {
|
|
703
918
|
if (!model.name.includes("-thinking")) {
|
|
@@ -792,6 +1007,7 @@ var Config4 = import_koishi.Schema.intersect([
|
|
|
792
1007
|
urlContext: import_koishi.Schema.boolean().default(false),
|
|
793
1008
|
thinkingBudget: import_koishi.Schema.number().min(-1).max(24576).default(-1),
|
|
794
1009
|
includeThoughts: import_koishi.Schema.boolean().default(false),
|
|
1010
|
+
nonStreaming: import_koishi.Schema.boolean().default(false),
|
|
795
1011
|
imageGeneration: import_koishi.Schema.boolean().default(false),
|
|
796
1012
|
groundingContentDisplay: import_koishi.Schema.boolean().default(false),
|
|
797
1013
|
searchThreshold: import_koishi.Schema.number().min(0).max(1).step(0.1).default(0.5)
|