Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/adapter-gemini/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export interface Config extends ChatLunaPlugin.Config {
includeThoughts: boolean
groundingContentDisplay: boolean
useCamelCaseSystemInstruction: boolean
useCamelCaseInlineData: boolean
useCamelCaseMimeType: boolean
nonStreaming: boolean
}

Expand Down Expand Up @@ -86,6 +88,8 @@ export const Config: Schema<Config> = Schema.intersect([
groundingContentDisplay: Schema.boolean().default(false),
imageGeneration: Schema.boolean().default(false),
useCamelCaseSystemInstruction: Schema.boolean().default(false),
useCamelCaseInlineData: Schema.boolean().default(false),
useCamelCaseMimeType: Schema.boolean().default(false),
nonStreaming: Schema.boolean().default(false)
})
]).i18n({
Expand Down
2 changes: 2 additions & 0 deletions packages/adapter-gemini/src/locales/en-US.schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ $inner:
codeExecution: 'Enable code execution tool'
urlContext: 'Enable URL context retrieval tool'
useCamelCaseSystemInstruction: 'Use camelCase systemInstruction instead of snake_case system_instruction'
useCamelCaseInlineData: 'Use camelCase inlineData instead of snake_case inline_data'
useCamelCaseMimeType: 'Use camelCase mimeType instead of snake_case mime_type'
nonStreaming: 'Force disable streaming response. When enabled, requests will always be made in non-streaming mode, even if the stream parameter is configured.'
2 changes: 2 additions & 0 deletions packages/adapter-gemini/src/locales/zh-CN.schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ $inner:
codeExecution: '为模型启用代码执行工具。(开启后将无法使用工具调用)'
urlContext: '为模型启用 URL 内容获取工具。(开启后将无法使用工具调用)'
useCamelCaseSystemInstruction: 使用大写的 systemInstruction 而不是小写的 system_instruction
useCamelCaseInlineData: 使用大写的 inlineData 而不是小写的 inline_data
useCamelCaseMimeType: 使用大写的 mimeType 而不是小写的 mime_type
Comment on lines +25 to +26
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这里的翻译使用了“大写”,但实际上 inlineDatamimeType 是驼峰命名(camelCase)而非全大写(UPPERCASE)。建议将其修改为“驼峰式”,以保证描述的准确性。

      useCamelCaseInlineData: 使用驼峰式的 inlineData 而不是小写的 inline_data
      useCamelCaseMimeType: 使用驼峰式的 mimeType 而不是小写的 mime_type

nonStreaming: 强制不启用流式返回。开启后,将总是以非流式发起请求,即便配置了 stream 参数。
62 changes: 53 additions & 9 deletions packages/adapter-gemini/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,7 @@ async function processGeminiImageContent(
const mineType = url.match(/^data:([^;]+);base64,/)?.[1] ?? 'image/jpeg'
const data = url.replace(/^data:image\/\w+;base64,/, '')

return {
inline_data: { data, mime_type: mineType }
}
return createGeminiInlineDataPart(plugin, data, mineType)
}

type GeminiFileLikeContent = MessageContentComplex &
Expand Down Expand Up @@ -254,18 +252,56 @@ function isGeminiFileLikeContent(
)
}

type GeminiInlineDataContent = MessageContentComplex & {
inline_data?: {
data: string
mime_type?: string
mimeType?: string
}
inlineData?: {
data: string
mime_type?: string
mimeType?: string
}
}

function isGeminiInlineDataContent(
part: MessageContentComplex
): part is GeminiInlineDataContent {
return (
part != null &&
typeof part === 'object' &&
('inline_data' in part || 'inlineData' in part)
)
}

function createGeminiInlineDataPart(
plugin: ChatLunaPlugin,
data: string,
mimeType: string
) {
const config = plugin.config as Config
const blob = {
data,
[config.useCamelCaseMimeType ? 'mimeType' : 'mime_type']: mimeType
}

return {
[config.useCamelCaseInlineData ? 'inlineData' : 'inline_data']: blob
}
}

async function processGeminiFileLikeContent(
plugin: ChatLunaPlugin,
part: GeminiFileLikeContent
) {
try {
const { buffer, mimeType } = await fetchFileLikeUrl(plugin, part)
return {
inline_data: {
data: buffer.toString('base64'),
mime_type: mimeType
}
}
return createGeminiInlineDataPart(
plugin,
buffer.toString('base64'),
mimeType
)
} catch (e) {
logger.warn(`Failed to fetch ${part.type}`, e)
return null
Expand All @@ -288,6 +324,14 @@ async function processGeminiContentParts(
if (isGeminiFileLikeContent(part)) {
return await processGeminiFileLikeContent(plugin, part)
}
if (isGeminiInlineDataContent(part)) {
const inline = part.inline_data ?? part.inlineData
return createGeminiInlineDataPart(
plugin,
inline.data,
inline.mime_type ?? inline.mimeType
)
}
Comment on lines +327 to +334
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

isGeminiInlineDataContent(part) 返回 true 时,part.inline_datapart.inlineData 仍有可能为 undefined(例如属性存在但值为 undefined)。此外,如果 inline.data 缺失,直接访问 inline.data 会导致运行时错误,且不符合 createGeminiInlineDataPart 的参数类型要求。建议在调用前进行空值安全检查。

            if (isGeminiInlineDataContent(part)) {
                const inline = part.inline_data ?? part.inlineData
                if (inline?.data != null) {
                    return createGeminiInlineDataPart(
                        plugin,
                        inline.data,
                        inline.mime_type ?? inline.mimeType ?? 'image/jpeg'
                    )
                }
            }

return part as any
})
)
Expand Down
Loading