{"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 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 } = 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: {\n          include_thoughts: true;\n          thinking_level: string;\n        };\n      };\n    };\n  } = {};\n\n  if (reasoningEnabled !== 'default') {\n    // Gemini cannot fully disable native thinking, so \"disabled\" maps to the\n    // lowest thinking level. `include_thoughts` only controls whether Gemini\n    // returns thought summaries; it does not enable thinking, and thought token\n    // usage is based on the full thoughts Gemini generates regardless of\n    // whether summaries are included.\n    modelSpecificConfig.extra_body = {\n      google: {\n        thinking_config: {\n          include_thoughts: true,\n          thinking_level: reasoningEnabled\n            ? (reasoningEffort ?? 'medium')\n            : 'minimal',\n        },\n      },\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 content = typeof message.content === 'string' ? message.content : '';\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(content) || '';\n\n  return {\n    content,\n    reasoning_content: formatReasoningContent({\n      geminiReasoningContent,\n      providerReasoningContent:\n        typeof message.reasoning_content === 'string'\n          ? message.reasoning_content\n          : '',\n    }),\n  };\n};\n\nexport const geminiAdapters = {\n  gemini: {\n    chatCompletion: {\n      unsupportedUserConfig: ['reasoningBudget'],\n      buildChatCompletionParams: buildGeminiChatCompletionParams,\n      messageExtraction: {\n        kind: 'custom',\n        extractContentAndReasoning: extractGeminiContentAndReasoning,\n      },\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","reasoningEnabled","reasoningEffort","commonOverrideConfig","undefined","modelSpecificConfig","extractInlineThought","content","match","isRecord","value","Array","formatReasoningContent","geminiReasoningContent","providerReasoningContent","extractGeminiThoughtParts","contentParts","reasoningParts","part","text","extractGeminiContentAndReasoning","message","extracted","geminiAdapters"],"mappings":"AA4BA,MAAMA,kCAAkC,CACtCC;IAEA,MAAM,EAAEC,gBAAgB,EAAEC,UAAU,EAAE,GAAGF;IACzC,MAAM,EAAEG,gBAAgB,EAAEC,eAAe,EAAE,GAAGF;IAC9C,MAAMG,uBAAgD,CAAC;IAEvD,IAAIH,AAA2BI,WAA3BJ,WAAW,WAAW,EACxBG,qBAAqB,WAAW,GAAGH,WAAW,WAAW;IAG3D,MAAMK,sBASF,CAAC;IAEL,IAAIJ,AAAqB,cAArBA,kBAMFI,oBAAoB,UAAU,GAAG;QAC/B,QAAQ;YACN,iBAAiB;gBACf,kBAAkB;gBAClB,gBAAgBJ,mBACXC,mBAAmB,WACpB;YACN;QACF;IACF;IAGF,OAAO;QACL,QAAQ;YACN,GAAGH,gBAAgB;YACnB,GAAGI,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,MAAMd,UAAU,AAA2B,YAA3B,OAAOc,QAAQ,OAAO,GAAgBA,QAAQ,OAAO,GAAG;IAMxE,MAAMR,yBAAyBP,qBAAqBC,YAAY;IAEhE,OAAO;QACLA;QACA,mBAAmBK,uBAAuB;YACxCC;YACA,0BACE,AAAqC,YAArC,OAAOQ,QAAQ,iBAAiB,GAC5BA,QAAQ,iBAAiB,GACzB;QACR;IACF;AACF;AAEO,MAAME,iBAAiB;IAC5B,QAAQ;QACN,gBAAgB;YACd,uBAAuB;gBAAC;aAAkB;YAC1C,2BAA2B1B;YAC3B,mBAAmB;gBACjB,MAAM;gBACN,4BAA4BuB;YAC9B;QACF;QACA,QAAQ;YACN,eAAe;gBACb,aAAa;oBAAE,OAAO;oBAAQ,OAAO;oBAAM,cAAc;gBAAK;YAChE;QACF;IACF;AACF"}