{"version":3,"file":"ai-model/models/gemini.mjs","sources":["../../../../src/ai-model/models/gemini.ts"],"sourcesContent":["import type { TModelFamily } from '@midscene/shared/env';\nimport type OpenAI from 'openai';\nimport { defaultExtractContentAndReasoning } from '../model-adapter/chat-completion';\nimport type {\n  ChatCompletionCallContext,\n  ChatCompletionContentSource,\n  ChatCompletionParamsResult,\n  ContentAndReasoning,\n  ModelAdapterDefinition,\n} from '../model-adapter/types';\n\ntype GeminiContentPart = Record<string, unknown> & {\n  text?: string;\n  thought?: boolean;\n};\n\ntype GeminiContentExtension = {\n  content: string | null | GeminiContentPart[];\n  reasoning_content?: string;\n  extra_content?: unknown;\n};\n\ntype GeminiContentSource =\n  | ChatCompletionContentSource\n  | (Omit<OpenAI.Chat.Completions.ChatCompletionMessage, 'content'> &\n      GeminiContentExtension)\n  | (Omit<OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta, 'content'> &\n      GeminiContentExtension);\n\nconst buildGeminiChatCompletionParams = (\n  input: ChatCompletionCallContext,\n): ChatCompletionParamsResult => {\n  const { midsceneDefaults, userConfig, intent } = input;\n  const { reasoningEnabled, reasoningEffort } = userConfig;\n  const commonOverrideConfig: Record<string, unknown> = {};\n\n  if (userConfig.temperature !== undefined) {\n    commonOverrideConfig.temperature = userConfig.temperature;\n  }\n\n  const modelSpecificConfig: {\n    extra_body?: {\n      google?: {\n        thinking_config: Record<string, unknown>;\n      };\n    };\n    reasoning_effort?: unknown;\n  } = {};\n\n  if (reasoningEnabled !== 'default') {\n    if (reasoningEffort) {\n      modelSpecificConfig.extra_body = {\n        google: {\n          thinking_config: {\n            thinking_level: reasoningEffort,\n            include_thoughts: true,\n          },\n        },\n      };\n    } else {\n      if (intent === 'insight') {\n        modelSpecificConfig.extra_body = {\n          google: {\n            thinking_config: {\n              // In real Gemini tests, insight calls need `include_thoughts` to get\n              // the model's thinking, and Gemini puts that thinking into `content`.\n              include_thoughts: true,\n            },\n          },\n        };\n      }\n\n      // Gemini 3.x cannot fully disable native thinking, so use the lowest\n      // supported effort unless the user explicitly requests another level.\n      modelSpecificConfig.reasoning_effort = 'minimal';\n    }\n  }\n\n  return {\n    config: {\n      ...midsceneDefaults,\n      ...commonOverrideConfig,\n      ...modelSpecificConfig,\n    },\n  };\n};\n\nconst extractInlineThought = (content: string): string | undefined => {\n  const match = content.match(/<thought>([\\s\\S]*?)<\\/thought>/i);\n  return match?.[1]?.trim() || undefined;\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return !!value && typeof value === 'object' && !Array.isArray(value);\n}\n\ntype GeminiExtractedContent = {\n  content: string;\n  geminiReasoningContent: string;\n};\n\nconst formatReasoningContent = ({\n  geminiReasoningContent,\n  providerReasoningContent,\n}: {\n  geminiReasoningContent: string;\n  providerReasoningContent: string;\n}): string => {\n  if (geminiReasoningContent && providerReasoningContent) {\n    return `thoughtParts：${geminiReasoningContent}; reasoning_content：${providerReasoningContent}`;\n  }\n\n  return geminiReasoningContent || providerReasoningContent || '';\n};\n\nconst extractGeminiThoughtParts = (\n  content: GeminiContentPart[],\n): GeminiExtractedContent => {\n  const contentParts: string[] = [];\n  const reasoningParts: string[] = [];\n\n  for (const part of content) {\n    if (!isRecord(part)) {\n      continue;\n    }\n\n    const text = typeof part.text === 'string' ? part.text : undefined;\n    if (!text) {\n      continue;\n    }\n\n    if (part.thought === true) {\n      reasoningParts.push(text);\n    } else {\n      contentParts.push(text);\n    }\n  }\n\n  return {\n    content: contentParts.join(''),\n    geminiReasoningContent: reasoningParts.join(''),\n  };\n};\n\nexport const extractGeminiContentAndReasoning = (\n  message: GeminiContentSource | undefined,\n): ContentAndReasoning => {\n  // Gemini native API exposes thought summaries in\n  // `response.candidates[0].content.parts`, where each text part can carry\n  // `thought: true`. Some OpenAI-compatible adapters may pass through a\n  // native-like content-parts shape, even though the Gemini OpenAI\n  // compatibility docs do not guarantee it.\n  // Docs: https://ai.google.dev/gemini-api/docs/thinking#thought-summaries\n\n  if (!message) {\n    return {\n      content: '',\n      reasoning_content: '',\n    };\n  }\n\n  if (Array.isArray(message.content)) {\n    const extracted = extractGeminiThoughtParts(message.content);\n\n    return {\n      content: extracted.content,\n      reasoning_content: formatReasoningContent({\n        geminiReasoningContent: extracted.geminiReasoningContent,\n        providerReasoningContent:\n          typeof message.reasoning_content === 'string'\n            ? message.reasoning_content\n            : '',\n      }),\n    };\n  }\n\n  const result = defaultExtractContentAndReasoning(\n    message as ChatCompletionContentSource,\n  );\n  // In real Gemini OpenAI-compatible responses we observed that\n  // `include_thoughts` can still return a plain string `message.content`,\n  // with the thought summary prepended as `<thought>...</thought>` before the\n  // visible answer. Keep content unchanged, but extract that leading thought\n  // text for report display.\n  const geminiReasoningContent = extractInlineThought(result.content) || '';\n\n  return {\n    content: result.content,\n    reasoning_content: formatReasoningContent({\n      geminiReasoningContent,\n      providerReasoningContent: result.reasoning_content,\n    }),\n  };\n};\n\nexport const geminiAdapters = {\n  gemini: {\n    chatCompletion: {\n      unsupportedUserConfig: ['reasoningEnabled', 'reasoningBudget'],\n      buildChatCompletionParams: buildGeminiChatCompletionParams,\n      extractContentAndReasoning: extractGeminiContentAndReasoning,\n    },\n    locate: {\n      resultAdapter: {\n        coordinates: { shape: 'bbox', order: 'yx', normalizedBy: 1000 },\n      },\n    },\n  },\n} satisfies Pick<Record<TModelFamily, ModelAdapterDefinition>, 'gemini'>;\n"],"names":["buildGeminiChatCompletionParams","input","midsceneDefaults","userConfig","intent","reasoningEnabled","reasoningEffort","commonOverrideConfig","undefined","modelSpecificConfig","extractInlineThought","content","match","isRecord","value","Array","formatReasoningContent","geminiReasoningContent","providerReasoningContent","extractGeminiThoughtParts","contentParts","reasoningParts","part","text","extractGeminiContentAndReasoning","message","extracted","result","defaultExtractContentAndReasoning","geminiAdapters"],"mappings":";AA6BA,MAAMA,kCAAkC,CACtCC;IAEA,MAAM,EAAEC,gBAAgB,EAAEC,UAAU,EAAEC,MAAM,EAAE,GAAGH;IACjD,MAAM,EAAEI,gBAAgB,EAAEC,eAAe,EAAE,GAAGH;IAC9C,MAAMI,uBAAgD,CAAC;IAEvD,IAAIJ,AAA2BK,WAA3BL,WAAW,WAAW,EACxBI,qBAAqB,WAAW,GAAGJ,WAAW,WAAW;IAG3D,MAAMM,sBAOF,CAAC;IAEL,IAAIJ,AAAqB,cAArBA,kBACF,IAAIC,iBACFG,oBAAoB,UAAU,GAAG;QAC/B,QAAQ;YACN,iBAAiB;gBACf,gBAAgBH;gBAChB,kBAAkB;YACpB;QACF;IACF;SACK;QACL,IAAIF,AAAW,cAAXA,QACFK,oBAAoB,UAAU,GAAG;YAC/B,QAAQ;gBACN,iBAAiB;oBAGf,kBAAkB;gBACpB;YACF;QACF;QAKFA,oBAAoB,gBAAgB,GAAG;IACzC;IAGF,OAAO;QACL,QAAQ;YACN,GAAGP,gBAAgB;YACnB,GAAGK,oBAAoB;YACvB,GAAGE,mBAAmB;QACxB;IACF;AACF;AAEA,MAAMC,uBAAuB,CAACC;IAC5B,MAAMC,QAAQD,QAAQ,KAAK,CAAC;IAC5B,OAAOC,OAAO,CAAC,EAAE,EAAE,UAAUJ;AAC/B;AAEA,SAASK,SAASC,KAAc;IAC9B,OAAO,CAAC,CAACA,SAAS,AAAiB,YAAjB,OAAOA,SAAsB,CAACC,MAAM,OAAO,CAACD;AAChE;AAOA,MAAME,yBAAyB,CAAC,EAC9BC,sBAAsB,EACtBC,wBAAwB,EAIzB;IACC,IAAID,0BAA0BC,0BAC5B,OAAO,CAAC,aAAa,EAAED,uBAAuB,oBAAoB,EAAEC,0BAA0B;IAGhG,OAAOD,0BAA0BC,4BAA4B;AAC/D;AAEA,MAAMC,4BAA4B,CAChCR;IAEA,MAAMS,eAAyB,EAAE;IACjC,MAAMC,iBAA2B,EAAE;IAEnC,KAAK,MAAMC,QAAQX,QAAS;QAC1B,IAAI,CAACE,SAASS,OACZ;QAGF,MAAMC,OAAO,AAAqB,YAArB,OAAOD,KAAK,IAAI,GAAgBA,KAAK,IAAI,GAAGd;QACzD,IAAKe,MAIL,IAAID,AAAiB,SAAjBA,KAAK,OAAO,EACdD,eAAe,IAAI,CAACE;aAEpBH,aAAa,IAAI,CAACG;IAEtB;IAEA,OAAO;QACL,SAASH,aAAa,IAAI,CAAC;QAC3B,wBAAwBC,eAAe,IAAI,CAAC;IAC9C;AACF;AAEO,MAAMG,mCAAmC,CAC9CC;IASA,IAAI,CAACA,SACH,OAAO;QACL,SAAS;QACT,mBAAmB;IACrB;IAGF,IAAIV,MAAM,OAAO,CAACU,QAAQ,OAAO,GAAG;QAClC,MAAMC,YAAYP,0BAA0BM,QAAQ,OAAO;QAE3D,OAAO;YACL,SAASC,UAAU,OAAO;YAC1B,mBAAmBV,uBAAuB;gBACxC,wBAAwBU,UAAU,sBAAsB;gBACxD,0BACE,AAAqC,YAArC,OAAOD,QAAQ,iBAAiB,GAC5BA,QAAQ,iBAAiB,GACzB;YACR;QACF;IACF;IAEA,MAAME,SAASC,kCACbH;IAOF,MAAMR,yBAAyBP,qBAAqBiB,OAAO,OAAO,KAAK;IAEvE,OAAO;QACL,SAASA,OAAO,OAAO;QACvB,mBAAmBX,uBAAuB;YACxCC;YACA,0BAA0BU,OAAO,iBAAiB;QACpD;IACF;AACF;AAEO,MAAME,iBAAiB;IAC5B,QAAQ;QACN,gBAAgB;YACd,uBAAuB;gBAAC;gBAAoB;aAAkB;YAC9D,2BAA2B7B;YAC3B,4BAA4BwB;QAC9B;QACA,QAAQ;YACN,eAAe;gBACb,aAAa;oBAAE,OAAO;oBAAQ,OAAO;oBAAM,cAAc;gBAAK;YAChE;QACF;IACF;AACF"}