Download Blessing-RisuAI-latest.js from m6dd8m/blessing-provider: direct link, hf CLI and curl.
- Browser
- Download file 434 kB
-
https://huggingface.co/m6dd8m/blessing-provider/resolve/main/Blessing-RisuAI-latest.js
- Command line
-
hf download hf://m6dd8m/blessing-provider/Blessing-RisuAI-latest.js
-
curl -L -o Blessing-RisuAI-latest.js https://huggingface.co/m6dd8m/blessing-provider/resolve/main/Blessing-RisuAI-latest.js
434 kB
| //@name Blessing | |
| //@display-name Blessing 2.0.1 (RisuAI) | |
| //@version 2.0.1 | |
| //@api 3.0 | |
| //@update-url https://huggingface.co/m6dd8m/blessing-provider/resolve/main/Blessing-RisuAI-latest.js | |
| //Blessing — RisuAI/Risuai-fork 용 통합 프로바이더·UX 플러그인. | |
| //설정은 자체 UI에서 관리하며, 호스트의 //@arg 필드는 사용하지 않는다 (UX 일관성 + Risuai 본체와 분리). | |
| //LBI 사용자의 기존 설정은 [Blessing → 가져오기] 탭에서 1회 마이그레이션한다. | |
| // SPDX-License-Identifier: GPL-3.0 | |
| // Source and license texts: https://drive.proton.me/urls/QVVWP68HMM#UQqhnwGZH2yk | |
| (()=>{var __defProp=Object.defineProperty;var __getOwnPropNames=Object.getOwnPropertyNames;var __esm=(fn,res)=>function(){return fn&&(res=(0,fn[__getOwnPropNames(fn)[0]])(fn=0)),res};var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})};function injectStyles(){try{let doc=globalThis.document;if(!doc||!doc.head)return!1;if(doc.getElementById(STYLE_ID))return!0;let el=doc.createElement("style");return el.id=STYLE_ID,el.textContent=CSS2,doc.head.appendChild(el),!0}catch{return!1}}var STYLE_ID,CSS2,init_styles=__esm({"src/ui/styles.js"(){STYLE_ID="blessing-style-root",CSS2=` | |
| :root { | |
| --blessing-bg: #1a1d21; | |
| --blessing-bg-elev: #232830; | |
| --blessing-bg-hover: #2a3038; | |
| --blessing-fg: #e6e9ef; | |
| --blessing-fg-muted: #9aa3b2; | |
| --blessing-border: #353c47; | |
| --blessing-border-soft: #2a3038; | |
| --blessing-accent: #7c9cff; | |
| --blessing-accent-soft: #2a3a6b; | |
| --blessing-danger: #ef6f6f; | |
| --blessing-radius-sm: 6px; | |
| --blessing-radius: 8px; | |
| --blessing-radius-lg: 12px; | |
| --blessing-gap-xs: 4px; | |
| --blessing-gap-sm: 8px; | |
| --blessing-gap: 12px; | |
| --blessing-gap-lg: 16px; | |
| --blessing-gap-xl: 24px; | |
| --blessing-shadow: 0 10px 30px rgba(0,0,0,.45); | |
| --blessing-z: 2147483600; | |
| } | |
| .blessing-overlay { | |
| position: fixed; inset: 0; | |
| background: rgba(0,0,0,.55); | |
| z-index: var(--blessing-z); | |
| display: flex; align-items: center; justify-content: center; | |
| padding: var(--blessing-gap-lg); | |
| } | |
| .blessing-panel { | |
| width: min(960px, 100%); | |
| height: min(640px, 100%); | |
| background: var(--blessing-bg); | |
| color: var(--blessing-fg); | |
| border: 1px solid var(--blessing-border); | |
| border-radius: var(--blessing-radius-lg); | |
| box-shadow: var(--blessing-shadow); | |
| display: flex; flex-direction: column; | |
| overflow: hidden; | |
| font-family: -apple-system, BlinkMacSystemFont, "Pretendard Variable", | |
| Pretendard, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif; | |
| } | |
| .blessing-panel * { box-sizing: border-box; } | |
| .blessing-header { | |
| display: flex; align-items: center; gap: var(--blessing-gap); | |
| padding: var(--blessing-gap) var(--blessing-gap-lg); | |
| border-bottom: 1px solid var(--blessing-border-soft); | |
| flex: 0 0 auto; | |
| } | |
| .blessing-header__brand { | |
| display: flex; align-items: center; gap: var(--blessing-gap-sm); | |
| font-weight: 600; font-size: 16px; | |
| } | |
| .blessing-header__brand svg { width: 18px; height: 18px; color: var(--blessing-accent); } | |
| .blessing-header__subtitle { color: var(--blessing-fg-muted); font-size: 12px; } | |
| .blessing-header__spacer { flex: 1 1 auto; } | |
| .blessing-body { | |
| flex: 1 1 auto; | |
| display: grid; | |
| grid-template-columns: 200px 1fr; | |
| min-height: 0; | |
| } | |
| .blessing-tabs { | |
| border-right: 1px solid var(--blessing-border-soft); | |
| padding: var(--blessing-gap-sm); | |
| display: flex; flex-direction: column; gap: 2px; | |
| overflow-y: auto; | |
| } | |
| .blessing-tab { | |
| display: flex; align-items: center; gap: var(--blessing-gap-sm); | |
| padding: 8px 10px; | |
| border-radius: var(--blessing-radius-sm); | |
| cursor: pointer; | |
| color: var(--blessing-fg-muted); | |
| background: transparent; | |
| border: 1px solid transparent; | |
| text-align: left; font: inherit; | |
| } | |
| .blessing-tab svg { width: 16px; height: 16px; flex: 0 0 auto; } | |
| .blessing-tab:hover { background: var(--blessing-bg-hover); color: var(--blessing-fg); } | |
| .blessing-tab.is-active { | |
| background: var(--blessing-accent-soft); | |
| color: var(--blessing-fg); | |
| border-color: var(--blessing-border); | |
| } | |
| .blessing-content { | |
| padding: var(--blessing-gap-lg); | |
| overflow: auto; | |
| min-width: 0; | |
| } | |
| .blessing-content h2 { | |
| margin: 0 0 var(--blessing-gap-sm) 0; | |
| font-size: 18px; font-weight: 600; | |
| display: flex; align-items: center; gap: var(--blessing-gap-sm); | |
| } | |
| .blessing-content p { | |
| margin: 0 0 var(--blessing-gap) 0; | |
| color: var(--blessing-fg-muted); | |
| line-height: 1.5; | |
| } | |
| .blessing-card { | |
| background: var(--blessing-bg-elev); | |
| border: 1px solid var(--blessing-border-soft); | |
| border-radius: var(--blessing-radius); | |
| padding: var(--blessing-gap); | |
| margin-bottom: var(--blessing-gap); | |
| } | |
| .blessing-iconbtn { | |
| display: inline-flex; align-items: center; justify-content: center; | |
| width: 32px; height: 32px; | |
| border-radius: var(--blessing-radius-sm); | |
| background: transparent; | |
| border: 1px solid transparent; | |
| color: var(--blessing-fg-muted); | |
| cursor: pointer; padding: 0; | |
| } | |
| .blessing-iconbtn svg { width: 18px; height: 18px; } | |
| .blessing-iconbtn:hover { background: var(--blessing-bg-hover); color: var(--blessing-fg); } | |
| .blessing-panel { font-size: 13px; line-height: 1.5; } | |
| .blessing-panel :is(button, input, select, textarea, summary):focus-visible { | |
| outline: 2px solid var(--blessing-accent) !important; outline-offset: 2px; | |
| } | |
| .blessing-content { min-width: 0; overflow-wrap: anywhere; } | |
| .blessing-content :is(input, select, textarea) { max-width: 100%; } | |
| .blessing-explanation, .blessing-section { margin: 8px 0; min-width: 0; font-size: 12px; font-weight: 400; } | |
| .blessing-explanation > summary, .blessing-section > summary { | |
| display: flex; align-items: center; gap: 8px; list-style: none; cursor: pointer; | |
| min-height: 32px; padding: 4px 0; color: var(--blessing-fg-muted); font-weight: 500; | |
| } | |
| .blessing-explanation > summary::-webkit-details-marker, .blessing-section > summary::-webkit-details-marker { display: none; } | |
| .blessing-explanation > summary::before, .blessing-section > summary::before { | |
| content: ""; width: 6px; height: 6px; flex: 0 0 auto; | |
| border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(-45deg); | |
| } | |
| .blessing-explanation[open] > summary::before, .blessing-section[open] > summary::before { transform: rotate(45deg); } | |
| .blessing-explanation__body { padding: 4px 0 8px 14px; white-space: pre-wrap; color: var(--blessing-fg-muted); line-height: 1.7; font-weight: 400; } | |
| .blessing-section { padding: 8px 12px; border: 1px solid var(--blessing-border-soft); border-radius: var(--blessing-radius); } | |
| .blessing-section > summary { color: var(--blessing-fg); } | |
| .blessing-maintenance { margin-top: 16px; } | |
| .blessing-maintenance > button { margin: 4px; } | |
| .blessing-service-hint { display: block; color: var(--blessing-fg-muted); font-size: 11px; margin-top: 2px; } | |
| .blessing-provider-search { width: calc(100% - 24px); margin: 0 12px 8px; padding: 8px; } | |
| .blessing-search-empty { padding: 0 12px; } | |
| .blessing-input { background: var(--blessing-bg); color: var(--blessing-fg); border: 1px solid var(--blessing-border); border-radius: var(--blessing-radius-sm); padding: 6px 8px; font: inherit; } | |
| .blessing-purpose { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; } | |
| .blessing-check-row { display: flex; align-items: center; gap: 8px; min-height: 32px; } | |
| .blessing-model-row { display: grid; grid-template-columns: 1fr 1.4fr auto; gap: 8px; align-items: end; } | |
| .blessing-model-row > label { display: flex; flex-direction: column; gap: 4px; min-width: 0; color: var(--blessing-fg-muted); font-size: 12px; } | |
| .blessing-primary-action { background: var(--blessing-accent-soft) !important; border-color: var(--blessing-accent) !important; } | |
| .blessing-next-step { margin-top: 12px !important; } | |
| .blessing-content [role="status"]:not(:empty) { margin: 8px 0; padding: 10px; border-left: 2px solid var(--blessing-accent); background: var(--blessing-bg); } | |
| /* \uBAA8\uBC14\uC77C - 720px \uC774\uD558: \uC0AC\uC774\uB4DC\uBC14 \u2192 \uC0C1\uB2E8 \uAC00\uB85C \uC2A4\uD06C\uB864 */ | |
| @media (max-width: 720px) { | |
| .blessing-overlay { padding: 0; } | |
| .blessing-panel { | |
| width: 100%; height: 100%; | |
| border-radius: 0; | |
| border: 0; | |
| } | |
| .blessing-body { | |
| grid-template-columns: 1fr; | |
| grid-template-rows: auto 1fr; | |
| } | |
| .blessing-tabs { | |
| flex-direction: row; | |
| overflow-x: auto; | |
| overflow-y: hidden; | |
| border-right: 0; | |
| border-bottom: 1px solid var(--blessing-border-soft); | |
| padding: var(--blessing-gap-sm); | |
| flex-wrap: nowrap; | |
| } | |
| .blessing-tab { | |
| flex: 0 0 auto; | |
| white-space: nowrap; | |
| } | |
| .blessing-content { padding: var(--blessing-gap); } | |
| .blessing-panel :is(button, summary, select, textarea, input:not([type="checkbox"]):not([type="radio"]):not([type="file"])) { min-height: 44px; } | |
| .blessing-panel :is(button, summary) { min-width: 44px; } | |
| .blessing-panel label:has(input[type="checkbox"]), .blessing-panel label:has(input[type="radio"]) { min-height: 44px; } | |
| .blessing-tabs { scrollbar-width: thin; box-shadow: inset -10px 0 10px -10px var(--blessing-accent); } | |
| .blessing-tab { padding-inline: 10px; } | |
| .blessing-model-row { grid-template-columns: 1fr; padding: 8px 0; border-top: 1px solid var(--blessing-border-soft); } | |
| .blessing-model-row > button { justify-self: end; } | |
| .blessing-content button[data-model-id] { max-width: 100%; white-space: normal; overflow-wrap: anywhere; } | |
| } | |
| /* \uC2A4\uC704\uCE58 (toggle) - \uCCB4\uD06C\uBC15\uC2A4 \uD544\uB4DC \uC804\uC6A9 */ | |
| .blessing-field-switch { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: var(--blessing-gap); | |
| padding: 10px 12px; | |
| margin: 8px 0; | |
| background: var(--blessing-bg-elev); | |
| border: 1px solid var(--blessing-border-soft); | |
| border-radius: var(--blessing-radius); | |
| cursor: pointer; | |
| transition: border-color 120ms ease, background 120ms ease; | |
| } | |
| .blessing-field-switch:hover { | |
| border-color: var(--blessing-border); | |
| background: var(--blessing-bg-hover); | |
| } | |
| .blessing-field-switch__label { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-size: 13px; | |
| color: var(--blessing-fg); | |
| flex: 1 1 auto; | |
| min-width: 0; | |
| } | |
| .blessing-switch { | |
| position: relative; | |
| display: inline-block; | |
| width: 38px; | |
| height: 22px; | |
| flex: 0 0 auto; | |
| } | |
| .blessing-switch input { | |
| position: absolute; | |
| opacity: 0; | |
| width: 0; | |
| height: 0; | |
| pointer-events: none; | |
| } | |
| .blessing-switch__track { | |
| position: absolute; | |
| inset: 0; | |
| background: var(--blessing-border); | |
| border-radius: 999px; | |
| transition: background 160ms ease; | |
| } | |
| .blessing-switch__thumb { | |
| position: absolute; | |
| top: 3px; | |
| left: 3px; | |
| width: 16px; | |
| height: 16px; | |
| background: #f4f6fa; | |
| border-radius: 50%; | |
| box-shadow: 0 1px 3px rgba(0,0,0,.35); | |
| transition: transform 160ms ease; | |
| } | |
| .blessing-switch input:checked ~ .blessing-switch__track { | |
| background: var(--blessing-accent); | |
| } | |
| .blessing-switch input:checked ~ .blessing-switch__thumb { | |
| transform: translateX(16px); | |
| } | |
| .blessing-switch input:focus-visible ~ .blessing-switch__track { | |
| outline: 2px solid var(--blessing-accent); | |
| outline-offset: 2px; | |
| } | |
| /* \uB85C\uB529 \uC778\uB514\uCF00\uC774\uD130 */ | |
| @keyframes blessing-spin { | |
| from { transform: rotate(0deg); } | |
| to { transform: rotate(360deg); } | |
| } | |
| .blessing-spinner { | |
| display: inline-block; | |
| width: 16px; | |
| height: 16px; | |
| border-radius: 50%; | |
| border: 2px solid var(--blessing-border); | |
| border-top-color: var(--blessing-accent); | |
| animation: blessing-spin 700ms linear infinite; | |
| flex: 0 0 auto; | |
| vertical-align: middle; | |
| } | |
| .blessing-spinner--sm { width: 12px; height: 12px; border-width: 2px; } | |
| .blessing-spinner--lg { width: 28px; height: 28px; border-width: 3px; } | |
| /* \uC601\uC5ED \uAC00\uB9BC \uC624\uBC84\uB808\uC774 (\uBD80\uBD84 busy) */ | |
| .blessing-busy { | |
| position: relative; | |
| } | |
| .blessing-busy__overlay { | |
| position: absolute; | |
| inset: 0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| background: rgba(26, 29, 33, 0.62); | |
| backdrop-filter: blur(1px); | |
| border-radius: inherit; | |
| z-index: 5; | |
| color: var(--blessing-fg-muted); | |
| font-size: 12px; | |
| pointer-events: all; | |
| } | |
| .blessing-busy__msg { color: var(--blessing-fg-muted); } | |
| /* \uC778\uB77C\uC778 \uD589 \uC778\uB514\uCF00\uC774\uD130 (\uD14D\uC2A4\uD2B8 + \uC2A4\uD53C\uB108) */ | |
| .blessing-inline-busy { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 8px; | |
| color: var(--blessing-fg-muted); | |
| font-size: 12px; | |
| } | |
| `}});var alert_exports={};__export(alert_exports,{showAlert:()=>showAlert,surfaceClassification:()=>surfaceClassification});function showAlert(opts={}){return new Promise(resolve=>{let title=String(opts.title||"\uC54C\uB9BC"),message=String(opts.message||""),action=String(opts.action||""),providerId=String(opts.providerId||""),cls=String(opts.class||""),severity=String(opts.severity||"info");if(providerId&&cls){let key=`${providerId}|${cls}`,lastShown=RECENT_ALERTS.get(key);if(lastShown&&Date.now()-lastShown<DEBOUNCE_MS){resolve();return}RECENT_ALERTS.set(key,Date.now())}if(!injectStyles()){try{console.warn(`[Blessing alert] ${title}: ${message}`)}catch{}resolve();return}let doc=document,overlay=doc.createElement("div");overlay.className="blessing-overlay",overlay.style.zIndex="calc(var(--blessing-z) + 10)";let panel=doc.createElement("div");panel.className="blessing-panel",panel.setAttribute("role","alertdialog"),panel.setAttribute("aria-modal","true"),panel.style.width="min(440px, 100%)",panel.style.height="auto",panel.style.maxHeight="min(80vh, 480px)";let header=doc.createElement("div");header.className="blessing-header";let titleEl=doc.createElement("div");titleEl.className="blessing-header__brand";let titleText=doc.createElement("span");if(titleText.textContent=title,titleEl.appendChild(titleText),providerId){let pidEl=doc.createElement("span");pidEl.className="blessing-header__subtitle",pidEl.textContent=`[${providerId}]`,titleEl.appendChild(pidEl)}header.appendChild(titleEl),panel.appendChild(header);let body=doc.createElement("div");body.style.padding="var(--blessing-gap-lg)",body.style.display="flex",body.style.flexDirection="column",body.style.gap="var(--blessing-gap)",body.style.overflowY="auto",body.style.flex="1 1 auto",body.style.minHeight="0";let msgEl=doc.createElement("p");if(msgEl.textContent=message,msgEl.style.color=severity==="critical"?"var(--blessing-danger)":"var(--blessing-fg)",msgEl.style.margin="0",msgEl.style.fontSize="14px",msgEl.style.lineHeight="1.5",opts.preserveNewlines===!0&&(msgEl.style.whiteSpace="pre-wrap",msgEl.style.fontFamily="var(--blessing-font-mono, ui-monospace, monospace)",msgEl.style.fontSize="12px"),body.appendChild(msgEl),action){let actionEl=doc.createElement("p");actionEl.textContent=action,actionEl.style.color="var(--blessing-fg-muted)",actionEl.style.margin="0",actionEl.style.fontSize="13px",actionEl.style.lineHeight="1.5",body.appendChild(actionEl)}panel.appendChild(body);let footer=doc.createElement("div");footer.style.padding="var(--blessing-gap) var(--blessing-gap-lg)",footer.style.display="flex",footer.style.justifyContent="flex-end",footer.style.borderTop="1px solid var(--blessing-border-soft)",footer.style.flex="0 0 auto";let okBtn=doc.createElement("button");okBtn.type="button",okBtn.textContent="\uD655\uC778",okBtn.style.padding="6px 22px",okBtn.style.background="var(--blessing-accent)",okBtn.style.color="#fff",okBtn.style.border="1px solid var(--blessing-accent)",okBtn.style.borderRadius="var(--blessing-radius-sm)",okBtn.style.cursor="pointer",okBtn.style.fontSize="14px",okBtn.style.fontFamily="inherit";let dismissed=!1,dismiss=()=>{if(!dismissed){dismissed=!0;try{overlay.parentNode&&overlay.parentNode.removeChild(overlay)}catch{}try{doc.removeEventListener("keydown",escListener,!0)}catch{}resolve()}},escListener=e=>{e.key==="Escape"&&(e.stopPropagation(),dismiss())};okBtn.addEventListener("click",dismiss),overlay.addEventListener("click",e=>{e.target===overlay&&dismiss()}),footer.appendChild(okBtn),panel.appendChild(footer),overlay.appendChild(panel),doc.body.appendChild(overlay),doc.addEventListener("keydown",escListener,!0);try{okBtn.focus()}catch{}})}function surfaceClassification(c,opts={}){if(!c)return;let providerId=String(opts.providerId||""),displayName=String(opts.displayName||providerId||"Blessing"),ctx=opts.ctx,severity=c.class==="auth"?"critical":c.class==="network"||c.class==="cors"?"warning":"info";if(c.surface==="modal")showAlert({title:c.title,message:c.message,action:c.action,providerId,class:c.class,severity}).catch(()=>{});else if(c.surface==="log"){try{console.warn(`[Blessing/${providerId}] ${c.title}: ${c.message}`)}catch{}return}ctx&&typeof ctx.enqueue=="function"&&ctx.enqueue(` | |
| [${displayName}] ${c.message} | |
| ${c.action} | |
| `)}var RECENT_ALERTS,DEBOUNCE_MS,init_alert=__esm({"src/ui/alert.js"(){init_styles();RECENT_ALERTS=new Map,DEBOUNCE_MS=5e3}});var BLESSING_VERSION="2.0.1";var BLESSING_SETTINGS_ID="blessing-settings-entry";var BLESSING_DISPLAY_NAME="Blessing",BLESSING_SUBTITLE="AI \uBAA8\uB378 \uC5F0\uACB0",STORAGE_PREFIX="blessing.";var BUILD_TARGET="risuai",BUILD_TARGET_LABEL=BUILD_TARGET==="pocket"?"PocketRisu":"RisuAI";var cachedHost=null,cachedCaps=null;function getHost(){if(cachedHost)return cachedHost;let g=globalThis;return cachedHost=g.risuai??g.risuApi??g.risuAPI??null,cachedHost}function getCapabilities(){if(cachedCaps)return cachedCaps;let host=getHost(),hasV3=!!host&&typeof host.registerSetting=="function"&&typeof host.registerButton=="function",hasV2=!!globalThis.risu_addProvider||!!globalThis.risuPluginV2,supportsStreamingReturn=!1,isMobileLike=!1;try{let nav=globalThis.navigator;if(nav){let ua=String(nav.userAgent||"");isMobileLike=/Mobi|Android|iPhone|iPad|iPod/i.test(ua)||typeof nav.maxTouchPoints=="number"&&nav.maxTouchPoints>1}}catch{isMobileLike=!1}return cachedCaps={hasV3,hasV2,supportsStreamingReturn,isMobileLike,buildTarget:BUILD_TARGET},cachedCaps}function getStorage(){let host=getHost();return host&&host.pluginStorage?host.pluginStorage:fallbackStorage}var _fallbackMem=new Map,fallbackStorage={async getItem(k){return _fallbackMem.has(k)?_fallbackMem.get(k):null},async setItem(k,v){_fallbackMem.set(k,v)},async removeItem(k){_fallbackMem.delete(k)},async keys(){return Array.from(_fallbackMem.keys())}},cachedRuntimeInfo=null;function getRuntimeInfoOnce(){return cachedRuntimeInfo||(cachedRuntimeInfo=(async()=>{let host=getHost();if(!host||typeof host.getRuntimeInfo!="function")return null;try{let info=await host.getRuntimeInfo();return!info||typeof info!="object"?null:{apiVersion:String(info.apiVersion||""),platform:String(info.platform||""),saveMethod:String(info.saveMethod||"")}}catch{return null}})(),cachedRuntimeInfo)}async function hasReliableGetProxy(){let info=await getRuntimeInfoOnce();if(!info)return!1;let p=info.platform.toLowerCase();return p==="node"||p==="desktop"}/*! | |
| Heroicons outline icons: https://github.com/tailwindlabs/heroicons | |
| MIT License | |
| Copyright (c) Tailwind Labs, Inc. | |
| Permission is hereby granted, free of charge, to any person obtaining a copy | |
| of this software and associated documentation files (the "Software"), to deal | |
| in the Software without restriction, including without limitation the rights | |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| copies of the Software, and to permit persons to whom the Software is | |
| furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in all | |
| copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| SOFTWARE. | |
| */var SVG_BASE=path=>`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.6" stroke="currentColor" aria-hidden="true">${path}</svg>`;function icon(name){return ICONS[name]||""}function buttonIcon(name){return ICONS[name]||ICONS.sparkles}var ICONS={sparkles:SVG_BASE('<path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.847.814a4.5 4.5 0 0 0-3.09 3.09ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 0 0-2.456 2.456ZM16.894 20.567 16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394-1.183.394a2.25 2.25 0 0 0-1.423 1.423Z"/>'),cog:SVG_BASE('<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/>'),swatch:SVG_BASE('<path stroke-linecap="round" stroke-linejoin="round" d="M4.098 19.902a3.75 3.75 0 0 0 5.304 0l6.401-6.402M6.75 21A3.75 3.75 0 0 1 3 17.25V4.125C3 3.504 3.504 3 4.125 3h5.25c.621 0 1.125.504 1.125 1.125v4.072M6.75 21a3.75 3.75 0 0 0 3.75-3.75V8.197M6.75 21h13.125c.621 0 1.125-.504 1.125-1.125v-5.25c0-.621-.504-1.125-1.125-1.125h-4.073M10.5 8.197l2.88-2.88c.438-.439 1.15-.439 1.59 0l3.712 3.713c.44.44.44 1.152 0 1.59l-2.879 2.88M6.75 17.25h.008v.008H6.75v-.008Z"/>'),chart:SVG_BASE('<path stroke-linecap="round" stroke-linejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z"/>'),arrowDownTray:SVG_BASE('<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3"/>'),archiveBox:SVG_BASE('<path stroke-linecap="round" stroke-linejoin="round" d="m20.25 7.5-.625 10.632a2.25 2.25 0 0 1-2.247 2.118H6.622a2.25 2.25 0 0 1-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z" />'),info:SVG_BASE('<path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"/>'),questionMark:SVG_BASE('<path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z"/>'),xMark:SVG_BASE('<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>'),chevronLeft:SVG_BASE('<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5"/>'),chevronRight:SVG_BASE('<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5"/>')};init_styles();function createExplanation(question,text=""){let details=document.createElement("details");details.className="blessing-explanation";let summary=document.createElement("summary");summary.textContent=question;let body=document.createElement("div");return body.className="blessing-explanation__body",body.textContent=text,details.append(summary,body),details}function createSection(title,{maintenance=!1}={}){let details=createExplanation(title);return details.className=maintenance?"blessing-section blessing-maintenance":"blessing-section",details.lastElementChild.remove(),details}var ALIAS_NATIVE_GOOGLE_AI="google-ai",ALIAS_NATIVE_ANTHROPIC="anthropic",PROVIDER_PRIVACY={"custom-openai":{confidence:"high",endpoint:"api.openai.com (\uAE30\uBCF8; base URL \uBCC0\uACBD \uC2DC \uB2E4\uB978 \uD504\uB85C\uBC14\uC774\uB354 \uC815\uCC45 \uC801\uC6A9)",retention:"30 \uC77C (abuse \uBAA8\uB2C8\uD130\uB9C1 \uBAA9\uC801)",training:"API \uAE30\uBCF8 opt-out \u2014 \uD559\uC2B5\uC5D0 \uC0AC\uC6A9\uB418\uC9C0 \uC54A\uC74C",zdr:"Enterprise \uB610\uB294 \uC801\uACA9 \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uD55C\uC815",regions:"\uAE00\uB85C\uBC8C. EU \uB370\uC774\uD130 residency \uB294 Enterprise/Azure \uD544\uC694",fictional:"\uC815\uCC45\uC5D0 \uBA85\uC2DC \uC5C6\uC74C",nsfw:'\uAE08\uC9C0 \u2014 "sexual violence or non-consensual intimate content" + "pornography or explicit sexual content"',age:"\uD544\uC218 \u2014 \uBBF8\uC131\uB144\uC790 \uB300\uC0C1 sexualize \uAE08\uC9C0 \uBA85\uC2DC",bans:"Yes \u2014 quarterly threat-intel reports \uBC1C\uD589 (40+ networks \uCC28\uB2E8 \uBCF4\uACE0)",euAiAct:"\uD45C\uC900 corporate terms \uB85C \uB2E4\uB8F8",gdpr:"self-serve via settings",ccpa:"standard CCPA rights",sourceUrls:["https://openai.com/policies/api-data-usage/","https://openai.com/policies/usage-policies/","https://openai.com/policies/privacy-policy/","https://openai.com/global-affairs/disrupting-malicious-uses-of-ai-october-2025/"],lastVerified:"2026-05-23",notes:"custom-openai \uB294 base URL \uC790\uC720 \uC124\uC815. \uAE30\uBCF8\uC740 OpenAI \uC790\uCCB4\uC774\uBBC0\uB85C \uBCF8 \uC815\uCC45 \uC801\uC6A9. \uB2E4\uB978 OpenAI \uD638\uD658 \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uC0AC\uC6A9 \uC2DC \uD574\uB2F9 \uD504\uB85C\uBC14\uC774\uB354 \uC815\uCC45 \uCC38\uC870."},[ALIAS_NATIVE_ANTHROPIC]:{confidence:"high",endpoint:"api.anthropic.com",retention:"30 \uC77C (\uC790\uB3D9 \uC0AD\uC81C)",training:'\uAE30\uBCF8 opt-out \u2014 "Retained data is never used for model training without your express permission"',zdr:"\uC0AC\uC6A9 \uAC00\uB2A5 (specific application via Claude Messages API)",regions:"\uAE00\uB85C\uBC8C. \uC5C4\uACA9\uD55C region routing \uC740 AWS Bedrock / GCP Vertex \uB85C",fictional:"\uC815\uCC45\uC5D0 \uBA85\uC2DC \uC5C6\uC74C",nsfw:"\uC81C\uD55C\uC801 \u2014 CSAM / non-consensual sexual content \uBA85\uC2DC \uAE08\uC9C0. \uC77C\uBC18 explicit \uCF58\uD150\uCE20\uB294 safety \uAC00\uB4DC\uB808\uC77C\uC774 \uC81C\uC5B4",age:"API \uC5D0 \uBA85\uC2DC \uC5C6\uC74C",bans:"Filter blocks \uAC00 ban \uBCF4\uB2E4 \uC6B0\uC120 \u2014 \uACF5\uAC1C incident report \uC5C6\uC74C",euAiAct:"Trust Center \uC5D0 statement",gdpr:"self-serve via Trust Center",ccpa:"standard",sourceUrls:["https://www.anthropic.com/legal/privacy","https://www.anthropic.com/legal/aup","https://trust.anthropic.com/"],lastVerified:"2026-05-23"},[ALIAS_NATIVE_GOOGLE_AI]:{confidence:"high",endpoint:"generativelanguage.googleapis.com",retention:"60 \uC77C (abuse \uBAA8\uB2C8\uD130\uB9C1; human reviewer \uAC00 input/output \uAC80\uD1A0\uD560 \uC218 \uC788\uC74C)",training:"Paid tier \uB294 \uAE30\uBCF8 opt-out. Free tier \uB294 \uBA85\uC2DC\uC801 opt-in (\uD559\uC2B5\uC5D0 \uC0AC\uC6A9\uB428)",zdr:"\uC5C6\uC74C",regions:"\uAE00\uB85C\uBC8C",fictional:"\uC815\uCC45\uC5D0 \uBA85\uC2DC \uC5C6\uC74C",nsfw:'\uD544\uD130\uB85C \uAE08\uC9C0 \u2014 "You may not attempt to bypass these protective measures"',age:'\uD544\uC218 \u2014 "You must be 18 years of age or older to use the APIs"',bans:"\uD544\uD130\uAC00 \uC751\uB2F5 \uCC28\uB2E8\uC73C\uB85C \uC791\uB3D9 \u2014 \uACC4\uC815 ban \uBCF4\uB2E4 \uC751\uB2F5 \uAC70\uBD80\uAC00 \uC77C\uBC18\uC801",euAiAct:"overarching Google Terms",gdpr:"Cloud Data Processing Addendum",ccpa:"overarching Google Terms",sourceUrls:["https://ai.google.dev/terms/","https://policies.google.com/privacy","https://cloud.google.com/terms/data-processing-addendum"],lastVerified:"2026-05-23"},"custom-google":{confidence:"high",endpoint:"\uC0AC\uC6A9\uC790 \uC124\uC815 (\uAE30\uBCF8 generativelanguage.googleapis.com/v1beta)",retention:"\uAE30\uBCF8 base URL \uC0AC\uC6A9 \uC2DC Google AI Studio \uC640 \uB3D9\uC77C (60 \uC77C)",training:"Google AI Studio \uC640 \uB3D9\uC77C (Paid opt-out / Free opt-in)",zdr:"\uC5C6\uC74C (Google AI Studio \uC640 \uB3D9\uC77C)",regions:"\uAE00\uB85C\uBC8C",fictional:"\uC815\uCC45\uC5D0 \uBA85\uC2DC \uC5C6\uC74C",nsfw:"\uD544\uD130\uB85C \uAE08\uC9C0",age:"\uD544\uC218 18+",bans:"\uD544\uD130 \uC791\uB3D9",euAiAct:"overarching Google Terms",gdpr:"Cloud DPA",ccpa:"overarching Google Terms",sourceUrls:["https://ai.google.dev/terms/"],lastVerified:"2026-05-23",notes:"base URL \uC744 \uB2E4\uB978 \uAC8C\uC774\uD2B8\uC6E8\uC774 (OpenRouter \uB4F1) \uB85C \uBC14\uAFE8\uB2E4\uBA74 \uD574\uB2F9 \uD504\uB85C\uBC14\uC774\uB354 \uC815\uCC45\uC774 \uC801\uC6A9\uB428. \uBCF8 \uC815\uBCF4\uB294 \uAE30\uBCF8 Google AI Studio \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uAE30\uC900."},"custom-claude":{confidence:"high",endpoint:"\uC0AC\uC6A9\uC790 \uC124\uC815 (\uAE30\uBCF8 api.anthropic.com/v1)",retention:"\uAE30\uBCF8 base URL \uC0AC\uC6A9 \uC2DC Anthropic \uACFC \uB3D9\uC77C (30 \uC77C)",training:"\uAE30\uBCF8 opt-out (Anthropic \uB3D9\uC77C)",zdr:"specific application (Anthropic \uB3D9\uC77C)",regions:"\uAE00\uB85C\uBC8C",fictional:"\uC815\uCC45\uC5D0 \uBA85\uC2DC \uC5C6\uC74C",nsfw:"CSAM / non-consensual \uAE08\uC9C0",age:"API \uC5D0 \uBA85\uC2DC \uC5C6\uC74C",bans:"\uD544\uD130 \uC6B0\uC120",euAiAct:"Anthropic Trust Center",gdpr:"self-serve",ccpa:"standard",sourceUrls:["https://www.anthropic.com/legal/privacy","https://www.anthropic.com/legal/aup"],lastVerified:"2026-05-23",notes:"base URL \uC744 AWS Bedrock / Vertex Claude / OpenRouter Claude \uB4F1\uC73C\uB85C \uBC14\uAFE8\uB2E4\uBA74 \uD574\uB2F9 \uAC8C\uC774\uD2B8\uC6E8\uC774 \uC815\uCC45\uC774 \uC801\uC6A9\uB428."},cohere:{confidence:"medium",endpoint:"api.cohere.com",retention:'30 \uC77C (\uC790\uB3D9 \uC0AD\uC81C). Verbatim: "For the SaaS Platform, we automatically delete logged prompts and generations after 30 days, unless we need to retain them to comply with a legal requirement or a customer contract, or unless your usage is flagged as potentially violating our terms."',training:'SaaS API \uB294 \uC790\uB3D9 \uD559\uC2B5 \uC548 \uD568. Opt-in \uC73C\uB85C\uB9CC: "Prompts and generations that you allow Cohere to use for training purposes may be retained separately." Opt-out toggle: Settings \u2192 "Data Controls".',zdr:'\uC0AC\uC6A9 \uAC00\uB2A5 (application-required, NDA-gated). Verbatim: "If you have been approved for zero data retention, Cohere does not log any customer prompts or generations."',regions:`\uAE30\uBCF8 SaaS = Google Cloud US-Central. "We do not use servers outside of the US; however, we can, in certain circumstances, configure our offering to ensure that no customer data is stored on Cohere's systems." \uBC14\uC778\uB529 EU residency \uB294 private/VPC deployments \uB9CC.`,fictional:"\uC815\uCC45\uC5D0 \uBA85\uC2DC \uC5C6\uC74C (covered indirectly under content prohibitions)",nsfw:'Cohere Labs AUP \uAC00 "engaging in, promoting, or inciting violence, threats, hate speech self-harm, sexual exploitation, or targeting of individuals based on protected characteristics" \uAE08\uC9C0. \uC77C\uBC18 \uC131\uC778 \uCF58\uD150\uCE20\uB294 \uBCC4\uB3C4 \uCE74\uD14C\uACE0\uB9AC\uB85C \uBA85\uC2DC \uC548 \uB428 \u2014 "sexual exploitation" \uB9CC \uBA85\uC2DC.',age:'\uBCC4\uB3C4 age verification \uC694\uAD6C \uBA85\uC2DC \uC5C6\uC74C. Trust Center: "Enterprise customers should not submit Personal Health Information through the Cohere SaaS services."',bans:'Usage Policy: "we are entitled to restrict, suspend, or terminate your access to the Cohere Services." \uACF5\uAC1C transparency report \uB610\uB294 incident archive \uBBF8\uBC1C\uACAC.',euAiAct:"primary source \uC5D0 \uBA85\uC2DC\uC801 EU AI Act \uC5B8\uAE09 \uBBF8\uBC1C\uACAC (Trust Center \uAC00 GDPR \uB9CC \uB2E4\uB8F8).",gdpr:'DPA \uC874\uC7AC (multi-jurisdictional, NDA-gated). Verbatim: "Cohere has a DPA. If you would like to receive a copy, we will need a signed NDA. Please contact privacy@cohere.com." \uC790\uAC00 \uBC1C\uAE09 \uBD88\uAC00.',ccpa:"Privacy Policy \uC5D0 California-specific \uC139\uC158 \uBBF8\uBA85\uC2DC. \uC77C\uBC18 data-subject rights \uB9CC \uAE30\uC220.",sourceUrls:["https://cohere.com/privacy","https://cohere.com/enterprise-data-commitments","https://docs.cohere.com/docs/usage-policy","https://cohere.com/terms-of-use","https://cohere.com/saas-agreement","https://cohere.com/security","https://trustcenter.cohere.com/"],lastVerified:"2026-05-24",notes:"\uACF5\uC2DD \uC815\uCC45 \uCD9C\uCC98\uB294 \uD655\uC778\uD588\uC73C\uB098 \uD1A0\uD070 \uB2E8\uAC00\uD45C\uB294 \uCD94\uAC00 \uD655\uC778\uC774 \uD544\uC694\uD558\uB2E4. DPA NDA-gated \uB77C GDPR \uC790\uAC00-\uC11C\uBE44\uC2A4 \uBD88\uAC00 \u2014 \uAC1C\uC778 RP \uC720\uC800\uC5D0\uAC8C\uB294 \uBD80\uB2F4\uC77C \uC218 \uC788\uC74C."},vertex:{confidence:"high",endpoint:"*-aiplatform.googleapis.com",retention:"Customer controlled (Enterprise Cloud bounds)",training:"\uAE30\uBCF8 opt-out",zdr:"Yes",regions:"Multi-region, \uACC4\uC57D\uC0C1 binding",fictional:"\uC815\uCC45\uC5D0 \uBA85\uC2DC \uC5C6\uC74C",nsfw:"GCP AUP \u2014 illegal content \uC81C\uD55C\uC774\uC9C0\uB9CC adult fiction \uAD11\uBC94\uC704 \uAE08\uC9C0 \uC5C6\uC74C",age:"\uBCC4\uB3C4 \uBA85\uC2DC \uC5C6\uC74C",bans:"\uACF5\uAC1C \uBCF4\uACE0 \uC5C6\uC74C",euAiAct:"GCP \uCEF4\uD50C\uB77C\uC774\uC5B8\uC2A4\uB85C \uB2E4\uB8F8",gdpr:"Enterprise Addendum",ccpa:"GCP",sourceUrls:["https://cloud.google.com/vertex-ai/docs/data-privacy","https://cloud.google.com/terms/aup","https://cloud.google.com/terms/data-processing-addendum","https://cloud.google.com/privacy/gdpr"],lastVerified:"2026-05-23"},"compat-ollama":{confidence:"na",endpoint:"localhost (\uC0AC\uC6A9\uC790 \uD638\uC2A4\uD305)",retention:"\uD574\uB2F9 \uC5C6\uC74C \u2014 \uB85C\uCEEC",training:"\uD574\uB2F9 \uC5C6\uC74C \u2014 \uBAA8\uB378 \uD559\uC2B5\uC740 \uC0AC\uC6A9\uC790 \uCC45\uC784",zdr:"\uD574\uB2F9 \uC5C6\uC74C \u2014 \uB370\uC774\uD130\uAC00 \uC0AC\uC6A9\uC790 \uBA38\uC2E0\uC744 \uB5A0\uB098\uC9C0 \uC54A\uC74C",regions:"\uB85C\uCEEC",fictional:"\uD574\uB2F9 \uC5C6\uC74C",nsfw:"\uD574\uB2F9 \uC5C6\uC74C \u2014 \uC0AC\uC6A9\uC790 \uC790\uC728",age:"\uD574\uB2F9 \uC5C6\uC74C",bans:"\uD574\uB2F9 \uC5C6\uC74C",euAiAct:"\uD574\uB2F9 \uC5C6\uC74C",gdpr:"\uD574\uB2F9 \uC5C6\uC74C",ccpa:"\uD574\uB2F9 \uC5C6\uC74C",sourceUrls:[],lastVerified:"2026-05-23",notes:"\uB85C\uCEEC \uC804\uC6A9. \uC678\uBD80 \uD504\uB85C\uBC14\uC774\uB354 \uC815\uCC45 \uBD80\uC7AC. \uB370\uC774\uD130\uAC00 \uC0AC\uC6A9\uC790 \uBA38\uC2E0\uC744 \uB5A0\uB098\uC9C0 \uC54A\uC74C \u2014 Blessing \uC758 \uBAA8\uB4E0 \uD504\uB85C\uBC14\uC774\uB354 \uC911 \uD504\uB77C\uC774\uBC84\uC2DC \uBA74\uC5D0\uC11C \uAC00\uC7A5 \uC548\uC804."},"compat-vercel-gateway":{confidence:"medium",endpoint:"ai-gateway.vercel.sh",retention:"request/response body \uBCF4\uC874 \uC548 \uD568 (\uC0AC\uC6A9\uC790\uAC00 \uBA85\uC2DC\uC801\uC73C\uB85C telemetry \uCF24 \uB54C \uC81C\uC678)",training:"Vercel \uC790\uCCB4\uB294 \uD559\uC2B5 \uC548 \uD568. \uB2E4\uC6B4\uC2A4\uD2B8\uB9BC \uD504\uB85C\uBC14\uC774\uB354 \uC815\uCC45 \uC801\uC6A9",zdr:"\uB2E4\uC6B4\uC2A4\uD2B8\uB9BC\uC73C\uB85C pass-through",regions:"Edge network (\uAE00\uB85C\uBC8C)",fictional:"\uC815\uCC45\uC5D0 \uBA85\uC2DC \uC5C6\uC74C (\uC560\uADF8\uB9AC\uAC8C\uC774\uD130)",nsfw:"Vercel AUP \u2014 illegal content / CSAM \uAE08\uC9C0. \uB2E4\uC6B4\uC2A4\uD2B8\uB9BC \uC815\uCC45\uC774 \uBAA8\uB378 \uCD9C\uB825 \uC81C\uC5B4",age:"\uBCC4\uB3C4 \uBA85\uC2DC \uC5C6\uC74C",bans:"\uACF5\uAC1C \uBCF4\uACE0 \uC5C6\uC74C",euAiAct:"\uC560\uADF8\uB9AC\uAC8C\uC774\uD130",gdpr:"self-serve DPA",ccpa:"standard",sourceUrls:["https://vercel.com/docs/security/privacy","https://vercel.com/legal/acceptable-use-policy","https://vercel.com/legal/dpa"],lastVerified:"2026-05-23",notes:"vercel.com \uC758 \uC815\uD655\uD55C \uC815\uCC45 path \uC7AC\uAC80\uC99D \uD544\uC694 (\uC77C\uBD80 URL\uC774 \uAC80\uC0C9 \uACB0\uACFC \uACBD\uC720)."},"compat-openrouter":{confidence:"high",endpoint:"openrouter.ai",retention:"\uC560\uADF8\uB9AC\uAC8C\uC774\uD130 \u2014 \uB2E4\uC6B4\uC2A4\uD2B8\uB9BC endpoint \uC5D0 \uC758\uC874",training:"\uD504\uB85C\uBC14\uC774\uB354\uBCC4 \uCC28\uC774. `data_collection` API \uD30C\uB77C\uBBF8\uD130\uB85C \uC81C\uC5B4",zdr:"`zdr: true` \uB77C\uC6B0\uD305 \uD50C\uB798\uADF8\uB85C ZDR \uC9C0\uC6D0 \uBC31\uC5D4\uB4DC\uB9CC \uC0AC\uC6A9",regions:"\uAE00\uB85C\uBC8C",fictional:"\uD5C8\uC6A9 \u2014 uncensored \uBAA8\uB378 \uB77C\uC6B0\uD305 \uBA85\uC2DC\uC801 \uD5C8\uC6A9",nsfw:"\uB2E4\uC6B4\uC2A4\uD2B8\uB9BC \uC758\uC874 \u2014 OpenRouter \uC790\uCCB4\uB294 illegal/CSAM \uB9CC \uAE08\uC9C0",age:"\uACC4\uC815 18+ \uD544\uC218",bans:"\uACF5\uAC1C transparency report / incident archive \uC5C6\uC74C",euAiAct:"\uC560\uADF8\uB9AC\uAC8C\uC774\uD130",gdpr:"\uC694\uCCAD \uC2DC \uC81C\uACF5",ccpa:"standard",sourceUrls:["https://openrouter.ai/privacy","https://openrouter.ai/terms","https://openrouter.ai/docs#routing"],lastVerified:"2026-05-23",notes:"\uD604\uC7AC ToS \uC758 prompt-logging IP-license \uC870\uD56D (worldwide-perpetual-irrevocable) \uC815\uD655\uD55C phrasing \uC7AC\uAC80\uC99D \uD544\uC694 \u2014 opt-in logging \uC0AC\uC6A9\uC790\uB9CC \uC601\uD5A5."},"compat-copilot":{confidence:"medium",endpoint:"api.githubcopilot.com (OAuth device flow)",retention:"28 \uC77C (abuse \uBAA8\uB2C8\uD130\uB9C1)",training:"Individual tier \uAE30\uBCF8 opt-in. \uC124\uC815\uC5D0\uC11C opt-out \uAC00\uB2A5",zdr:"\uC5C6\uC74C",regions:"\uAE00\uB85C\uBC8C",fictional:"\uAE08\uC9C0 (out-of-scope use) \u2014 \uCF54\uB4DC \uAC1C\uBC1C\uC6A9 \uB77C\uC774\uC120\uC2A4. \uC77C\uBC18 chat backend \uC0AC\uC6A9\uC740 GitHub AUP \uC77C\uBC18 \uC81C\uC57D \uC704\uBC18",nsfw:'\uAE08\uC9C0 \u2014 "You may not use GitHub Copilot to generate... sexually explicit content"',age:"\uBCC4\uB3C4 \uBA85\uC2DC \uC5C6\uC74C",bans:"Microsoft RAI Transparency Report \uAC00 Copilot \uC704\uD5D8 \uB9E4\uD551 \uB2E4\uB8E8\uC9C0\uB9CC \uAC1C\uBCC4 ban \uC5F4\uAC70 \uC548 \uD568",euAiAct:"Microsoft \uAC00 \uB2E4\uB8F8",gdpr:"Microsoft DPA",ccpa:"standard",sourceUrls:["https://docs.github.com/en/copilot/responsible-use","https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies","https://docs.github.com/en/site-policy/github-terms/github-terms-of-service","https://github.com/trust-center"],lastVerified:"2026-05-23",notes:"Roleplay \uC6A9\uB3C4\uB294 Copilot ToS \uC704\uBC18 \uC704\uD5D8 \u2014 \uACC4\uC815 \uC815\uC9C0 \uC0AC\uB840 \uBCF4\uACE0\uB428. \uC0AC\uC6A9 \uC2DC \uBCF8\uC778 \uCC45\uC784."},"compat-deepseek":{confidence:"medium",endpoint:"api.deepseek.com",retention:"\uBC95\uB839 / abuse \uBAA8\uB2C8\uD130\uB9C1 \uBAA9\uC801 \uBCF4\uC874",training:"API \uAE30\uBCF8 opt-out",zdr:"\uBA85\uC2DC \uC5C6\uC74C",regions:"Mainland China",fictional:"\uC815\uCC45\uC5D0 \uBA85\uC2DC \uC5C6\uC74C",nsfw:"PRC \uBC95\uB839 \uC801\uC6A9 \u2014 \uD45C\uC900 illegal content \uC81C\uC57D",age:"\uD544\uC218 \u2014 18 \uC138 \uBBF8\uB9CC \uD398\uB974\uC18C\uB098 prompt \uAE08\uC9C0",bans:"\uACF5\uAC1C \uBCF4\uACE0 \uC5C6\uC74C",euAiAct:"\uBA85\uC2DC \uC5C6\uC74C",gdpr:"Privacy policy \uAC00 GDPR \uC815\uB82C \uC8FC\uC7A5",ccpa:"\uBA85\uC2DC \uC5C6\uC74C",sourceUrls:["https://platform.deepseek.com/policies/privacy","https://platform.deepseek.com/policies/terms"],lastVerified:"2026-05-23",notes:"PRC \uB370\uC774\uD130 \uB85C\uCEEC\uB77C\uC774\uC81C\uC774\uC158 \u2014 \uB370\uC774\uD130\uAC00 \uBCF8\uD1A0 \uB77C\uC6B0\uD305. NSFW \uB864\uD50C\uB808\uC774\uB294 \uC790\uB3D9 \uCC28\uB2E8 \uC704\uD5D8."},"compat-glm":{confidence:"medium",endpoint:"open.bigmodel.cn",retention:"PRC generative AI \uADDC\uC81C\uB85C \uCD5C\uC18C 6 \uAC1C\uC6D4 \uBCF4\uC874",training:"opt-out \uAC00\uB2A5",zdr:"\uC5C6\uC74C",regions:"Mainland China",fictional:"\uC815\uCC45\uC5D0 \uBA85\uC2DC \uC5C6\uC74C",nsfw:'PRC \uBC95\uB839 \uC5C4\uACA9 \uC801\uC6A9 \u2014 "pornographic, vulgar, or obscene content" \uBA85\uC2DC \uAE08\uC9C0',age:"\uACC4\uC815 \uC2E0\uC6D0 \uC778\uC99D \uD544\uC218",bans:"\uACF5\uAC1C \uBCF4\uACE0 \uC5C6\uC74C (NSFW \uCD9C\uB825 \uC790\uB3D9 ban \uAD00\uCC30\uB428)",euAiAct:"\uBA85\uC2DC \uC5C6\uC74C",gdpr:"\uBA85\uC2DC \uC5C6\uC74C",ccpa:"\uBA85\uC2DC \uC5C6\uC74C",sourceUrls:["https://open.bigmodel.cn/terms","https://open.bigmodel.cn/privacy"],lastVerified:"2026-05-23",notes:"PRC \uBC95\uB839\uC0C1 NSFW \uC808\uB300 \uAE08\uC9C0. \uC815\uCC45 \uD398\uC774\uC9C0 zh-CN only \u2014 JS-rendered SPA."},"compat-zai":{confidence:"low",endpoint:"api.z.ai",retention:'\uAD6C\uCCB4 \uAE30\uAC04 \uBBF8\uACF5\uAC1C \u2014 "for the period necessary"',training:"\uB450 tier: API/Enterprise = \uAE30\uBCF8 no-train. Individual = train (opt-out \uBBF8\uBA85\uC2DC)",zdr:"primary source not found",regions:"**Singapore** (\uC8FC\uC694 \uCC98\uB9AC location)",fictional:"primary source not found",nsfw:'**\uAE08\uC9C0** \u2014 Terms \xA7IV item (j) verbatim: "creating or disseminating content that is sexually explicit, suggestive, visually shocking, or otherwise disturbing." \uC77C\uBC18 \uC131\uC778 explicit \uCF58\uD150\uCE20 \uBA85\uC2DC \uAE08\uC9C0.',age:"18 \uC138 \uBBF8\uB9CC \uB300\uC0C1 \uC11C\uBE44\uC2A4 \uC544\uB2D8 (\uC790\uAC00 \uC120\uC5B8)",bans:"\uACF5\uAC1C \uBCF4\uACE0 \uC5C6\uC74C",euAiAct:"primary source not found",gdpr:"API \uC11C\uBE44\uC2A4\uC6A9 DPA \uC874\uC7AC. EU \uC0AC\uC6A9\uC790 \uBCC4\uB3C4 \uBA85\uC2DC \uC5C6\uC74C",ccpa:"primary source not found",sourceUrls:["https://chat.z.ai/legal-agreement/privacy-policy","https://chat.z.ai/legal-agreement/terms-of-service","https://docs.z.ai/legal-agreement/terms-of-use"],lastVerified:"2026-05-24",notes:"**\uC815\uCC45 \uD655\uC778 (2026-05-24)**: \uACF5\uC2DD \uCD9C\uCC98 (Z.ai Terms \xA7IV item j) \uAC00 \uC77C\uBC18 explicit \uCF58\uD150\uCE20 \uAD11\uBC94\uC704 \uAE08\uC9C0. RP \uC0AC\uC6A9\uC790 \uC8FC\uC758. **Ownership**: \uB370\uC774\uD130 \uCEE8\uD2B8\uB864\uB7EC = Jingsheng Hengxing Technology PTE. LTD. (\uC2F1\uAC00\uD3EC\uB974). \uBAA8\uD68C\uC0AC = Knowledge Atlas Technology JSC Ltd. (HKEX 2513, \uC61B Beijing Zhipu Huazhang Technology / \u667A\u8C31AI). Zhipu group \uC758 \uD574\uC678 \uBE0C\uB79C\uB4DC. Singapore \uCC98\uB9AC\uC774\uC9C0\uB9CC PRC \uBAA8\uD68C\uC0AC \uC0AC\uC2AC \uC778\uC9C0 \uD544\uC694."},"compat-hunyuan":{confidence:"low",endpoint:"api.hunyuan.cloud.tencent.com",retention:"abuse / PRC \uCEF4\uD50C\uB77C\uC774\uC5B8\uC2A4 (\uC77C\uBC18\uC801\uC73C\uB85C 6 \uAC1C\uC6D4) \u2014 **inference, \uCD94\uAC00 \uD655\uC778 \uD544\uC694**",training:"Tencent Cloud \uD45C\uC900 \uC57D\uAD00 \u2014 \uBAA8\uB378 \uAC1C\uC120\uC5D0 \uC0AC\uC6A9\uB420 \uC218 \uC788\uC74C (inference)",zdr:"\uC5C6\uC74C (inference)",regions:"Mainland China",fictional:"\uC815\uCC45\uC5D0 \uBA85\uC2DC \uC5C6\uC74C (PRC \uC77C\uBC18 AUP \uD328\uD134)",nsfw:'PRC \uBC95\uB839 \uC801\uC6A9 \u2014 "pornography, gambling, drug abuse" \uBA85\uC2DC \uAE08\uC9C0 (inference)',age:"API \uC2E0\uC6D0 \uC778\uC99D \uD544\uC218 (inference)",bans:"\uACF5\uAC1C \uBCF4\uACE0 \uC5C6\uC74C",euAiAct:"\uBA85\uC2DC \uC5C6\uC74C",gdpr:"Tencent Cloud International \uB85C \uB2E4\uB8F8 (inference)",ccpa:"\uBA85\uC2DC \uC5C6\uC74C",sourceUrls:[],lastVerified:"2026-05-23",notes:'**\uCD9C\uCC98 \uD655\uC778 \uD544\uC694**: \uAE30\uC874 URL (cloud.tencent.com/document/product/1729/97732) \uC740 "API \uAC1C\uC694" \uBB38\uC11C \u2014 \uC815\uCC45 \uD398\uC774\uC9C0 \uC544\uB2D8. \uBAA8\uB4E0 \uC140\uC774 inference. \uC815\uCC45\uC758 \uACF5\uC2DD \uCD9C\uCC98 \uC7AC\uD655\uC778 \uD544\uC694.'},"compat-groq":{confidence:"medium",endpoint:"api.groq.com",retention:"30 \uC77C (abuse \uBAA8\uB2C8\uD130\uB9C1)",training:"Groq \uB294 inference \uD638\uC2A4\uD305\uB9CC \u2014 \uC0AC\uC6A9\uC790 API \uB370\uC774\uD130\uB85C \uBAA8\uB378 \uD559\uC2B5 \uC548 \uD568",zdr:"\uBA85\uC2DC \uC5C6\uC74C",regions:"US",fictional:"\uC815\uCC45\uC5D0 \uBA85\uC2DC \uC5C6\uC74C",nsfw:"Open model \uC81C\uC57D \uC801\uC6A9 \u2014 illegal content \uAE08\uC9C0",age:"\uBCC4\uB3C4 \uBA85\uC2DC \uC5C6\uC74C",bans:"\uACF5\uAC1C \uBCF4\uACE0 \uC5C6\uC74C",euAiAct:"\uBA85\uC2DC \uC5C6\uC74C",gdpr:"self-serve",ccpa:"standard",sourceUrls:["https://groq.com/privacy-policy","https://groq.com/terms-of-use"],lastVerified:"2026-05-23",notes:"Website Terms \uC640 API/Cloud Services Agreement \uBCC4\uB3C4. API \uC0AC\uC6A9\uC740 console.groq.com/docs/legal/services-agreement."},"compat-cerebras":{confidence:"medium",endpoint:"api.cerebras.ai (canonical www.cerebras.ai)",retention:"30 \uC77C (telemetry / abuse)",training:"Cerebras \uB294 customer inference \uB370\uC774\uD130\uB85C \uBAA8\uB378 \uD559\uC2B5 \uC548 \uD568",zdr:"\uBA85\uC2DC \uC5C6\uC74C",regions:"US",fictional:"\uC815\uCC45\uC5D0 \uBA85\uC2DC \uC5C6\uC74C",nsfw:'**Silent** \u2014 \uC131\uC778 \uCF58\uD150\uCE20 \uBCC4\uB3C4 \uC870\uD56D \uC5C6\uC74C. \uC77C\uBC18 illegality + "harmful or objectionable (in our sole discretion)" catch-all \uB9CC \uC801\uC6A9',age:"\uBCC4\uB3C4 \uBA85\uC2DC \uC5C6\uC74C",bans:"\uACF5\uAC1C \uBCF4\uACE0 \uC5C6\uC74C",euAiAct:"\uBA85\uC2DC \uC5C6\uC74C",gdpr:"\uC0AC\uC6A9 \uAC00\uB2A5",ccpa:"standard",sourceUrls:["https://www.cerebras.ai/privacy-policy","https://www.cerebras.ai/terms-of-service"],lastVerified:"2026-05-23"},"compat-together":{confidence:"medium",endpoint:"api.together.xyz",retention:"\uBCF4\uC548 / abuse \uBAA8\uB2C8\uD130\uB9C1 \uBAA9\uC801 \uB85C\uAE45",training:"API \uB370\uC774\uD130\uB85C \uD559\uC2B5 \uC548 \uD568",zdr:"\uBA85\uC2DC \uC5C6\uC74C",regions:"US",fictional:"\uD5C8\uC6A9 \u2014 roleplay \uC5D0 \uAD11\uBC94\uC704 \uC0AC\uC6A9\uB418\uB294 open-weights \uBAA8\uB378 \uD638\uC2A4\uD305",nsfw:"\uB2E4\uC6B4\uC2A4\uD2B8\uB9BC open model AUP \uC801\uC6A9 \u2014 \uD45C\uC900 illegal content \uAE08\uC9C0",age:"\uBCC4\uB3C4 \uBA85\uC2DC \uC5C6\uC74C",bans:"\uACF5\uAC1C \uBCF4\uACE0 \uC5C6\uC74C",euAiAct:"\uBA85\uC2DC \uC5C6\uC74C",gdpr:"\uC0AC\uC6A9 \uAC00\uB2A5",ccpa:"standard",sourceUrls:["https://www.together.ai/privacy","https://www.together.ai/terms-of-service"],lastVerified:"2026-05-23"},"compat-mistral":{confidence:"high",endpoint:"api.mistral.ai (canonical legal.mistral.ai for terms)",retention:"30 \uC77C (abuse \uBAA8\uB2C8\uD130\uB9C1)",training:"API \uB370\uC774\uD130 \uAE30\uBCF8 opt-out",zdr:"self-serve \uC544\uB2D8",regions:"EU (France) / US",fictional:"\uC815\uCC45\uC5D0 \uBA85\uC2DC \uC5C6\uC74C",nsfw:'**\uC81C\uD55C\uC801** \u2014 \uC77C\uBC18 "sexually explicit" \uAE08\uC9C0 \uC870\uD56D *\uC81C\uAC70\uB428* (2026-03-25 \uC815\uCC45 \uC7AC\uC791\uC131). \uD604\uC7AC\uB294 3 \uAC1C \uC881\uC740 \uAE08\uC9C0: (1) CSAM, (2) Non-consensual intimate imagery (NCII), (3) Sex trafficking/services. **\uC131\uC778-\uD569\uC758-explicit \uC77C\uBC18 \uAE08\uC9C0 \uC5C6\uC74C**',age:"\uBCC4\uB3C4 \uBA85\uC2DC \uC5C6\uC74C",bans:"\uACF5\uAC1C \uBCF4\uACE0 \uC5C6\uC74C",euAiAct:"Mistral.ai/news \uC5D0 statements",gdpr:"EU \uBC95 \uC9C1\uC811 \uC801\uC6A9",ccpa:"standard",sourceUrls:["https://legal.mistral.ai/terms","https://legal.mistral.ai/terms/usage-policy","https://mistral.ai/news/"],lastVerified:"2026-05-23",notes:"Mistral \uC815\uCC45 2026-03-25 \uC7AC\uC791\uC131\uC73C\uB85C NSFW \uC77C\uBC18 \uAE08\uC9C0\uAC00 \uC881\uC544\uC9D0 (CSAM/NCII/sex-trafficking \uB9CC). \uD569\uC758-\uC131\uC778 explicit \uCF58\uD150\uCE20\uB294 *\uC815\uCC45\uC0C1* \uD5C8\uC6A9. \uB2E4\uB9CC \uBAA8\uB378 \uAC00\uB4DC\uB808\uC77C\uC740 \uBCC4\uAC1C\uB85C \uB3D9\uC791."},"compat-xai":{confidence:"medium",endpoint:"api.x.ai (canonical x.ai/legal/*)",retention:"30 \uC77C (abuse \uBAA8\uB2C8\uD130\uB9C1)",training:"opt-out \uC0AC\uC6A9 \uAC00\uB2A5",zdr:"\uBA85\uC2DC \uC5C6\uC74C",regions:"US",fictional:"\uD5C8\uC6A9 \u2014 \uC81C\uD488 \uD3EC\uC9C0\uC154\uB2DD\uC0C1 \uAD11\uBC94\uC704 \uD5C8\uC6A9",nsfw:"\uD5C8\uC6A9 (\uC81C\uD55C \uC788\uC74C) \u2014 \uD569\uBC95\uC778 \uD55C \uC77C\uBC18\uC801\uC73C\uB85C \uD5C8\uC6A9. CSAM \uAE08\uC9C0",age:"API \uACC4\uC815 18+ \uD544\uC218",bans:"\uACF5\uAC1C \uBCF4\uACE0 \uC5C6\uC74C",euAiAct:"\uBA85\uC2DC \uC5C6\uC74C",gdpr:"\uC0AC\uC6A9 \uAC00\uB2A5",ccpa:"standard",sourceUrls:["https://x.ai/legal/privacy-policy","https://x.ai/legal/terms-of-service"],lastVerified:"2026-05-23",notes:"cached input multiplier (~85% \uC808\uAC10) \uC740 third-party tracker \uC778\uC6A9 \u2014 xAI primary source \uBBF8\uD655\uC778."},"compat-fireworks":{confidence:"low",endpoint:"api.fireworks.ai",retention:"primary source not found",training:"API \uB370\uC774\uD130\uB85C \uD559\uC2B5 \uC548 \uD568 (general policy, primary \uBBF8\uD655\uC778)",zdr:"primary source not found",regions:"US (inference \uD638\uC2A4\uD2B8)",fictional:"open-weight \uBAA8\uB378 \uD638\uC2A4\uD305 \u2014 \uB2E4\uC6B4\uC2A4\uD2B8\uB9BC \uC815\uCC45 \uC801\uC6A9",nsfw:"\uB2E4\uC6B4\uC2A4\uD2B8\uB9BC \uBAA8\uB378 AUP \uC801\uC6A9",age:"\uBCC4\uB3C4 \uBA85\uC2DC \uC5C6\uC74C",bans:"\uACF5\uAC1C \uBCF4\uACE0 \uC5C6\uC74C",euAiAct:"primary source not found",gdpr:"primary source not found",ccpa:"primary source not found",sourceUrls:[],lastVerified:"2026-05-23",notes:"\uC2E0\uADDC \uD504\uB9AC\uC14B \u2014 primary-source \uC815\uCC45 \uAC80\uC99D \uBBF8\uC218\uD589. \uC0AC\uC6A9 \uC804 https://fireworks.ai/legal \uC9C1\uC811 \uD655\uC778 \uAD8C\uC7A5."},"compat-nvidia":{confidence:"low",endpoint:"integrate.api.nvidia.com",retention:"primary source not found",training:"primary source not found",zdr:"primary source not found",regions:"US (NVIDIA cloud)",fictional:"primary source not found",nsfw:"\uB2E4\uC6B4\uC2A4\uD2B8\uB9BC \uBAA8\uB378 AUP \uC801\uC6A9 \u2014 Llama / Nemotron \uB4F1 \uAC01\uC790\uC758 \uB77C\uC774\uC120\uC2A4",age:"\uBCC4\uB3C4 \uBA85\uC2DC \uC5C6\uC74C",bans:"\uACF5\uAC1C \uBCF4\uACE0 \uC5C6\uC74C",euAiAct:"primary source not found",gdpr:"primary source not found",ccpa:"primary source not found",sourceUrls:[],lastVerified:"2026-05-23",notes:"\uC2E0\uADDC \uD504\uB9AC\uC14B \u2014 primary-source \uC815\uCC45 \uAC80\uC99D \uBBF8\uC218\uD589. \uC0AC\uC6A9 \uC804 https://www.nvidia.com/en-us/about-nvidia/privacy-policy/ + build.nvidia.com \uC57D\uAD00 \uD655\uC778 \uAD8C\uC7A5."}};function getPrivacyFor(providerId){return PROVIDER_PRIVACY[providerId]||null}function formatPrivacyDetail(record){if(!record)return"\uC815\uCC45 \uC815\uBCF4\uAC00 \uB4F1\uB85D\uB418\uC9C0 \uC54A\uC740 \uD504\uB85C\uBC14\uC774\uB354\uC785\uB2C8\uB2E4.";if(record.confidence==="na")return["Ollama \uB294 \uB85C\uCEEC \uBA38\uC2E0\uC5D0\uC11C \uC9C1\uC811 \uBAA8\uB378\uC744 \uD638\uC2A4\uD305\uD569\uB2C8\uB2E4.","\uB370\uC774\uD130\uAC00 \uC0AC\uC6A9\uC790\uC758 \uBA38\uC2E0\uC744 \uB5A0\uB098\uC9C0 \uC54A\uC73C\uBBC0\uB85C \uC678\uBD80 \uD504\uB85C\uBC14\uC774\uB354 \uC815\uCC45\uC774 \uC801\uC6A9\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",record.notes||""].filter(Boolean).join(` | |
| `);let lines=[];if(lines.push(`[${record.confidence.toUpperCase()}] ${record.endpoint}`),lines.push(`\uB9C8\uC9C0\uB9C9 \uAC80\uC99D: ${record.lastVerified}`),lines.push(""),lines.push(`\uBCF4\uC874: ${record.retention}`),lines.push(`\uD559\uC2B5 \uC0AC\uC6A9: ${record.training}`),lines.push(`ZDR: ${record.zdr}`),lines.push(`Region: ${record.regions}`),lines.push(""),lines.push(`Fiction/Roleplay: ${record.fictional}`),lines.push(`NSFW/\uC131\uC778: ${record.nsfw}`),lines.push(`\uC5F0\uB839: ${record.age}`),lines.push(`\uC815\uC9C0 \uC0AC\uB840: ${record.bans}`),lines.push(""),lines.push(`EU AI Act: ${record.euAiAct}`),lines.push(`GDPR/DPA: ${record.gdpr}`),lines.push(`CCPA: ${record.ccpa}`),record.sourceUrls&&record.sourceUrls.length>0){lines.push(""),lines.push("\uCD9C\uCC98:");for(let u of record.sourceUrls)lines.push(`\xB7 ${u}`)}return record.notes&&(lines.push(""),lines.push(`\uC8FC\uC758: ${record.notes}`)),lines.join(` | |
| `)}function providerKey(providerId,field){return`${STORAGE_PREFIX}provider.${providerId}.${field}`}async function getProviderSetting(providerId,field,fallback=""){try{let v=await getStorage().getItem(providerKey(providerId,field));return v??fallback}catch{return fallback}}async function setProviderSetting(providerId,field,value){try{let stored=typeof value=="boolean"?String(value):value;await getStorage().setItem(providerKey(providerId,field),stored)}catch{}}async function loadProviderSettings(providerId,schema){let out={};for(let f of schema)out[f.key]=await getProviderSetting(providerId,f.key,f.default??"");return out}async function loadAllProviderSettings(){let storage=getStorage(),out=new Map,keys;try{keys=await storage.keys()||[]}catch{return out}let prefix=`${STORAGE_PREFIX}provider.`,relevant=[];for(let k of keys)typeof k=="string"&&k.startsWith(prefix)&&relevant.push(k);if(relevant.length===0)return out;let values=await Promise.all(relevant.map(k=>storage.getItem(k).catch(()=>null)));for(let i=0;i<relevant.length;i++){let rest=relevant[i].slice(prefix.length),dot=rest.indexOf(".");if(dot<=0)continue;let providerId=rest.slice(0,dot),fieldKey=rest.slice(dot+1);out.set(`${providerId}/${fieldKey}`,values[i])}return out}var VISIBLE_PROVIDERS_KEY=`${STORAGE_PREFIX}ui.visibleProviders`;async function loadVisibleProviderIds(){try{let raw=await getStorage().getItem(VISIBLE_PROVIDERS_KEY);if(raw==null||raw==="")return new Set;let parsed=typeof raw=="string"?JSON.parse(raw):raw;return Array.isArray(parsed)?new Set(parsed.filter(x=>typeof x=="string"&&x)):new Set}catch{return new Set}}async function saveVisibleProviderIds(ids){try{let arr=[...new Set([...ids].filter(x=>typeof x=="string"&&x))];await getStorage().setItem(VISIBLE_PROVIDERS_KEY,JSON.stringify(arr))}catch{}}async function setProviderVisible(providerId,visible){let cur=await loadVisibleProviderIds();visible?cur.add(providerId):cur.delete(providerId),await saveVisibleProviderIds(cur)}var SUBMODEL_FIELD="submodels";async function loadSubmodels(providerId){let raw=await getProviderSetting(providerId,SUBMODEL_FIELD,"");if(!raw)return[];try{let parsed=typeof raw=="string"?JSON.parse(raw):raw;if(!Array.isArray(parsed))return[];let out=[];for(let item of parsed){if(!item||typeof item!="object")continue;let model=typeof item.model=="string"?item.model.trim():"";if(!model)continue;let label=typeof item.label=="string"?item.label.trim():"";out.push(label?{label,model}:{model})}return out}catch{return[]}}async function saveSubmodels(providerId,list){let clean=(Array.isArray(list)?list:[]).map(it=>({label:typeof it?.label=="string"?it.label.trim():"",model:typeof it?.model=="string"?it.model.trim():""})).filter(it=>it.model!=="").map(it=>it.label?it:{model:it.model});return await getStorage().setItem(providerKey(providerId,SUBMODEL_FIELD),JSON.stringify(clean)),clean}var streamOutcomes=new WeakMap;function getStreamOutcome(stream){return streamOutcomes.get(stream)}function createTokenStream(producer,abortSignal){let outcome={ok:!0},fail=message=>{outcome.ok=!1,outcome.error||=String(message||"\uC751\uB2F5 \uCC98\uB9AC \uC2E4\uD328")},stream=new ReadableStream({async start(controller){let enqueue=chunk=>{String(chunk??"").length&&controller.enqueue(String(chunk))},ctx={enqueue,fail:(message,emit=!0)=>{fail(message),emit&&enqueue(message)},signal:abortSignal},onAbort=()=>{fail("aborted");try{controller.error(new Error("aborted"))}catch{}};try{if(abortSignal?.aborted){onAbort();return}abortSignal?.addEventListener("abort",onAbort,{once:!0}),await producer(ctx),controller.close()}catch(err){fail(err?.message||err);try{controller.error(err)}catch{}}finally{abortSignal?.removeEventListener("abort",onAbort)}},cancel(){fail("cancelled")}});return streamOutcomes.set(stream,outcome),stream}async function fetchWithFallback(url,init){throwIfAborted(init?.signal);let retryConfig=(()=>{if(!init?.retry)return null;let r=init.retry===!0?{}:init.retry;return{maxAttempts:Number.isFinite(r.maxAttempts)?Math.max(1,Math.floor(r.maxAttempts)):3,retryOn:Array.isArray(r.retryOn)?new Set(r.retryOn):new Set([429,500,502,503,504]),baseDelayMs:Number.isFinite(r.baseDelayMs)?r.baseDelayMs:1e3}})(),cleanInit=init?{...init}:void 0;if(cleanInit&&"retry"in cleanInit&&delete cleanInit.retry,!retryConfig)return _fetchSingle(url,cleanInit);for(let attempt=0;attempt<retryConfig.maxAttempts;attempt++){throwIfAborted(init.signal);let res=await _fetchSingle(url,cleanInit);if(res.ok||!retryConfig.retryOn.has(res.status)||attempt===retryConfig.maxAttempts-1)return res;let delay=parseRetryAfter(res.headers?.get?.("Retry-After"))??retryConfig.baseDelayMs*Math.pow(2,attempt);console.warn(`[Blessing] HTTP ${res.status}: retry ${attempt+1}/${retryConfig.maxAttempts-1} in ${delay}ms`),res.body?.cancel().catch(()=>{}),await sleepWithAbort(delay,init.signal)}throw new Error("Retry exhausted")}function parseRetryAfter(header){if(!header)return null;let seconds=/^\d+(?:\.\d+)?$/.test(header.trim())?Number(header):NaN;if(Number.isFinite(seconds)&&seconds>=0)return seconds*1e3;let date=Date.parse(header);if(Number.isFinite(date)){let diff=date-Date.now();return diff>0?diff:0}return null}function throwIfAborted(signal){if(signal?.aborted)throw new Error("aborted")}function sleepWithAbort(ms,signal){return new Promise((resolve,reject)=>{if(signal?.aborted)return reject(new Error("aborted"));let timer=setTimeout(()=>{signal?.removeEventListener("abort",abort),resolve()},ms),abort=()=>{clearTimeout(timer),signal.removeEventListener("abort",abort),reject(new Error("aborted"))};signal?.addEventListener("abort",abort,{once:!0})})}function canChangeTransport(error,method,signal,apiMethod){return throwIfAborted(signal),/^(AbortError|TimeoutError)$/.test(error?.name)||/^(aborted|STREAM_TIMEOUT)$/.test(error?.message)?!1:error?.message===`API method ${apiMethod} not found`?!0:/^(GET|HEAD)$/.test(method)}async function _fetchSingle(url,init){throwIfAborted(init?.signal);let host=getHost(),attemptErrors=[],method=String(init?.method||"GET").toUpperCase(),proxyRejectSet=new Set(/^(GET|HEAD)$/.test(method)&&Array.isArray(init?.proxyRejectStatuses)?init.proxyRejectStatuses:[]),fetchInit=init?{...init}:void 0;if(fetchInit&&"proxyRejectStatuses"in fetchInit&&delete fetchInit.proxyRejectStatuses,fetchInit&&"loggedFetch"in fetchInit&&delete fetchInit.loggedFetch,init?.loggedFetch===!0&&host&&typeof host.risuFetch=="function")try{let logged=await risuFetchAsResponse(host,url,fetchInit);if(logged)return logged}catch(err){if(!canChangeTransport(err,method,init?.signal,"risuFetch"))throw err;let msg=err?.message||String(err);attemptErrors.push(`risuFetch: ${msg}`);try{console.warn("[Blessing] risuFetch unavailable; changing transport")}catch{}}if(host&&typeof host.nativeFetch=="function")try{throwIfAborted(init?.signal);let res=await host.nativeFetch(url,fetchInit);if(proxyRejectSet.size>0&&proxyRejectSet.has(res.status)){res.body?.cancel().catch(()=>{}),attemptErrors.push(`nativeFetch: proxy-reject HTTP ${res.status}`);try{console.warn(`[Blessing] host.nativeFetch \uAC00 HTTP ${res.status} \uBC18\uD658 \u2192 \uD3F4\uBC31`)}catch{}}else return res}catch(err){if(!canChangeTransport(err,method,init?.signal,"nativeFetch"))throw err;let msg=err?.message||String(err);attemptErrors.push(`nativeFetch: ${msg}`);try{console.warn("[Blessing] transport unavailable")}catch{}}if(host&&typeof host.fetch=="function")try{throwIfAborted(init?.signal);let res=await host.fetch(url,fetchInit);if(proxyRejectSet.size>0&&proxyRejectSet.has(res.status)){res.body?.cancel().catch(()=>{}),attemptErrors.push(`host.fetch: proxy-reject HTTP ${res.status}`);try{console.warn(`[Blessing] host.fetch \uAC00 HTTP ${res.status} \uBC18\uD658 \u2192 \uD3F4\uBC31`)}catch{}}else return res}catch(err){if(!canChangeTransport(err,method,init?.signal,"fetch"))throw err;let msg=err?.message||String(err);attemptErrors.push(`host.fetch: ${msg}`);try{console.warn("[Blessing] transport unavailable")}catch{}}try{return throwIfAborted(init?.signal),await fetch(url,fetchInit)}catch(err){let msg=err?.message||String(err);attemptErrors.push(`fetch: ${msg}`);try{console.warn("[Blessing] direct fetch failed")}catch{}throw attemptErrors.length>1?new Error(`${msg} [\uC2DC\uB3C4: ${attemptErrors.join(" / ")}]`):err}}async function risuFetchAsResponse(host,url,fetchInit){let signal=fetchInit?.signal;if(signal?.aborted)throw new Error("aborted");let bodyObj=fetchInit?.body;if(typeof bodyObj=="string")if(bodyObj==="")bodyObj=void 0;else try{bodyObj=JSON.parse(bodyObj)}catch{return null}let arg={body:bodyObj,headers:fetchInit?.headers,method:fetchInit?.method||"POST"},onAbort,racers=[host.risuFetch(url,arg)];signal&&racers.push(new Promise((_,reject)=>{onAbort=()=>reject(new Error("aborted")),signal.addEventListener("abort",onAbort,{once:!0})}));let r;try{r=await Promise.race(racers)}finally{signal&&onAbort&&signal.removeEventListener("abort",onAbort)}if(signal?.aborted)throw new Error("aborted");if(!r||typeof r!="object")throw new Error("Invalid risuFetch response");if(!r.ok&&r.data==="aborted")throw new Error("aborted");let status=Number.isFinite(r.status)?r.status:r.ok?200:400,payload=r.data==null?"":typeof r.data=="string"?r.data:(()=>{try{return JSON.stringify(r.data)}catch{return String(r.data)}})();try{return new Response(payload,{status,headers:{"content-type":"application/json"}})}catch{return new Response(payload,{status:200,headers:{"content-type":"application/json"}})}}async function*parseSSE(response,abortSignal,opts={}){if(!response.ok){let text=await response.text().catch(()=>"");throw new Error(`HTTP ${response.status}: ${text||response.statusText}`)}if(throwIfAborted(abortSignal),!response.body)throw new Error("Empty response body");let reader=response.body.getReader(),decoder=new TextDecoder("utf-8"),timeoutMs=Number.isFinite(opts.timeoutMs)?opts.timeoutMs:3e4,buf="",block="",finished=!1;try{for(;!finished;){throwIfAborted(abortSignal);let timer,abort,stopped=new Promise((_,reject)=>{abort=()=>reject(new Error("aborted")),abortSignal?.addEventListener("abort",abort,{once:!0}),timeoutMs>0&&(timer=setTimeout(()=>reject(new Error("STREAM_TIMEOUT")),timeoutMs))}),result;try{result=await Promise.race([reader.read(),stopped])}finally{clearTimeout(timer),abortSignal?.removeEventListener("abort",abort)}for(throwIfAborted(abortSignal),finished=result.done,buf+=finished?decoder.decode():decoder.decode(result.value,{stream:!0});;){let match=/[\r\n]/.exec(buf);if(!match)break;let at=match.index;if(buf[at]==="\r"&&at===buf.length-1&&!finished)break;let line=buf.slice(0,at),width=buf[at]==="\r"&&buf[at+1]===` | |
| `?2:1;if(buf=buf.slice(at+width),line!==""){block+=line+` | |
| `;continue}let msg2=parseSSEBlock(block);if(block="",msg2&&(yield msg2,msg2.data==="[DONE]"))return}}let msg=parseSSEBlock(block+buf);msg&&(yield msg)}finally{if(!finished)try{reader.cancel().catch(()=>{})}catch{}try{reader.releaseLock()}catch{}}}function parseSSEBlock(block){let event,dataLines=[];for(let raw of block.split(/\r?\n/)){if(!raw||raw.startsWith(":"))continue;let colon=raw.indexOf(":"),field=colon===-1?raw:raw.slice(0,colon),val=colon===-1?"":raw.slice(colon+1);val.startsWith(" ")&&(val=val.slice(1)),field==="event"?event=val:field==="data"&&dataLines.push(val)}return dataLines.length===0?null:{event,data:dataLines.join(` | |
| `)}}function toOpenAIMessages(pluginRequest){let arr=pluginRequest?.prompt_chat||pluginRequest?.messages||[];return Array.isArray(arr)?arr.map(m=>{let role=m?.role||"user",content=m?.content;return Array.isArray(content)&&(content=content.map(p=>typeof p=="string"?p:p?.text||"").filter(Boolean).join(` | |
| `)),typeof content!="string"&&(content=String(content??"")),{role,content}}).filter(m=>m.content||m.role==="assistant"):[]}function normalizeOpenAIBase(value,fallback){let s=String(value||"").trim();return s?(s=s.replace(/\/+$/,""),s=s.replace(/\/(chat\/completions|completions|responses|embeddings|images\/generations)$/i,""),s):fallback}function normalizeClaudeBase(value,fallback){let s=String(value||"").trim();return s?(s=s.replace(/\/+$/,""),s=s.replace(/\/(messages|complete)$/i,""),s):fallback}function normalizeGoogleBase(value,fallback){let s=String(value||"").trim();return s?(s=s.replace(/\/+$/,""),s=s.replace(/\/models(\/[^/]+)?(:generateContent|:streamGenerateContent)?$/i,""),s):fallback}function needsMaxCompletionTokens(modelId){let id=String(modelId||"").toLowerCase().trim();if(!id)return!1;let tail=id.includes("/")?id.slice(id.lastIndexOf("/")+1):id;return/^(o[134]|gpt-[56])(?:[-._]|$)/.test(tail)}var STREAMING_PREF_KEY=`${STORAGE_PREFIX}ui.useStreaming`,DEFAULT_STREAMING=!0;async function loadStreamingPreference(){try{let raw=await getStorage().getItem(STREAMING_PREF_KEY);return raw==null||raw===""?DEFAULT_STREAMING:raw===!0||raw==="true"?!0:raw===!1||raw==="false"?!1:DEFAULT_STREAMING}catch{return DEFAULT_STREAMING}}async function saveStreamingPreference(value){try{await getStorage().setItem(STREAMING_PREF_KEY,value?"true":"false")}catch{}}var STYLE_KEY=`${STORAGE_PREFIX}ui.truncationMarkerStyle`,VALID_STYLES=new Set(["bilingual","kr","en","off"]),DEFAULT_STYLE="bilingual";async function loadMarkerStyle(){try{let raw=await getStorage().getItem(STYLE_KEY);if(typeof raw=="string"&&VALID_STYLES.has(raw))return raw}catch{}return DEFAULT_STYLE}var TEXTS={safety:{en:"Response truncated by safety filter",kr:"\uC548\uC804 \uD544\uD130\uB85C \uC751\uB2F5\uC774 \uC798\uB838\uC2B5\uB2C8\uB2E4"},max_tokens:{en:"Response hit max-token limit",kr:"\uCD5C\uB300 \uD1A0\uD070 \uD55C\uB3C4\uC5D0 \uB3C4\uB2EC\uD588\uC2B5\uB2C8\uB2E4"},pause_turn:{en:"Long turn paused; resend to continue",kr:"\uAE34 \uC751\uB2F5\uC774 \uC77C\uC2DC\uC815\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uBCF4\uB0B4\uBA74 \uC774\uC5B4\uC9D1\uB2C8\uB2E4"},recitation:{en:"Response truncated (recitation filter)",kr:"\uD45C\uC808 \uD544\uD130\uB85C \uC751\uB2F5\uC774 \uC798\uB838\uC2B5\uB2C8\uB2E4"},eof:{en:"Stream interrupted",kr:"\uC2A4\uD2B8\uB9BC\uC774 \uB04A\uACBC\uC2B5\uB2C8\uB2E4"},unknown:{en:"Response ended unexpectedly",kr:"\uC751\uB2F5\uC774 \uC608\uAE30\uCE58 \uC54A\uAC8C \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4"}};function markerFor(cause,style){if(style==="off")return"";let t=TEXTS[cause]||TEXTS.unknown;return style==="kr"?` | |
| \u2014 ${t.kr} \u2014`:style==="en"?` | |
| \u2014 ${t.en} \u2014`:` | |
| \u2014 ${t.en} / ${t.kr} \u2014`}function anthropicCause(stopReason){switch(stopReason){case"end_turn":case"stop_sequence":case"tool_use":return null;case"max_tokens":return"max_tokens";case"pause_turn":return"pause_turn";case"refusal":return"safety";default:return stopReason?"unknown":null}}function geminiCause(finishReason){switch(finishReason){case"STOP":case"FINISH_REASON_UNSPECIFIED":case void 0:case null:return null;case"MAX_TOKENS":return"max_tokens";case"SAFETY":case"BLOCKLIST":case"PROHIBITED_CONTENT":case"SPII":return"safety";case"RECITATION":return"recitation";case"LANGUAGE":case"OTHER":case"MALFORMED_FUNCTION_CALL":default:return"unknown"}}function openaiCause(finishReason){switch(finishReason){case"stop":case"tool_calls":case"function_call":case void 0:case null:return null;case"length":return"max_tokens";case"content_filter":return"safety";default:return"unknown"}}function geminiThinkingBudgetHint(style){if(style==="off")return"";let en="Thinking budget overrun \u2014 try increasing max_tokens or lowering thinkingBudget",kr="Thinking \uC608\uC0B0 \uCD08\uACFC\uB85C \uBCF4\uC785\uB2C8\uB2E4 \u2014 max_tokens \uB97C \uB298\uB9AC\uAC70\uB098 thinkingBudget \uC744 \uB0AE\uCDB0\uBCF4\uC138\uC694";return style==="kr"?` | |
| (${kr})`:style==="en"?` | |
| (${en})`:` | |
| (${en} / ${kr})`}var TEMPLATES={auth:{surface:"modal",title:"\uC778\uC99D \uC2E4\uD328",message:"API \uD0A4\uAC00 \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (HTTP {status}).",action:"Providers \uD0ED\uC5D0\uC11C API \uD0A4\uB97C \uB2E4\uC2DC \uD655\uC778\uD558\uAC70\uB098 \uBC1C\uAE09 \uD398\uC774\uC9C0\uC5D0\uC11C \uC7AC\uBC1C\uAE09\uD574 \uC8FC\uC138\uC694."},rate_limit:{surface:"marker",title:"\uC694\uCCAD \uD55C\uB3C4 \uCD08\uACFC",message:"API rate limit \uC5D0 \uB3C4\uB2EC\uD588\uC2B5\uB2C8\uB2E4 (HTTP 429).",action:"\uC790\uB3D9 retry (1s/2s/4s \xD7 3 \uD68C) \uD6C4\uC5D0\uB3C4 \uC2E4\uD328. \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uAC70\uB098 \uAC8C\uC774\uD2B8\uC6E8\uC774 \uCF58\uC194\uC5D0\uC11C \uD55C\uB3C4/\uD50C\uB79C \uD655\uC778."},server:{surface:"marker",title:"\uC11C\uBC84 \uC624\uB958",message:"\uD504\uB85C\uBC14\uC774\uB354 \uC11C\uBC84\uC5D0 \uC77C\uC2DC\uC801 \uBB38\uC81C\uAC00 \uC788\uC2B5\uB2C8\uB2E4 (HTTP {status}).",action:"\uC7A0\uC2DC \uD6C4 \uC7AC\uC2DC\uB3C4. \uC9C0\uC18D\uB418\uBA74 \uD574\uB2F9 \uD504\uB85C\uBC14\uC774\uB354\uC758 status \uD398\uC774\uC9C0 \uD655\uC778."},network:{surface:"modal",title:"\uB124\uD2B8\uC6CC\uD06C \uC5F0\uACB0 \uC2E4\uD328",message:"\uD504\uB85C\uBC14\uC774\uB354\uC5D0 \uB3C4\uB2EC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. {error_message}",action:"\uC778\uD130\uB137 \uC5F0\uACB0\uACFC Base URL \uC124\uC815\uC744 \uD655\uC778\uD558\uC138\uC694. Tauri/desktop \uBE4C\uB4DC\uB294 CSP \uC81C\uC57D\uC744 \uC6B0\uD68C\uD569\uB2C8\uB2E4."},cors:{surface:"modal",title:"CORS / mixed-content \uCC28\uB2E8",message:"\uBE0C\uB77C\uC6B0\uC800 \uC815\uCC45\uC73C\uB85C \uC694\uCCAD\uC774 \uB9C9\uD614\uC2B5\uB2C8\uB2E4.",action:"RisuAI \uC124\uC815 \u2192 \uACE0\uAE09 \u2192 'Use plain fetch instead of server' \uD1A0\uAE00 ON, \uB610\uB294 \uB370\uC2A4\uD06C\uD0D1/Tauri \uBE4C\uB4DC \uC0AC\uC6A9\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4."},parse:{surface:"marker",title:"\uC751\uB2F5 \uD30C\uC2F1 \uC2E4\uD328",message:"\uD504\uB85C\uBC14\uC774\uB354 \uC751\uB2F5 \uD615\uC2DD\uC774 \uC608\uAE30\uCE58 \uC54A\uC2B5\uB2C8\uB2E4.",action:"Streaming=off \uB85C \uC7AC\uC2DC\uB3C4\uD558\uAC70\uB098 \uBAA8\uB378 ID \uAC00 \uC62C\uBC14\uB978\uC9C0 \uD655\uC778."},unknown:{surface:"marker",title:"\uC54C \uC218 \uC5C6\uB294 \uC624\uB958",message:"\uC624\uB958: {error_message}",action:"Insights \uD0ED\uC5D0\uC11C \uCD5C\uADFC \uD638\uCD9C \uB85C\uADF8 \uD655\uC778."}};function classifyByStatus(status){return Number.isFinite(status)?status===401||status===403?"auth":status===429?"rate_limit":status>=500&&status<600?"server":null:null}function classifyByError(err){if(!err)return null;let msg=String(err.message||err).toLowerCase();return msg.includes("cors")||msg.includes("mixed content")||msg.includes("blocked by")?"cors":msg.includes("network")||msg.includes("failed to fetch")||msg.includes("connection")||msg.includes("enotfound")||msg.includes("dns")||msg.includes("typeerror")?"network":msg.includes("parse")||msg.includes("json")||msg.includes("malformed")||msg.includes("unexpected token")?"parse":msg.includes("stream_timeout")?"network":null}function parseStatusFromMessage(msg){if(!msg)return null;let m=String(msg).match(/^HTTP\s+(\d+)/i);return m?parseInt(m[1],10):null}function fillTemplate(s,status,errMsg){return String(s||"").replace(/\{status\}/g,status!=null?String(status):"?").replace(/\{error_message\}/g,errMsg||"(\uC0C1\uC138 \uC5C6\uC74C)")}function classifyError(input={}){let parsedStatus=(Number.isFinite(input.status)?input.status:null)||parseStatusFromMessage(input.error?.message),cls=classifyByStatus(parsedStatus)||classifyByError(input.error)||"unknown",template=TEMPLATES[cls]||TEMPLATES.unknown,errMsg=input.error?input.error.message||String(input.error):"";return{class:cls,surface:template.surface,title:template.title,message:fillTemplate(template.message,parsedStatus,errMsg),action:fillTemplate(template.action,parsedStatus,errMsg),status:parsedStatus}}init_alert();var PRICING={"claude-opus-4-7":{input:5,output:25,cacheRead:.5,cacheWrite5m:6.25,cacheWrite1h:10,batchInput:2.5,batchOutput:12.5,priceMode:"verbatim",source:"https://platform.claude.com/docs/en/about-claude/pricing",verified:"2026-05-24",notes:"Fast mode (priority) = 6\xD7 rates ($30 / $150). US-only inference = +10% multiplier. Web search = $10/1k searches."},"claude-opus-4-6":{input:5,output:25,cacheRead:.5,cacheWrite5m:6.25,cacheWrite1h:10,batchInput:2.5,batchOutput:12.5,priceMode:"verbatim",source:"https://platform.claude.com/docs/en/about-claude/pricing",verified:"2026-05-24",notes:"Fast mode = 6\xD7. US-only inference +10%."},"claude-opus-4-5":{input:5,output:25,cacheRead:.5,batchInput:2.5,batchOutput:12.5,priceMode:"verbatim",source:"https://platform.claude.com/docs/en/about-claude/pricing",verified:"2026-05-24"},"claude-opus-4-1":{input:15,output:75,cacheRead:1.5,cacheWrite5m:18.75,cacheWrite1h:30,batchInput:7.5,batchOutput:37.5,priceMode:"verbatim",source:"https://platform.claude.com/docs/en/about-claude/pricing",verified:"2026-05-24"},"claude-sonnet-4-6":{input:3,output:15,cacheRead:.3,cacheWrite5m:3.75,cacheWrite1h:6,batchInput:1.5,batchOutput:7.5,priceMode:"verbatim",source:"https://platform.claude.com/docs/en/about-claude/pricing",verified:"2026-05-24",notes:"1M context option. US-only inference +10%."},"claude-sonnet-4-5":{input:3,output:15,cacheRead:.3,batchInput:1.5,batchOutput:7.5,priceMode:"verbatim",source:"https://platform.claude.com/docs/en/about-claude/pricing",verified:"2026-05-24"},"claude-haiku-4-5":{input:1,output:5,cacheRead:.1,cacheWrite5m:1.25,cacheWrite1h:2,batchInput:.5,batchOutput:2.5,priceMode:"verbatim",source:"https://platform.claude.com/docs/en/about-claude/pricing",verified:"2026-05-24"},"claude-3-5-haiku-20241022":{input:.8,output:4,cacheRead:.08,batchInput:.4,batchOutput:2,priceMode:"verbatim",source:"https://platform.claude.com/docs/en/about-claude/pricing",verified:"2026-05-24",notes:"First-party retired; Bedrock/Vertex only."},"deepseek-v4-flash":{input:.14,output:.28,cacheRead:.014,priceMode:"verbatim",source:"https://api-docs.deepseek.com/quick_start/pricing",verified:"2026-05-24",notes:"Both non-thinking + thinking modes. Legacy slugs deepseek-chat / deepseek-reasoner map to this."},"deepseek-v4-pro":{input:1.74,output:3.48,cacheRead:.0145,priceMode:"verbatim",source:"https://api-docs.deepseek.com/quick_start/pricing",verified:"2026-05-24",notes:"Promo through 2026-05-31 15:59 UTC: $0.435 / $0.87 (75% off). Post-promo: standard \uADF8\uB300\uB85C."},"deepseek-v4-pro:promo":{input:.435,output:.87,cacheRead:.003625,priceMode:"promo",source:"https://api-docs.deepseek.com/quick_start/pricing",verified:"2026-05-24",notes:'75% off promo until 2026-05-31 15:59 UTC. After expiry, primary docs: "officially adjusted to 1/4 of the original price".'},"deepseek-chat":{input:.14,output:.28,cacheRead:.014,priceMode:"verbatim",source:"https://api-docs.deepseek.com/quick_start/pricing",verified:"2026-05-24",notes:"Legacy alias \u2192 maps to deepseek-v4-flash non-thinking."},"deepseek-reasoner":{input:.14,output:.28,cacheRead:.014,priceMode:"verbatim",source:"https://api-docs.deepseek.com/quick_start/pricing",verified:"2026-05-24",notes:"Legacy alias \u2192 maps to deepseek-v4-flash thinking mode."},"glm-5.1":{input:1.4,output:4.4,cacheRead:.26,priceMode:"verbatim",source:"https://docs.z.ai/guides/overview/pricing",verified:"2026-05-24"},"glm-5":{input:1,output:3.2,cacheRead:.2,priceMode:"verbatim",source:"https://docs.z.ai/guides/overview/pricing",verified:"2026-05-24"},"glm-5-turbo":{input:1.2,output:4,cacheRead:.24,priceMode:"verbatim",source:"https://docs.z.ai/guides/overview/pricing",verified:"2026-05-24"},"glm-4.7":{input:.6,output:2.2,cacheRead:.11,priceMode:"verbatim",source:"https://docs.z.ai/guides/overview/pricing",verified:"2026-05-24"},"glm-4.7-flashx":{input:.07,output:.4,cacheRead:.01,priceMode:"verbatim",source:"https://docs.z.ai/guides/overview/pricing",verified:"2026-05-24"},"glm-4.6":{input:.6,output:2.2,cacheRead:.11,priceMode:"verbatim",source:"https://docs.z.ai/guides/overview/pricing",verified:"2026-05-24"},"glm-4.5":{input:.6,output:2.2,cacheRead:.11,priceMode:"verbatim",source:"https://docs.z.ai/guides/overview/pricing",verified:"2026-05-24"},"glm-4.5-x":{input:2.2,output:8.9,cacheRead:.45,priceMode:"verbatim",source:"https://docs.z.ai/guides/overview/pricing",verified:"2026-05-24"},"glm-4.5-air":{input:.2,output:1.1,cacheRead:.03,priceMode:"verbatim",source:"https://docs.z.ai/guides/overview/pricing",verified:"2026-05-24"},"glm-4.5-airx":{input:1.1,output:4.5,cacheRead:.22,priceMode:"verbatim",source:"https://docs.z.ai/guides/overview/pricing",verified:"2026-05-24"},"glm-4.7-flash":{input:0,output:0,priceMode:"verbatim",source:"https://docs.z.ai/guides/overview/pricing",verified:"2026-05-24",notes:"Free tier."},"glm-4.5-flash":{input:0,output:0,priceMode:"verbatim",source:"https://docs.z.ai/guides/overview/pricing",verified:"2026-05-24",notes:"Free tier."},"glm-4.6v":{input:.3,output:.9,cacheRead:.05,priceMode:"verbatim",source:"https://docs.z.ai/guides/overview/pricing",verified:"2026-05-24",notes:"Vision."},"glm-4.5v":{input:.6,output:1.8,cacheRead:.11,priceMode:"verbatim",source:"https://docs.z.ai/guides/overview/pricing",verified:"2026-05-24",notes:"Vision."},"llama-3.1-8b-instant":{input:.05,output:.08,priceMode:"verbatim",source:"https://groq.com/pricing",verified:"2026-05-24",notes:"$1 buys 20M input / 12.5M output tokens. Batch = 50% off, Developer tier = 25% off."},"openai/gpt-oss-20b":{input:.075,output:.3,priceMode:"verbatim",source:"https://groq.com/pricing",verified:"2026-05-24"},"openai/gpt-oss-120b":{input:.15,output:.6,priceMode:"verbatim",source:"https://groq.com/pricing",verified:"2026-05-24"},"llama-4-scout":{input:.11,output:.34,priceMode:"verbatim",source:"https://groq.com/pricing",verified:"2026-05-24"},"qwen3-32b":{input:.29,output:.59,priceMode:"verbatim",source:"https://groq.com/pricing",verified:"2026-05-24"},"llama-3.3-70b-versatile":{input:.59,output:.79,priceMode:"verbatim",source:"https://groq.com/pricing",verified:"2026-05-24"},"moonshotai/kimi-k2":{input:1,output:3,cacheRead:.5,priceMode:"verbatim",source:"https://groq.com/pricing",verified:"2026-05-24",notes:"Only Kimi K2 has documented cached-input row on Groq."},"gpt-oss-120b":{input:.35,output:.75,priceMode:"verbatim",source:"https://inference-docs.cerebras.ai/models/openai-oss",verified:"2026-05-24",notes:"Cerebras Production tier. Context 131k / max output 40k."},"qwen-3-235b-a22b-instruct-2507":{input:.6,output:1.2,priceMode:"verbatim",source:"https://inference-docs.cerebras.ai/models/overview",verified:"2026-05-24",notes:"Cerebras Preview tier \u2014 deprecated 2026-05-27."},"mistral-large-2512":{input:.5,output:1.5,priceMode:"verbatim",source:"https://docs.mistral.ai/models/mistral-large-3-25-12",verified:"2026-05-24",notes:"Mistral Large 3 \u2014 sparse MoE 41B active / 675B total. Context 256k."},"mistral-medium-2604":{input:1.5,output:7.5,priceMode:"verbatim",source:"https://docs.mistral.ai/models/model-cards/mistral-medium-3-5-26-04",verified:"2026-05-24",notes:"Medium 3.5. Context 256k."},"mistral-medium-2508":{input:.4,output:2,priceMode:"verbatim",source:"https://docs.mistral.ai/models/mistral-medium-3-1-25-08",verified:"2026-05-24",notes:"Medium 3.1. Context 128k."},"grok-4.3":{input:1.25,output:2.5,priceMode:"verbatim",source:"https://docs.x.ai/developers/models",verified:"2026-05-24",notes:"Flagship as of 2026-04-30. Context 1M. Batch = 50% off all token types. Higher tier > 128k context."},"grok-4-1-fast":{input:.2,output:.5,priceMode:"verbatim",source:"https://docs.x.ai/developers/migration/may-15-retirement",verified:"2026-05-24",notes:"Retired 2026-05-15 12:00 PT \u2014 redirects to grok-4.3."},"grok-4-0709":{input:3,output:15,priceMode:"verbatim",source:"https://docs.x.ai/developers/migration/may-15-retirement",verified:"2026-05-24",notes:"Retired 2026-05-15."},"accounts/fireworks/models/deepseek-v4-pro":{input:1.74,output:3.48,priceMode:"verbatim",source:"https://fireworks.ai/models",verified:"2026-05-24",notes:"Fireworks Max tier. Context 1,048,576."},"accounts/fireworks/models/gpt-oss-120b":{input:.15,output:.6,priceMode:"verbatim",source:"https://fireworks.ai/models",verified:"2026-05-24"},"accounts/fireworks/models/llama-v3p1-405b-instruct":{input:3,output:3,priceMode:"verbatim",source:"https://fireworks.ai/models",verified:"2026-05-24"},"accounts/fireworks/models/llama-v3p3-70b-instruct":{input:.9,output:.9,priceMode:"verbatim",source:"https://fireworks.ai/models",verified:"2026-05-24",notes:"Recommended default (v3p1-70b deprecated)."},"accounts/fireworks/models/kimi-k2.6":{input:.6,output:2.5,priceMode:"verbatim",source:"https://fireworks.ai/models",verified:"2026-05-24"},"gemini-3.1-pro-preview":{input:2,output:12,longContextInput:4,longContextOutput:18,longContextThresholdTok:2e5,cacheRead:.2,batchInput:1,batchOutput:6,priceMode:"verbatim",source:"https://ai.google.dev/gemini-api/docs/pricing",verified:"2026-05-24",notes:"Paid only since 2026-04-01. Output incl. thinking tokens. Grounding $14/1k queries (5k/mo free shared)."},"gemini-3-flash-preview":{input:.5,output:3,cacheRead:.05,batchInput:.25,batchOutput:1.5,priceMode:"verbatim",source:"https://ai.google.dev/gemini-api/docs/pricing",verified:"2026-05-24",notes:"Audio input $1/M. Free tier available."},"gemini-3.5-flash":{input:1.5,output:9,cacheRead:.15,batchInput:.75,batchOutput:4.5,priceMode:"verbatim",source:"https://ai.google.dev/gemini-api/docs/pricing",verified:"2026-05-24",notes:'Free-tier data "used to improve our products".'},"gemini-3.1-flash-lite":{input:.25,output:1.5,cacheRead:.025,batchInput:.125,batchOutput:.75,priceMode:"verbatim",source:"https://ai.google.dev/gemini-api/docs/pricing",verified:"2026-05-24",notes:"Audio input $0.50/M."},"gemini-2.5-pro":{input:1.25,output:10,longContextInput:2.5,longContextOutput:15,longContextThresholdTok:2e5,cacheRead:.125,batchInput:.625,batchOutput:5,priceMode:"verbatim",source:"https://ai.google.dev/gemini-api/docs/pricing",verified:"2026-05-24"},"gemini-2.5-flash":{input:.3,output:2.5,cacheRead:.03,batchInput:.15,batchOutput:1.25,priceMode:"verbatim",source:"https://ai.google.dev/gemini-api/docs/pricing",verified:"2026-05-24",notes:"Audio input $1.00/M."},"gemini-2.5-flash-lite":{input:.1,output:.4,cacheRead:.01,batchInput:.05,batchOutput:.2,priceMode:"verbatim",source:"https://ai.google.dev/gemini-api/docs/pricing",verified:"2026-05-24",notes:"Cheapest tier. Audio input $0.30/M."},"gemini-2.0-flash":{input:.1,output:.4,cacheRead:.025,batchInput:.05,batchOutput:.2,priceMode:"verbatim",source:"https://ai.google.dev/gemini-api/docs/pricing",verified:"2026-05-24",notes:"Deprecated \u2014 shutdown 2026-06-01."},"gemini-2.0-flash-lite":{input:.075,output:.3,batchInput:.0375,batchOutput:.15,priceMode:"verbatim",source:"https://ai.google.dev/gemini-api/docs/pricing",verified:"2026-05-24",notes:"Deprecated \u2014 shutdown 2026-06-01."},"gpt-5.5":{input:5,output:30,cacheRead:.5,batchInput:2.5,batchOutput:15,priceMode:"verbatim",source:"https://developers.openai.com/api/docs/pricing",verified:"2026-05-24",notes:"Long-context (>272K) doubles input, +50% output. Data-residency endpoints +10%. $5 trial credit."},"gpt-5.5-pro":{input:30,output:180,batchInput:15,batchOutput:90,priceMode:"verbatim",source:"https://developers.openai.com/api/docs/pricing",verified:"2026-05-24",notes:"No cache-input discount."},"gpt-5.4":{input:2.5,output:15,cacheRead:.25,batchInput:1.25,batchOutput:7.5,priceMode:"verbatim",source:"https://developers.openai.com/api/docs/pricing",verified:"2026-05-24",notes:"Long-context doubles input. Blessing default OpenAI tier."},"gpt-5.4-mini":{input:.75,output:4.5,cacheRead:.075,batchInput:.375,batchOutput:2.25,priceMode:"verbatim",source:"https://developers.openai.com/api/docs/pricing",verified:"2026-05-24"},"gpt-5.4-nano":{input:.2,output:1.25,cacheRead:.02,batchInput:.1,batchOutput:.625,priceMode:"verbatim",source:"https://developers.openai.com/api/docs/pricing",verified:"2026-05-24",notes:"Cheapest tier."},"gpt-5.4-pro":{input:30,output:180,batchInput:15,batchOutput:90,priceMode:"verbatim",source:"https://developers.openai.com/api/docs/pricing",verified:"2026-05-24"},"hunyuan-turbos":{input:.011,output:.028,priceMode:"inferred",source:"https://cloud.tencent.com/document/product/1729/97731",verified:"2026-05-24",notes:"Primary CNY \xA50.08/1K in / \xA50.20/1K out, converted at CNY 7.20/USD. **Tencent Hunyuan \uC740 TokenHub \uB85C \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC9C4\uD589 \uC911** (per /1729/111007)."},"hunyuan-vision":{input:.25,output:.25,priceMode:"inferred",source:"https://cloud.tencent.com/document/product/1729/97731",verified:"2026-05-24",notes:"Primary CNY \xA51.8/1K both directions, converted at CNY 7.20/USD."}};function getPricing(modelId){if(!modelId)return null;let key=String(modelId).toLowerCase().trim();if(PRICING[key])return PRICING[key];if(key.includes("/")){let tail=key.slice(key.lastIndexOf("/")+1);if(PRICING[tail])return PRICING[tail]}let stripped=key.replace(/:(floor|nitro)$/,"");if(stripped!==key&&PRICING[stripped])return PRICING[stripped];if(key.endsWith("-latest")){let base=key.slice(0,-7);for(let[k,v]of Object.entries(PRICING))if(k.startsWith(base+"-")&&/\d{8}$/.test(k))return v}return null}function estimateCost(modelId,inputTokens,outputTokens,opts={}){let record=getPricing(modelId);if(!record)return{usd:null,breakdown:{},source:null,mode:null};let{cacheReadTokens=0,cacheWriteTokens=0,useExtendedCache=!1,batch=!1,longContext=!1}=opts,inRate=record.input,outRate=record.output;batch?(record.batchInput!=null&&(inRate=record.batchInput),record.batchOutput!=null&&(outRate=record.batchOutput)):longContext&&(record.longContextInput!=null&&(inRate=record.longContextInput),record.longContextOutput!=null&&(outRate=record.longContextOutput));let cacheReadRate=record.cacheRead!=null?record.cacheRead:0,cacheWriteRate=useExtendedCache?record.cacheWrite1h!=null?record.cacheWrite1h:0:record.cacheWrite5m!=null?record.cacheWrite5m:0,costIn=Math.max(0,inputTokens-cacheReadTokens-cacheWriteTokens)/1e6*inRate,costOut=outputTokens/1e6*outRate,costCacheRead=cacheReadTokens/1e6*cacheReadRate,costCacheWrite=cacheWriteTokens/1e6*cacheWriteRate,usd=costIn+costOut+costCacheRead+costCacheWrite;return{usd:round6(usd),breakdown:{input:round6(costIn),output:round6(costOut),cacheRead:round6(costCacheRead),cacheWrite:round6(costCacheWrite)},source:record.source,mode:record.priceMode}}function formatPricingHint(modelId){let r=getPricing(modelId);if(!r)return"\uB2E8\uAC00 \uC815\uBCF4 \uC5C6\uC74C";let parts=[`$${formatRate(r.input)}/M in`,`$${formatRate(r.output)}/M out`];return r.cacheRead!=null&&parts.push(`cache $${formatRate(r.cacheRead)}/M`),r.priceMode==="promo"&&parts.push("(promo)"),r.priceMode==="inferred"&&parts.push("(approx)"),parts.join(" \xB7 ")}function round6(n){return Math.round(n*1e6)/1e6}function formatRate(n){return n===0?"0":n<.01?n.toFixed(4):n<1?n.toFixed(3):n.toFixed(2)}var STORAGE_KEY=`${STORAGE_PREFIX}insights.events`,MAX_EVENTS=500,FLUSH_DEBOUNCE_MS=1500,cache=null,flushTimer=null,nextId=1;async function ensureLoaded(){if(cache)return cache;try{let raw=await getStorage().getItem(STORAGE_KEY);if(raw){let parsed=typeof raw=="object"?raw:JSON.parse(String(raw));if(Array.isArray(parsed)){cache=parsed.slice(-MAX_EVENTS);for(let e of cache)typeof e.id=="number"&&e.id>=nextId&&(nextId=e.id+1);return cache}}}catch{}return cache=[],cache}function scheduleFlush(){flushTimer||(flushTimer=setTimeout(async()=>{flushTimer=null;try{await getStorage().setItem(STORAGE_KEY,JSON.stringify(cache||[]))}catch{}},FLUSH_DEBOUNCE_MS))}async function recordEvent(ev){let list=await ensureLoaded(),entry={id:nextId++,providerId:String(ev.providerId||"unknown"),startedAt:Number(ev.startedAt)||Date.now(),endedAt:Number(ev.endedAt)||Date.now(),ok:!!ev.ok,bytes:Number(ev.bytes)||0,error:ev.error?String(ev.error).slice(0,240):void 0};for(list.push(entry);list.length>MAX_EVENTS;)list.shift();scheduleFlush()}async function loadEvents(){return(await ensureLoaded()).slice()}async function clearEvents(){cache=[],nextId=1;try{await getStorage().removeItem(STORAGE_KEY)}catch{}}async function summarize(){let list=await loadEvents(),out={total:list.length,ok:0,failed:0,sinceMs:list.length>0?list[0].startedAt:null,perProvider:[]},byId=new Map;for(let e of list){e.ok?out.ok+=1:out.failed+=1;let agg=byId.get(e.providerId);agg||(agg={providerId:e.providerId,count:0,ok:0,failed:0,totalMs:0,totalBytes:0,lastAt:0},byId.set(e.providerId,agg)),agg.count+=1,e.ok?agg.ok+=1:agg.failed+=1,agg.totalMs+=Math.max(0,(e.endedAt||0)-(e.startedAt||0)),agg.totalBytes+=Number(e.bytes)||0,e.endedAt>agg.lastAt&&(agg.lastAt=e.endedAt)}for(let a of byId.values())out.perProvider.push({providerId:a.providerId,count:a.count,ok:a.ok,failed:a.failed,avgMs:a.count>0?Math.round(a.totalMs/a.count):0,totalBytes:a.totalBytes,lastAt:a.lastAt});return out.perProvider.sort((a,b)=>b.lastAt-a.lastAt),out}async function flushNow(){flushTimer&&(clearTimeout(flushTimer),flushTimer=null),usageFlushTimer&&(clearTimeout(usageFlushTimer),usageFlushTimer=null);try{await getStorage().setItem(STORAGE_KEY,JSON.stringify(cache||[]))}catch{}try{await getStorage().setItem(USAGE_STORAGE_KEY,JSON.stringify(usageCache||{}))}catch{}}var USAGE_STORAGE_KEY=`${STORAGE_PREFIX}insights.usage`,usageCache=null,usageFlushTimer=null;async function ensureUsageLoaded(){if(usageCache)return usageCache;try{let raw=await getStorage().getItem(USAGE_STORAGE_KEY);if(raw){let parsed=typeof raw=="object"?raw:JSON.parse(String(raw));if(parsed&&typeof parsed=="object"&&!Array.isArray(parsed))return usageCache=parsed,usageCache}}catch{}return usageCache={},usageCache}function scheduleUsageFlush(){usageFlushTimer||(usageFlushTimer=setTimeout(async()=>{usageFlushTimer=null;try{await getStorage().setItem(USAGE_STORAGE_KEY,JSON.stringify(usageCache||{}))}catch{}},FLUSH_DEBOUNCE_MS))}async function recordUsage(providerId,usage,modelId){if(!providerId||!usage)return;let map=await ensureUsageLoaded(),id=String(providerId);map[id]||(map[id]={input:0,output:0,cacheCreation:0,cacheRead:0,calls:0,lastAt:0,usd:0,monthKey:"",monthUsd:0,lastModel:""});let r=map[id];r.input+=Number(usage.inputTokens)||0,r.output+=Number(usage.outputTokens)||0,r.cacheCreation+=Number(usage.cacheCreation)||0,r.cacheRead+=Number(usage.cacheRead)||0,r.calls+=1,r.lastAt=Date.now();let estUsdDelta=0;if(modelId){let est=estimateCost(modelId,Number(usage.inputTokens)||0,Number(usage.outputTokens)||0,{cacheReadTokens:Number(usage.cacheRead)||0,cacheWriteTokens:Number(usage.cacheCreation)||0});if(est.usd!=null){estUsdDelta=est.usd,r.usd=(Number(r.usd)||0)+estUsdDelta,r.lastModel=String(modelId);let thisMonth=monthKey(Date.now());r.monthKey!==thisMonth&&(r.monthKey=thisMonth,r.monthUsd=0),r.monthUsd=(Number(r.monthUsd)||0)+estUsdDelta}}scheduleUsageFlush(),estUsdDelta>0&&checkBudgetThreshold(id,r).catch(()=>{})}async function summarizeUsage(){let map=await ensureUsageLoaded(),thisMonth=monthKey(Date.now());return Object.entries(map).map(([providerId,r])=>({providerId,calls:r.calls||0,input:r.input||0,output:r.output||0,cacheCreation:r.cacheCreation||0,cacheRead:r.cacheRead||0,lastAt:r.lastAt||0,usd:Number(r.usd)||0,monthUsd:r.monthKey===thisMonth&&Number(r.monthUsd)||0,monthKey:r.monthKey===thisMonth?r.monthKey:thisMonth,lastModel:r.lastModel||""})).sort((a,b)=>b.lastAt-a.lastAt)}async function clearUsage(){usageCache={},usageFlushTimer&&(clearTimeout(usageFlushTimer),usageFlushTimer=null);try{await getStorage().removeItem(USAGE_STORAGE_KEY)}catch{}}var BUDGET_STORAGE_KEY=`${STORAGE_PREFIX}budget.providers`,BUDGET_ALERTS_KEY=`${STORAGE_PREFIX}budget.alerts`,budgetCache=null,budgetAlertsCache=null;async function ensureBudgetLoaded(){if(budgetCache)return budgetCache;try{let raw=await getStorage().getItem(BUDGET_STORAGE_KEY);if(raw){let parsed=typeof raw=="object"?raw:JSON.parse(String(raw));if(parsed&&typeof parsed=="object"&&!Array.isArray(parsed))return budgetCache=parsed,budgetCache}}catch{}return budgetCache={},budgetCache}async function ensureBudgetAlertsLoaded(){if(budgetAlertsCache)return budgetAlertsCache;try{let raw=await getStorage().getItem(BUDGET_ALERTS_KEY);if(raw){let parsed=typeof raw=="object"?raw:JSON.parse(String(raw));if(parsed&&typeof parsed=="object"&&!Array.isArray(parsed))return budgetAlertsCache=parsed,budgetAlertsCache}}catch{}return budgetAlertsCache={},budgetAlertsCache}async function getBudgets(){let map=await ensureBudgetLoaded(),out={};for(let[id,v]of Object.entries(map))out[id]=Number(v?.monthlyUsd)||0;return out}async function setBudget(providerId,usd){if(!providerId)return;let map=await ensureBudgetLoaded(),id=String(providerId),v=Number(usd);!Number.isFinite(v)||v<=0?delete map[id]:map[id]={monthlyUsd:v};try{await getStorage().setItem(BUDGET_STORAGE_KEY,JSON.stringify(map))}catch{}let alerts=await ensureBudgetAlertsLoaded();if(alerts[id]){delete alerts[id];try{await getStorage().setItem(BUDGET_ALERTS_KEY,JSON.stringify(alerts))}catch{}}}async function checkBudgetThreshold(providerId,usageRecord){let b=(await ensureBudgetLoaded())[providerId],limit=Number(b?.monthlyUsd)||0;if(limit<=0)return;let used=Number(usageRecord.monthUsd)||0;if(used<=0)return;let pct=used/limit*100,month=usageRecord.monthKey,alerts=await ensureBudgetAlertsLoaded();alerts[providerId]||(alerts[providerId]={}),alerts[providerId][month]||(alerts[providerId][month]={});let state=alerts[providerId][month],needSave=!1,fireMessage=null,fireSeverity="warning";if(pct>=100&&!state.warn100?(state.warn100=!0,state.warn80=!0,needSave=!0,fireMessage=`${providerId} \uC758 ${month} \uC6D4\uAC04 \uC608\uC0B0 \uD55C\uB3C4\uC5D0 \uB3C4\uB2EC\uD588\uC2B5\uB2C8\uB2E4. | |
| \uC0AC\uC6A9: $${used.toFixed(4)} / \uD55C\uB3C4: $${limit.toFixed(2)} (${pct.toFixed(1)}%). | |
| \uCC38\uACE0: \uBCF8 \uC608\uC0B0\uC740 *\uACBD\uACE0* \uC77C \uBFD0, \uD638\uCD9C \uC790\uCCB4\uB294 \uACC4\uC18D \uC9C4\uD589\uB429\uB2C8\uB2E4. \uC0AC\uC6A9 \uC911\uB2E8\uC744 \uC6D0\uD558\uC2DC\uBA74 \uCC44\uD305\uC744 \uADF8\uB9CC\uB450\uC2DC\uAC70\uB098 \uC608\uC0B0\uC744 \uC870\uC815\uD574 \uC8FC\uC138\uC694.`,fireSeverity="critical"):pct>=80&&!state.warn80&&(state.warn80=!0,needSave=!0,fireMessage=`${providerId} \uC758 ${month} \uC6D4\uAC04 \uC608\uC0B0 80% \uC0AC\uC6A9 \uC911. | |
| \uC0AC\uC6A9: $${used.toFixed(4)} / \uD55C\uB3C4: $${limit.toFixed(2)} (${pct.toFixed(1)}%).`,fireSeverity="warning"),needSave)try{await getStorage().setItem(BUDGET_ALERTS_KEY,JSON.stringify(alerts))}catch{}if(fireMessage)try{(await Promise.resolve().then(()=>(init_alert(),alert_exports))).showAlert({title:"\uC6D4\uAC04 \uC608\uC0B0 \uC54C\uB9BC",message:fireMessage,severity:fireSeverity,preserveNewlines:!0})}catch{}}async function resetBudgetAlerts(providerId){let alerts=await ensureBudgetAlertsLoaded();providerId?delete alerts[providerId]:budgetAlertsCache={};try{await getStorage().setItem(BUDGET_ALERTS_KEY,JSON.stringify(budgetAlertsCache||{}))}catch{}}function monthKey(ms){let d=new Date(ms);return`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,"0")}`}var CONTEXT_LIMITS={"gpt-4o":128e3,"gpt-4o-2024-11-20":128e3,"gpt-4o-2024-08-06":128e3,"gpt-4o-2024-05-13":128e3,"gpt-4o-mini":128e3,"gpt-4o-mini-2024-07-18":128e3,"gpt-4-turbo":128e3,"gpt-4-turbo-2024-04-09":128e3,"gpt-4":8192,"gpt-4.1":1e6,"gpt-4.1-mini":1e6,"gpt-4.1-nano":1e6,"gpt-5":4e5,"gpt-5-mini":4e5,"gpt-5-nano":4e5,"gpt-5.5":4e5,o1:2e5,"o1-preview":128e3,"o1-mini":128e3,"o1-pro":2e5,o3:2e5,"o3-mini":2e5,"o4-mini":2e5,"gpt-4o-search-preview":128e3,"gpt-4o-mini-search-preview":128e3,"gpt-oss-120b":131072,"gpt-oss-20b":131072,"claude-3-haiku-20240307":2e5,"claude-3-sonnet-20240229":2e5,"claude-3-opus-20240229":2e5,"claude-3-5-haiku-latest":2e5,"claude-3-5-haiku-20241022":2e5,"claude-3-5-sonnet-latest":2e5,"claude-3-5-sonnet-20241022":2e5,"claude-3-5-sonnet-20240620":2e5,"claude-3-7-sonnet-latest":2e5,"claude-3-7-sonnet-20250219":2e5,"claude-haiku-4-5":2e5,"claude-sonnet-4-5":2e5,"claude-opus-4-6":2e5,"claude-sonnet-4-6":1e6,"claude-opus-4-7":1e6,"gemini-1.5-pro":2e6,"gemini-1.5-pro-latest":2e6,"gemini-1.5-pro-002":2e6,"gemini-1.5-flash":1e6,"gemini-1.5-flash-latest":1e6,"gemini-1.5-flash-002":1e6,"gemini-1.5-flash-8b":1e6,"gemini-2.0-flash":1e6,"gemini-2.0-flash-exp":1e6,"gemini-2.0-flash-001":1e6,"gemini-2.5-flash":1e6,"gemini-2.5-pro":1e6,"gemini-2.5-flash-lite":1e6,"gemini-3-flash-preview":1e6,"gemini-3.1-flash":1e6,"gemini-3.1-flash-lite":1e6,"gemini-3-pro-preview":1e6,"gemini-3.1-pro-preview":1e6,"gemini-3.1-pro":1e6,"deepseek-chat":64e3,"deepseek-reasoner":64e3,"deepseek-v3p1":128e3,"deepseek-v4-pro":128e3,"mistral-large-latest":128e3,"mistral-large-2411":128e3,"mistral-medium-latest":128e3,"mistral-medium-2505":128e3,"mistral-small-latest":32e3,"mistral-small-2503":32e3,"ministral-8b-latest":128e3,"ministral-3b-latest":128e3,"command-r-plus":128e3,"command-r-plus-08-2024":128e3,"command-r-plus-04-2024":128e3,"command-r":128e3,"command-r-08-2024":128e3,"command-r-03-2024":128e3,"command-r7b-12-2024":128e3,"grok-2-latest":131072,"grok-2-1212":131072,"grok-4-fast":131072,"grok-4.1-fast":131072,"grok-4.3":131072,"llama-3.1-8b-instant":128e3,"llama-3.1-70b-versatile":128e3,"llama-3.3-70b-versatile":128e3,"llama-3.3-70b-instruct":128e3,"llama3.1-8b":8192,"llama3.1-70b":128e3,"meta-llama/llama-3.3-70b-instruct":128e3,"meta-llama/llama-3-8b-chat-hf":8192,"qwen3-32b":131072,"qwen3-235b-a22b-instruct-2507-fp8":131072,"deepseek-ai/deepseek-v3.1":128e3,"glm-4-plus":128e3,"glm-4-air":128e3,"glm-4-flash":128e3,"glm-4.5":131072,"glm-4.5-air":131072,"glm-4.6":2e5,"hunyuan-pro":32e3,"hunyuan-standard":32e3,"hunyuan-lite":256e3,"llama3.2":128e3,"llama3.2:3b":128e3,"llama4:scout":1e6,"mistral:7b":32e3};function getContextLimit(modelId){if(!modelId)return null;let lower=String(modelId).toLowerCase().trim();if(CONTEXT_LIMITS[lower]!==void 0)return CONTEXT_LIMITS[lower];if(lower.includes("/")){let tail=lower.slice(lower.lastIndexOf("/")+1);if(CONTEXT_LIMITS[tail]!==void 0)return CONTEXT_LIMITS[tail];for(let[pattern,limit]of Object.entries(CONTEXT_LIMITS))if(tail.startsWith(pattern)&&pattern.length>5)return limit}let stripped=lower.replace(/:(floor|nitro)$/,"");return stripped!==lower?getContextLimit(stripped):null}function estimateInputTokensFromPluginRequest(pluginRequest){let arr=pluginRequest?.prompt_chat||pluginRequest?.messages||[];if(!Array.isArray(arr))return 0;let totalChars=0;for(let m of arr){let content=m?.content;if(typeof content=="string")totalChars+=content.length;else if(Array.isArray(content))for(let p of content)typeof p=="string"?totalChars+=p.length:typeof p?.text=="string"&&(totalChars+=p.text.length);else content!=null&&(totalChars+=String(content).length)}return Math.floor(totalChars*.3)}function markerForContextWarn(percent,style){if(style==="off")return"";let en=`Context window ~${percent}% used \u2014 consider summarizing or clearing older messages`,kr=`\uCEE8\uD14D\uC2A4\uD2B8 \uC57D ${percent}% \uC0AC\uC6A9 \uC911 \u2014 \uC624\uB798\uB41C \uBA54\uC2DC\uC9C0 \uC694\uC57D/\uC815\uB9AC \uAD8C\uC7A5`;return style==="kr"?` | |
| \u2014 ${kr} \u2014`:style==="en"?` | |
| \u2014 ${en} \u2014`:` | |
| \u2014 ${en} / ${kr} \u2014`}function appendContextWarnIfNeeded(ctx,resolvedModel,pluginRequest,markerStyle){if(!ctx||typeof ctx.enqueue!="function"||markerStyle==="off")return;let limit=getContextLimit(resolvedModel);if(!limit)return;let estimated=estimateInputTokensFromPluginRequest(pluginRequest);if(estimated<=0)return;let percent=Math.round(estimated/limit*100);if(percent<80)return;let warn=markerForContextWarn(percent,markerStyle);warn&&ctx.enqueue(warn)}var PROVIDER_ID="custom-openai",DISPLAY_NAME="Custom OpenAI",SETTINGS_SCHEMA=[{key:"apiKey",label:"API Key",type:"password",default:"",help:"OpenAI \uB610\uB294 \uD638\uD658 \uAC8C\uC774\uD2B8\uC6E8\uC774\uC758 API \uD0A4. pluginStorage\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4."},{key:"baseUrl",label:"Base URL",type:"text",default:"https://api.openai.com/v1",help:"\uC5D4\uB4DC\uD3EC\uC778\uD2B8 prefix. \uC608: https://api.openai.com/v1, https://openrouter.ai/api/v1."},{key:"model",label:"Model",type:"text",default:"gpt-4o-mini",help:"\uBAA8\uB378 \uC2DD\uBCC4\uC790. \uBC31\uC5D4\uB4DC\uAC00 \uC778\uC2DD\uD558\uB294 \uAC12 \uADF8\uB300\uB85C \uC785\uB825\uD569\uB2C8\uB2E4."},{key:"systemPrompt",label:"System prepend (\uC120\uD0DD)",type:"textarea",default:"",help:"\uBE44\uC5B4 \uC788\uC9C0 \uC54A\uC73C\uBA74 \uCCAB \uBA54\uC2DC\uC9C0\uB85C system \uC5ED\uD560\uC744 \uCD94\uAC00\uD569\uB2C8\uB2E4."},{key:"temperature",label:"Temperature",type:"number",default:"1",help:"0 ~ 2 \uBC94\uC704 \uAD8C\uC7A5. \uBE48 \uAC12\uC774\uBA74 \uAE30\uBCF8\uAC12 1."},{key:"useResponsesApi",label:"Responses API \uC0AC\uC6A9 (\uC120\uD0DD)",type:"checkbox",default:!1,help:`OpenAI \uC758 \uC0C8 Responses API (\`/v1/responses\`) \uB97C \uC0AC\uC6A9\uD569\uB2C8\uB2E4. Chat Completions \uC640 deprecation \uC5C6\uC774 \uBCD1\uC874\uD558\uBA70, \uB354 \uC548\uC815\uC801\uC778 \uAD6C\uC870\uC801 \uCD9C\uB825 \xB7 \uBCD1\uB82C \uB3C4\uAD6C \uD638\uCD9C \xB7 reasoning budget \uC81C\uACF5. | |
| \xB7 \uD638\uD658 endpoint: api.openai.com (\uC815\uC2DD), Azure OpenAI, \uC77C\uBD80 OpenAI \uD638\uD658 \uC11C\uBC84 (Bifrost \uB4F1). | |
| \xB7 \uD638\uD658 \uBAA8\uB378: gpt-4o / gpt-4.1 / gpt-5 \uACC4\uC5F4 + o1 / o3 reasoning. | |
| \xB7 **OpenRouter \uB4F1 \uB300\uBD80\uBD84\uC758 \uAC8C\uC774\uD2B8\uC6E8\uC774\uB294 \uBBF8\uC9C0\uC6D0** \u2014 \uBCF8 \uD1A0\uAE00\uC774 ON \uC778\uB370 endpoint \uAC00 \uBBF8\uC9C0\uC6D0\uC774\uBA74 404 \uAC00 \uC635\uB2C8\uB2E4. | |
| \xB7 system prompt \uB294 \`instructions\` \uD544\uB4DC\uB85C, messages \uB294 \`input\` \uBC30\uC5F4\uB85C \uC790\uB3D9 \uBCC0\uD658\uB429\uB2C8\uB2E4. | |
| \xB7 Streaming \uC774\uBCA4\uD2B8 \uD3EC\uB9F7 (response.output_text.delta \uB4F1) \uB3C4 \uC790\uB3D9 \uCC98\uB9AC. | |
| \xB7 \uAE30\uBCF8 OFF \u2014 \uAE30\uC874 Chat Completions \uB3D9\uC791\uACFC \uD638\uD658 \uC720\uC9C0.`},{key:"useWebSearch",label:"Web search \uADF8\uB77C\uC6B4\uB529 (Responses API \uC804\uC6A9)",type:"checkbox",default:!1,help:"OpenAI Responses API \uC758 `web_search` \uB3C4\uAD6C \uD65C\uC131\uD654 \u2014 \uBAA8\uB378\uC774 \uC751\uB2F5 \uC804 \uC6F9 \uAC80\uC0C9\uC73C\uB85C \uC815\uBCF4\uB97C \uAC00\uC838\uC634.\n\n\xB7 **`Responses API \uC0AC\uC6A9` \uD1A0\uAE00\uC774 ON \uC774\uC5B4\uC57C \uC791\uB3D9**. Chat Completions \uB9CC \uC4F0\uB294 \uACBD\uC6B0 \uBCF8 \uD1A0\uAE00 \uBB34\uC2DC.\n\xB7 **\uBE44\uC6A9 \uC8FC\uC758**: $10 / 1,000 calls (\uB300\uBD80\uBD84 \uBAA8\uB378 + reasoning preview), \uB610\uB294 $25 / 1,000 (non-reasoning preview). gpt-4o-mini / gpt-4.1-mini \uC758 web_search \uB294 \uAC80\uC0C9 \uACB0\uACFC \uD1A0\uD070\uC774 8,000 input \uD1A0\uD070 \uACE0\uC815 \uBE14\uB85D\uC73C\uB85C \uCCAD\uAD6C.\n\xB7 \uD638\uD658: GPT-4o / GPT-4.1 / GPT-5 + reasoning preview.\n\xB7 Chat Completions \uB9CC \uC4F0\uBA74\uC11C web search \uAC00 \uD544\uC694\uD558\uBA74 \uBAA8\uB378\uC744 `gpt-4o-search-preview` \uB610\uB294 `gpt-4o-mini-search-preview` \uB85C \uAD50\uCCB4 (model \uD544\uB4DC \uC9C1\uC811 \uC218\uC815, \uBCF8 \uD1A0\uAE00 \uBB34\uAD00)."},{key:"costMode",label:"\uBE44\uC6A9 \uBAA8\uB4DC (service_tier)",type:"select",default:"default",options:[{value:"default",label:"\uAE30\uBCF8 (auto)"},{value:"save",label:"\uC808\uC57D (flex, \u22480.5\xD7 cost, \uB290\uB9BC \xB7 429 \uAC00\uB2A5)"},{value:"priority",label:"\uC6B0\uC120 (priority, \u22482\xD7 cost, \uBE60\uB974\uACE0 \uC548\uC815)"}],help:`OpenAI \`service_tier\` \uD30C\uB77C\uBBF8\uD130\uB85C \uAC00\uACA9\xB7\uC9C0\uC5F0 \uD2B8\uB808\uC774\uB4DC\uC624\uD504 \uC870\uC808. | |
| \xB7 flex: Batch API rate (\u22480.5\xD7 default) \u2014 \uC751\uB2F5 \uC2DC\uAC04 \uAE38\uC5B4\uC9D0, 'Resource Unavailable' 429 \uAC00\uB2A5. | |
| \xB7 auto (default): \uD45C\uC900 \uCC98\uB9AC. | |
| \xB7 priority: \u22482\xD7 default \u2014 \uC6B0\uC120 \uD050, \uB354 \uBE60\uB974\uACE0 \uC77C\uAD00\uB41C \uC9C0\uC5F0. | |
| \xB7 \uD638\uD658: OpenAI \uC815\uC2DD (api.openai.com). \uAE30\uD0C0 \uD638\uD658 endpoint \uB294 \uBB34\uC2DC\uB418\uAC70\uB098 400 \uAC00\uB2A5. | |
| \xB7 Chat Completions \uC640 Responses API \uC591\uCABD \uBAA8\uB450\uC5D0 \uC801\uC6A9.`}];function runProvider(pluginRequest,abortSignal,options){return createTokenStream(async ctx=>{let cfg=await loadProviderSettings(PROVIDER_ID,SETTINGS_SCHEMA);if(!cfg.apiKey){ctx.fail("[Blessing/Custom-OpenAI] API Key\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. Blessing \u2192 \uC124\uC815 \u2192 Custom OpenAI \uC5D0\uC11C \uC785\uB825\uD558\uC138\uC694.");return}let baseUrl=normalizeOpenAIBase(cfg.baseUrl,"https://api.openai.com/v1"),overrideModel=options&&typeof options.modelOverride=="string"?options.modelOverride.trim():"",model=String(overrideModel||cfg.model||"gpt-4o-mini"),messages=toOpenAIMessages(pluginRequest);cfg.systemPrompt&&String(cfg.systemPrompt).trim()&&messages.unshift({role:"system",content:String(cfg.systemPrompt)});let useResponses=cfg.useResponsesApi===!0||cfg.useResponsesApi==="true",useStreaming=await loadStreamingPreference(),body,url;if(useResponses){let systemParts=[],input=[];for(let m of messages)m.role==="system"?m.content&&systemParts.push(String(m.content)):(m.role==="user"||m.role==="assistant")&&input.push({role:m.role,content:String(m.content||"")});body={model,input},useStreaming&&(body.stream=!0),systemParts.length>0&&(body.instructions=systemParts.join(` | |
| `)),typeof pluginRequest?.max_tokens=="number"&&(body.max_output_tokens=pluginRequest.max_tokens);let tempR=Number(cfg.temperature);Number.isFinite(tempR)&&(body.temperature=tempR),typeof pluginRequest?.top_p=="number"&&(body.top_p=pluginRequest.top_p),(cfg.useWebSearch===!0||cfg.useWebSearch==="true")&&(body.tools=body.tools||[],body.tools.push({type:"web_search"})),url=`${baseUrl}/responses`}else{body={model,messages},useStreaming&&(body.stream=!0,body.stream_options={include_usage:!0});let temp=Number(cfg.temperature);Number.isFinite(temp)&&(body.temperature=temp),typeof pluginRequest?.max_tokens=="number"&&(needsMaxCompletionTokens(model)?body.max_completion_tokens=pluginRequest.max_tokens:body.max_tokens=pluginRequest.max_tokens),typeof pluginRequest?.top_p=="number"&&(body.top_p=pluginRequest.top_p),typeof pluginRequest?.frequency_penalty=="number"&&(body.frequency_penalty=pluginRequest.frequency_penalty),typeof pluginRequest?.presence_penalty=="number"&&(body.presence_penalty=pluginRequest.presence_penalty),url=`${baseUrl}/chat/completions`}cfg.costMode==="save"?body.service_tier="flex":cfg.costMode==="priority"&&(body.service_tier="priority");let response=await fetchWithFallback(url,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${cfg.apiKey}`,Accept:useStreaming?"text/event-stream":"application/json"},body:JSON.stringify(body),signal:abortSignal,retry:!0,loggedFetch:!useStreaming});if(!response.ok){let text=await response.text().catch(()=>""),c=classifyError({status:response.status,body:text});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID,displayName:DISPLAY_NAME}),ctx.fail(`[Blessing/${DISPLAY_NAME}] HTTP ${response.status}: ${text||response.statusText} | |
| ${c.action}`);return}let markerStyle=await loadMarkerStyle(),finishReason=null,openaiUsage={inputTokens:0,outputTokens:0,cacheCreation:0,cacheRead:0};if(!useStreaming){try{let data=await response.json();if(useResponses){if(typeof data?.output_text=="string"&&data.output_text.length>0)ctx.enqueue(data.output_text);else if(Array.isArray(data?.output))for(let item of data.output){let parts=Array.isArray(item?.content)?item.content:[];for(let p of parts)typeof p?.text=="string"&&p.text.length>0&&ctx.enqueue(p.text)}let reason=data?.incomplete_details?.reason;reason==="max_output_tokens"?finishReason="length":reason==="content_filter"&&(finishReason="content_filter");let u=data?.usage;u&&(openaiUsage.inputTokens=Number(u.input_tokens)||0,openaiUsage.outputTokens=Number(u.output_tokens)||0,openaiUsage.cacheRead=Number(u.input_tokens_details?.cached_tokens)||0)}else{let choice=data?.choices?.[0],content=choice?.message?.content;typeof content=="string"&&content.length>0&&ctx.enqueue(content),finishReason=choice?.finish_reason||null;let u=data?.usage;u&&(openaiUsage.inputTokens=Number(u.prompt_tokens)||0,openaiUsage.outputTokens=Number(u.completion_tokens)||0,openaiUsage.cacheRead=Number(u.prompt_tokens_details?.cached_tokens)||0)}let cause=openaiCause(finishReason);if(cause){let marker=markerFor(cause,markerStyle);marker&&ctx.enqueue(marker)}}catch(err){let c=classifyError({error:err});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID,displayName:DISPLAY_NAME}),ctx.fail("\uC751\uB2F5 \uC218\uC2E0 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.",!1);let marker=markerFor("eof",markerStyle);marker&&ctx.enqueue(marker);try{console.warn(`[Blessing/custom-openai] non-stream parse error: ${err?.message||err}`)}catch{}}(openaiUsage.inputTokens>0||openaiUsage.outputTokens>0)&&recordUsage(PROVIDER_ID,openaiUsage,model).catch(()=>{}),appendContextWarnIfNeeded(ctx,model,pluginRequest,markerStyle);return}try{for await(let msg of parseSSE(response,abortSignal)){if(!msg)continue;if(useResponses){let evt=msg.event;if(!evt&&!msg.data)continue;if(evt==="response.output_text.delta")try{let delta2=JSON.parse(msg.data)?.delta;typeof delta2=="string"&&delta2.length>0&&ctx.enqueue(delta2)}catch{}else if(evt==="response.completed"){try{let payload2=JSON.parse(msg.data),reason=payload2?.response?.incomplete_details?.reason;reason==="max_output_tokens"?finishReason="length":reason==="content_filter"&&(finishReason="content_filter");let u2=payload2?.response?.usage;u2&&(openaiUsage.inputTokens=Number(u2.input_tokens)||0,openaiUsage.outputTokens=Number(u2.output_tokens)||0,openaiUsage.cacheRead=Number(u2.input_tokens_details?.cached_tokens)||0)}catch{}break}else if(evt==="response.failed"||evt==="error"){try{let payload2=JSON.parse(msg.data),errMsg=payload2?.error?.message||payload2?.response?.error?.message||"unknown";ctx.fail(` | |
| [Custom-OpenAI Responses error] `+errMsg)}catch{ctx.fail(` | |
| [Custom-OpenAI Responses error] (parse failed)`)}let marker=markerFor("eof",markerStyle);marker&&ctx.enqueue(marker);break}continue}if(!msg.data)continue;if(msg.data==="[DONE]")break;let payload;try{payload=JSON.parse(msg.data)}catch{continue}let choice=payload?.choices?.[0],delta=choice?.delta?.content;typeof delta=="string"&&delta.length>0&&ctx.enqueue(delta),choice?.finish_reason&&(finishReason=choice.finish_reason);let u=payload?.usage;u&&(openaiUsage.inputTokens=Number(u.prompt_tokens)||0,openaiUsage.outputTokens=Number(u.completion_tokens)||0,openaiUsage.cacheRead=Number(u.prompt_tokens_details?.cached_tokens)||0)}let cause=openaiCause(finishReason);if(cause){let marker=markerFor(cause,markerStyle);marker&&ctx.enqueue(marker)}}catch(err){if(err?.message==="aborted")throw err;let c=classifyError({error:err});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID,displayName:DISPLAY_NAME}),ctx.fail("\uC751\uB2F5 \uC218\uC2E0 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.",!1);let marker=markerFor("eof",markerStyle);marker&&ctx.enqueue(marker);try{console.warn(`[Blessing/custom-openai] stream error: ${err?.message||err}`)}catch{}}(openaiUsage.inputTokens>0||openaiUsage.outputTokens>0)&&recordUsage(PROVIDER_ID,openaiUsage,model).catch(()=>{}),appendContextWarnIfNeeded(ctx,model,pluginRequest,markerStyle)},abortSignal)}var BETA_HEADER_EPHEMERAL="prompt-caching-2024-07-31",BETA_HEADER_EXTENDED="extended-cache-ttl-2025-04-11",ALLOWED_TTL=new Set(["5m","1h"]);function validTtl(ttl){return ALLOWED_TTL.has(ttl)?ttl:"5m"}function markBreakpoint(messages,options={}){if(!Array.isArray(messages)||messages.length===0)return!1;let last=messages[messages.length-1];if(!last||!Array.isArray(last.content)||last.content.length===0)return!1;let lastBlock=last.content[last.content.length-1];return!lastBlock||typeof lastBlock!="object"?!1:(lastBlock.cache_control={type:"ephemeral",ttl:validTtl(options.ttl)},!0)}init_alert();var PROVIDER_ID2="anthropic",DISPLAY_NAME2="Anthropic",SETTINGS_SCHEMA2=[{key:"apiKey",label:"API Key",type:"password",default:"",help:"Anthropic Console\uC5D0\uC11C \uBC1C\uAE09\uD55C sk-ant- \uD0A4."},{key:"model",label:"Model",type:"text",default:"claude-3-5-sonnet-latest",help:"\uC608: claude-3-5-sonnet-latest, claude-3-opus-20240229. \uBE44\uC6A9 \uBAA8\uB4DC\uB97C 'default' \uC678\uB85C \uC124\uC815\uD558\uBA74 \uBCF8 \uD544\uB4DC\uB294 \uBB34\uC2DC\uB418\uACE0 \uBAA8\uB4DC\uBCC4 \uBAA8\uB378\uB85C \uC790\uB3D9 swap."},{key:"costMode",label:"\uBE44\uC6A9 \uBAA8\uB4DC (Haiku / Sonnet / Opus)",type:"select",default:"default",options:[{value:"default",label:"\uAE30\uBCF8 (\uC704 model \uD544\uB4DC \uADF8\uB300\uB85C)"},{value:"save",label:"\uC808\uC57D (claude-haiku-4-5, $1/$5 per MTok)"},{value:"priority",label:"\uC6B0\uC120 (claude-opus-4-7, $5/$25 per MTok)"}],help:`Claude tier \uC790\uB3D9 swap (Option D \u2014 model swap + \uAE30\uC874 caching/thinking \uD1A0\uAE00 \uBCF4\uC874). | |
| \xB7 save: claude-haiku-4-5 \u2014 \uAC00\uC7A5 \uC800\uB834 ($1.00/$5.00 per MTok), \uBE60\uB978 \uC751\uB2F5. \uAC00\uBCBC\uC6B4 RP \uCC44\uD305. | |
| \xB7 default: \uC704 'Model' \uD544\uB4DC \uAC12 \uADF8\uB300\uB85C \uC0AC\uC6A9 (\uC608: claude-3-5-sonnet-latest \uB610\uB294 claude-sonnet-4-6). | |
| \xB7 priority: claude-opus-4-7 \u2014 \uCD5C\uACE0 reasoning ($5.00/$25.00 per MTok). \uBCF5\uC7A1\uD55C \uBA40\uD2F0-\uCE90\uB9AD\uD130 RP. | |
| \xB7 **\uC8FC\uC758**: Opus 4.7 \uC740 \uC0C8 tokenizer \u2014 \uAC19\uC740 input \uC5D0 \uCD5C\uB300 35% \uB354 \uB9CE\uC740 \uD1A0\uD070 \uC0DD\uC131 \uAC00\uB2A5 \u2192 \uC2E4\uD6A8 \uBE44\uC6A9\uC774 \uD5E4\uB4DC\uB77C\uC778 \uAC00\uACA9\uBCF4\uB2E4 \uB192\uC744 \uC218 \uC788\uC74C. | |
| \xB7 \uC774 dropdown \uC740 *model \uB9CC* \uC790\uB3D9 swap. caching / thinking / web search \uD1A0\uAE00\uC740 *\uC544\uB798 \uC601\uC5ED\uC5D0\uC11C \uB3C5\uB9BD\uC801\uC73C\uB85C* \uC81C\uC5B4 (Option D \uB514\uC790\uC778 \u2014 \uCD08\uBCF4 simplicity + \uD30C\uC6CC \uC720\uC800 \uC790\uC720\uB3C4).`},{key:"useCaching",label:"Prompt caching \uC0AC\uC6A9",type:"checkbox",default:!1,help:`Anthropic prompt caching \uD65C\uC131\uD654. \uAC19\uC740 system + \uCD08\uBC18 history \uB97C \uB2E4\uC74C \uD134\uC5D0 \uC7AC\uC0AC\uC6A9\uD574\uC11C input \uBE44\uC6A9\uC744 90% \uC808\uAC10\uD569\uB2C8\uB2E4. | |
| \xB7 Cache *read*: input \uAC00\uACA9\uC758 0.1\xD7 (= 90% \uC808\uAC10). | |
| \xB7 Cache *write*: input \uAC00\uACA9\uC758 1.25\xD7 (5m ttl \uAE30\uBCF8). 'extended cache' \uD65C\uC131 \uC2DC 2\xD7 (1h ttl). | |
| \xB7 \uBCF8 \uC635\uC158 ON \uC2DC system \uBE14\uB85D + history \uB9C8\uC9C0\uB9C9 \uBA54\uC2DC\uC9C0\uC5D0 cache_control marker \uBD80\uCC29 (multi-breakpoint). | |
| \xB7 \uB9E4 \uD134 system \uC774 \uBC14\uC774\uD2B8-\uB3D9\uC77C\uD574\uC57C cache hit. system prompt \uAC00 \uC790\uC8FC \uBC14\uB00C\uB294 \uC2DC\uB098\uB9AC\uC624\uB294 cache \uBB34\uD6A8\uD654\uB85C \uC624\uD788\uB824 \uBE44\uC6A9 \uC99D\uAC00 \uAC00\uB2A5. | |
| \xB7 \uD638\uD658 \uBAA8\uB378: Claude 3.x / 4.x \uC804\uBC18.`},{key:"useCacheExtended",label:"Extended cache (1h ttl)",type:"checkbox",default:!1,help:`Prompt caching \uC758 ttl \uC744 5 \uBD84 \u2192 1 \uC2DC\uAC04 \uC73C\uB85C \uC5F0\uC7A5\uD569\uB2C8\uB2E4 (useCaching \uC774 ON \uC774\uC5B4\uC57C \uC791\uB3D9). | |
| \xB7 Cache write \uBE44\uC6A9\uC774 1.25\xD7 \u2192 2\xD7 input \uC73C\uB85C \uC99D\uAC00. read \uBE44\uC6A9\uC740 \uB3D9\uC77C (0.1\xD7 input). | |
| \xB7 \uAE34 \uD734\uC2DD \uD6C4\uC5D0\uB3C4 cache \uC720\uC9C0 \u2014 \uD55C \uCE90\uB9AD\uD130\uB85C \uD55C \uC2DC\uAC04 \uB3D9\uC548 \uAC04\uD5D0\uC801 \uCC44\uD305 \uC2DC \uD6A8\uC728\uC801. | |
| \xB7 5 \uBD84 \uC548\uC5D0 \uB2E4\uC74C \uD134\uC774 \uD56D\uC0C1 \uB4E4\uC5B4\uAC00\uB294 \uBE60\uB978 \uCC44\uD305\uC774\uB77C\uBA74 5m (\uAE30\uBCF8) \uAC00 \uB354 \uC800\uB834. | |
| \xB7 LBI 0.31.1 \uC758 '[\uD074\uB85C\uB4DC \uCE90\uC2F1 \uC5F0\uC7A5]' \uACFC \uC720\uC0AC \uAC1C\uB150 \u2014 \uB2E8, *\uC790\uB3D9 keep-alive \uD638\uCD9C* \uC740 \uC758\uB3C4\uC801\uC73C\uB85C \uC0DD\uB7B5 (v3 sandbox timer leak \uC704\uD5D8). 1h ttl \uC548\uC5D0 \uB2E4\uC74C \uD134\uC774 \uB4E4\uC5B4\uC624\uBA74 cache \uC720\uC9C0, \uC2DC\uAC04 \uCD08\uACFC \uC2DC \uC790\uC5F0\uC2A4\uB7FD\uAC8C \uC7AC\uAD6C\uCD95.`},{key:"useWebSearch",label:"Web search \uADF8\uB77C\uC6B4\uB529 \uC0AC\uC6A9",type:"checkbox",default:!1,help:`Anthropic \uC758 server-side web_search \uB3C4\uAD6C \uD65C\uC131\uD654 \u2014 \uBAA8\uB378\uC774 \uC751\uB2F5 \uC804 \uC2E4\uC2DC\uAC04 \uC6F9 \uAC80\uC0C9\uC73C\uB85C \uC815\uBCF4\uB97C \uAC00\uC838\uC640 \uB2F5\uBCC0\uC5D0 \uBC18\uC601\uD569\uB2C8\uB2E4 (\uADF8\uB77C\uC6B4\uB529). | |
| \xB7 **\uBE44\uC6A9 \uC8FC\uC758**: \uAC80\uC0C9 1,000 \uD68C\uB2F9 $10 (Anthropic \uCCAD\uAD6C) + \uAC80\uC0C9 \uACB0\uACFC \uD1A0\uD070\uC740 input \uAC00\uACA9\uC73C\uB85C \uBCC4\uB3C4 \uCCAD\uAD6C. | |
| \xB7 \uD638\uD658 \uBAA8\uB378: Claude 4.6+/Sonnet 4.6+ \uAD8C\uC7A5 (web_search_20260209 dynamic filtering). \uAD6C\uBC84\uC804\uC740 web_search_20250305. | |
| \xB7 \uB9E4 \uD134 \uAC80\uC0C9\uC774 \uBC1C\uC0DD\uD560 \uC218 \uC788\uC5B4 \uBE44\uC6A9 \uD3ED\uC99D \uC704\uD5D8 \u2014 \uAC00\uB054 \uC0AC\uC6A9 \uC2DC \uD1A0\uAE00 ON, \uD3C9\uC18C OFF \uAD8C\uC7A5. | |
| \xB7 \uB864\uD50C\uB808\uC774 \uC2DC: \uD604\uC7AC \uC0AC\uAC74 / \uC2E4\uC874 \uC778\uBB3C\xB7\uC7A5\uC18C \uC0AC\uC2E4 \uD655\uC778 \uB4F1\uC5D0 \uC720\uD6A8. \uB2E8\uC21C fiction \uC9C4\uD589\uC5D0\uB294 \uBD88\uD544\uC694. | |
| \xB7 \uAC80\uC0C9 \uB3C4\uC911 \uBAA8\uB378\uC774 server_tool_use / web_search_tool_result content block \uC744 emit \uD558\uC9C0\uB9CC Blessing \uC740 \uCD5C\uC885 text \uC751\uB2F5\uB9CC \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uD45C\uC2DC (raw \uAC80\uC0C9 \uACB0\uACFC\uB294 \uB514\uBC84\uADF8\uC6A9\uC73C\uB85C\uB9CC).`},{key:"useThinking",label:"Extended thinking \uC0AC\uC6A9",type:"checkbox",default:!1,help:`Claude 4.x / 3.7 \uC758 extended thinking \uD65C\uC131\uD654. \uBAA8\uB378\uC774 \uB2F5\uD558\uAE30 \uC804 \uB0B4\uBD80 \uCD94\uB860\uC744 \uC218\uD589\uD574 \uBCF5\uC7A1\uD55C \uCD94\uB860\xB7\uB864\uD50C\uB808\uC774 \uC77C\uAD00\uC131\uC744 \uAC1C\uC120\uD569\uB2C8\uB2E4. | |
| \xB7 \uBE44\uC6A9 \uC8FC\uC758: thinking \uD1A0\uD070\uC740 *output* \uAC00\uACA9\uC73C\uB85C \uCCAD\uAD6C\uB429\uB2C8\uB2E4 (\uC608: Sonnet 4.6 = $15/MTok output). \uD55C \uC751\uB2F5\uB2F9 \uCD94\uAC00 \uBE44\uC6A9\uC774 \uBC1C\uC0DD\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | |
| \xB7 \uD638\uD658 \uBAA8\uB378: claude-opus-4-*, claude-sonnet-4-*, claude-3-7-sonnet-* \uB4F1. claude-3-5-* \uC774\uD558\uB294 \uBBF8\uC9C0\uC6D0 \u2192 API \uAC00 400 \uC5D0\uB7EC\uB85C \uC751\uB2F5\uD569\uB2C8\uB2E4. | |
| \xB7 \uC751\uB2F5 \uBCF8\uBB38 \uC804\uC5D0 <Thoughts>...</Thoughts> \uBE14\uB85D\uC774 \uC2A4\uD2B8\uB9AC\uBC0D\uB429\uB2C8\uB2E4 (RisuAI \uAC00 \uD45C\uC2DC \uC5EC\uBD80 \uD1A0\uAE00 \uAC00\uB2A5). | |
| \xB7 thinkingBudget \uD544\uB4DC\uB85C \uCD94\uB860 \uD1A0\uD070 \uC0C1\uD55C \uC870\uC808.`},{key:"thinkingBudget",label:"Thinking budget (\uD1A0\uD070)",type:"number",default:"5000",help:`\uD55C \uC751\uB2F5\uC5D0\uC11C extended thinking \uC5D0 \uC4F8 \uC218 \uC788\uB294 \uD1A0\uD070 \uC0C1\uD55C\uC785\uB2C8\uB2E4. | |
| \xB7 \uAD8C\uC7A5 \uBC94\uC704: 1024-32000. (Anthropic \uAC15\uC81C \uD558\uD55C = 1024) | |
| \xB7 max_tokens \uC758 80% \uC774\uD558\uB85C \uC124\uC815 \uAD8C\uC7A5 \u2014 thinking \uC774 max_tokens \uBCF4\uB2E4 \uD06C\uBA74 \uC751\uB2F5\uC774 thinking \uB3C4\uC911 \uC798\uB9BD\uB2C8\uB2E4. | |
| \xB7 \uC608: max_tokens=4096 \uC774\uBA74 thinkingBudget=2000~3000 \uC815\uB3C4 \uC801\uC808. | |
| \xB7 useThinking \uC774 OFF \uBA74 \uC774 \uAC12\uC740 \uBB34\uC2DC\uB429\uB2C8\uB2E4. | |
| \xB7 thinking \uC774 \uB354 \uAE4A\uC744\uC218\uB85D \uC751\uB2F5 \uD488\uC9C8\uC774 \uB192\uC544\uC9C8 \uAC00\uB2A5\uC131\uC740 \uC788\uC73C\uB098 \uBE44\uC6A9\xB7\uC9C0\uC5F0\uB3C4 \uBE44\uB840 \uC99D\uAC00\uD569\uB2C8\uB2E4.`},{key:"maxTokens",label:"max_tokens (\uC751\uB2F5 \uC0C1\uD55C)",type:"number",default:"4096",help:`\uD55C \uC751\uB2F5\uC5D0\uC11C \uBAA8\uB378\uC774 \uC0DD\uC131\uD560 \uCD9C\uB825 \uD1A0\uD070\uC758 \uC0C1\uD55C\uC785\uB2C8\uB2E4 (\uC785\uB825 \uD1A0\uD070\uC740 \uD3EC\uD568\uD558\uC9C0 \uC54A\uC74C). | |
| \xB7 Anthropic API\uC5D0\uC11C\uB294 max_tokens\uAC00 \uD544\uC218 \uD30C\uB77C\uBBF8\uD130\uC774\uBBC0\uB85C, \uBE44\uC6CC\uB450\uBA74 4096\uC744 \uAC15\uC81C \uC801\uC6A9\uD569\uB2C8\uB2E4. | |
| \xB7 RisuAI\uAC00 \uD638\uCD9C \uC2DC pluginRequest.max_tokens\uB97C \uD568\uAED8 \uC804\uB2EC\uD558\uBA74 \uADF8 \uAC12\uC774 \uC6B0\uC120\uD569\uB2C8\uB2E4 (RisuAI \uC124\uC815 > Max Response). | |
| \xB7 \uBAA8\uB378\uBCC4 \uCD9C\uB825 \uD55C\uB3C4: Claude 3.5 Sonnet/Haiku = 8192, Claude 3 Opus/Sonnet/Haiku = 4096. \uCD08\uACFC \uC2DC API\uAC00 400\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4. | |
| \xB7 \uAC12\uC744 \uB108\uBB34 \uC791\uAC8C \uC7A1\uC73C\uBA74 \uC751\uB2F5\uC774 \uC911\uAC04\uC5D0 \uC798\uB9BD\uB2C8\uB2E4 (stop_reason='max_tokens'). \uAE38\uC774 \uC81C\uC5B4\uC6A9\uC73C\uB85C\uB294 system prompt\uB97C \uC0AC\uC6A9\uD558\uB294 \uD3B8\uC774 \uC548\uC804\uD569\uB2C8\uB2E4.`}],API_URL="https://api.anthropic.com/v1/messages",USER_ROLE="user",ASSISTANT_ROLE="assistant",SYSTEM_ROLE="system";function buildClaudeBody(pluginRequest,settings){let chats=(Array.isArray(pluginRequest?.prompt_chat)?pluginRequest.prompt_chat:Array.isArray(pluginRequest?.messages)?pluginRequest.messages:[]).map(m=>{let content=m?.content;return Array.isArray(content)&&(content=content.map(p=>typeof p=="string"?p:p?.text||"").filter(Boolean).join(` | |
| `)),{role:m?.role||USER_ROLE,content:String(content??"")}}),splitIndex=chats.findIndex(m=>m.role===USER_ROLE||m.role===ASSISTANT_ROLE);splitIndex===-1&&(splitIndex=chats.length);let systemText="";for(let i=0;i<splitIndex;i++){let t=chats[i].content.trim();t&&(systemText&&(systemText+=` | |
| `),systemText+=t)}let rest=chats.slice(splitIndex);(rest.length===0||rest[0].role!==USER_ROLE)&&rest.unshift({role:USER_ROLE,content:"Start"});let messages=[];for(let m of rest){let text=m.content.trim(),last=messages.length?messages[messages.length-1]:null;if(m.role===SYSTEM_ROLE){let injected="system: "+text;last?.role===USER_ROLE?last.content[0].text+=` | |
| `+injected:messages.push({role:USER_ROLE,content:[{type:"text",text:injected}]})}else(m.role===USER_ROLE||m.role===ASSISTANT_ROLE)&&(last?.role===m.role?last.content[0].text+=` | |
| `+text:messages.push({role:m.role,content:[{type:"text",text}]}))}let maxTokensRaw=parseInt(settings.maxTokens,10),maxTokens=Number.isFinite(maxTokensRaw)&&maxTokensRaw>0?maxTokensRaw:Number.isFinite(pluginRequest?.max_tokens)&&pluginRequest.max_tokens>0?pluginRequest.max_tokens:4096,resolvedModel=settings.model||"claude-3-5-sonnet-latest";settings.costMode==="save"?resolvedModel="claude-haiku-4-5":settings.costMode==="priority"&&(resolvedModel="claude-opus-4-7");let body={model:resolvedModel,messages,max_tokens:maxTokens};if(systemText){let sysBlock={type:"text",text:systemText};if(settings.useCaching===!0||settings.useCaching==="true"){let ttl=settings.useCacheExtended===!0||settings.useCacheExtended==="true"?"1h":"5m";sysBlock.cache_control={type:"ephemeral",ttl:validTtl(ttl)},markBreakpoint(messages,{ttl})}body.system=[sysBlock]}if(settings.useThinking===!0||settings.useThinking==="true"){let budgetRaw=parseInt(settings.thinkingBudget,10),budget=Number.isFinite(budgetRaw)&&budgetRaw>=1024?budgetRaw:5e3;body.thinking={type:"enabled",budget_tokens:budget}}return(settings.useWebSearch===!0||settings.useWebSearch==="true")&&(body.tools=body.tools||[],body.tools.push({type:"web_search_20260209",name:"web_search"})),pluginRequest?.temperature!=null&&(body.temperature=Number(pluginRequest.temperature)),pluginRequest?.top_p!=null&&(body.top_p=Number(pluginRequest.top_p)),pluginRequest?.top_k!=null&&(body.top_k=Number(pluginRequest.top_k)),body}function runProvider2(pluginRequest,abortSignal,options){return createTokenStream(async ctx=>{let settings=await loadProviderSettings(PROVIDER_ID2,SETTINGS_SCHEMA2);if(options&&typeof options.modelOverride=="string"&&options.modelOverride.trim()&&(settings.model=options.modelOverride.trim(),settings.costMode="default"),!settings.apiKey){ctx.fail("[Blessing/anthropic] API Key\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uC88C\uCE21 \uC0AC\uC774\uB4DC\uBC14\uC758 Blessing \u2192 Providers \uD0ED\uC5D0\uC11C \uC785\uB825\uD558\uC138\uC694.");return}let useStreaming=await loadStreamingPreference(),body=buildClaudeBody(pluginRequest,settings);useStreaming&&(body.stream=!0);let headers={"x-api-key":String(settings.apiKey),"anthropic-version":"2023-06-01","anthropic-dangerous-direct-browser-access":"true","content-type":"application/json"};if(settings.useCaching===!0||settings.useCaching==="true"){let useExt=settings.useCacheExtended===!0||settings.useCacheExtended==="true";headers["anthropic-beta"]=useExt?`${BETA_HEADER_EPHEMERAL},${BETA_HEADER_EXTENDED}`:BETA_HEADER_EPHEMERAL}let response=await fetchWithFallback(API_URL,{method:"POST",headers,body:JSON.stringify(body),signal:abortSignal,retry:!0,loggedFetch:!useStreaming}),markerStyle=await loadMarkerStyle(),usage={inputTokens:0,outputTokens:0,cacheCreation:0,cacheRead:0},inThinking=!1,stopReason=null;try{if(useStreaming)for await(let msg of parseSSE(response,abortSignal)){let parsed;try{parsed=JSON.parse(msg.data)}catch{continue}let t=parsed?.type||msg.event;if(t==="message_start"){let u=parsed?.message?.usage;u&&(usage.inputTokens=Number(u.input_tokens)||0,usage.cacheCreation=Number(u.cache_creation_input_tokens)||0,usage.cacheRead=Number(u.cache_read_input_tokens)||0)}else if(t==="content_block_delta"){let d=parsed.delta;if(!d)continue;if(d.type==="thinking_delta"||d.type==="thinking"){if(!d.thinking)continue;inThinking||(inThinking=!0,ctx.enqueue(`<Thoughts> | |
| `)),ctx.enqueue(d.thinking)}else if(d.type==="text_delta"||d.type==="text"){if(!d.text)continue;inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `)),ctx.enqueue(d.text)}}else if(t==="message_delta"){parsed?.delta?.stop_reason&&(stopReason=parsed.delta.stop_reason);let u=parsed?.usage;u&&typeof u.output_tokens=="number"&&(usage.outputTokens=u.output_tokens)}else if(t==="message_stop"){inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `));let cause=anthropicCause(stopReason);if(cause){let marker=markerFor(cause,markerStyle);marker&&ctx.enqueue(marker)}break}else if(t==="error"){ctx.fail(` | |
| [Anthropic error] `+(parsed?.error?.message||"unknown"));let marker=markerFor("eof",markerStyle);marker&&ctx.enqueue(marker);break}}else if(response.ok){let data=await response.json();if(data?.error){ctx.fail(` | |
| [Anthropic error] `+(data.error?.message||JSON.stringify(data.error)));let marker=markerFor("eof",markerStyle);marker&&ctx.enqueue(marker)}else{let blocks=Array.isArray(data?.content)?data.content:[];for(let b of blocks)b?.type==="thinking"&&typeof b?.thinking=="string"&&b.thinking.length>0?(inThinking||(inThinking=!0,ctx.enqueue(`<Thoughts> | |
| `)),ctx.enqueue(b.thinking)):b?.type==="text"&&typeof b?.text=="string"&&b.text.length>0&&(inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `)),ctx.enqueue(b.text));inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `));let u=data?.usage;u&&(usage.inputTokens=Number(u.input_tokens)||0,usage.outputTokens=Number(u.output_tokens)||0,usage.cacheCreation=Number(u.cache_creation_input_tokens)||0,usage.cacheRead=Number(u.cache_read_input_tokens)||0);let cause=anthropicCause(data?.stop_reason);if(cause){let marker=markerFor(cause,markerStyle);marker&&ctx.enqueue(marker)}}}else{let text=await response.text().catch(()=>""),c=classifyError({status:response.status,body:text});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID2,displayName:DISPLAY_NAME2}),ctx.fail(`[Blessing/${DISPLAY_NAME2}] HTTP ${response.status}: ${text||response.statusText} | |
| ${c.action}`)}}catch(err){if(err?.message==="aborted")throw err;let c=classifyError({error:err});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID2,displayName:DISPLAY_NAME2}),inThinking&&ctx.enqueue(` | |
| </Thoughts> | |
| `),ctx.fail("\uC751\uB2F5 \uC218\uC2E0 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.",!1);let marker=markerFor("eof",markerStyle);marker&&ctx.enqueue(marker);try{console.warn(`[Blessing/anthropic] stream error: ${err?.message||err}`)}catch{}}(usage.inputTokens>0||usage.outputTokens>0||usage.cacheCreation>0||usage.cacheRead>0)&&recordUsage(PROVIDER_ID2,usage,body.model).catch(()=>{}),appendContextWarnIfNeeded(ctx,body.model,pluginRequest,markerStyle)},abortSignal)}var GEMINI_THINKING_FIELD={key:"thinkingLevel",label:"\uCD94\uB860 \uC218\uC900 (Gemini 3)",type:"select",default:"",options:[{value:"",label:"\uAE30\uBCF8\uAC12"},{value:"minimal",label:"\uCD5C\uC18C"},{value:"low",label:"\uB0AE\uC74C"},{value:"medium",label:"\uBCF4\uD1B5"},{value:"high",label:"\uB192\uC74C"}],help:`\uB2F5\uD558\uAE30 \uC804\uC5D0 \uC5BC\uB9C8\uB098 \uAE4A\uAC8C \uCD94\uB860\uD560\uC9C0 \uACE0\uB985\uB2C8\uB2E4. \uB0AE\uC74C\uC740 \uC751\uB2F5 \uC18D\uB3C4\uB97C, \uB192\uC74C\uC740 \uB354 \uAE4A\uC740 \uCD94\uB860\uC744 \uC6B0\uC120\uD569\uB2C8\uB2E4. \uB192\uC774\uBA74 \uC751\uB2F5 \uC2DC\uAC04\uACFC \uBE44\uC6A9\uC774 \uB298\uC5B4\uB0A0 \uC218 \uC788\uC73C\uBA70 \uD56D\uC0C1 \uB354 \uC88B\uC740 \uB2F5\uC744 \uBCF4\uC7A5\uD558\uC9C0\uB294 \uC54A\uC2B5\uB2C8\uB2E4. | |
| Gemini 3\uC5D0\uB9CC \uC801\uC6A9\uD558\uBA70, \uAE30\uBCF8\uAC12\uC740 \uBAA8\uB378\uC758 \uAE30\uBCF8 \uC124\uC815\uC744 \uC720\uC9C0\uD569\uB2C8\uB2E4. \uC774 \uC11C\uBE44\uC2A4\uC758 Gemini 3 \uCC44\uD305\uACFC \uBAA8\uB378 \uD638\uCD9C \uD14C\uC2A4\uD2B8\uC5D0 \uACF5\uD1B5\uC73C\uB85C \uC801\uC6A9\uB429\uB2C8\uB2E4. \uCC44\uD305 \uBAA8\uB378 \uC790\uCCB4\uB294 \uD638\uC2A4\uD2B8\uC5D0\uC11C \uACE0\uB985\uB2C8\uB2E4. | |
| \uCD5C\uC18C\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uBAA8\uB378\uC740 \uB0AE\uC74C\uC73C\uB85C \uBCF4\uB0C5\uB2C8\uB2E4. Gemini 3 Pro\uC758 \uBCF4\uD1B5\uC740 \uB0AE\uC74C\uC73C\uB85C, Flash-Lite Image\uC758 \uB0AE\uC74C\xB7\uBCF4\uD1B5\uC740 \uCD5C\uC18C\uB85C \uBCF4\uB0C5\uB2C8\uB2E4. \uCD5C\uC18C\uB294 \uCD94\uB860\uC744 \uC644\uC804\uD788 \uB048\uB2E4\uB294 \uB73B\uC774 \uC544\uB2D9\uB2C8\uB2E4. | |
| \uACE0\uAE09 \uC124\uC815\uC758 \uCD94\uAC00 \uC0DD\uC131 JSON\uC5D0 \uC0AC\uACE0 \uC218\uC900\uC774\uB098 \uC61B \uC0AC\uACE0 \uD1A0\uD070 \uC608\uC0B0 0\uC774 \uC788\uC73C\uBA74 \uADF8 \uAC12\uC774 \uC6B0\uC120\uD569\uB2C8\uB2E4. \uC120\uD0DD\uC774 \uBC18\uC601\uB418\uC9C0 \uC54A\uB294\uB2E4\uBA74 \uBA3C\uC800 \uADF8 \uC124\uC815\uC744 \uD655\uC778\uD558\uC138\uC694. | |
| Gemini 2.5\uB294 \uC774 \uC120\uD0DD\uC744 \uC0AC\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. Vertex\uC5D0\uC11C 2.5\uC758 \uCD94\uB860 \uD1A0\uD070 \uC608\uC0B0\uC744 \uC9C0\uC815\uD558\uB824\uBA74 \uACE0\uAE09 \uC124\uC815 \u2192 \uCD94\uAC00 \uC0DD\uC131 \uC124\uC815 (Gemini JSON)\uC5D0 {"thinkingConfig":{"thinkingBudget":1024}}\uCC98\uB7FC \uC785\uB825\uD558\uC138\uC694. \uD5C8\uC6A9 \uBC94\uC704\uB294 \uBAA8\uB378\uB9C8\uB2E4 \uB2E4\uB985\uB2C8\uB2E4. Vertex\uC758 Claude\uC5D0\uB294 \uC801\uC6A9\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`},GEMINI_CONFIG_HELP=`\uC694\uCCAD\uC5D0 \uCD94\uAC00\uD560 JSON\uC785\uB2C8\uB2E4. | |
| Gemini 3: {"thinkingConfig":{"thinkingLevel":"low"}} | |
| Gemini 2.5: {"thinkingConfig":{"thinkingBudget":0}} | |
| Gemini 3\uC5D0\uB294 temperature\xB7topP\xB7topK\xB7\uC0AC\uACE0 \uD1A0\uD070 \uC608\uC0B0\uC744 \uBCF4\uB0B4\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC61B \uC608\uC0B0\uC774 0\uC774\uBA74 \uC9C0\uC6D0\uD558\uB294 \uCD5C\uC18C \uC218\uC900\uC73C\uB85C \uBC14\uAFB8\uACE0, \uC591\uC218\xB7-1\uC740 \uC0AC\uACE0 \uC218\uC900 \uC124\uC815\uC744 \uB530\uB985\uB2C8\uB2E4. \uCD5C\uC18C\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC73C\uBA74 \uB0AE\uC74C\uC744 \uC0AC\uC6A9\uD569\uB2C8\uB2E4. \uC798\uBABB\uB41C JSON\uC740 \uD638\uCD9C \uC804\uC5D0 \uC624\uB958\uB85C \uD45C\uC2DC\uD569\uB2C8\uB2E4.`;function modelInfo(model){let id=String(model||"").trim().toLowerCase(),match=/(?:^|\/)gemini-?3(?:\.(\d+))?(?:-|$)/.exec(id);if(!match)return null;let minor=Number(match[1]||0);return{imageLite:/flash-lite-image/.test(id),minimal:!(/pro/.test(id)||/flash/.test(id)&&!/lite/.test(id)&&minor>=7),oldPro:minor===0&&/pro/.test(id)}}function mergeGeminiConfig(config,extra){extra&&(Object.assign(config,extra),Object.hasOwn(extra,"thinking_config")&&!Object.hasOwn(extra,"thinkingConfig")&&(config.thinkingConfig=extra.thinking_config),delete config.thinking_config)}function normalizeGeminiConfig(config,model,options={}){let info=modelInfo(model);if(!info)return config;let result={...config};for(let key of["temperature","topP","topK","top_p","top_k","candidateCount","candidate_count"])delete result[key];let raw=result.thinkingConfig??result.thinking_config,thinking=raw&&typeof raw=="object"&&!Array.isArray(raw)?{...raw}:{},budget=thinking.thinkingBudget??thinking.thinking_budget,level=thinking.thinkingLevel??thinking.thinking_level;if(delete thinking.thinkingBudget,delete thinking.thinking_budget,delete thinking.thinking_level,delete result.thinking_config,!level&&budget===0&&(level=info.minimal?"minimal":"low"),level||(level=options.thinkingLevel||""),level){if(level=String(level).toLowerCase(),!["minimal","low","medium","high"].includes(level))throw Error("Gemini \uC0AC\uACE0 \uC218\uC900\uC740 minimal, low, medium, high \uC911\uC5D0\uC11C \uC120\uD0DD\uD558\uC138\uC694.");level==="minimal"&&!info.minimal&&(level="low"),info.oldPro&&level==="medium"&&(level="low"),info.imageLite&&["low","medium"].includes(level)&&(level="minimal"),thinking.thinkingLevel=level}else delete thinking.thinkingLevel;return Object.keys(thinking).length?result.thinkingConfig=thinking:delete result.thinkingConfig,result}var GEMINI_EXTRA_FIELD={key:"customGenConfig",label:"\uCD94\uAC00 \uC0DD\uC131 \uC124\uC815 (Gemini JSON)",type:"textarea",default:"",help:GEMINI_CONFIG_HELP};function prepareGeminiConfig(config,model,settings={}){let merged={...config},budget=parseInt(settings.thinkingBudget,10);if(!merged.thinkingConfig&&Number.isFinite(budget)&&budget>0&&(merged.thinkingConfig={thinkingBudget:budget,includeThoughts:!0}),settings.customGenConfig&&String(settings.customGenConfig).trim()){let extra;try{extra=JSON.parse(settings.customGenConfig)}catch{throw Error("Gemini \uCD94\uAC00 \uC0DD\uC131 \uC124\uC815\uC758 JSON \uD615\uC2DD\uC744 \uD655\uC778\uD558\uC138\uC694.")}if(!extra||typeof extra!="object"||Array.isArray(extra))throw Error("Gemini \uCD94\uAC00 \uC0DD\uC131 \uC124\uC815\uC740 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.");let thinking=extra.thinkingConfig??extra.thinking_config;if(thinking!==void 0&&(!thinking||typeof thinking!="object"||Array.isArray(thinking)))throw Error("thinkingConfig\uB294 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.");mergeGeminiConfig(merged,extra)}return normalizeGeminiConfig(merged,model,{thinkingLevel:settings.thinkingLevel})}var GeminiInputBlockError=class extends Error{constructor(reason){super(`Gemini \uC785\uB825 \uCC28\uB2E8: ${reason}`),this.name="GeminiInputBlockError"}};function checkGeminiInputBlock(response){let reason=response?.promptFeedback?.blockReason;if(reason&&reason!=="BLOCK_REASON_UNSPECIFIED")throw new GeminiInputBlockError(reason)}init_alert();var REGISTRY_KEY=`${STORAGE_PREFIX}gemini.cache.registry`,DEFAULT_TTL_SECONDS=3600,REFRESH_BUFFER_MS=5*60*1e3,MIN_TOKENS_PRO=4096,MIN_TOKENS_FLASH=1024;function getMinTokensFor(model){let m=String(model||"").toLowerCase();return m.includes("flash")?MIN_TOKENS_FLASH:(m.includes("pro"),MIN_TOKENS_PRO)}function estimateTokens(text){return Math.floor(String(text||"").length*.3)}async function computePrefixHash(text){let data=new TextEncoder().encode(String(text||"")),buf=await crypto.subtle.digest("SHA-256",data);return Array.from(new Uint8Array(buf)).map(b=>b.toString(16).padStart(2,"0")).join("")}async function loadRegistry(){try{let raw=await getStorage().getItem(REGISTRY_KEY);if(raw){let parsed=typeof raw=="object"?raw:JSON.parse(String(raw));if(parsed&&typeof parsed=="object"&&!Array.isArray(parsed))return parsed}}catch{}return{}}async function saveRegistry(reg){try{await getStorage().setItem(REGISTRY_KEY,JSON.stringify(reg))}catch{}}async function pruneExpired(reg){let now=Date.now(),changed=!1;for(let key of Object.keys(reg))(!reg[key]||!reg[key].expireTime||reg[key].expireTime<now)&&(delete reg[key],changed=!0);changed&&await saveRegistry(reg)}function buildKey(chaId,modelId,prefixHash){return`${chaId||"_"}|${modelId}|${prefixHash}`}async function getCurrentCharId(){try{let host=getHost();if(host&&typeof host.getCharacter=="function"){let ch=await host.getCharacter();return String(ch?.chaId||ch?.id||"_")}}catch{}return"_"}async function findCache(chaId,modelId,prefixHash){let reg=await loadRegistry();await pruneExpired(reg);let key=buildKey(chaId,modelId,prefixHash),entry=reg[key];return!entry||entry.expireTime<Date.now()?null:{cacheName:entry.cacheName,expireTime:entry.expireTime}}function shouldRefresh(expireTime){return expireTime?expireTime-Date.now()<REFRESH_BUFFER_MS:!1}async function createCache(apiKey,baseUrl,modelId,chaId,prefixHash,contents,systemInstruction,ttlSec=DEFAULT_TTL_SECONDS){if(!apiKey||!baseUrl||!modelId||!contents)return null;let url=`${baseUrl}/cachedContents?key=${encodeURIComponent(apiKey)}`,body={model:`models/${modelId}`,contents,ttl:`${ttlSec}s`};systemInstruction&&(body.systemInstruction=systemInstruction);let response;try{response=await fetch(url,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(body)})}catch(err){try{console.warn(`[Blessing/gemini-cache] create failed (network): ${err?.message}`)}catch{}return null}if(!response.ok){try{console.warn(`[Blessing/gemini-cache] create failed HTTP ${response.status}`)}catch{}return null}let result;try{result=await response.json()}catch{return null}let cacheName=result?.name;if(!cacheName)return null;let expireTimeStr=result?.expireTime,expireTime=expireTimeStr?new Date(expireTimeStr).getTime():Date.now()+ttlSec*1e3,reg=await loadRegistry(),key=buildKey(chaId,modelId,prefixHash);return reg[key]={cacheName,expireTime},await saveRegistry(reg),{cacheName,expireTime}}async function patchCache(apiKey,baseUrl,cacheName,ttlSec=DEFAULT_TTL_SECONDS){if(!cacheName)return null;let url=`${baseUrl}/${cacheName}?key=${encodeURIComponent(apiKey)}`,response;try{response=await fetch(url,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({ttl:`${ttlSec}s`})})}catch(err){try{console.warn(`[Blessing/gemini-cache] patch failed (network): ${err?.message}`)}catch{}return null}if(!response.ok){try{console.warn(`[Blessing/gemini-cache] patch failed HTTP ${response.status}`)}catch{}return null}let result;try{result=await response.json()}catch{return null}let expireTimeStr=result?.expireTime,expireTime=expireTimeStr?new Date(expireTimeStr).getTime():Date.now()+ttlSec*1e3;return{cacheName,expireTime}}async function createCacheVertex(token,vertexBaseUrl,projectId,region,modelId,chaId,prefixHash,contents,systemInstruction,ttlSec=DEFAULT_TTL_SECONDS){if(!token||!vertexBaseUrl||!projectId||!region||!modelId||!contents)return null;let url=`${vertexBaseUrl}/cachedContents`,body={model:`projects/${projectId}/locations/${region}/publishers/google/models/${modelId}`,contents,ttl:`${ttlSec}s`};systemInstruction&&(body.systemInstruction=systemInstruction);let response;try{response=await fetch(url,{method:"POST",headers:{authorization:"Bearer "+token,"content-type":"application/json"},body:JSON.stringify(body)})}catch(err){try{console.warn(`[Blessing/vertex-gemini-cache] create failed (network): ${err?.message}`)}catch{}return null}if(!response.ok){try{let text=await response.text().catch(()=>"");console.warn(`[Blessing/vertex-gemini-cache] create failed HTTP ${response.status}: ${text}`)}catch{}return null}let result;try{result=await response.json()}catch{return null}let cacheName=result?.name;if(!cacheName)return null;let expireTimeStr=result?.expireTime,expireTime=expireTimeStr?new Date(expireTimeStr).getTime():Date.now()+ttlSec*1e3,reg=await loadRegistry(),key=buildKey(chaId,`vertex-gemini/${modelId}`,prefixHash);return reg[key]={cacheName,expireTime},await saveRegistry(reg),{cacheName,expireTime}}async function patchCacheVertex(token,cacheName,ttlSec=DEFAULT_TTL_SECONDS){if(!token||!cacheName)return null;let url=`https://aiplatform.googleapis.com/v1/${cacheName}`,response;try{response=await fetch(url,{method:"PATCH",headers:{authorization:"Bearer "+token,"content-type":"application/json"},body:JSON.stringify({ttl:`${ttlSec}s`})})}catch(err){try{console.warn(`[Blessing/vertex-gemini-cache] patch failed (network): ${err?.message}`)}catch{}return null}if(!response.ok){try{console.warn(`[Blessing/vertex-gemini-cache] patch failed HTTP ${response.status}`)}catch{}return null}let result;try{result=await response.json()}catch{return null}let expireTimeStr=result?.expireTime,expireTime=expireTimeStr?new Date(expireTimeStr).getTime():Date.now()+ttlSec*1e3;return{cacheName,expireTime}}async function findCacheVertex(chaId,modelId,prefixHash){return findCache(chaId,`vertex-gemini/${modelId}`,prefixHash)}function buildStableCachePrefix(contents,systemInstruction,minTokens){if(!Array.isArray(contents)||contents.length<2)return null;let sysText=(systemInstruction?.parts||[]).map(p=>String(p?.text||"")).join(""),acc=estimateTokens(sysText),maxBoundary=contents.length-1;for(let i=0;i<maxBoundary;i++){let text=(contents[i]?.parts||[]).map(p=>String(p?.text||"")).join("");if(acc+=estimateTokens(text),acc>=minTokens)return{prefix:contents.slice(0,i+1),tail:contents.slice(i+1)}}return null}function serializePrefixForHash(prefix,systemInstruction){let sysText=systemInstruction?.parts?.map(p=>String(p?.text||"")).join("")||"",histText=(prefix||[]).map(m=>{let role=String(m?.role||""),text=(m?.parts||[]).map(p=>String(p?.text||"")).join("");return`${role}:${text}`}).join(` | |
| --- | |
| `);return`${sysText} | |
| ===SYSTEM_END=== | |
| ${histText}`}var PROVIDER_ID3="google-ai",DISPLAY_NAME3="GoogleAI (Google AI Studio)",SETTINGS_SCHEMA3=[{key:"apiKey",label:"API Key",type:"password",default:"",help:"Google AI Studio (aistudio.google.com)\uC5D0\uC11C \uBC1C\uAE09\uD55C API \uD0A4."},{key:"model",label:"Model",type:"text",default:"gemini-2.5-flash",help:"\uC608: gemini-2.5-flash, gemini-2.5-pro, gemini-2.0-flash-exp."},GEMINI_THINKING_FIELD,GEMINI_EXTRA_FIELD,{key:"disableSafety",label:"\uC548\uC804 \uD544\uD130 \uBE44\uD65C\uC131\uD654",type:"checkbox",default:!0,help:"\uCCB4\uD06C \uC2DC \uBAA8\uB4E0 \uCE74\uD14C\uACE0\uB9AC\uC5D0 BLOCK_NONE \uC801\uC6A9 (RP \uC6A9\uB3C4)."},{key:"thinkingBudget",label:"thinking budget (\uD1A0\uD070)",type:"number",default:"0",help:"Gemini 2.5\uC6A9\uC785\uB2C8\uB2E4. 0\uC774\uBA74 \uC608\uC0B0\uC744 \uC9C0\uC815\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC591\uC218\uBA74 \uC0AC\uACE0 \uD1A0\uD070 \uC608\uC0B0\uC744 \uC81C\uD55C\uD569\uB2C8\uB2E4. Gemini 3\uC5D0\uB294 \uC0AC\uACE0 \uC218\uC900\uC744 \uC0AC\uC6A9\uD558\uC138\uC694."},{key:"useWebSearch",label:"Google Search \uADF8\uB77C\uC6B4\uB529",type:"checkbox",default:!1,help:`Gemini \uC758 google_search \uB3C4\uAD6C\uB97C \uD65C\uC131\uD654 \u2014 \uBAA8\uB378\uC774 \uC751\uB2F5 \uC804 Google \uAC80\uC0C9\uC73C\uB85C \uC815\uBCF4\uB97C \uAC00\uC838\uC640 \uB2F5\uBCC0\uC5D0 \uBC18\uC601 (\uADF8\uB77C\uC6B4\uB529). | |
| \xB7 **\uBE44\uC6A9 \uC8FC\uC758**: Gemini 3.x \uB294 \uC6D4 5,000 grounded prompts \uBB34\uB8CC, \uADF8 \uB4A4 \uAC80\uC0C9 \uCFFC\uB9AC\uB2F9 $14 / 1,000. Gemini 2.5 \uB294 \uC77C 1,500 \uBB34\uB8CC, \uADF8 \uB4A4 prompt \uB2F9 $35 / 1,000. | |
| \xB7 \uD638\uD658 \uBAA8\uB378: Gemini 2.5 / 3.x \uACC4\uC5F4. | |
| \xB7 \uB9E4 \uD638\uCD9C\uC774 grounding \uAC80\uC0C9\uC744 \uD2B8\uB9AC\uAC70\uD560 \uC218 \uC788\uC5B4 \uBB34\uB8CC \uD55C\uB3C4 \uCD08\uACFC \uAC00\uB2A5 \u2014 \uC0AC\uC6A9 \uBE48\uB3C4 \uBAA8\uB2C8\uD130\uB9C1 \uAD8C\uC7A5. | |
| \xB7 \uB864\uD50C\uB808\uC774 \uC2DC \uC2E4\uC874 \uC778\uBB3C / \uD604\uC7AC \uC0AC\uAC74 / \uCD5C\uC2E0 \uD2B8\uB80C\uB4DC \uC0AC\uC2E4 \uD655\uC778\uC5D0 \uC720\uD6A8.`},{key:"useContextCaching",label:"Context caching \uC0AC\uC6A9",type:"checkbox",default:!1,help:`Gemini cachedContent API \uD65C\uC131\uD654 \u2014 system + \uCD08\uBC18 history \uB97C \uC7AC\uC0AC\uC6A9\uD574\uC11C input \uBE44\uC6A9 \uC808\uAC10 (W3.X). | |
| \xB7 **\uBE44\uC6A9**: Cache read \uB294 input \uAC00\uACA9\uC758 \uC57D 10% (~90% \uC808\uAC10). Cache storage \uB294 \uBB34\uB8CC (TTL \uC548). | |
| \xB7 **\uCD5C\uC18C \uD06C\uAE30**: Flash \uBAA8\uB378 1,024 \uD1A0\uD070 / Pro \uBAA8\uB378 4,096 \uD1A0\uD070 \uBBF8\uB9CC prefix \uB294 \uCE90\uC2F1 skip. | |
| \xB7 **TTL**: 1 \uC2DC\uAC04 \uC790\uB3D9. expire 5 \uBD84 \uC804\uBD80\uD130 \uC790\uB3D9 refresh PATCH \uC2DC\uB3C4. | |
| \xB7 **character \uBCC4 \uACA9\uB9AC**: cache \uB294 \uD604\uC7AC \uD65C\uC131 character ID (risuai.getCharacter().chaId) \uB85C namespaced \u2014 \uB2E4\uB978 character \uC758 cache \uC640 \uC11E\uC774\uC9C0 \uC54A\uC74C. | |
| \xB7 **\uCCAB \uD638\uCD9C\uC740 normal cost** + \uBC31\uADF8\uB77C\uC6B4\uB4DC cache \uC0DD\uC131 \u2192 \uB450 \uBC88\uC9F8 \uD638\uCD9C\uBD80\uD130 \uC808\uAC10. | |
| \xB7 **prefix \uAC00 \uBC14\uC774\uD2B8 \uB3D9\uC77C** \uD574\uC57C cache hit. system prompt / \uCD08\uBC18 history \uAC00 \uC790\uC8FC \uBC14\uB00C\uBA74 cache \uBB34\uD6A8\uD654\uB85C \uBE44\uC6A9 \uC99D\uAC00 \uAC00\uB2A5. | |
| \xB7 **Vertex \uB294 \uBBF8\uC9C0\uC6D0** (OAuth2 \uBCF5\uC7A1\uB3C4\uB85C V3 sandbox \uBD80\uC801\uD569) \u2014 \uBCF8 \uC635\uC158\uC740 ai.google.dev \uD55C\uC815.`},{key:"maxTokens",label:"max_tokens (\uC751\uB2F5 \uC0C1\uD55C)",type:"number",default:"8192",help:`Gemini API\uC758 generationConfig.maxOutputTokens\uB85C \uC804\uB2EC\uB429\uB2C8\uB2E4. \uD55C \uC751\uB2F5\uC5D0\uC11C \uC0DD\uC131\uD560 \uCD9C\uB825 \uD1A0\uD070\uC758 \uC0C1\uD55C\uC785\uB2C8\uB2E4. | |
| \xB7 \uBE44\uC6CC\uB450\uBA74 8192\uB97C \uC0AC\uC6A9\uD569\uB2C8\uB2E4. | |
| \xB7 RisuAI\uAC00 pluginRequest.max_tokens\uB97C \uC804\uB2EC\uD558\uBA74 \uADF8 \uAC12\uC774 \uC6B0\uC120\uD569\uB2C8\uB2E4. | |
| \xB7 \uBAA8\uB378\uBCC4 \uCD9C\uB825 \uD55C\uB3C4 \uCC38\uACE0: gemini-2.5-pro/flash \u2252 65536, gemini-2.0-flash \u2252 8192, gemini-1.5-pro/flash \u2252 8192. \uD55C\uB3C4\uB97C \uCD08\uACFC\uD558\uBA74 API\uAC00 400\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4. | |
| \xB7 thinking budget\uC744 \uCF20 \uBAA8\uB378(gemini-2.5 \uACC4\uC5F4)\uC740 thinking \uD1A0\uD070\uB3C4 \uC774 \uD55C\uB3C4\uB97C \uACF5\uC720\uD569\uB2C8\uB2E4 \u2014 thinking \uC608\uC0B0\uC774 \uD06C\uBA74 \uC2E4\uC81C \uC751\uB2F5\uC774 \uC9E7\uC544\uC9C8 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | |
| \xB7 \uC751\uB2F5\uC774 finishReason='MAX_TOKENS'\uB85C \uC885\uB8CC\uB418\uBA74 \uAC12\uC744 \uB298\uB9AC\uAC70\uB098 prompt\uB97C \uC904\uC774\uC138\uC694.`}],USER_ROLE2="user",ASSISTANT_ROLE2="assistant",SYSTEM_ROLE2="system",GEMINI_USER="user",GEMINI_MODEL="model";function toGeminiRole(role){return role===ASSISTANT_ROLE2?GEMINI_MODEL:GEMINI_USER}function buildGeminiBody(pluginRequest,settings){let chats=(Array.isArray(pluginRequest?.prompt_chat)?pluginRequest.prompt_chat:Array.isArray(pluginRequest?.messages)?pluginRequest.messages:[]).map(m=>{let content=m?.content;return Array.isArray(content)&&(content=content.map(p=>typeof p=="string"?p:p?.text||"").filter(Boolean).join(` | |
| `)),{role:m?.role||USER_ROLE2,content:String(content??"").trim()}}),splitIndex=chats.findIndex(m=>m.role===USER_ROLE2||m.role===ASSISTANT_ROLE2);splitIndex===-1&&(splitIndex=chats.length);let systemText=chats.slice(0,splitIndex).map(m=>m.content).filter(Boolean).join(` | |
| `),rest=chats.slice(splitIndex);(rest.length===0||rest[0].role!==USER_ROLE2)&&rest.unshift({role:USER_ROLE2,content:"Start"});let contents=[];for(let m of rest){let role=toGeminiRole(m.role),text=m.content;if(m.role===SYSTEM_ROLE2){if(!text)continue;text="system: "+text}if(!text)continue;let last=contents.length?contents[contents.length-1]:null;last&&last.role===role?last.parts[last.parts.length-1].text+=` | |
| `+text:contents.push({role,parts:[{text}]})}let maxTokensRaw=parseInt(settings.maxTokens,10),generationConfig={maxOutputTokens:Number.isFinite(pluginRequest?.max_tokens)&&pluginRequest.max_tokens>0?pluginRequest.max_tokens:Number.isFinite(maxTokensRaw)&&maxTokensRaw>0?maxTokensRaw:8192};pluginRequest?.temperature!=null&&(generationConfig.temperature=Number(pluginRequest.temperature)),pluginRequest?.top_p!=null&&(generationConfig.topP=Number(pluginRequest.top_p)),pluginRequest?.top_k!=null&&(generationConfig.topK=Number(pluginRequest.top_k)),Array.isArray(pluginRequest?.stop_sequences)&&pluginRequest.stop_sequences.length&&(generationConfig.stopSequences=pluginRequest.stop_sequences);let thinkingBudget=parseInt(settings.thinkingBudget,10);Number.isFinite(thinkingBudget)&&thinkingBudget>0&&(generationConfig.thinkingConfig={thinkingBudget,includeThoughts:!0});let threshold=settings.disableSafety===!0||settings.disableSafety==="true"?"BLOCK_NONE":"BLOCK_ONLY_HIGH",safetySettings=["HATE_SPEECH","DANGEROUS_CONTENT","HARASSMENT","SEXUALLY_EXPLICIT","CIVIC_INTEGRITY"].map(c=>({category:`HARM_CATEGORY_${c}`,threshold})),body={contents,safetySettings,generationConfig};return systemText&&(body.systemInstruction={parts:[{text:systemText}]}),(settings.useWebSearch===!0||settings.useWebSearch==="true")&&(body.tools=body.tools||[],body.tools.push({google_search:{}})),body}function runProvider3(pluginRequest,abortSignal,options){return createTokenStream(async ctx=>{let settings=await loadProviderSettings(PROVIDER_ID3,SETTINGS_SCHEMA3);if(options&&typeof options.modelOverride=="string"&&options.modelOverride.trim()&&(settings.model=options.modelOverride.trim()),!settings.apiKey){ctx.fail("[Blessing/google-ai] API Key\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uC88C\uCE21 \uC0AC\uC774\uB4DC\uBC14\uC758 Blessing \u2192 Providers \uD0ED\uC5D0\uC11C \uC785\uB825\uD558\uC138\uC694.");return}let model=String(settings.model||"gemini-2.5-flash"),useStreaming=await loadStreamingPreference(),endpointPath=useStreaming?":streamGenerateContent?alt=sse&key=":":generateContent?key=",url=`https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}${endpointPath}${encodeURIComponent(String(settings.apiKey))}`,body=buildGeminiBody(pluginRequest,settings);if(body.generationConfig=prepareGeminiConfig(body.generationConfig,model,settings),settings.useContextCaching===!0||settings.useContextCaching==="true")try{let split=buildStableCachePrefix(body.contents||[],body.systemInstruction,getMinTokensFor(model));if(split){let{prefix,tail}=split,prefixText=serializePrefixForHash(prefix,body.systemInstruction),prefixHash=await computePrefixHash(prefixText),chaId=await getCurrentCharId(),apiKey=String(settings.apiKey),baseUrl="https://generativelanguage.googleapis.com/v1beta",cached=await findCache(chaId,model,prefixHash);if(cached&&shouldRefresh(cached.expireTime)){let refreshed=await patchCache(apiKey,baseUrl,cached.cacheName);refreshed&&(cached=refreshed)}if(cached){let carriedTools=body.tools;body={contents:tail,safetySettings:body.safetySettings,generationConfig:body.generationConfig,cachedContent:cached.cacheName},carriedTools&&(body.tools=carriedTools)}else createCache(apiKey,baseUrl,model,chaId,prefixHash,prefix,body.systemInstruction).catch(()=>{})}}catch(err){try{console.warn(`[Blessing/google-ai] context caching error (continuing without cache): ${err?.message||err}`)}catch{}}let response=await fetchWithFallback(url,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(body),signal:abortSignal,loggedFetch:!useStreaming}),markerStyle=await loadMarkerStyle(),usage={inputTokens:0,outputTokens:0,cacheCreation:0,cacheRead:0},inThinking=!1,visibleTextEmitted=!1,finishReason=null;try{if(useStreaming){for await(let msg of parseSSE(response,abortSignal)){if(msg.data.trim()==="[DONE]")break;let parsed;if(parsed=JSON.parse(msg.data),checkGeminiInputBlock(parsed),parsed?.error){ctx.fail(` | |
| [GoogleAI error] `+(parsed.error?.message||JSON.stringify(parsed.error)));let errMarker=markerFor("eof",markerStyle);errMarker&&ctx.enqueue(errMarker);break}let cands=parsed?.candidates;if(!Array.isArray(cands)||cands.length===0)continue;let parts=cands[0]?.content?.parts||[];for(let p of parts)typeof p?.text=="string"&&(p.thought===!0?(inThinking||(inThinking=!0,ctx.enqueue(`<Thoughts> | |
| `)),ctx.enqueue(p.text)):(p.text.trim().length>0&&(visibleTextEmitted=!0),inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `)),ctx.enqueue(p.text)));let finish=cands[0]?.finishReason;finish&&(finishReason=finish);let um=parsed?.usageMetadata;um&&(usage.inputTokens=Number(um.promptTokenCount)||0,usage.outputTokens=Number(um.candidatesTokenCount)||0,usage.cacheRead=Number(um.cachedContentTokenCount)||0)}inThinking&&ctx.enqueue(` | |
| </Thoughts> | |
| `)}else{if(response.ok){let data=await response.json();if(checkGeminiInputBlock(data),data?.error){ctx.fail(` | |
| [GoogleAI error] `+(data.error?.message||JSON.stringify(data.error)));let errMarker=markerFor("eof",markerStyle);errMarker&&ctx.enqueue(errMarker)}else{let cands=Array.isArray(data?.candidates)?data.candidates:[];if(cands.length>0){let parts=cands[0]?.content?.parts||[];for(let p of parts)typeof p?.text=="string"&&(p.thought===!0?(inThinking||(inThinking=!0,ctx.enqueue(`<Thoughts> | |
| `)),ctx.enqueue(p.text)):(p.text.trim().length>0&&(visibleTextEmitted=!0),inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `)),ctx.enqueue(p.text)));finishReason=cands[0]?.finishReason||null}let um=data?.usageMetadata;um&&(usage.inputTokens=Number(um.promptTokenCount)||0,usage.outputTokens=Number(um.candidatesTokenCount)||0,usage.cacheRead=Number(um.cachedContentTokenCount)||0)}}else{let text=await response.text().catch(()=>""),c=classifyError({status:response.status,body:text});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID3,displayName:DISPLAY_NAME3}),ctx.fail(`[Blessing/${DISPLAY_NAME3}] HTTP ${response.status}: ${text||response.statusText} | |
| ${c.action}`)}inThinking&&ctx.enqueue(` | |
| </Thoughts> | |
| `)}let cause=geminiCause(finishReason);if(cause==="safety"?ctx.fail(`Gemini \uCD9C\uB825 \uCC28\uB2E8: ${finishReason}`,!1):(!visibleTextEmitted||finishReason!=="STOP")&&ctx.fail(visibleTextEmitted?`Gemini \uC751\uB2F5\uC774 \uC644\uACB0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 (${finishReason||"\uC885\uB8CC \uC0AC\uC720 \uC5C6\uC74C"}).`:"Gemini \uAC00\uC2DC \uC751\uB2F5\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",!1),cause){let marker=markerFor(cause,markerStyle);if(marker&&ctx.enqueue(marker),cause==="max_tokens"&&!visibleTextEmitted){let hint=geminiThinkingBudgetHint(markerStyle);hint&&ctx.enqueue(hint)}}}catch(err){if(err?.message==="aborted")throw err;ctx.fail(err?.message||"Gemini \uC751\uB2F5 \uCC98\uB9AC \uC2E4\uD328",!1);let c=classifyError({error:err});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID3,displayName:DISPLAY_NAME3}),inThinking&&ctx.enqueue(` | |
| </Thoughts> | |
| `);let marker=err instanceof GeminiInputBlockError?err.message:markerFor("eof",markerStyle);marker&&ctx.enqueue(marker);try{console.warn(`[Blessing/google-ai] response error: ${err?.message||err}`)}catch{}}(usage.inputTokens>0||usage.outputTokens>0||usage.cacheRead>0)&&recordUsage(PROVIDER_ID3,usage,model).catch(()=>{}),appendContextWarnIfNeeded(ctx,model,pluginRequest,markerStyle)},abortSignal)}init_alert();var PROVIDER_ID4="custom-google",DISPLAY_NAME4="Custom Google (Gemini)",SETTINGS_SCHEMA4=[{key:"apiKey",label:"API Key",type:"password",default:""},{key:"baseUrl",label:"Base URL",type:"text",default:"https://generativelanguage.googleapis.com/v1beta",help:'"/models/{model}:streamGenerateContent" \uAE4C\uC9C0 \uC54C\uC544\uC11C \uBD99\uC74C.'},{key:"model",label:"Model",type:"text",default:"gemini-2.5-flash"},GEMINI_THINKING_FIELD,GEMINI_EXTRA_FIELD,{key:"disableSafety",label:"\uC548\uC804 \uD544\uD130 \uBE44\uD65C\uC131\uD654",type:"checkbox",default:!0},{key:"thinkingBudget",label:"thinking budget (\uD1A0\uD070)",type:"number",default:"0"},{key:"maxTokens",label:"max_tokens (\uC751\uB2F5 \uC0C1\uD55C)",type:"number",default:"8192",help:`Gemini \uD638\uD658 \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uC758 generationConfig.maxOutputTokens\uB85C \uC804\uB2EC\uB429\uB2C8\uB2E4. | |
| \xB7 \uBE44\uC6CC\uB450\uBA74 8192\uB97C \uC0AC\uC6A9\uD569\uB2C8\uB2E4. | |
| \xB7 RisuAI\uAC00 pluginRequest.max_tokens\uB97C \uC804\uB2EC\uD558\uBA74 \uADF8 \uAC12\uC774 \uC6B0\uC120\uD569\uB2C8\uB2E4. | |
| \xB7 \uC785\uB825 \uD1A0\uD070\uC740 \uD3EC\uD568\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 \uCEE8\uD14D\uC2A4\uD2B8 \uC708\uB3C4\uC6B0(\uC785\uB825+\uCD9C\uB825) \uD55C\uB3C4\uC640\uB294 \uBCC4\uAC1C\uC785\uB2C8\uB2E4. | |
| \xB7 \uB108\uBB34 \uC791\uAC8C \uC7A1\uC73C\uBA74 finishReason='MAX_TOKENS'\uB85C \uC751\uB2F5\uC774 \uC798\uB9BD\uB2C8\uB2E4.`},{key:"authStyle",label:"Auth style",type:"text",default:"query",help:'"query" \u2192 ?key=, "bearer" \u2192 Authorization: Bearer.'},{key:"useWebSearch",label:"Google Search \uADF8\uB77C\uC6B4\uB529",type:"checkbox",default:!1,help:`Gemini \uD638\uD658 endpoint \uC758 google_search \uB3C4\uAD6C \uD65C\uC131\uD654 (\uD638\uD658\uB418\uB294 \uACBD\uC6B0). | |
| \xB7 \uAE30\uBCF8 base URL (Google AI Studio) \uC5D0\uC11C\uB294 google-ai.js \uC640 \uB3D9\uC77C \uBE44\uC6A9 (Gemini 3.x \uC6D4 5k \uBB34\uB8CC, \uADF8 \uB4A4 $14/1k). | |
| \xB7 \uC790\uCCB4 \uAC8C\uC774\uD2B8\uC6E8\uC774/\uD504\uB85D\uC2DC \uC0AC\uC6A9 \uC2DC \uADF8\uCABD \uC815\uCC45 \uC801\uC6A9. | |
| \xB7 \uBAA8\uB378\uC774 \uBBF8\uC9C0\uC6D0\uC774\uBA74 API 400 \uC751\uB2F5.`}],USER_ROLE3="user",ASSISTANT_ROLE3="assistant",SYSTEM_ROLE3="system",GEMINI_USER2="user",GEMINI_MODEL2="model";function toGeminiRole2(role){return role===ASSISTANT_ROLE3?GEMINI_MODEL2:GEMINI_USER2}function buildGeminiBody2(pluginRequest,settings){let chats=(Array.isArray(pluginRequest?.prompt_chat)?pluginRequest.prompt_chat:Array.isArray(pluginRequest?.messages)?pluginRequest.messages:[]).map(m=>{let content=m?.content;return Array.isArray(content)&&(content=content.map(p=>typeof p=="string"?p:p?.text||"").filter(Boolean).join(` | |
| `)),{role:m?.role||USER_ROLE3,content:String(content??"").trim()}}),splitIndex=chats.findIndex(m=>m.role===USER_ROLE3||m.role===ASSISTANT_ROLE3);splitIndex===-1&&(splitIndex=chats.length);let systemText=chats.slice(0,splitIndex).map(m=>m.content).filter(Boolean).join(` | |
| `),rest=chats.slice(splitIndex);(rest.length===0||rest[0].role!==USER_ROLE3)&&rest.unshift({role:USER_ROLE3,content:"Start"});let contents=[];for(let m of rest){let role=toGeminiRole2(m.role),text=m.content;if(m.role===SYSTEM_ROLE3){if(!text)continue;text="system: "+text}if(!text)continue;let last=contents.length?contents[contents.length-1]:null;last&&last.role===role?last.parts[last.parts.length-1].text+=` | |
| `+text:contents.push({role,parts:[{text}]})}let maxTokensRaw=parseInt(settings.maxTokens,10),generationConfig={maxOutputTokens:Number.isFinite(pluginRequest?.max_tokens)&&pluginRequest.max_tokens>0?pluginRequest.max_tokens:Number.isFinite(maxTokensRaw)&&maxTokensRaw>0?maxTokensRaw:8192};pluginRequest?.temperature!=null&&(generationConfig.temperature=Number(pluginRequest.temperature)),pluginRequest?.top_p!=null&&(generationConfig.topP=Number(pluginRequest.top_p)),pluginRequest?.top_k!=null&&(generationConfig.topK=Number(pluginRequest.top_k));let tb=parseInt(settings.thinkingBudget,10);Number.isFinite(tb)&&tb>0&&(generationConfig.thinkingConfig={thinkingBudget:tb,includeThoughts:!0});let threshold=settings.disableSafety===!0||settings.disableSafety==="true"?"BLOCK_NONE":"BLOCK_ONLY_HIGH",safetySettings=["HATE_SPEECH","DANGEROUS_CONTENT","HARASSMENT","SEXUALLY_EXPLICIT","CIVIC_INTEGRITY"].map(c=>({category:`HARM_CATEGORY_${c}`,threshold})),body={contents,safetySettings,generationConfig};return systemText&&(body.systemInstruction={parts:[{text:systemText}]}),(settings.useWebSearch===!0||settings.useWebSearch==="true")&&(body.tools=body.tools||[],body.tools.push({google_search:{}})),body}function runProvider4(pluginRequest,abortSignal,options){return createTokenStream(async ctx=>{let settings=await loadProviderSettings(PROVIDER_ID4,SETTINGS_SCHEMA4);if(options&&typeof options.modelOverride=="string"&&options.modelOverride.trim()&&(settings.model=options.modelOverride.trim()),!settings.apiKey){ctx.fail("[Blessing/custom-google] API Key\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");return}let model=String(settings.model||"gemini-2.5-flash"),base=normalizeGoogleBase(settings.baseUrl,"https://generativelanguage.googleapis.com/v1beta"),authStyle=String(settings.authStyle||"query").toLowerCase(),useStreaming=await loadStreamingPreference(),url=useStreaming?`${base}/models/${encodeURIComponent(model)}:streamGenerateContent?alt=sse`:`${base}/models/${encodeURIComponent(model)}:generateContent`,headers={"content-type":"application/json"};if(authStyle==="bearer")headers.authorization="Bearer "+String(settings.apiKey);else{let sep=url.includes("?")?"&":"?";url+=`${sep}key=${encodeURIComponent(String(settings.apiKey))}`}let body=buildGeminiBody2(pluginRequest,settings);body.generationConfig=prepareGeminiConfig(body.generationConfig,model,settings);let response=await fetchWithFallback(url,{method:"POST",headers,body:JSON.stringify(body),signal:abortSignal,loggedFetch:!useStreaming}),markerStyle=await loadMarkerStyle(),usage={inputTokens:0,outputTokens:0,cacheCreation:0,cacheRead:0},inThinking=!1,visibleTextEmitted=!1,finishReason=null;try{if(useStreaming){for await(let msg of parseSSE(response,abortSignal)){if(msg.data.trim()==="[DONE]")break;let parsed;if(parsed=JSON.parse(msg.data),checkGeminiInputBlock(parsed),parsed?.error){ctx.fail(` | |
| [Custom-Google error] `+(parsed.error?.message||JSON.stringify(parsed.error)));let errMarker=markerFor("eof",markerStyle);errMarker&&ctx.enqueue(errMarker);break}let cands=parsed?.candidates;if(!Array.isArray(cands)||cands.length===0)continue;let parts=cands[0]?.content?.parts||[];for(let p of parts)typeof p?.text=="string"&&(p.thought===!0?(inThinking||(inThinking=!0,ctx.enqueue(`<Thoughts> | |
| `)),ctx.enqueue(p.text)):(p.text.trim().length>0&&(visibleTextEmitted=!0),inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `)),ctx.enqueue(p.text)));let finish=cands[0]?.finishReason;finish&&(finishReason=finish);let um=parsed?.usageMetadata;um&&(usage.inputTokens=Number(um.promptTokenCount)||0,usage.outputTokens=Number(um.candidatesTokenCount)||0,usage.cacheRead=Number(um.cachedContentTokenCount)||0)}inThinking&&ctx.enqueue(` | |
| </Thoughts> | |
| `)}else{if(response.ok){let data=await response.json();if(checkGeminiInputBlock(data),data?.error){ctx.fail(` | |
| [Custom-Google error] `+(data.error?.message||JSON.stringify(data.error)));let errMarker=markerFor("eof",markerStyle);errMarker&&ctx.enqueue(errMarker)}else{let cands=Array.isArray(data?.candidates)?data.candidates:[];if(cands.length>0){let parts=cands[0]?.content?.parts||[];for(let p of parts)typeof p?.text=="string"&&(p.thought===!0?(inThinking||(inThinking=!0,ctx.enqueue(`<Thoughts> | |
| `)),ctx.enqueue(p.text)):(p.text.trim().length>0&&(visibleTextEmitted=!0),inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `)),ctx.enqueue(p.text)));finishReason=cands[0]?.finishReason||null}let um=data?.usageMetadata;um&&(usage.inputTokens=Number(um.promptTokenCount)||0,usage.outputTokens=Number(um.candidatesTokenCount)||0,usage.cacheRead=Number(um.cachedContentTokenCount)||0)}}else{let text=await response.text().catch(()=>""),c=classifyError({status:response.status,body:text});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID4,displayName:DISPLAY_NAME4}),ctx.fail(`[Blessing/${DISPLAY_NAME4}] HTTP ${response.status}: ${text||response.statusText} | |
| ${c.action}`)}inThinking&&ctx.enqueue(` | |
| </Thoughts> | |
| `)}let cause=geminiCause(finishReason);if(cause==="safety"?ctx.fail(`Gemini \uCD9C\uB825 \uCC28\uB2E8: ${finishReason}`,!1):(!visibleTextEmitted||finishReason!=="STOP")&&ctx.fail(visibleTextEmitted?`Gemini \uC751\uB2F5\uC774 \uC644\uACB0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 (${finishReason||"\uC885\uB8CC \uC0AC\uC720 \uC5C6\uC74C"}).`:"Gemini \uAC00\uC2DC \uC751\uB2F5\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",!1),cause){let marker=markerFor(cause,markerStyle);if(marker&&ctx.enqueue(marker),cause==="max_tokens"&&!visibleTextEmitted){let hint=geminiThinkingBudgetHint(markerStyle);hint&&ctx.enqueue(hint)}}}catch(err){if(err?.message==="aborted")throw err;ctx.fail(err?.message||"Gemini \uC751\uB2F5 \uCC98\uB9AC \uC2E4\uD328",!1);let c=classifyError({error:err});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID4,displayName:DISPLAY_NAME4}),inThinking&&ctx.enqueue(` | |
| </Thoughts> | |
| `);let marker=err instanceof GeminiInputBlockError?err.message:markerFor("eof",markerStyle);marker&&ctx.enqueue(marker);try{console.warn(`[Blessing/custom-google] response error: ${err?.message||err}`)}catch{}}(usage.inputTokens>0||usage.outputTokens>0||usage.cacheRead>0)&&recordUsage(PROVIDER_ID4,usage,model).catch(()=>{}),appendContextWarnIfNeeded(ctx,model,pluginRequest,markerStyle)},abortSignal)}init_alert();var PROVIDER_ID5="custom-claude",DISPLAY_NAME5="Custom Claude",SETTINGS_SCHEMA5=[{key:"apiKey",label:"API Key",type:"password",default:""},{key:"baseUrl",label:"Base URL",type:"text",default:"https://api.anthropic.com/v1",help:"/messages \uAE4C\uC9C0 \uC54C\uC544\uC11C \uBD99\uC74C. \uB05D\uC5D0 \uC2AC\uB798\uC2DC \uC5C6\uC774."},{key:"authStyle",label:"Auth style",type:"text",default:"x-api-key",help:'"x-api-key" \uB610\uB294 "bearer".'},{key:"model",label:"Model",type:"text",default:"claude-3-5-sonnet-latest"},{key:"useCaching",label:"Prompt caching \uC0AC\uC6A9",type:"checkbox",default:!1},{key:"maxTokens",label:"max_tokens (\uC751\uB2F5 \uC0C1\uD55C)",type:"number",default:"4096",help:`Anthropic Messages API \uD638\uD658 \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uC5D0\uC11C max_tokens\uB294 \uD544\uC218 \uD30C\uB77C\uBBF8\uD130\uC785\uB2C8\uB2E4. | |
| \xB7 \uBE44\uC6CC\uB450\uBA74 4096\uC744 \uAC15\uC81C \uC801\uC6A9\uD569\uB2C8\uB2E4. | |
| \xB7 RisuAI\uAC00 pluginRequest.max_tokens\uB97C \uC804\uB2EC\uD558\uBA74 \uADF8 \uAC12\uC774 \uC6B0\uC120\uD569\uB2C8\uB2E4. | |
| \xB7 \uD55C \uC751\uB2F5\uC5D0\uC11C \uC0DD\uC131\uD560 \uCD9C\uB825 \uD1A0\uD070\uC758 \uC0C1\uD55C\uC774\uBA70, \uC785\uB825\uC740 \uD3EC\uD568\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. | |
| \xB7 \uAC8C\uC774\uD2B8\uC6E8\uC774/\uD504\uB85D\uC2DC\uAC00 Claude \uBAA8\uB378\uBCC4 \uCD9C\uB825 \uD55C\uB3C4\uB97C \uB530\uB974\uB294 \uACBD\uC6B0 \uCD08\uACFC \uC2DC 400 \uC751\uB2F5\uC774 \uC635\uB2C8\uB2E4 (Sonnet/Haiku 3.5 \u2252 8192, Opus/Sonnet 3 \u2252 4096). | |
| \xB7 \uB108\uBB34 \uC791\uC73C\uBA74 stop_reason='max_tokens'\uB85C \uC751\uB2F5\uC774 \uC911\uAC04\uC5D0 \uC798\uB9BD\uB2C8\uB2E4.`},{key:"anthropicVersion",label:"anthropic-version",type:"text",default:"2023-06-01",help:"\uBE44\uC6B0\uBA74 \uD5E4\uB354\uB97C \uBCF4\uB0B4\uC9C0 \uC54A\uC74C (\uD504\uB85D\uC2DC\uAC00 \uC790\uCCB4 \uCC98\uB9AC)."}],USER_ROLE4="user",ASSISTANT_ROLE4="assistant",SYSTEM_ROLE4="system";function buildClaudeBody2(pluginRequest,settings){let chats=(Array.isArray(pluginRequest?.prompt_chat)?pluginRequest.prompt_chat:Array.isArray(pluginRequest?.messages)?pluginRequest.messages:[]).map(m=>{let content=m?.content;return Array.isArray(content)&&(content=content.map(p=>typeof p=="string"?p:p?.text||"").filter(Boolean).join(` | |
| `)),{role:m?.role||USER_ROLE4,content:String(content??"")}}),splitIndex=chats.findIndex(m=>m.role===USER_ROLE4||m.role===ASSISTANT_ROLE4);splitIndex===-1&&(splitIndex=chats.length);let systemText="";for(let i=0;i<splitIndex;i++){let t=chats[i].content.trim();t&&(systemText&&(systemText+=` | |
| `),systemText+=t)}let rest=chats.slice(splitIndex);(rest.length===0||rest[0].role!==USER_ROLE4)&&rest.unshift({role:USER_ROLE4,content:"Start"});let messages=[];for(let m of rest){let text=m.content.trim(),last=messages.length?messages[messages.length-1]:null;if(m.role===SYSTEM_ROLE4){let injected="system: "+text;last?.role===USER_ROLE4?last.content[0].text+=` | |
| `+injected:messages.push({role:USER_ROLE4,content:[{type:"text",text:injected}]})}else(m.role===USER_ROLE4||m.role===ASSISTANT_ROLE4)&&(last?.role===m.role?last.content[0].text+=` | |
| `+text:messages.push({role:m.role,content:[{type:"text",text}]}))}let maxTokensRaw=parseInt(settings.maxTokens,10),maxTokens=Number.isFinite(maxTokensRaw)&&maxTokensRaw>0?maxTokensRaw:Number.isFinite(pluginRequest?.max_tokens)&&pluginRequest.max_tokens>0?pluginRequest.max_tokens:4096,body={model:settings.model||"claude-3-5-sonnet-latest",messages,max_tokens:maxTokens};if(systemText){let sysBlock={type:"text",text:systemText};(settings.useCaching===!0||settings.useCaching==="true")&&(sysBlock.cache_control={type:"ephemeral"}),body.system=[sysBlock]}return pluginRequest?.temperature!=null&&(body.temperature=Number(pluginRequest.temperature)),pluginRequest?.top_p!=null&&(body.top_p=Number(pluginRequest.top_p)),pluginRequest?.top_k!=null&&(body.top_k=Number(pluginRequest.top_k)),body}function runProvider5(pluginRequest,abortSignal,options){return createTokenStream(async ctx=>{let settings=await loadProviderSettings(PROVIDER_ID5,SETTINGS_SCHEMA5);if(options&&typeof options.modelOverride=="string"&&options.modelOverride.trim()&&(settings.model=options.modelOverride.trim()),!settings.apiKey){ctx.fail("[Blessing/custom-claude] API Key\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");return}let useStreaming=await loadStreamingPreference(),url=normalizeClaudeBase(settings.baseUrl,"https://api.anthropic.com/v1")+"/messages",body=buildClaudeBody2(pluginRequest,settings);useStreaming&&(body.stream=!0);let headers={"content-type":"application/json"};String(settings.authStyle||"x-api-key").toLowerCase()==="bearer"?headers.authorization="Bearer "+String(settings.apiKey):headers["x-api-key"]=String(settings.apiKey);let av=String(settings.anthropicVersion||"").trim();av&&(headers["anthropic-version"]=av),headers["anthropic-dangerous-direct-browser-access"]="true",(settings.useCaching===!0||settings.useCaching==="true")&&(headers["anthropic-beta"]="prompt-caching-2024-07-31");let response=await fetchWithFallback(url,{method:"POST",headers,body:JSON.stringify(body),signal:abortSignal,retry:!0,loggedFetch:!useStreaming}),markerStyle=await loadMarkerStyle(),usage={inputTokens:0,outputTokens:0,cacheCreation:0,cacheRead:0},inThinking=!1,stopReason=null;try{if(useStreaming)for await(let msg of parseSSE(response,abortSignal)){let parsed;try{parsed=JSON.parse(msg.data)}catch{continue}let t=parsed?.type||msg.event;if(t==="message_start"){let u=parsed?.message?.usage;u&&(usage.inputTokens=Number(u.input_tokens)||0,usage.cacheCreation=Number(u.cache_creation_input_tokens)||0,usage.cacheRead=Number(u.cache_read_input_tokens)||0)}else if(t==="content_block_delta"){let d=parsed.delta;if(!d)continue;if(d.type==="thinking_delta"||d.type==="thinking"){if(!d.thinking)continue;inThinking||(inThinking=!0,ctx.enqueue(`<Thoughts> | |
| `)),ctx.enqueue(d.thinking)}else if(d.type==="text_delta"||d.type==="text"){if(!d.text)continue;inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `)),ctx.enqueue(d.text)}}else if(t==="message_delta"){parsed?.delta?.stop_reason&&(stopReason=parsed.delta.stop_reason);let u=parsed?.usage;u&&typeof u.output_tokens=="number"&&(usage.outputTokens=u.output_tokens)}else if(t==="message_stop"){inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `));let cause=anthropicCause(stopReason);if(cause){let marker=markerFor(cause,markerStyle);marker&&ctx.enqueue(marker)}break}else if(t==="error"){ctx.fail(` | |
| [Custom-Claude error] `+(parsed?.error?.message||"unknown"));let marker=markerFor("eof",markerStyle);marker&&ctx.enqueue(marker);break}}else if(response.ok){let data=await response.json();if(data?.error){ctx.fail(` | |
| [Custom-Claude error] `+(data.error?.message||JSON.stringify(data.error)));let marker=markerFor("eof",markerStyle);marker&&ctx.enqueue(marker)}else{let blocks=Array.isArray(data?.content)?data.content:[];for(let b of blocks)b?.type==="thinking"&&typeof b?.thinking=="string"&&b.thinking.length>0?(inThinking||(inThinking=!0,ctx.enqueue(`<Thoughts> | |
| `)),ctx.enqueue(b.thinking)):b?.type==="text"&&typeof b?.text=="string"&&b.text.length>0&&(inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `)),ctx.enqueue(b.text));inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `));let u=data?.usage;u&&(usage.inputTokens=Number(u.input_tokens)||0,usage.outputTokens=Number(u.output_tokens)||0,usage.cacheCreation=Number(u.cache_creation_input_tokens)||0,usage.cacheRead=Number(u.cache_read_input_tokens)||0);let cause=anthropicCause(data?.stop_reason);if(cause){let marker=markerFor(cause,markerStyle);marker&&ctx.enqueue(marker)}}}else{let text=await response.text().catch(()=>""),c=classifyError({status:response.status,body:text});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID5,displayName:DISPLAY_NAME5}),ctx.fail(`[Blessing/${DISPLAY_NAME5}] HTTP ${response.status}: ${text||response.statusText} | |
| ${c.action}`)}}catch(err){if(err?.message==="aborted")throw err;let c=classifyError({error:err});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID5,displayName:DISPLAY_NAME5}),inThinking&&ctx.enqueue(` | |
| </Thoughts> | |
| `),ctx.fail("\uC751\uB2F5 \uC218\uC2E0 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.",!1);let marker=markerFor("eof",markerStyle);marker&&ctx.enqueue(marker);try{console.warn(`[Blessing/custom-claude] stream error: ${err?.message||err}`)}catch{}}(usage.inputTokens>0||usage.outputTokens>0||usage.cacheCreation>0||usage.cacheRead>0)&&recordUsage(PROVIDER_ID5,usage,body.model).catch(()=>{}),appendContextWarnIfNeeded(ctx,body.model,pluginRequest,markerStyle)},abortSignal)}init_alert();var PROVIDER_ID6="vertex",DISPLAY_NAME6="Vertex AI",SETTINGS_SCHEMA6=[{key:"serviceAccountJson",label:"Service Account JSON",type:"textarea",default:"",help:"GCP IAM\uC5D0\uC11C \uBC1B\uC740 \uC11C\uBE44\uC2A4 \uACC4\uC815 \uD0A4 \uD30C\uC77C \uC804\uCCB4 JSON. client_email/private_key/project_id \uD544\uC694."},{key:"region",label:"Region",type:"text",default:"global",help:"\uC608: us-central1, asia-northeast3, global."},{key:"fallbackRegions",label:"Fallback regions (\uC120\uD0DD, \uC27C\uD45C/\uACF5\uBC31 \uAD6C\uBD84)",type:"text",default:"",help:`Primary region \uC774 429 (quota \uCD08\uACFC) \uC751\uB2F5 \uC2DC \uC790\uB3D9\uC73C\uB85C \uC2DC\uB3C4\uD560 fallback region \uBAA9\uB85D. | |
| \xB7 \uC608: \`us-central1, asia-northeast3, europe-west4\` | |
| \xB7 \uBE44\uC5B4 \uC788\uC73C\uBA74 primary region \uB9CC \uC0AC\uC6A9. | |
| \xB7 \uCCAB \uBE44-429 \uC751\uB2F5\uC744 \uBC1B\uC73C\uBA74 \uC989\uC2DC \uADF8 region \uC0AC\uC6A9. \uBAA8\uB4E0 region \uC774 429 \uBA74 \uB9C8\uC9C0\uB9C9 \uC751\uB2F5 \uBC18\uD658. | |
| \xB7 Anthropic on Vertex \uC758 \uACBD\uC6B0 \uBAA8\uB378 \uAC00\uC6A9\uC131\uC774 region \uBCC4 \uB2E4\uB984 \u2014 fallback region \uC774 model \uC744 \uBBF8\uC9C0\uC6D0\uD558\uBA74 404. \uBCF8 fallback \uC740 *quota \uD68C\uD53C\uC6A9*, \uBAA8\uB378 \uAC00\uC6A9\uC131 \uD68C\uD53C\uC6A9 \uC544\uB2D8. | |
| \xB7 \`global\` \uB3C4 fallback \uC73C\uB85C \uCD94\uAC00 \uAC00\uB2A5. | |
| \xB7 Gemini\uB294 \uAC19\uC740 region\uC5D0\uC11C \uC790\uB3D9 \uC7AC\uC2DC\uB3C4\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC9C0\uC815\uD55C \uB300\uCCB4 region\uC740 429\uC77C \uB54C\uB9CC \uC2DC\uB3C4\uD569\uB2C8\uB2E4. Claude\uB294 \uAE30\uC874 \uC7AC\uC2DC\uB3C4 \uC815\uCC45\uC744 \uC720\uC9C0\uD569\uB2C8\uB2E4.`},{key:"model",label:"Model",type:"text",default:"gemini-2.5-flash",help:"\uC608: gemini-2.5-flash, gemini-2.0-flash-exp, claude-3-5-sonnet@20241022."},GEMINI_THINKING_FIELD,GEMINI_EXTRA_FIELD,{key:"disableSafety",label:"\uC548\uC804 \uD544\uD130 \uBE44\uD65C\uC131\uD654 (Gemini)",type:"checkbox",default:!0},{key:"useWebSearch",label:"Google Search \uADF8\uB77C\uC6B4\uB529 (Gemini)",type:"checkbox",default:!1,help:`Vertex Gemini \uBAA8\uB378\uC5D0 google_search \uB3C4\uAD6C \uD65C\uC131\uD654. Anthropic on Vertex (claude-*) \uBAA8\uB378\uC740 \uC601\uD5A5 \uC5C6\uC74C. | |
| \xB7 **\uBE44\uC6A9 \uC8FC\uC758**: Vertex \uCCAD\uAD6C \uC2DC\uC2A4\uD15C \u2014 "Grounding with Google Search and Web Grounding for enterprise is billed only when a prompt successfully returns web results." \uAC80\uC0C9 \uACB0\uACFC \uD1A0\uD070\uC740 input \uAC00\uACA9\uC73C\uB85C \uBCC4\uB3C4 \uCCAD\uAD6C. | |
| \xB7 \uD638\uD658 \uBAA8\uB378: Gemini 2.5 / 3.x on Vertex.`},{key:"useCaching",label:"\uD504\uB86C\uD504\uD2B8 \uCE90\uC2DC \u2014 Claude on Vertex \uC804\uC6A9",type:"checkbox",default:!1,help:`**Anthropic on Vertex (claude-*) \uBAA8\uB378\uC5D0\uB9CC \uC801\uC6A9. Gemini \uBAA8\uB378\uC740 \uBCF8 \uD1A0\uAE00\uC758 \uC601\uD5A5 0** \u2014 Gemini \uC0AC\uC6A9 \uC2DC \uC544\uB798 'Gemini context caching' \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694. | |
| \xB7 system \uBE14\uB85D \uB05D + \uD788\uC2A4\uD1A0\uB9AC \uB9C8\uC9C0\uB9C9 \uBA54\uC2DC\uC9C0 \uB05D = 2 breakpoints multi-cache. | |
| \xB7 Vertex Claude \uC758 :rawPredict \uB3C4 standard Anthropic Messages API \uD638\uD658 \u2014 cache_control content block \uADF8\uB300\uB85C \uC778\uC2DD. | |
| \xB7 Cache write 1.25\xD7 input \uAC00\uACA9, cache read 0.1\xD7 input \uAC00\uACA9 (= 90% \uC808\uAC10).`},{key:"useCacheExtended",label:"\uCE90\uC2DC 1\uC2DC\uAC04 TTL \u2014 Claude on Vertex \uC804\uC6A9",type:"checkbox",default:!1,help:`\uAE30\uBCF8 5\uBD84 TTL \u2192 1\uC2DC\uAC04 TTL \uB85C \uD655\uC7A5. Cache write 2\xD7 input \uAC00\uACA9 (5\uBD84 ttl 1.25\xD7 \uB300\uBE44 1.6\xD7 \uB354 \uBE44\uC308), cache read \uB3D9\uC77C 0.1\xD7. | |
| \xB7 Vertex Claude \uAC00 \`anthropic-beta: extended-cache-ttl-2025-04-11\` \uD5E4\uB354 \uC778\uC2DD (standard Anthropic API \uC640 \uB3D9\uC77C \uD638\uD658). | |
| \xB7 \uAE34 \uCE90\uB9AD\uD130 \uCE74\uB4DC + \uAE34 \uC138\uC158 (1 \uC2DC\uAC04 \uB0B4 5~10 \uD68C \uD638\uCD9C) \uC5D0 \uC720\uB9AC. | |
| \xB7 \uC704\uC758 '\uD504\uB86C\uD504\uD2B8 \uCE90\uC2DC' \uC640 \uAC19\uC774 ON \uC774\uC5B4\uC57C \uB3D9\uC791. | |
| \xB7 **Gemini \uBAA8\uB378\uC740 \uBCF8 \uD1A0\uAE00\uC758 \uC601\uD5A5 0**.`},{key:"useGeminiCaching",label:"Gemini context caching \u2014 Vertex Gemini \uC804\uC6A9",type:"checkbox",default:!1,help:`**Vertex Gemini (gemini-*) \uBAA8\uB378\uC5D0\uB9CC \uC801\uC6A9. Claude \uBAA8\uB378\uC740 \uBCF8 \uD1A0\uAE00\uC758 \uC601\uD5A5 0** \u2014 Claude \uB294 \uC704\uC758 '\uD504\uB86C\uD504\uD2B8 \uCE90\uC2DC' \uB97C \uC0AC\uC6A9\uD558\uC138\uC694. | |
| \xB7 Vertex \uC758 cachedContents API (POST /v1/projects/.../cachedContents) \uD65C\uC6A9. | |
| \xB7 prefix SHA-256 \uD574\uC2DC + \uD65C\uC131 \uCE90\uB9AD\uD130 ID \uC774\uC911 \uD0A4\uB85C cache \uC5D4\uD2B8\uB9AC binding. | |
| \xB7 \uBAA8\uB378\uBCC4 \uCD5C\uC18C \uD1A0\uD070: Flash = 1,024 / Pro = 4,096 (prefix \uAC00 \uBBF8\uB9CC\uC774\uBA74 caching skip). | |
| \xB7 \uCCAB \uD638\uCD9C\uC740 normal cost + \uBC31\uADF8\uB77C\uC6B4\uB4DC cache \uC0DD\uC131. \uB450 \uBC88\uC9F8 \uD638\uCD9C\uBD80\uD130 input 90% \uC808\uAC10. | |
| \xB7 \uB9CC\uB8CC 5\uBD84 \uC804 \uC790\uB3D9 PATCH (background timer \uC5C6\uC74C \u2014 \uC0AC\uC6A9\uC790 \uD638\uCD9C \uC2DC\uC810\uC5D0 lazy refresh). | |
| \xB7 \uBAA8\uB4E0 caching \uC5D0\uB7EC\uB294 silent fallback (normal request \uC9C4\uD589, \uC0AC\uC6A9\uC790 \uD750\uB984 \uB04A\uC9C0 \uC54A\uC74C). | |
| \xB7 Storage key: \`blessing.gemini.cache.registry\` (google-ai \uC640 \uACF5\uC720, \`vertex-gemini/\` prefix \uB85C \uBD84\uB9AC).`},{key:"costMode",label:"\uBE44\uC6A9 \uBAA8\uB4DC (Cost-saving)",type:"select",default:"default",options:[{value:"default",label:"\uAE30\uBCF8 (\uC0AC\uC6A9\uC790 model \uADF8\uB300\uB85C)"},{value:"save",label:"\uC808\uC57D (Gemini 2.5 Flash-Lite / Claude Haiku 4.5)"},{value:"priority",label:"\uC6B0\uC120 (Gemini 3.1 Pro / Claude Opus 4.7)"}],help:`Vertex \uBAA8\uB378 \uC790\uB3D9 swap. **\uBAA8\uB378 family \uAC00\uB85C\uC9C0\uB974\uC9C0 \uC54A\uC74C** \u2014 Gemini \uBAA8\uB378\uC774\uBA74 Gemini family \uC548\uC5D0\uC11C, Claude \uBAA8\uB378\uC774\uBA74 Claude family \uC548\uC5D0\uC11C\uB9CC swap. | |
| \xB7 save: Gemini \u2192 gemini-2.5-flash-lite ($0.10/$0.40 paid, Free \uAC00\uB2A5) / Claude \u2192 claude-haiku-4-5@latest ($1/$5 verbatim). | |
| \xB7 default: \uC0AC\uC6A9\uC790 model \uADF8\uB300\uB85C. | |
| \xB7 priority: Gemini \u2192 gemini-3.1-pro-preview ($2.00/$12.00 paid only) / Claude \u2192 claude-opus-4-7@latest ($5/$25 verbatim). | |
| \xB7 \uC0AC\uC6A9\uC790\uAC00 \uBA85\uC2DC\uD55C model \uC774 family \uACB0\uC815 \u2014 claude-* \uBA74 Claude family, \uADF8 \uC678\uB294 Gemini family. | |
| \xB7 **\uB2E8\uAC00\uB294 Vertex Gemini 3 SKU \uAC00 \uC544\uC9C1 React-rendered (\uACF5\uC2DD \uAC00\uACA9 \uBBF8\uD655\uC778) \uB77C AI Studio paid tier \uC640 \uB3D9\uB4F1 \uAC00\uC815**.`},{key:"maxTokens",label:"max_tokens (\uC751\uB2F5 \uC0C1\uD55C)",type:"number",default:"8192",help:`Vertex AI \uC751\uB2F5\uC5D0\uC11C \uBAA8\uB378\uC774 \uC0DD\uC131\uD560 \uCD9C\uB825 \uD1A0\uD070\uC758 \uC0C1\uD55C\uC785\uB2C8\uB2E4. | |
| \xB7 Gemini \uBAA8\uB378: generationConfig.maxOutputTokens\uB85C \uC804\uB2EC\uB429\uB2C8\uB2E4 (thinking \uD1A0\uD070\uB3C4 \uC774 \uD55C\uB3C4\uB97C \uACF5\uC720). | |
| \xB7 Anthropic on Vertex \uBAA8\uB378(claude-* publisher=anthropic): Messages API\uC758 \uD544\uC218 \uD30C\uB77C\uBBF8\uD130 max_tokens\uB85C \uC804\uB2EC\uB429\uB2C8\uB2E4. | |
| \xB7 \uBE44\uC6CC\uB450\uBA74 8192\uB97C \uC0AC\uC6A9\uD569\uB2C8\uB2E4. | |
| \xB7 RisuAI\uAC00 pluginRequest.max_tokens\uB97C \uC804\uB2EC\uD558\uBA74 \uADF8 \uAC12\uC774 \uC6B0\uC120\uD569\uB2C8\uB2E4. | |
| \xB7 \uBAA8\uB378\uBCC4 \uCD9C\uB825 \uD55C\uB3C4\uB97C \uCD08\uACFC\uD558\uBA74 \uBC31\uC5D4\uB4DC\uAC00 400\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4 (Gemini 2.5 \u2252 65536, Gemini 2.0 \u2252 8192, Claude 3.5 \u2252 8192, Claude 3 \u2252 4096). | |
| \xB7 \uC751\uB2F5\uC774 \uC798\uB9AC\uBA74 finishReason='MAX_TOKENS'(Gemini) \uB610\uB294 stop_reason='max_tokens'(Anthropic)\uB85C \uD45C\uC2DC\uB429\uB2C8\uB2E4.`}],USER_ROLE5="user",ASSISTANT_ROLE5="assistant",SYSTEM_ROLE5="system",tokenCache=new Map;function cacheKey(cred){return`${cred.client_email}|${cred.project_id}`}function base64url(bytes){let bin=String.fromCharCode.apply(null,Array.from(bytes));return btoa(bin).replace(/=+$/,"").replace(/\+/g,"-").replace(/\//g,"_")}function pemToArrayBuffer(pem){let b64=pem.replace(/-----BEGIN PRIVATE KEY-----/g,"").replace(/-----END PRIVATE KEY-----/g,"").replace(/\\n/g,"").replace(/\s+/g,""),bin=atob(b64),bytes=new Uint8Array(bin.length);for(let i=0;i<bin.length;i++)bytes[i]=bin.charCodeAt(i);return bytes.buffer}async function generateJWT(clientEmail,privateKey){if(!clientEmail.includes("gserviceaccount.com"))throw new Error("client_email\uC774 \uC11C\uBE44\uC2A4 \uACC4\uC815 \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4.");if(!privateKey.includes("BEGIN PRIVATE KEY")||!privateKey.includes("END PRIVATE KEY"))throw new Error("private_key\uAC00 PKCS#8 PEM \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4.");let header={alg:"RS256",typ:"JWT"},now=Math.floor(Date.now()/1e3),claim={iss:clientEmail,scope:"https://www.googleapis.com/auth/cloud-platform",aud:"https://oauth2.googleapis.com/token",exp:now+3600,iat:now},enc=o=>base64url(new TextEncoder().encode(JSON.stringify(o))),signingInput=`${enc(header)}.${enc(claim)}`,key=await crypto.subtle.importKey("pkcs8",pemToArrayBuffer(privateKey),{name:"RSASSA-PKCS1-v1_5",hash:{name:"SHA-256"}},!1,["sign"]),sig=await crypto.subtle.sign("RSASSA-PKCS1-v1_5",key,new TextEncoder().encode(signingInput));return`${signingInput}.${base64url(new Uint8Array(sig))}`}async function exchangeAccessToken(jwt){let response=await fetchWithFallback("https://oauth2.googleapis.com/token",{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:`grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=${jwt}`});if(!response.ok){let text=await response.text().catch(()=>""),hint="",lower=text.toLowerCase();throw lower.includes("invalid_grant")||lower.includes("invalid jwt signature")?hint=` | |
| \u2192 Service Account \uD0A4\uAC00 \uAC70\uBD80\uB428. \uAC00\uB2A5\uD55C \uC6D0\uC778: | |
| 1. GCP IAM\uC5D0\uC11C \uD574\uB2F9 \uD0A4\uAC00 \uC774\uBBF8 \uC0AD\uC81C/\uBE44\uD65C\uC131\uD654\uB418\uC5C8\uC74C (\uAC00\uC7A5 \uD754\uD568). \u2192 \uC0C8 \uD0A4 \uBC1C\uAE09 \uD544\uC694. | |
| 2. client_email\uACFC private_key\uAC00 \uC11C\uB85C \uB2E4\uB978 \uD0A4 \uC30D\uC5D0\uC11C \uC9DC\uAE41\uAE30\uB428. \u2192 \uC6D0\uBCF8 JSON \uADF8\uB300\uB85C \uB2E4\uC2DC \uBD99\uC5EC\uB123\uAE30. | |
| 3. \uC2DC\uC2A4\uD15C \uC2DC\uACC4\uAC00 1\uC2DC\uAC04 \uC774\uC0C1 \uC5B4\uAE0B\uB0A8. \u2192 OS \uC2DC\uAC04 \uC790\uB3D9 \uB3D9\uAE30\uD654 \uD65C\uC131\uD654.`:lower.includes("invalid_scope")?hint=` | |
| \u2192 scope\uC774 \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uC74C. SA\uC5D0 'cloud-platform' \uAD8C\uD55C\uC774 \uBD80\uC5EC\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778.`:(response.status===401||response.status===403)&&(hint=` | |
| \u2192 \uC778\uC99D \uAC70\uBD80. SA\uAC00 \uBE44\uD65C\uC131\uD654\uB418\uC5C8\uAC70\uB098 \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uC81C\uAC70\uB428.`),new Error(`OAuth \uD1A0\uD070 \uAD50\uD658 \uC2E4\uD328: ${response.status} ${text}${hint}`)}let data=await response.json();if(!data?.access_token)throw new Error("access_token\uC774 \uC751\uB2F5\uC5D0 \uC5C6\uC74C");return{token:data.access_token,expiresIn:data.expires_in||3600}}async function getAccessToken(cred,force=!1){let k=cacheKey(cred),now=Date.now(),cached=tokenCache.get(k);if(!force&&cached&&cached.expiresAt>now+6e4)return cached.token;let jwt=await generateJWT(cred.client_email,cred.private_key),fresh=await exchangeAccessToken(jwt);return tokenCache.set(k,{token:fresh.token,expiresAt:now+Math.max(6e4,(fresh.expiresIn-60)*1e3)}),fresh.token}async function getAccessTokenFromServiceAccount(rawJson){let trimmed=String(rawJson||"").trim();if(!trimmed)throw new Error("Service Account JSON\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");let cred;try{cred=JSON.parse(trimmed)}catch{let extracted=extractFirstJsonObject(trimmed);if(!extracted)throw new Error("Service Account JSON \uD30C\uC2F1 \uC2E4\uD328: \uC720\uD6A8\uD55C JSON \uAC1D\uCCB4\uB97C \uCC3E\uC9C0 \uBABB\uD568");try{cred=JSON.parse(extracted)}catch(err){throw new Error("Service Account JSON \uD30C\uC2F1 \uC2E4\uD328: "+err.message)}}if(!cred?.client_email||!cred?.private_key||!cred?.project_id)throw new Error("JSON\uC5D0 client_email / private_key / project_id \uAC00 \uBAA8\uB450 \uD544\uC694\uD569\uB2C8\uB2E4.");return{token:await getAccessToken(cred,!1),projectId:cred.project_id}}function extractFirstJsonObject(s){let start=s.indexOf("{");if(start<0)return null;let depth=0,inStr=!1,escape=!1;for(let i=start;i<s.length;i++){let c=s[i];if(inStr){if(escape){escape=!1;continue}if(c==="\\"){escape=!0;continue}c==='"'&&(inStr=!1);continue}if(c==='"'){inStr=!0;continue}if(c==="{")depth++;else if(c==="}"&&(depth--,depth===0))return s.slice(start,i+1)}return null}function identifyFamily(modelId){return String(modelId).toLowerCase().includes("claude")?"claude":"gemini"}function buildGeminiBody3(pluginRequest,settings){let chats=(Array.isArray(pluginRequest?.prompt_chat)?pluginRequest.prompt_chat:Array.isArray(pluginRequest?.messages)?pluginRequest.messages:[]).map(m=>{let content=m?.content;return Array.isArray(content)&&(content=content.map(p=>typeof p=="string"?p:p?.text||"").filter(Boolean).join(` | |
| `)),{role:m?.role||USER_ROLE5,content:String(content??"").trim()}}),splitIndex=chats.findIndex(m=>m.role===USER_ROLE5||m.role===ASSISTANT_ROLE5);splitIndex===-1&&(splitIndex=chats.length);let systemText=chats.slice(0,splitIndex).map(m=>m.content).filter(Boolean).join(` | |
| `),rest=chats.slice(splitIndex);(rest.length===0||rest[0].role!==USER_ROLE5)&&rest.unshift({role:USER_ROLE5,content:"Start"});let contents=[];for(let m of rest){let role=m.role===ASSISTANT_ROLE5?"model":"user",text=m.content;if(m.role===SYSTEM_ROLE5){if(!text)continue;text="system: "+text}if(!text)continue;let last=contents.length?contents[contents.length-1]:null;last&&last.role===role?last.parts[last.parts.length-1].text+=` | |
| `+text:contents.push({role,parts:[{text}]})}let maxTokensRaw=parseInt(settings.maxTokens,10),generationConfig={maxOutputTokens:Number.isFinite(pluginRequest?.max_tokens)&&pluginRequest.max_tokens>0?pluginRequest.max_tokens:Number.isFinite(maxTokensRaw)&&maxTokensRaw>0?maxTokensRaw:8192};pluginRequest?.temperature!=null&&(generationConfig.temperature=Number(pluginRequest.temperature)),pluginRequest?.top_p!=null&&(generationConfig.topP=Number(pluginRequest.top_p)),pluginRequest?.top_k!=null&&(generationConfig.topK=Number(pluginRequest.top_k));let threshold=settings.disableSafety===!0||settings.disableSafety==="true"?"BLOCK_NONE":"BLOCK_ONLY_HIGH",safetySettings=["HATE_SPEECH","DANGEROUS_CONTENT","HARASSMENT","SEXUALLY_EXPLICIT","CIVIC_INTEGRITY"].map(c=>({category:`HARM_CATEGORY_${c}`,threshold})),body={contents,safetySettings,generationConfig};return systemText&&(body.systemInstruction={parts:[{text:systemText}]}),(settings.useWebSearch===!0||settings.useWebSearch==="true")&&(body.tools=body.tools||[],body.tools.push({google_search:{}})),body}function buildClaudeBody3(pluginRequest,settings){let chats=(Array.isArray(pluginRequest?.prompt_chat)?pluginRequest.prompt_chat:Array.isArray(pluginRequest?.messages)?pluginRequest.messages:[]).map(m=>{let content=m?.content;return Array.isArray(content)&&(content=content.map(p=>typeof p=="string"?p:p?.text||"").filter(Boolean).join(` | |
| `)),{role:m?.role||USER_ROLE5,content:String(content??"")}}),splitIndex=chats.findIndex(m=>m.role===USER_ROLE5||m.role===ASSISTANT_ROLE5);splitIndex===-1&&(splitIndex=chats.length);let systemText="";for(let i=0;i<splitIndex;i++){let t=chats[i].content.trim();t&&(systemText&&(systemText+=` | |
| `),systemText+=t)}let rest=chats.slice(splitIndex);(rest.length===0||rest[0].role!==USER_ROLE5)&&rest.unshift({role:USER_ROLE5,content:"Start"});let messages=[];for(let m of rest){let text=m.content.trim(),last=messages.length?messages[messages.length-1]:null;if(m.role===SYSTEM_ROLE5){let injected="system: "+text;last?.role===USER_ROLE5?last.content[0].text+=` | |
| `+injected:messages.push({role:USER_ROLE5,content:[{type:"text",text:injected}]})}else(m.role===USER_ROLE5||m.role===ASSISTANT_ROLE5)&&(last?.role===m.role?last.content[0].text+=` | |
| `+text:messages.push({role:m.role,content:[{type:"text",text}]}))}let maxTokensRaw=parseInt(settings.maxTokens,10),maxTokens=Number.isFinite(maxTokensRaw)&&maxTokensRaw>0?maxTokensRaw:Number.isFinite(pluginRequest?.max_tokens)&&pluginRequest.max_tokens>0?pluginRequest.max_tokens:4096,body={anthropic_version:"vertex-2023-10-16",messages,max_tokens:maxTokens};if(systemText&&(body.system=[{type:"text",text:systemText}]),pluginRequest?.temperature!=null&&(body.temperature=Number(pluginRequest.temperature)),pluginRequest?.top_p!=null&&(body.top_p=Number(pluginRequest.top_p)),pluginRequest?.top_k!=null&&(body.top_k=Number(pluginRequest.top_k)),settings.useCaching===!0||settings.useCaching==="true"){let ttl=settings.useCacheExtended===!0||settings.useCacheExtended==="true"?"1h":"5m";if(Array.isArray(body.system)&&body.system.length>0){let lastSys=body.system[body.system.length-1];lastSys&&typeof lastSys=="object"&&(lastSys.cache_control={type:"ephemeral",ttl:validTtl(ttl)})}markBreakpoint(body.messages,{ttl})}return body}function applyVertexCostMode(originalModel,costMode){if(!costMode||costMode==="default")return originalModel;if(identifyFamily(originalModel)==="claude"){if(costMode==="save")return"claude-haiku-4-5@latest";if(costMode==="priority")return"claude-opus-4-7@latest"}else{if(costMode==="save")return"gemini-2.5-flash-lite";if(costMode==="priority")return"gemini-3.1-pro-preview"}return originalModel}function endpointBase(region,projectId){return region==="global"?`https://aiplatform.googleapis.com/v1/projects/${projectId}/locations/global`:`https://${region}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${region}`}function endpointGemini(region,projectId,model){return`${endpointBase(region,projectId)}/publishers/google/models/${encodeURIComponent(model)}:streamGenerateContent?alt=sse`}function endpointGeminiOneShot(region,projectId,model){return`${endpointBase(region,projectId)}/publishers/google/models/${encodeURIComponent(model)}:generateContent`}function endpointClaude(region,projectId,model){return`${endpointBase(region,projectId)}/publishers/anthropic/models/${encodeURIComponent(model)}:rawPredict`}function endpointClaudeStream(region,projectId,model){return`${endpointBase(region,projectId)}/publishers/anthropic/models/${encodeURIComponent(model)}:streamRawPredict`}function parseFallbackRegions(raw){return String(raw||"").split(/[,\s]+/).map(s=>s.trim()).filter(s=>s.length>0)}function runProvider6(pluginRequest,abortSignal,options){return createTokenStream(async ctx=>{let settings=await loadProviderSettings(PROVIDER_ID6,SETTINGS_SCHEMA6),rawJson=String(settings.serviceAccountJson||"").trim();if(!rawJson){ctx.fail("[Blessing/vertex] Service Account JSON\uC774 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. Providers \uD0ED\uC5D0\uC11C \uC785\uB825\uD558\uC138\uC694.");return}let cred;try{cred=JSON.parse(rawJson)}catch(err){ctx.fail("[Blessing/vertex] Service Account JSON \uD30C\uC2F1 \uC2E4\uD328: "+err.message);return}if(!cred?.client_email||!cred?.private_key||!cred?.project_id){ctx.fail("[Blessing/vertex] JSON\uC5D0 client_email / private_key / project_id \uAC00 \uBAA8\uB450 \uD544\uC694\uD569\uB2C8\uB2E4.");return}let region=String(settings.region||"global").trim()||"global",overrideModel=options&&typeof options.modelOverride=="string"?options.modelOverride.trim():"",userModel=overrideModel||String(settings.model||"gemini-2.5-flash").trim(),model=overrideModel?userModel:applyVertexCostMode(userModel,String(settings.costMode||"default")),family=identifyFamily(model),token;try{token=await getAccessToken(cred,!1)}catch(err){ctx.fail("[Blessing/vertex] \uD1A0\uD070 \uBC1C\uAE09 \uC2E4\uD328: "+err.message);return}if(family==="gemini"){let regions=[region,...parseFallbackRegions(settings.fallbackRegions)],body=buildGeminiBody3(pluginRequest,settings);if(body.generationConfig=prepareGeminiConfig(body.generationConfig,model,settings),settings.useGeminiCaching===!0||settings.useGeminiCaching==="true")try{let split=buildStableCachePrefix(body.contents||[],body.systemInstruction,getMinTokensFor(model));if(split){let{prefix,tail}=split,prefixText=serializePrefixForHash(prefix,body.systemInstruction),prefixHash=await computePrefixHash(prefixText),chaId=await getCurrentCharId(),vertexBaseUrl=endpointBase(region,cred.project_id),cached=await findCacheVertex(chaId,model,prefixHash);if(cached&&shouldRefresh(cached.expireTime)){let refreshed=await patchCacheVertex(token,cached.cacheName);refreshed&&(cached=refreshed)}if(cached){let carriedTools=body.tools;body={contents:tail,safetySettings:body.safetySettings,generationConfig:body.generationConfig,cachedContent:cached.cacheName},carriedTools&&(body.tools=carriedTools)}else createCacheVertex(token,vertexBaseUrl,cred.project_id,region,model,chaId,prefixHash,prefix,body.systemInstruction).catch(()=>{})}}catch(err){try{console.warn(`[Blessing/vertex-gemini] context caching error (continuing without cache): ${err?.message||err}`)}catch{}}let useStreaming=await loadStreamingPreference(),vertexGeminiUsage={inputTokens:0,outputTokens:0,cacheCreation:0,cacheRead:0},response=null,usedRegion=region;for(let r of regions){let url=useStreaming?endpointGemini(r,cred.project_id,model):endpointGeminiOneShot(r,cred.project_id,model);if(response=await fetchWithFallback(url,{method:"POST",headers:{authorization:"Bearer "+token,"content-type":"application/json"},body:JSON.stringify(body),signal:abortSignal,loggedFetch:!useStreaming}),response.status===401&&(await response.body?.cancel().catch(()=>{}),token=await getAccessToken(cred,!0),response=await fetchWithFallback(url,{method:"POST",headers:{authorization:"Bearer "+token,"content-type":"application/json"},body:JSON.stringify(body),signal:abortSignal,loggedFetch:!useStreaming})),usedRegion=r,response.status!==429)break;if(r!==regions.at(-1)&&await response.body?.cancel().catch(()=>{}),regions.length>1)try{console.warn(`[Blessing/vertex-gemini] region ${r} \u2192 429 quota, trying next region`)}catch{}}let markerStyle=await loadMarkerStyle(),inThinking=!1,visibleTextEmitted=!1,finishReason=null;try{if(useStreaming){for await(let msg of parseSSE(response,abortSignal)){if(msg.data.trim()==="[DONE]")break;let parsed;if(parsed=JSON.parse(msg.data),checkGeminiInputBlock(parsed),parsed?.error){ctx.fail(` | |
| [Vertex/Gemini error] `+(parsed.error?.message||JSON.stringify(parsed.error)));let errMarker=markerFor("eof",markerStyle);errMarker&&ctx.enqueue(errMarker);break}let cands=parsed?.candidates;if(!Array.isArray(cands)||cands.length===0)continue;let parts=cands[0]?.content?.parts||[];for(let p of parts)typeof p?.text=="string"&&(p.thought===!0?(inThinking||(inThinking=!0,ctx.enqueue(`<Thoughts> | |
| `)),ctx.enqueue(p.text)):(p.text.trim().length>0&&(visibleTextEmitted=!0),inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `)),ctx.enqueue(p.text)));let finish=cands[0]?.finishReason;finish&&(finishReason=finish);let um=parsed?.usageMetadata;um&&(vertexGeminiUsage.inputTokens=Number(um.promptTokenCount)||0,vertexGeminiUsage.outputTokens=Number(um.candidatesTokenCount)||0,vertexGeminiUsage.cacheRead=Number(um.cachedContentTokenCount)||0)}inThinking&&ctx.enqueue(` | |
| </Thoughts> | |
| `)}else{if(response.ok){let data=await response.json();if(checkGeminiInputBlock(data),data?.error)ctx.fail(` | |
| [Vertex/Gemini error] `+(data.error?.message||JSON.stringify(data.error)));else{let cands=data?.candidates||[];if(cands.length>0){let parts=cands[0]?.content?.parts||[];for(let p of parts)typeof p?.text=="string"&&(p.thought===!0?(inThinking||(inThinking=!0,ctx.enqueue(`<Thoughts> | |
| `)),ctx.enqueue(p.text)):(p.text.trim().length>0&&(visibleTextEmitted=!0),inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `)),ctx.enqueue(p.text)));finishReason=cands[0]?.finishReason||null}let um=data?.usageMetadata;um&&(vertexGeminiUsage.inputTokens=Number(um.promptTokenCount)||0,vertexGeminiUsage.outputTokens=Number(um.candidatesTokenCount)||0,vertexGeminiUsage.cacheRead=Number(um.cachedContentTokenCount)||0)}}else{let text=await response.text().catch(()=>""),c=classifyError({status:response.status,body:text});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID6,displayName:"Vertex (Gemini)"}),ctx.fail(`[Vertex/Gemini] HTTP ${response.status}: ${text||response.statusText} | |
| ${c.action}`)}inThinking&&ctx.enqueue(` | |
| </Thoughts> | |
| `)}let cause=geminiCause(finishReason);if(cause==="safety"?ctx.fail(`Gemini \uCD9C\uB825 \uCC28\uB2E8: ${finishReason}`,!1):(!visibleTextEmitted||finishReason!=="STOP")&&ctx.fail(visibleTextEmitted?`Gemini \uC751\uB2F5\uC774 \uC644\uACB0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 (${finishReason||"\uC885\uB8CC \uC0AC\uC720 \uC5C6\uC74C"}).`:"Gemini \uAC00\uC2DC \uC751\uB2F5\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",!1),cause){let marker=markerFor(cause,markerStyle);if(marker&&ctx.enqueue(marker),cause==="max_tokens"&&!visibleTextEmitted){let hint=geminiThinkingBudgetHint(markerStyle);hint&&ctx.enqueue(hint)}}}catch(err){if(err?.message==="aborted")throw err;ctx.fail(err?.message||"Gemini \uC751\uB2F5 \uCC98\uB9AC \uC2E4\uD328",!1);let c=classifyError({error:err});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID6,displayName:"Vertex (Gemini)"}),inThinking&&ctx.enqueue(` | |
| </Thoughts> | |
| `);let marker=err instanceof GeminiInputBlockError?err.message:markerFor("eof",markerStyle);marker&&ctx.enqueue(marker);try{console.warn(`[Blessing/vertex-gemini] response error: ${err?.message||err}`)}catch{}}(vertexGeminiUsage.inputTokens>0||vertexGeminiUsage.outputTokens>0||vertexGeminiUsage.cacheRead>0)&&recordUsage(PROVIDER_ID6,vertexGeminiUsage,model).catch(()=>{}),appendContextWarnIfNeeded(ctx,model,pluginRequest,markerStyle)}else{let useStreaming=await loadStreamingPreference(),regions=[region,...parseFallbackRegions(settings.fallbackRegions)],body=buildClaudeBody3(pluginRequest,settings),baseHeaders={authorization:"Bearer "+token,"content-type":"application/json"},useCaching=settings.useCaching===!0||settings.useCaching==="true",useCacheExtended=settings.useCacheExtended===!0||settings.useCacheExtended==="true";if(useCaching){let betas=[BETA_HEADER_EPHEMERAL];useCacheExtended&&betas.push(BETA_HEADER_EXTENDED),baseHeaders["anthropic-beta"]=betas.join(",")}let response=null,usedRegion=region;for(let r of regions){let url=useStreaming?endpointClaudeStream(r,cred.project_id,model):endpointClaude(r,cred.project_id,model);if(response=await fetchWithFallback(url,{method:"POST",headers:baseHeaders,body:JSON.stringify(body),signal:abortSignal,retry:!0,loggedFetch:!useStreaming}),response.status===401&&(token=await getAccessToken(cred,!0),baseHeaders.authorization="Bearer "+token,response=await fetchWithFallback(url,{method:"POST",headers:baseHeaders,body:JSON.stringify(body),signal:abortSignal,loggedFetch:!useStreaming})),usedRegion=r,response.status!==429)break;if(regions.length>1)try{console.warn(`[Blessing/vertex-claude] region ${r} \u2192 429 quota, trying next region`)}catch{}}if(!response.ok){let text=await response.text().catch(()=>""),c=classifyError({status:response.status,body:text});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID6,displayName:"Vertex (Claude)"}),ctx.fail(`[Vertex/Claude] HTTP ${response.status}: ${text||response.statusText} | |
| ${c.action}`);return}let markerStyle=await loadMarkerStyle(),vertexClaudeUsage={inputTokens:0,outputTokens:0,cacheCreation:0,cacheRead:0},inThinking=!1,stopReason=null;try{if(useStreaming)for await(let msg of parseSSE(response,abortSignal)){let parsed;try{parsed=JSON.parse(msg.data)}catch{continue}let t=parsed?.type||msg.event;if(t==="message_start"){let u=parsed?.message?.usage;u&&(vertexClaudeUsage.inputTokens=Number(u.input_tokens)||0,vertexClaudeUsage.cacheCreation=Number(u.cache_creation_input_tokens)||0,vertexClaudeUsage.cacheRead=Number(u.cache_read_input_tokens)||0)}else if(t==="content_block_delta"){let d=parsed.delta;if(!d)continue;if(d.type==="thinking_delta"||d.type==="thinking"){if(!d.thinking)continue;inThinking||(inThinking=!0,ctx.enqueue(`<Thoughts> | |
| `)),ctx.enqueue(d.thinking)}else if(d.type==="text_delta"||d.type==="text"){if(!d.text)continue;inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `)),ctx.enqueue(d.text)}}else if(t==="message_delta"){parsed?.delta?.stop_reason&&(stopReason=parsed.delta.stop_reason);let u=parsed?.usage;u&&typeof u.output_tokens=="number"&&(vertexClaudeUsage.outputTokens=u.output_tokens)}else if(t==="message_stop"){inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `));let cause=anthropicCause(stopReason);if(cause){let marker=markerFor(cause,markerStyle);marker&&ctx.enqueue(marker)}break}else if(t==="error"){ctx.fail(` | |
| [Vertex/Claude error] `+(parsed?.error?.message||"unknown"));let marker=markerFor("eof",markerStyle);marker&&ctx.enqueue(marker);break}}else{let data=await response.json(),blocks=Array.isArray(data?.content)?data.content:[];for(let b of blocks)b?.type==="thinking"&&typeof b?.thinking=="string"&&b.thinking.length>0?(inThinking||(inThinking=!0,ctx.enqueue(`<Thoughts> | |
| `)),ctx.enqueue(b.thinking)):b?.type==="text"&&typeof b?.text=="string"&&(inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `)),ctx.enqueue(b.text));inThinking&&(inThinking=!1,ctx.enqueue(` | |
| </Thoughts> | |
| `));let u=data?.usage;u&&(vertexClaudeUsage.inputTokens=Number(u.input_tokens)||0,vertexClaudeUsage.outputTokens=Number(u.output_tokens)||0,vertexClaudeUsage.cacheCreation=Number(u.cache_creation_input_tokens)||0,vertexClaudeUsage.cacheRead=Number(u.cache_read_input_tokens)||0);let cause=anthropicCause(data?.stop_reason);if(cause){let marker=markerFor(cause,markerStyle);marker&&ctx.enqueue(marker)}}}catch(err){if(err?.message==="aborted")throw err;let c=classifyError({error:err});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID6,displayName:"Vertex (Claude)"}),inThinking&&ctx.enqueue(` | |
| </Thoughts> | |
| `),ctx.fail("\uC751\uB2F5 \uC218\uC2E0 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.",!1);let marker=markerFor("eof",markerStyle);marker&&ctx.enqueue(marker);try{console.warn(`[Blessing/vertex-claude] response error: ${err?.message||err}`)}catch{}}(vertexClaudeUsage.inputTokens>0||vertexClaudeUsage.outputTokens>0)&&recordUsage(PROVIDER_ID6,vertexClaudeUsage,model).catch(()=>{}),appendContextWarnIfNeeded(ctx,model,pluginRequest,markerStyle)}},abortSignal)}init_alert();var PROVIDER_ID7="cohere",DISPLAY_NAME7="Cohere",SETTINGS_SCHEMA7=[{key:"apiKey",label:"API Key",type:"password",default:"",help:`Cohere dashboard \uC5D0\uC11C \uBC1C\uAE09\uD55C API \uD0A4 (https://dashboard.cohere.com/api-keys). | |
| \xB7 Production / Trial \uD0A4 \uBAA8\uB450 \uB3D9\uC77C endpoint \uC0AC\uC6A9 \u2014 trial \uD0A4\uB294 rate limit \uC788\uC74C. | |
| \xB7 \uD0A4\uB294 pluginStorage \uC5D0\uB9CC \uC800\uC7A5\uB418\uBA70 \uC678\uBD80 \uC804\uC1A1 \uC548 \uB428.`},{key:"model",label:"Model",type:"text",default:"command-r-plus-08-2024",help:"\uC608: command-r-plus-08-2024 (\uCD5C\uC2E0 R+, flagship), command-r-08-2024 (R), command-r7b-12-2024 (\uC18C\uD615 7B). \uBE44\uC6A9 \uBAA8\uB4DC\uB97C 'default' \uC678\uB85C \uC124\uC815\uD558\uBA74 \uBCF8 \uD544\uB4DC\uB294 \uBB34\uC2DC\uB418\uACE0 \uBAA8\uB4DC\uBCC4 \uBAA8\uB378\uB85C swap."},{key:"costMode",label:"\uBE44\uC6A9 \uBAA8\uB4DC",type:"select",default:"default",options:[{value:"default",label:"\uAE30\uBCF8 (\uC704 model \uD544\uB4DC \uADF8\uB300\uB85C)"},{value:"save",label:"\uC808\uC57D (command-r7b-12-2024, $0.0375/$0.15)"},{value:"priority",label:"\uC6B0\uC120 (command-r-plus-08-2024, $2.50/$10.00)"}],help:`Cohere \uBAA8\uB378 tier \uC790\uB3D9 swap. | |
| \xB7 save: command-r7b-12-2024 \u2014 7B \uC18C\uD615, \uAC00\uC7A5 \uC800\uB834 ($0.0375/$0.15 per MTok). | |
| \xB7 default: \uC704 model \uD544\uB4DC \uADF8\uB300\uB85C (\uC608: command-r-plus-08-2024). | |
| \xB7 priority: command-r-plus-08-2024 \u2014 flagship reasoning ($2.50/$10.00 per MTok), multilingual \uAC15\uC810. | |
| \xB7 \uC911\uAC04 tier (command-r-08-2024 = $0.15/$0.60) \uAC00 \uD544\uC694\uD558\uBA74 default \uBAA8\uB4DC + model \uD544\uB4DC \uC9C1\uC811 \uC785\uB825. | |
| \xB7 Source: cohere.com/pricing (2026-05-23 \uC2DC\uC810).`},{key:"temperature",label:"Temperature",type:"number",default:"0.3",help:`0 ~ 1 \uBC94\uC704 \uAD8C\uC7A5. Cohere \uC758 default \uB294 0.3 (OpenAI \uC758 1.0 \uBCF4\uB2E4 \uB0AE\uC74C \u2014 Cohere \uBAA8\uB378\uC774 \uAC19\uC740 temperature \uC5D0\uC11C \uB354 \uBC1C\uC0B0\uC801 \uC751\uB2F5). | |
| \xB7 RP \uC6A9\uB3C4: 0.7-1.0 \uC815\uB3C4\uAC00 \uC790\uC5F0\uC2A4\uB7EC\uC6B4 \uCE90\uB9AD\uD130 \uC751\uB2F5. | |
| \xB7 \uBD84\uB958\xB7\uAD6C\uC870\uC801 \uCD9C\uB825: 0-0.3. | |
| \xB7 \uBE48 \uAC12\uC774\uBA74 0.3 \uC801\uC6A9.`},{key:"maxTokens",label:"max_tokens (\uC751\uB2F5 \uC0C1\uD55C)",type:"number",default:"4096",help:`Cohere V2 \uC758 max_tokens \uD30C\uB77C\uBBF8\uD130\uB85C \uC804\uB2EC. | |
| \xB7 \uBE44\uC6CC\uB450\uBA74 4096. | |
| \xB7 RisuAI \uAC00 pluginRequest.max_tokens \uBCF4\uB0B4\uBA74 \uADF8 \uAC12\uC774 \uC6B0\uC120. | |
| \xB7 Command R+ output context \uB294 4096 \uD1A0\uD070. \uADF8 \uC774\uC0C1\uC740 max_tokens \uBB34\uC2DC\uB418\uAC70\uB098 \uC798\uB9BC.`}],API_URL2="https://api.cohere.com/v2/chat",USER_ROLE6="user",ASSISTANT_ROLE6="assistant",SYSTEM_ROLE6="system";function buildCohereBody(pluginRequest,settings){let messagesRaw=(Array.isArray(pluginRequest?.prompt_chat)?pluginRequest.prompt_chat:Array.isArray(pluginRequest?.messages)?pluginRequest.messages:[]).map(m=>{let content=m?.content;return Array.isArray(content)&&(content=content.map(p=>typeof p=="string"?p:p?.text||"").filter(Boolean).join(` | |
| `)),{role:m?.role||USER_ROLE6,content:String(content??"")}}),messages=[],systemBuffer="";for(let m of messagesRaw){let text=m.content.trim();if(text){if(m.role===SYSTEM_ROLE6)systemBuffer&&(systemBuffer+=` | |
| `),systemBuffer+=text;else if(m.role===USER_ROLE6||m.role===ASSISTANT_ROLE6){if(systemBuffer&&messages.length===0)messages.push({role:SYSTEM_ROLE6,content:systemBuffer}),systemBuffer="";else if(systemBuffer&&m.role===USER_ROLE6){messages.push({role:USER_ROLE6,content:"system: "+systemBuffer+` | |
| `+text}),systemBuffer="";continue}messages.push({role:m.role,content:text})}}}if(systemBuffer){let lastUserIdx=(()=>{for(let i=messages.length-1;i>=0;i--)if(messages[i].role===USER_ROLE6)return i;return-1})();lastUserIdx>=0?messages[lastUserIdx].content="system: "+systemBuffer+` | |
| `+messages[lastUserIdx].content:messages.push({role:USER_ROLE6,content:"system: "+systemBuffer})}(messages.length===0||messages[messages.length-1].role!==USER_ROLE6)&&messages.push({role:USER_ROLE6,content:"Start"});let resolvedModel=settings.model||"command-r-plus-08-2024";settings.costMode==="save"?resolvedModel="command-r7b-12-2024":settings.costMode==="priority"&&(resolvedModel="command-r-plus-08-2024");let maxTokensRaw=parseInt(settings.maxTokens,10),maxTokens=Number.isFinite(maxTokensRaw)&&maxTokensRaw>0?maxTokensRaw:Number.isFinite(pluginRequest?.max_tokens)&&pluginRequest.max_tokens>0?pluginRequest.max_tokens:4096,body={model:resolvedModel,messages,max_tokens:maxTokens},temp=pluginRequest?.temperature!=null?Number(pluginRequest.temperature):Number(settings.temperature);return Number.isFinite(temp)&&(body.temperature=temp),typeof pluginRequest?.top_p=="number"&&(body.p=pluginRequest.top_p),typeof pluginRequest?.top_k=="number"&&(body.k=pluginRequest.top_k),body}function cohereCause(finishReason){if(!finishReason)return null;let r=String(finishReason).toLowerCase();return r==="complete"||r==="tool_call"||r==="stop_sequence"?null:r==="max_tokens"?"max_tokens":"unknown"}function runProvider7(pluginRequest,abortSignal,options){return createTokenStream(async ctx=>{let settings=await loadProviderSettings(PROVIDER_ID7,SETTINGS_SCHEMA7);if(options&&typeof options.modelOverride=="string"&&options.modelOverride.trim()&&(settings.model=options.modelOverride.trim(),settings.costMode="default"),!settings.apiKey){ctx.fail("[Blessing/cohere] API Key\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uC88C\uCE21 \uC0AC\uC774\uB4DC\uBC14\uC758 Blessing \u2192 Providers \uD0ED\uC5D0\uC11C \uC785\uB825\uD558\uC138\uC694.");return}let useStreaming=await loadStreamingPreference(),body=buildCohereBody(pluginRequest,settings);useStreaming&&(body.stream=!0);let headers={Authorization:`Bearer ${String(settings.apiKey)}`,"Content-Type":"application/json",Accept:useStreaming?"text/event-stream":"application/json"},response=await fetchWithFallback(API_URL2,{method:"POST",headers,body:JSON.stringify(body),signal:abortSignal,retry:!0,loggedFetch:!useStreaming});if(!response.ok){let text=await response.text().catch(()=>""),c=classifyError({status:response.status,body:text});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID7,displayName:DISPLAY_NAME7}),ctx.fail(`[Blessing/${DISPLAY_NAME7}] HTTP ${response.status}: ${text||response.statusText} | |
| ${c.action}`);return}let markerStyle=await loadMarkerStyle(),finishReason=null,usage={inputTokens:0,outputTokens:0,cacheCreation:0,cacheRead:0};try{if(useStreaming)for await(let msg of parseSSE(response,abortSignal)){if(!msg?.data)continue;let payload;try{payload=JSON.parse(msg.data)}catch{continue}let t=payload?.type;if(t==="content-delta"){let text=payload?.delta?.message?.content?.text;typeof text=="string"&&text.length>0&&ctx.enqueue(text)}else if(t==="message-end"){let reason=payload?.delta?.finish_reason;reason&&(finishReason=String(reason).toLowerCase());let billed=payload?.delta?.usage?.billed_units;if(billed)usage.inputTokens=Number(billed.input_tokens)||0,usage.outputTokens=Number(billed.output_tokens)||0;else{let tk=payload?.delta?.usage?.tokens;tk&&(usage.inputTokens=Number(tk.input_tokens)||0,usage.outputTokens=Number(tk.output_tokens)||0)}break}else if(t==="error"){ctx.fail(` | |
| [Cohere error] `+(payload?.message||payload?.error?.message||"unknown"));let marker=markerFor("eof",markerStyle);marker&&ctx.enqueue(marker);break}}else{let data=await response.json();if(data?.error||data?.message&&data?.id==null){ctx.fail(` | |
| [Cohere error] `+(data?.message||data?.error?.message||JSON.stringify(data)));let marker=markerFor("eof",markerStyle);marker&&ctx.enqueue(marker)}else{let contentArr=Array.isArray(data?.message?.content)?data.message.content:[];for(let c of contentArr)c?.type==="text"&&typeof c?.text=="string"&&c.text.length>0&&ctx.enqueue(c.text);data?.finish_reason&&(finishReason=String(data.finish_reason).toLowerCase());let billed=data?.usage?.billed_units;if(billed)usage.inputTokens=Number(billed.input_tokens)||0,usage.outputTokens=Number(billed.output_tokens)||0;else{let tk=data?.usage?.tokens;tk&&(usage.inputTokens=Number(tk.input_tokens)||0,usage.outputTokens=Number(tk.output_tokens)||0)}}}let cause=cohereCause(finishReason);if(cause){let marker=markerFor(cause,markerStyle);marker&&ctx.enqueue(marker)}}catch(err){if(err?.message==="aborted")throw err;let c=classifyError({error:err});c.surface==="modal"&&surfaceClassification(c,{providerId:PROVIDER_ID7,displayName:DISPLAY_NAME7}),ctx.fail("\uC751\uB2F5 \uC218\uC2E0 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.",!1);let marker=markerFor("eof",markerStyle);marker&&ctx.enqueue(marker);try{console.warn(`[Blessing/cohere] stream error: ${err?.message||err}`)}catch{}}(usage.inputTokens>0||usage.outputTokens>0)&&recordUsage(PROVIDER_ID7,usage,body.model).catch(()=>{}),appendContextWarnIfNeeded(ctx,body.model,pluginRequest,markerStyle)},abortSignal)}var COPILOT_CLIENT_ID="01ab8ac9400c4e429b23",COPILOT_DEVICE_SCOPE="user:email";var VSCODE_VERSION="1.111.0",CHAT_VERSION="0.40.2026031401",CHROME_VERSION="142.0.7444.265",ELECTRON_VERSION="39.3.0";var GITHUB_TOKEN_API_VERSION="2024-12-15",EDITOR_VERSION=`vscode/${VSCODE_VERSION}`,EDITOR_PLUGIN_VERSION=`copilot-chat/${CHAT_VERSION}`,ELECTRON_USER_AGENT=`Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/${VSCODE_VERSION} Chrome/${CHROME_VERSION} Electron/${ELECTRON_VERSION} Safari/537.36`,COPILOT_CHAT_USER_AGENT=`GitHubCopilotChat/${CHAT_VERSION}`;var CACHE_KEY_PREFIX=`${STORAGE_PREFIX}copilot.apiTokenCache.`;async function startCopilotDeviceFlow(){let res=await fetchWithFallback("https://github.com/login/device/code",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({client_id:COPILOT_CLIENT_ID,scope:COPILOT_DEVICE_SCOPE})});if(!res.ok){let text=await res.text().catch(()=>"");throw new Error(`device code \uC694\uCCAD \uC2E4\uD328: HTTP ${res.status} ${text||res.statusText}`)}let data=await res.json().catch(err=>{throw new Error(`device code \uC751\uB2F5 \uD30C\uC2F1 \uC2E4\uD328: ${err?.message||err}. risuai.xyz \uC6F9\uC5D0\uC11C \uC4F0\uB294 \uACBD\uC6B0 RisuAI \uC124\uC815 \u2192 \uACE0\uAE09\uC124\uC815 \u2192 "Use plain fetch instead of server(\uC9C1\uC811 \uC694\uCCAD \uBCF4\uB0B4\uAE30)"\uB97C \uB04C\uC5B4\uC8FC\uC138\uC694 (CORS \uC6B0\uD68C\uC6A9 \uC11C\uBC84 \uD504\uB85D\uC2DC \uACBD\uC720). \uADF8\uAC8C \uCF1C\uC838 \uC788\uC73C\uBA74 GitHub OAuth \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uAC00 CORS\uB85C \uCC28\uB2E8\uB429\uB2C8\uB2E4.`)});if(!data||typeof data.device_code!="string"||typeof data.user_code!="string")throw new Error("device code \uC751\uB2F5\uC774 \uC608\uC0C1\uACFC \uB2E4\uB985\uB2C8\uB2E4: "+JSON.stringify(data));return{device_code:data.device_code,user_code:data.user_code,verification_uri:data.verification_uri||"https://github.com/login/device",interval:Number(data.interval)||5,expires_in:Number(data.expires_in)||900}}async function pollCopilotDeviceFlow(deviceCode,intervalSec,signal,onTick){let started=Date.now(),interval=Math.max(1,Number(intervalSec)||5),deadline=started+15*60*1e3;for(;;){if(signal?.aborted)throw new Error("\uC0AC\uC6A9\uC790\uAC00 \uB85C\uADF8\uC778\uC744 \uCDE8\uC18C\uD588\uC2B5\uB2C8\uB2E4.");if(Date.now()>deadline)throw new Error("device code\uAC00 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.");await sleep(interval*1e3,signal);let res;try{res=await fetchWithFallback("https://github.com/login/oauth/access_token",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({client_id:COPILOT_CLIENT_ID,device_code:deviceCode,grant_type:"urn:ietf:params:oauth:grant-type:device_code"})})}catch(err){onTick?.({elapsedSec:Math.round((Date.now()-started)/1e3),lastError:err?.message||String(err)});continue}if(!res.ok){onTick?.({elapsedSec:Math.round((Date.now()-started)/1e3),lastError:`HTTP ${res.status}`});continue}let data;try{data=await res.json()}catch{data=null}if(!data){onTick?.({elapsedSec:Math.round((Date.now()-started)/1e3),lastError:"empty body"});continue}if(data.error){if(data.error==="authorization_pending"){onTick?.({elapsedSec:Math.round((Date.now()-started)/1e3)});continue}if(data.error==="slow_down"){interval=Math.min(interval+5,60),onTick?.({elapsedSec:Math.round((Date.now()-started)/1e3),lastError:"slow_down"});continue}throw new Error(`GitHub \uC778\uC99D \uC2E4\uD328: ${data.error}${data.error_description?` \u2014 ${data.error_description}`:""}`)}if(typeof data.access_token=="string"&&data.access_token.length>0)return{access_token:data.access_token,token_type:data.token_type||"bearer",scope:data.scope||""};onTick?.({elapsedSec:Math.round((Date.now()-started)/1e3),lastError:"unknown response"})}}async function exchangeCopilotApiToken(oauthToken){if(!oauthToken)throw new Error("gho_ \uD1A0\uD070\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");let res=await fetchWithFallback("https://api.github.com/copilot_internal/v2/token",{method:"GET",headers:{Authorization:`Bearer ${oauthToken}`,Accept:"application/json","User-Agent":ELECTRON_USER_AGENT,"Editor-Version":EDITOR_VERSION,"Editor-Plugin-Version":EDITOR_PLUGIN_VERSION,"X-GitHub-Api-Version":GITHUB_TOKEN_API_VERSION}});if(!res.ok){let text=await res.text().catch(()=>"");throw res.status===401||res.status===403?new Error(`Copilot \uB2E8\uAE30 \uD1A0\uD070 \uBC1C\uAE09 \uAC70\uBD80 (HTTP ${res.status}). gho_ \uD1A0\uD070\uC774 \uB9CC\uB8CC\uB410\uAC70\uB098 \uD65C\uC131 Copilot \uAD6C\uB3C5\uC774 \uC5C6\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4. 'GitHub\uB85C \uB85C\uADF8\uC778'\uC744 \uB2E4\uC2DC \uB20C\uB7EC\uC8FC\uC138\uC694.`):new Error(`Copilot \uB2E8\uAE30 \uD1A0\uD070 \uBC1C\uAE09 \uC2E4\uD328: HTTP ${res.status} ${text||res.statusText}`)}let data=await res.json().catch(()=>null);if(!data)throw new Error("Copilot \uB2E8\uAE30 \uD1A0\uD070 \uC751\uB2F5 \uD30C\uC2F1 \uC2E4\uD328.");let endpoint=typeof data?.endpoints?.api=="string"?data.endpoints.api.replace(/\/$/,""):"";if(typeof data.token=="string"&&data.token.length>0)return{token:data.token,endpoint,expires_at:Number(data.expires_at)||Math.floor(Date.now()/1e3)+25*60,refresh_in:Number(data.refresh_in)||1500};if(Array.isArray(data.data))return{token:oauthToken,endpoint,expires_at:Math.floor(Date.now()/1e3)+15*60,refresh_in:900};throw new Error("Copilot \uB2E8\uAE30 \uD1A0\uD070 \uC751\uB2F5\uC774 \uC608\uC0C1\uACFC \uB2E4\uB985\uB2C8\uB2E4: "+JSON.stringify(data).slice(0,200))}async function ensureCopilotApiToken(oauthToken,cacheKey2="default"){let fullKey=CACHE_KEY_PREFIX+cacheKey2,storage=getStorage(),nowSec=Math.floor(Date.now()/1e3);try{let raw=await storage.getItem(fullKey);if(raw){let cached=typeof raw=="string"?JSON.parse(raw):raw;if(cached&&typeof cached.token=="string"&&typeof cached.expires_at=="number"&&typeof cached.oauthToken=="string"&&cached.oauthToken===oauthToken&&cached.expires_at-nowSec>60)return{token:cached.token,endpoint:typeof cached.endpoint=="string"?cached.endpoint:""}}}catch{}let fresh=await exchangeCopilotApiToken(oauthToken);try{await storage.setItem(fullKey,JSON.stringify({oauthToken,token:fresh.token,endpoint:fresh.endpoint||"",expires_at:fresh.expires_at}))}catch{}return{token:fresh.token,endpoint:fresh.endpoint||""}}async function clearCopilotApiTokenCache(cacheKey2="default"){try{await getStorage().setItem(CACHE_KEY_PREFIX+cacheKey2,"")}catch{}}function sleep(ms,signal){return new Promise((resolve,reject)=>{if(signal?.aborted){reject(new Error("aborted"));return}let t=setTimeout(()=>{signal&&signal.removeEventListener("abort",onAbort),resolve()},ms),onAbort=()=>{clearTimeout(t),reject(new Error("aborted"))};signal&&signal.addEventListener("abort",onAbort,{once:!0})})}init_alert();function formatOpenAICompatError(displayName,status,statusText,body){let raw=String(body||"").trim(),snippet=raw.length>500?raw.slice(0,500)+" \u2026":raw,lower=raw.toLowerCase(),hint="";return status===401||status===403?hint=` | |
| \u2192 \uC778\uC99D \uAC70\uBD80. \uAC00\uB2A5\uD55C \uC6D0\uC778: | |
| \xB7 API \uD0A4\uAC00 \uC798\uBABB\uB418\uC5C8\uAC70\uB098 \uB9CC\uB8CC/\uCDE8\uC18C\uB428 \u2192 \uBC1C\uAE09 \uD398\uC774\uC9C0\uC5D0\uC11C \uC7AC\uBC1C\uAE09. | |
| \xB7 \uD0A4\uC5D0 \uD574\uB2F9 \uBAA8\uB378/\uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uAD8C\uD55C\uC774 \uC5C6\uC74C. | |
| \xB7 \uAC8C\uC774\uD2B8\uC6E8\uC774\uAC00 Referer/User-Agent \uB97C \uAC80\uC0AC (\uBCF4\uD1B5 spec\uC5D0\uC11C \uCC98\uB9AC).`:status===402?hint=` | |
| \u2192 \uACB0\uC81C\xB7\uC794\uC561 \uBB38\uC81C. OpenRouter\xB7Vercel\xB7NanoGPT \uB4F1 \uC120\uBD88 \uAC8C\uC774\uD2B8\uC6E8\uC774\uC5D0\uC11C \uC790\uC8FC \uBC1C\uC0DD. | |
| \xB7 \uCF58\uC194\uC5D0\uC11C \uC794\uC561\uC744 \uCDA9\uC804\uD558\uAC70\uB098 \uACB0\uC81C \uC218\uB2E8\uC744 \uB4F1\uB85D\uD558\uC138\uC694.`:status===404?lower.includes("model")||lower.includes("not_found")?hint="\n\u2192 \uBAA8\uB378 ID \uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. '\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uBAA8\uB378 \uAC00\uC838\uC624\uAE30' \uB85C \uC815\uD655\uD55C ID \uB97C \uD655\uC778\uD558\uC138\uC694.\n \xB7 \uAC8C\uC774\uD2B8\uC6E8\uC774\uB294 `provider/model` \uD615\uC2DD\uC778 \uACBD\uC6B0\uAC00 \uB9CE\uC2B5\uB2C8\uB2E4 (\uC608: anthropic/claude-3.5-sonnet).":hint="\n\u2192 \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uACBD\uB85C\uAC00 \uC798\uBABB\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4. Base URL \uB05D\uC5D0 `/v1` \uC774 \uD3EC\uD568\uB410\uB294\uC9C0 \uD655\uC778.":status===408||status===504||status===524?hint=` | |
| \u2192 \uD0C0\uC784\uC544\uC6C3 (504/524=Cloudflare/\uAC8C\uC774\uD2B8\uC6E8\uC774). \uBAA8\uB378 \uC751\uB2F5\uC774 \uB108\uBB34 \uAE38\uAC70\uB098, \uCD94\uB860 \uBAA8\uB378\uC774 thinking \uB2E8\uACC4\uC5D0 \uBA38\uBB34\uB294 \uACBD\uC6B0. | |
| \xB7 max_tokens \uB97C \uC904\uC774\uAC70\uB098, Streaming=auto \uB85C \uB450\uACE0 \uB2E4\uC2DC \uC2DC\uB3C4.`:status===429?hint=` | |
| \u2192 rate limit. \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uAC70\uB098, \uAC8C\uC774\uD2B8\uC6E8\uC774 \uCF58\uC194\uC5D0\uC11C \uD55C\uB3C4/\uD50C\uB79C\uC744 \uD655\uC778\uD558\uC138\uC694.`:status>=500&&status<600?hint=` | |
| \u2192 \uD504\uB85C\uBC14\uC774\uB354 \uC11C\uBC84 \uC624\uB958. \uC7A0\uC2DC \uD6C4 \uC7AC\uC2DC\uB3C4\uD558\uC138\uC694. \uC9C0\uC18D\uB418\uBA74 status \uD398\uC774\uC9C0\uB97C \uD655\uC778.`:status===400&&(lower.includes("reasoning")||lower.includes("reasoning_effort")?hint=` | |
| \u2192 \uC774 \uBAA8\uB378\uC740 reasoning_effort \uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC124\uC815\uC5D0\uC11C 'Reasoning Effort' \uB97C 'none' \uC73C\uB85C \uB450\uC138\uC694.`:lower.includes("max_tokens")||lower.includes("max tokens")||lower.includes("context_length")?hint=` | |
| \u2192 max_tokens \uAC00 \uBAA8\uB378 \uD55C\uB3C4\uB97C \uB118\uC5C8\uAC70\uB098, \uC785\uB825+\uCD9C\uB825 \uD569\uC774 \uCEE8\uD14D\uC2A4\uD2B8 \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4. | |
| \xB7 'max_tokens (\uC751\uB2F5 \uC0C1\uD55C)' \uAC12\uC744 \uC904\uC774\uAC70\uB098, \uB300\uD654 \uAE38\uC774\uB97C \uC904\uC5EC \uBCF4\uC138\uC694.`:lower.includes("unsupported")||lower.includes("unknown_parameter")||lower.includes("unrecognized")?hint=` | |
| \u2192 \uC774 \uD504\uB85C\uBC14\uC774\uB354\uAC00 \uBC1B\uC9C0 \uC54A\uB294 \uD30C\uB77C\uBBF8\uD130\uAC00 \uD3EC\uD568\uB410\uC2B5\uB2C8\uB2E4. 'Custom Params (JSON)' \uAC12\uC744 \uC810\uAC80\uD558\uC138\uC694.`:hint=` | |
| \u2192 \uC694\uCCAD \uD615\uC2DD \uAC70\uBD80. \uC751\uB2F5 \uBCF8\uBB38\uC758 \uC624\uB958 \uBA54\uC2DC\uC9C0\uB97C \uD655\uC778\uD558\uC138\uC694.`),`[${displayName}] HTTP ${status} ${statusText||""} | |
| ${snippet}${hint}`}function formatCompatExtraHeadersDefault(spec){return!spec?.extraHeaders||typeof spec.extraHeaders!="object"||Object.keys(spec.extraHeaders).length===0?"":JSON.stringify(spec.extraHeaders,null,2)}function shouldInjectOllamaNumCtx(specId,model,hasUserOptions){return!(specId!=="ollama"||hasUserOptions||/[:\-]cloud\b/i.test(String(model||"")))}function estimateOllamaNumCtx(messages){let chars=0;if(Array.isArray(messages))for(let m of messages){let c=m&&m.content;if(typeof c=="string")chars+=c.length;else if(Array.isArray(c))for(let p of c)p&&typeof p.text=="string"&&(chars+=p.text.length)}let estTokens=Math.ceil(chars/3)+2048,buckets=[4096,8192,16384,32768];for(let b of buckets)if(estTokens<=b)return b;return 32768}function resolveCompatExtraHeaders(spec,settings,onError){let fallback=spec?.extraHeaders&&typeof spec.extraHeaders=="object"?{...spec.extraHeaders}:{},raw=String(settings?.requestHeadersJson||"").trim();if(!raw)return fallback;try{let parsed=JSON.parse(raw);if(!parsed||typeof parsed!="object"||Array.isArray(parsed))throw new Error("JSON object \uD615\uD0DC\uC5EC\uC57C \uD569\uB2C8\uB2E4.");let headers={};for(let[key,value]of Object.entries(parsed)){let name=String(key||"").trim();!name||value==null||(headers[name]=String(value))}return headers}catch(err){return typeof onError=="function"&&onError(err),fallback}}var OPENAI_COMPAT_PRESETS=[{id:"ollama",name:"Ollama (Local)",baseUrl:"http://localhost:11434/v1",model:"llama3.2",keyHint:"Ollama \uB85C\uCEEC\uC740 \uD0A4\uAC00 \uD544\uC694 \uC5C6\uC2B5\uB2C8\uB2E4. \uBAA8\uB378 \uBBF8\uB9AC `ollama pull`\uB85C \uBC1B\uC544\uB450\uC138\uC694. https://ollama.com/library",auth:"none",costModes:{options:[{value:"default",label:"\uAE30\uBCF8 (\uC0AC\uC6A9\uC790 model \uADF8\uB300\uB85C)"},{value:"save",label:"\uC808\uC57D (llama3.2:3b, 4-6 GB VRAM)"},{value:"priority",label:"\uC6B0\uC120 (llama4:scout, MoE 109B)"}],apply:(mode,body)=>{mode==="save"?body.model="llama3.2:3b":mode==="priority"&&(body.model="llama4:scout")},help:`\uB85C\uCEEC Ollama \uBAA8\uB378 \uC790\uB3D9 \uC120\uD0DD. '\uBE44\uC6A9' \uC758\uBBF8\uB294 *VRAM \xB7 tokens/sec trade-off* (\uC2E4\uC81C $ 0). | |
| \xB7 save: llama3.2:3b \u2014 \uAC00\uC7A5 \uC791\uC740 \uBAA8\uB378 (4-6 GB VRAM \uAC00\uB2A5). | |
| \xB7 default: \uC0AC\uC6A9\uC790 model \uD544\uB4DC \uADF8\uB300\uB85C (\uC608: llama3.1:8b, mistral:7b \u2014 RP \uC778\uAE30 default). | |
| \xB7 priority: llama4:scout \u2014 MoE 109B (10 GB+ VRAM \uAD8C\uC7A5). 24GB+ \uD658\uACBD\uC740 qwen3:32b \uB3C4 \uC790\uC720 \uC124\uC815 \uAC00\uB2A5. | |
| \xB7 \uBAA8\uB4E0 \uBAA8\uB378\uC740 \uC0AC\uC804\uC5D0 \`ollama pull MODEL\` \uB2E4\uC6B4\uB85C\uB4DC \uD544\uC694. \uCE74\uD0C8\uB85C\uADF8: https://ollama.com/library`}},{id:"vercel-gateway",name:"Vercel AI Gateway",baseUrl:"https://ai-gateway.vercel.sh/v1",model:"openai/gpt-4o-mini",keyHint:"Vercel \uD1A0\uD070\uC744 \uC785\uB825\uD558\uC138\uC694. \uBAA8\uB378\uC740 `openai/gpt-4o`, `anthropic/claude-sonnet-4`, `google/gemini-2.5-pro` \uB4F1 \uAC8C\uC774\uD2B8\uC6E8\uC774 ID. \uD0A4 \uBC1C\uAE09: https://vercel.com/dashboard/ai",costModes:{options:[{value:"default",label:"\uAE30\uBCF8 (omit service_tier)"},{value:"save",label:"\uC808\uC57D (flex, OpenAI \uBAA8\uB378\uB9CC \uC801\uC6A9)"},{value:"priority",label:"\uC6B0\uC120 (priority, OpenAI \uBAA8\uB378\uB9CC \uC801\uC6A9)"}],apply:(mode,body)=>{String(body.model||"").toLowerCase().startsWith("openai/")&&(mode==="save"?(body.providerOptions=body.providerOptions||{},body.providerOptions.openai=body.providerOptions.openai||{},body.providerOptions.openai.serviceTier="flex"):mode==="priority"&&(body.providerOptions=body.providerOptions||{},body.providerOptions.openai=body.providerOptions.openai||{},body.providerOptions.openai.serviceTier="priority"))},help:`Vercel AI Gateway \uC758 service_tier pass-through. | |
| \xB7 **OpenAI \uBAA8\uB378\uB9CC \uC9C0\uC6D0** \u2014 model \uC2AC\uB7EC\uADF8\uAC00 \`openai/\` \uB85C \uC2DC\uC791\uD574\uC57C \uC791\uB3D9. | |
| \xB7 anthropic/ google/ xai/ \uB4F1 \uBE44-OpenAI \uBAA8\uB378 \uC0AC\uC6A9 \uC2DC \uBCF8 dropdown \uC740 \uBB34\uC758\uBBF8 (Vercel \uC774 silently ignore). | |
| \xB7 save: flex (Batch API rate, \u22480.5\xD7 cost, \uB290\uB9BC). | |
| \xB7 priority: priority (\u22482\xD7 cost, \uBE60\uB974\uACE0 \uC548\uC815). | |
| \xB7 Source: vercel.com/docs/ai-gateway/capabilities/service-tiers (2026-05-23 \uD655\uC778).`}},{id:"openrouter",name:"OpenRouter",baseUrl:"https://openrouter.ai/api/v1",model:"openai/gpt-4o-mini",keyHint:`OpenRouter \uD0A4. \uBAA8\uB378 ID\uB294 \`anthropic/claude-3.5-sonnet\`, \`google/gemini-pro-1.5\`, \`deepseek/deepseek-chat\` \uB4F1. \uD0A4 \uBC1C\uAE09: https://openrouter.ai/keys | |
| provider \uB77C\uC6B0\uD305 (\uC120\uD0DD) | |
| \xB7 \uD2B9\uC815 \uBC31\uC5D4\uB4DC\uB85C \uAC15\uC81C\uD558\uB824\uBA74 \uC544\uB798 'Custom Params (JSON)'\uC5D0 \uB2E4\uC74C\uC744 \uB123\uC73C\uC138\uC694: | |
| {"provider": {"order": ["Cerebras", "Hyperbolic"], "allow_fallbacks": false}} | |
| \xB7 \uCD94\uB860 \uAC15\uB3C4\uB294 'Reasoning Effort' \uD544\uB4DC (\uB610\uB294 customParams \uC758 \`reasoning: {effort: 'high'}\`).`,extraHeaders:{"HTTP-Referer":"https://risuai.xyz","X-Title":"RisuAI \xB7 Blessing"},costModes:{options:[{value:"default",label:"\uAE30\uBCF8"},{value:"save",label:"\uC808\uC57D (:floor \uCD5C\uC800\uAC00 \uBC31\uC5D4\uB4DC)"},{value:"priority",label:"\uC6B0\uC120 (:nitro \uCD5C\uACE0 throughput)"}],apply:(mode,body)=>{let base=String(body.model||"").replace(/:(floor|nitro)$/,"");mode==="save"?body.model=base+":floor":mode==="priority"?body.model=base+":nitro":body.model=base},help:"OpenRouter \uC758 model slug \uC811\uBBF8\uC0AC\uB97C \uD1B5\uD55C \uBE44\uC6A9/throughput \uBAA8\uB4DC.\n\n\xB7 `:floor` \u2014 \uAC19\uC740 \uBAA8\uB378\uC744 \uC81C\uACF5\uD558\uB294 \uBC31\uC5D4\uB4DC \uC911 \uAC00\uC7A5 \uC800\uB834\uD55C \uACF3. \uC57D\uAC04 \uB290\uB9B4 \uC218 \uC788\uC74C.\n\xB7 `:nitro` \u2014 throughput \uAC00\uC7A5 \uBE60\uB978 \uBC31\uC5D4\uB4DC. \uAC00\uACA9 \uC57D\uAC04 \uC99D\uAC00 \uAC00\uB2A5.\n\xB7 OpenRouter \uAC00 \uC790\uB3D9 \uB77C\uC6B0\uD305. \uC704 'Routing - Sort by' \uD544\uB4DC\uC640 \uB3D9\uC2DC \uC0AC\uC6A9 \uAC00\uB2A5 (\uC774 cost mode \uAC00 \uD6C4\uCC98\uB9AC\uB85C suffix \uCD94\uAC00)."}},{id:"copilot",name:"GitHub Copilot",baseUrl:"https://api.githubcopilot.com",model:"gpt-4.1",costModes:{options:[{value:"default",label:"\uAE30\uBCF8 (\uC0AC\uC6A9\uC790 model \uADF8\uB300\uB85C)"},{value:"save",label:"\uC808\uC57D (gpt-4o-mini, \uB0AE\uC740 multiplier)"},{value:"priority",label:"\uC6B0\uC120 (claude-opus-4.6, \uB192\uC740 multiplier)"}],apply:(mode,body)=>{mode==="save"?body.model="gpt-4o-mini":mode==="priority"&&(body.model="claude-opus-4.6")},help:`Copilot \uBAA8\uB378 \uC790\uB3D9 \uC120\uD0DD \u2014 Copilot \uC740 'premium request multiplier' \uBC29\uC2DD \uCCAD\uAD6C (2026-06-01 \uC774\uD6C4 'GitHub AI Credits' usage-based \uB85C \uC804\uD658 \uC608\uC815). | |
| \xB7 save: gpt-4o-mini \u2014 \uAC00\uC7A5 \uB0AE\uC740 multiplier (\uB610\uB294 \uC0AC\uC6A9\uC790 \uD50C\uB79C\uC758 Auto-eligible cheap \uBAA8\uB378). | |
| \xB7 default: \uC0AC\uC6A9\uC790 model (\uAE30\uBCF8 gpt-4.1). | |
| \xB7 priority: claude-opus-4.6 \u2014 \uCD5C\uACE0 reasoning (\uB192\uC740 multiplier). \uC790\uC720 \uC124\uC815 \uC2DC gpt-5.5 (promotional 7.5\xD7) \uB3C4 \uAC00\uB2A5. | |
| \xB7 **ToS \uC8FC\uC758**: Copilot \uB294 *coding \uC791\uC5C5\uC6A9* \uC73C\uB85C \uB77C\uC774\uC120\uC2A4\uB428. \uC77C\uBC18 chat / roleplay \uC0AC\uC6A9\uC740 AUP / Required Mitigations \uC704\uBC18 \uC704\uD5D8. 02-FINAL \xA7A.7 \uCC38\uC870. | |
| \xB7 Source: docs.github.com/en/copilot/reference/ai-models/supported-models (2026-05-23).`},authMode:"copilot-oauth",keyHint:`GitHub Copilot\uC740 \uC77C\uBC18 PAT\uAC00 \uC544\uB2C8\uB77C OAuth Device Code\uB85C \uBC1C\uAE09\uB418\uB294 'Copilot \uD1A0\uD070'\uC744 \uC501\uB2C8\uB2E4. \uBE44\uACF5\uC2DD \uC0AC\uC6A9\uC785\uB2C8\uB2E4 \u2014 GitHub ToS / Copilot \uC57D\uAD00\uC744 \uD655\uC778\uD558\uACE0 \uBCF8\uC778 \uCC45\uC784\uC73C\uB85C \uC0AC\uC6A9\uD558\uC138\uC694. | |
| \uC0AC\uC804 \uC900\uBE44 (risuai.xyz \uC6F9 \uC0AC\uC6A9\uC790 \uD544\uC218) | |
| \xB7 RisuAI \uC124\uC815 \u2192 \uACE0\uAE09\uC124\uC815 \u2192 'Use plain fetch instead of server(\uC9C1\uC811 \uC694\uCCAD \uBCF4\uB0B4\uAE30)' \uB97C OFF. | |
| \xB7 OFF \uC5EC\uC57C RisuAI \uC11C\uBC84 \uD504\uB85D\uC2DC(/proxy2)\uAC00 GitHub API\uC758 CORS\uB97C \uC6B0\uD68C\uD574\uC90D\uB2C8\uB2E4. | |
| \xB7 \uB370\uC2A4\uD06C\uD0D1/Tauri \uB610\uB294 noderisu-only \uBE4C\uB4DC \uC0AC\uC6A9\uC790\uB294 \uBB34\uAD00. | |
| \uAD8C\uC7A5: \uC544\uB798 'GitHub\uB85C \uB85C\uADF8\uC778' \uBC84\uD2BC | |
| 1) \uCE74\uB4DC \uD558\uB2E8\uC758 'GitHub\uB85C \uB85C\uADF8\uC778' \uBC84\uD2BC\uC744 \uB204\uB974\uBA74 user_code\uC640 https://github.com/login/device \uB9C1\uD06C\uAC00 \uD45C\uC2DC\uB429\uB2C8\uB2E4. | |
| 2) \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uADF8 \uD398\uC774\uC9C0\uB97C \uC5F4\uACE0 user_code \uB97C \uBD99\uC5EC\uB123\uC5B4 \uC2B9\uC778\uD558\uBA74 \uC790\uB3D9\uC73C\uB85C gho_ \uD1A0\uD070\uC774 \uC774 \uCE78\uC5D0 \uC800\uC7A5\uB429\uB2C8\uB2E4. | |
| 3) \uCC44\uD305 \uD638\uCD9C \uC2DC \uD50C\uB7EC\uADF8\uC778\uC774 gho_ \uD1A0\uD070\uC744 \uB2E8\uAE30 API \uD1A0\uD070('tid=...;exp=...')\uC73C\uB85C \uC790\uB3D9 \uAD50\uD658\xB7\uCE90\uC2DC\uD558\uBBC0\uB85C \uCD94\uAC00 \uC791\uC5C5\uC740 \uC5C6\uC2B5\uB2C8\uB2E4. | |
| \uC218\uB3D9: \uAE30\uC874 \uD074\uB77C\uC774\uC5B8\uD2B8\uC5D0\uC11C \uCD94\uCD9C | |
| \xB7 VSCode + Copilot \uD655\uC7A5 / Neovim copilot.vim / JetBrains Copilot \uD50C\uB7EC\uADF8\uC778 \uB85C\uADF8\uC778 \uD6C4 hosts.json \uC758 oauth_token (gho_...)\uC744 \uBCF5\uC0AC\uD574 \uC774 \uCE78\uC5D0 \uBD99\uC5EC\uB123\uC5B4\uB3C4 \uB429\uB2C8\uB2E4. | |
| \xB7 \uACBD\uB85C \u2014 Windows: %USERPROFILE%\\AppData\\Local\\github-copilot\\hosts.json / macOS\xB7Linux: ~/.config/github-copilot/hosts.json | |
| \uBAA8\uB378 | |
| \xB7 gpt-4o, gpt-4o-mini, o3-mini, claude-3.5-sonnet, claude-3.7-sonnet, gemini-2.0-flash-001, gemini-2.5-pro \uB4F1 (VSCode 'Pick a Model'\uACFC \uAC70\uC758 \uB3D9\uC77C). | |
| \uC8FC\uC758 | |
| \xB7 \uB2E8\uAE30 \uD1A0\uD070\uC740 ~25\uBD84 \uB2E8\uC704\uB85C \uC790\uB3D9 \uAC31\uC2E0\uB429\uB2C8\uB2E4. 401\uC774 \uC9C0\uC18D\uB418\uBA74 'GitHub\uB85C \uB85C\uADF8\uC778'\uC744 \uB2E4\uC2DC \uB20C\uB7EC gho_ \uD1A0\uD070\uC744 \uC7AC\uBC1C\uAE09\uD558\uC138\uC694. | |
| \xB7 \uC774 \uD1A0\uD070\uC740 GitHub \uACC4\uC815 \uC790\uACA9 \uC99D\uBA85\uC5D0 \uAC00\uAE5D\uC2B5\uB2C8\uB2E4 \u2014 \uC808\uB300 \uACF5\uC720/\uACF5\uAC1C \uC800\uC7A5\uC18C\uC5D0 \uCEE4\uBC0B\uD558\uC9C0 \uB9C8\uC138\uC694. | |
| \xB7 GitHub\uAC00 \uBE44\uACF5\uC2DD \uD074\uB77C\uC774\uC5B8\uD2B8\uB97C \uCC28\uB2E8\uD560 \uAC00\uB2A5\uC131\uC774 \uC788\uC2B5\uB2C8\uB2E4.`,extraHeaders:{"Copilot-Integration-Id":"vscode-chat","Editor-Version":"vscode/1.111.0","Editor-Plugin-Version":"copilot-chat/0.40.2026031401","User-Agent":"GitHubCopilotChat/0.40.2026031401","X-Github-Api-Version":"2025-10-01","X-Initiator":"user","X-Interaction-Type":"conversation-panel","X-Vscode-User-Agent-Library-Version":"electron-fetch"}},{id:"deepseek",name:"DeepSeek",baseUrl:"https://api.deepseek.com/v1",model:"deepseek-chat",keyHint:"\uD0A4 \uBC1C\uAE09: https://platform.deepseek.com/api_keys \xB7 \uBAA8\uB378: deepseek-chat, deepseek-reasoner\n \xB7 `deepseek-reasoner` \uC0AC\uC6A9 \uC2DC 'Reasoning Effort'\uB97C medium/high\uB85C \uB450\uBA74 \uCD94\uB860 \uD1A0\uD070\uC774 \uD65C\uC131\uD654\uB429\uB2C8\uB2E4.",costModes:{options:[{value:"default",label:"\uAE30\uBCF8 (\uC0AC\uC6A9\uC790 model \uADF8\uB300\uB85C)"},{value:"save",label:"\uC808\uC57D (deepseek-chat)"},{value:"priority",label:"\uC6B0\uC120 (deepseek-reasoner, \uCD94\uB860)"}],apply:(mode,body)=>{mode==="save"?body.model="deepseek-chat":mode==="priority"&&(body.model="deepseek-reasoner")},help:`DeepSeek \uC758 chat / reasoner \uC790\uB3D9 \uC120\uD0DD. | |
| \xB7 \uC808\uC57D (chat): \uC77C\uBC18 \uCC44\uD305\uC5D0 \uC801\uD569, \uAC00\uC7A5 \uC800\uB834. | |
| \xB7 \uC6B0\uC120 (reasoner): \uCD94\uB860\xB7\uB17C\uB9AC \uC791\uC5C5 \u2014 'Reasoning Effort' \uC640 \uD568\uAED8 \uC0AC\uC6A9. | |
| \xB7 \uCD94\uAC00\uB85C prompt caching cache-hit input \uC740 0.1\xD7 (2026-04-26 \uC774\uD6C4) \u2014 \uCE90\uC2DC \uD65C\uC6A9\uC73C\uB85C \uCD94\uAC00 \uC808\uAC10 \uAC00\uB2A5. | |
| \xB7 (\uCC38\uACE0) \uC61B off-peak (16:30\u201300:30 UTC) \uD560\uC778\uC740 2025-09-05 \uD3D0\uC9C0.`}},{id:"nanogpt",name:"NanoGPT",baseUrl:"https://nano-gpt.com/api/v1",model:"chatgpt-4o-latest",keyHint:"\uD0A4 \uBC1C\uAE09: https://nano-gpt.com/api \xB7 \uBAA8\uB378 ID\uB294 NanoGPT \uBAA8\uB378 \uD398\uC774\uC9C0\uC758 OpenAI-\uD638\uD658 ID\uB97C \uADF8\uB300\uB85C \uC0AC\uC6A9."},{id:"glm",name:"Zhipu GLM (\uC911\uAD6D \uBCF8\uD1A0)",baseUrl:"https://open.bigmodel.cn/api/paas/v4",model:"glm-4-plus",keyHint:`\uC911\uAD6D \uBCF8\uD1A0 BigModel \uCF58\uC194 \uC5D4\uB4DC\uD3EC\uC778\uD2B8. \uD0A4 \uBC1C\uAE09: https://open.bigmodel.cn/usercenter/apikeys \xB7 \uBAA8\uB378: glm-4-plus, glm-4-air, glm-4-flash | |
| \xB7 \uD55C\uAD6D\uC5D0\uC11C Z.ai \uCF54\uB529 \uD50C\uB79C\uC744 \uAD6C\uB3C5\uD588\uB2E4\uBA74 \uC704 GLM \uCE74\uB4DC \uB300\uC2E0 \uC544\uB798 'Z.ai (GLM Coding Plan)' \uCE74\uB4DC\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.`,costModes:{options:[{value:"default",label:"\uAE30\uBCF8 (\uC0AC\uC6A9\uC790 model \uADF8\uB300\uB85C)"},{value:"save",label:"\uC808\uC57D (glm-4-flash, \uBB34\uB8CC tier)"},{value:"priority",label:"\uC6B0\uC120 (glm-4-plus)"}],apply:(mode,body)=>{mode==="save"?body.model="glm-4-flash":mode==="priority"&&(body.model="glm-4-plus")},help:`GLM tier \uC790\uB3D9 \uC120\uD0DD. | |
| \xB7 flash: \uBB34\uB8CC tier (\uD55C\uB3C4 \uC788\uC74C) \u2014 \uAC00\uBCBC\uC6B4 \uCC44\uD305. | |
| \xB7 air: \uADE0\uD615. | |
| \xB7 plus: \uCD5C\uACE0 \uD488\uC9C8.`}},{id:"zai",name:"Z.ai (GLM Coding Plan)",baseUrl:"https://api.z.ai/api/coding/paas/v4",model:"glm-4.6",keyHint:`Z.ai \uCF54\uB529 \uD50C\uB79C \uC804\uC6A9 \uC5D4\uB4DC\uD3EC\uC778\uD2B8 (\uD55C\uAD6D\uC5D0\uC11C GLM \uAD6C\uB3C5 \uC2DC \uC77C\uBC18\uC801\uC73C\uB85C \uC774\uCABD). \uD0A4 \uBC1C\uAE09: https://z.ai \xB7 \uBAA8\uB378: glm-4.6, glm-4.5, glm-4.5-air \uB4F1. | |
| \xB7 \uC77C\uBC18 BigModel(open.bigmodel.cn) \uD0A4\uC640\uB294 \uBCC4\uAC1C\uC785\uB2C8\uB2E4. \uD0A4\uAC00 \uC704 \uCE74\uB4DC\uC640 \uC11E\uC774\uC9C0 \uC54A\uB3C4\uB85D \uC8FC\uC758.`,costModes:{options:[{value:"default",label:"\uAE30\uBCF8 (glm-4.6)"},{value:"save",label:"\uC808\uC57D (glm-4.5-air)"}],apply:(mode,body)=>{mode==="save"&&(body.model="glm-4.5-air")},help:`Z.ai tier \uC790\uB3D9 \uC120\uD0DD. | |
| \xB7 glm-4.5-air: \uC791\uACE0 \uBE60\uB978 \uBAA8\uB378 \u2014 \uC77C\uBC18 \uCC44\uD305 \uBE44\uC6A9 \uC808\uAC10. | |
| \xB7 glm-4.6: \uCD5C\uACE0 \uD488\uC9C8 (\uAE30\uBCF8). | |
| \xB7 (Z.ai \uC758 priority \uB294 default \uC640 \uAC19\uC740 \uBAA8\uB378\uC774\uB77C \uC635\uC158 hide.)`}},{id:"hunyuan",name:"Tencent Hunyuan (HLM)",baseUrl:"https://api.hunyuan.cloud.tencent.com/v1",model:"hunyuan-pro",keyHint:"\uD0A4 \uBC1C\uAE09: https://console.cloud.tencent.com/hunyuan/api-key \xB7 \uBAA8\uB378: hunyuan-pro, hunyuan-standard, hunyuan-lite",costModes:{options:[{value:"default",label:"\uAE30\uBCF8 (\uC0AC\uC6A9\uC790 model \uADF8\uB300\uB85C)"},{value:"save",label:"\uC808\uC57D (hunyuan-lite, \uBB34\uB8CC tier)"},{value:"priority",label:"\uC6B0\uC120 (hunyuan-pro)"}],apply:(mode,body)=>{mode==="save"?body.model="hunyuan-lite":mode==="priority"&&(body.model="hunyuan-pro")},help:`Hunyuan tier \uC790\uB3D9 \uC120\uD0DD. | |
| \xB7 lite: \uBB34\uB8CC tier \u2014 \uAC00\uBCBC\uC6B4 \uCC44\uD305\xB7\uD14C\uC2A4\uD2B8. | |
| \xB7 standard: \uADE0\uD615. | |
| \xB7 pro: \uCD5C\uACE0 \uD488\uC9C8, \uBE44\uC6A9 \uCD5C\uB300.`}},{id:"groq",name:"Groq",baseUrl:"https://api.groq.com/openai/v1",model:"llama-3.3-70b-versatile",keyHint:"\uD0A4 \uBC1C\uAE09: https://console.groq.com/keys",costModes:{options:[{value:"default",label:"\uAE30\uBCF8 (\uC0AC\uC6A9\uC790 model \uADF8\uB300\uB85C)"},{value:"save",label:"\uC808\uC57D (llama-3.1-8b-instant, $0.05/$0.08)"},{value:"priority",label:"\uC6B0\uC120 (gpt-oss-120b, $0.15/$0.60)"}],apply:(mode,body)=>{mode==="save"?body.model="llama-3.1-8b-instant":mode==="priority"&&(body.model="openai/gpt-oss-120b")},help:`Groq \uBAA8\uB378 \uC790\uB3D9 \uC120\uD0DD. \uBAA8\uB4E0 tier \uAC00 GPU \uB300\uBE44 \uB9E4\uC6B0 \uBE60\uB974\uBA70 (8B = 693 t/s), tier \uAC04 \uCC28\uC774\uB294 \uC8FC\uB85C \uBAA8\uB378 \uD488\uC9C8. | |
| \xB7 save: llama-3.1-8b-instant ($0.05/$0.08 per MTok) \u2014 \uB2E8\uC21C \uCC44\uD305\uC5D0 \uCDA9\uBD84. | |
| \xB7 default: \uC0AC\uC6A9\uC790 model (\uAE30\uBCF8 llama-3.3-70b-versatile, $0.59/$0.79). | |
| \xB7 priority: openai/gpt-oss-120b ($0.15/$0.60) \u2014 120B MoE reasoning, Groq \uCD5C\uB300 production \uBAA8\uB378. | |
| \xB7 \uBB34\uB8CC tier \uC81C\uACF5 (rate-limited, \uCE74\uB4DC \uC5C6\uC74C). Developer tier $10 \uC2DC\uC791. | |
| \xB7 Source: groq.com/pricing (2026-05-23).`}},{id:"cerebras",name:"Cerebras",baseUrl:"https://api.cerebras.ai/v1",model:"gpt-oss-120b",keyHint:`\uD0A4 \uBC1C\uAE09: https://cloud.cerebras.ai/ | |
| \xB7 **\uC8FC\uC758**: Cerebras \uACF5\uAC1C catalog \uAC00 \uB300\uD3ED \uC815\uB9AC\uB428 (2026-05). production tier = gpt-oss-120b ($0.35/$0.75) + Preview tier = zai-glm-4.7 (\uAC00\uACA9 \uBBF8\uD655\uC778). llama-3.3-70b / gpt-oss-20b / kimi / llama-4 \uB4F1\uC740 Dedicated Endpoints (contact sales) \uB9CC.`,costModes:{options:[{value:"default",label:"\uAE30\uBCF8 (gpt-oss-120b, production)"},{value:"save",label:"\uC808\uC57D (gpt-oss-120b, default \uC640 \uB3D9\uC77C)"},{value:"priority",label:"\uC6B0\uC120 (zai-glm-4.7, Preview tier)"}],apply:(mode,body)=>{mode==="priority"&&(body.model="zai-glm-4.7")},help:`Cerebras \uBAA8\uB378 \uC790\uB3D9 \uC120\uD0DD. \uC758 catalog \uC7AC\uD3B8 (2026-05) \uBC18\uC601. | |
| \xB7 default / save: gpt-oss-120b \u2014 \uC720\uC77C\uD55C production tier ($0.35/$0.75 per MTok, 131k ctx, ~3,000 tok/sec). \uBB34\uB8CC tier 1M tokens/day, 8K ctx, 30 RPM. | |
| \xB7 priority: zai-glm-4.7 \u2014 Preview tier, \uAC00\uACA9 \uBBF8\uACF5\uAC1C (React-rendered). | |
| \xB7 **\uBCC0\uACBD**: llama3.1-70b / llama-3.3-70b / gpt-oss-20b / kimi / llama-4 \uBAA8\uB450 public catalog \uBD80\uC7AC (Dedicated Endpoints \uB9CC). llama3.1-8b + qwen-3-235b-2507 \uB294 2026-05-27 deprecated. | |
| \xB7 Source: inference-docs.cerebras.ai/models/overview (2026-05-24).`}},{id:"together",name:"Together AI",baseUrl:"https://api.together.xyz/v1",model:"meta-llama/Llama-3.3-70B-Instruct-Turbo",keyHint:"\uD0A4 \uBC1C\uAE09: https://api.together.ai/settings/api-keys",costModes:{options:[{value:"default",label:"\uAE30\uBCF8 (\uC0AC\uC6A9\uC790 model \uADF8\uB300\uB85C)"},{value:"save",label:"\uC808\uC57D (Llama-3-8b-chat-hf, $0.10/M)"},{value:"priority",label:"\uC6B0\uC120 (DeepSeek-V3.1, $0.60/$3.00 reasoning)"}],apply:(mode,body)=>{mode==="save"?body.model="meta-llama/Llama-3-8b-chat-hf":mode==="priority"&&(body.model="deepseek-ai/DeepSeek-V3.1")},help:`Together AI \uBAA8\uB378 \uC790\uB3D9 \uC120\uD0DD \u2014 family-size swap \uD328\uD134 (2024 Lite/Turbo/Reference \uBCC0\uD615\uC740 \uB300\uBD80\uBD84 deprecated). | |
| \xB7 save: Llama-3-8b-chat-hf ($0.10/M, \uC57D 1/9 \uAC00\uACA9) \uB610\uB294 \uC790\uC720 \uC124\uC815 \uC2DC gpt-oss-20b ($0.05/$0.20). | |
| \xB7 default: \uC0AC\uC6A9\uC790 model (\uAE30\uBCF8 Llama-3.3-70B-Instruct-Turbo, $0.88/M). | |
| \xB7 priority: DeepSeek-V3.1 ($0.60 in / $3.00 out) reasoning-grade. \uB610\uB294 \uC790\uC720\uB85C Qwen/Qwen3-235B-A22B-Instruct-2507-FP8 ($0.20/$0.60). | |
| \xB7 Source: together.ai/pricing, together.ai/blog/together-inference-engine-2 (2026-05-23).`}},{id:"mistral",name:"Mistral",baseUrl:"https://api.mistral.ai/v1",model:"mistral-large-latest",keyHint:"\uD0A4 \uBC1C\uAE09: https://console.mistral.ai/api-keys/",costModes:{options:[{value:"default",label:"\uAE30\uBCF8 (\uC0AC\uC6A9\uC790 model \uADF8\uB300\uB85C)"},{value:"save",label:"\uC808\uC57D (mistral-small-latest, ~$0.10/MTok)"},{value:"priority",label:"\uC6B0\uC120 (mistral-large-latest, ~$2/MTok input)"}],apply:(mode,body)=>{mode==="save"?body.model="mistral-small-latest":mode==="priority"&&(body.model="mistral-large-latest")},help:`Mistral tier \uC790\uB3D9 \uAD50\uCCB4. | |
| \xB7 small: \uBD84\uB958\xB7\uAC04\uB2E8 \uCC44\uD305 (~$0.10/MTok). | |
| \xB7 medium: \uADE0\uD615. | |
| \xB7 large: \uBCF5\uC7A1 reasoning (~$2/MTok input / $6/MTok output).`}},{id:"xai",name:"xAI Grok",baseUrl:"https://api.x.ai/v1",model:"grok-4.3",keyHint:`\uD0A4 \uBC1C\uAE09: https://console.x.ai/ | |
| \xB7 **\uC8FC\uC758**: 2026-05-15 mass retirement \u2014 grok-4-1-fast-* / grok-4-0709 / grok-code-fast-1 \uBAA8\uB450 retired. grok-4.3 \uAC00 \uC0C8 flagship ($1.25/$2.50 per MTok, 1M context). grok-build-0.1 \uB294 agentic coding \uD6C4\uC18D.`,costModes:{options:[{value:"default",label:"\uAE30\uBCF8 (\uC0AC\uC6A9\uC790 model \uADF8\uB300\uB85C)"},{value:"save",label:"\uC808\uC57D (grok-4.3 + reasoning none, \uBE60\uB974\uACE0 \uC800\uB834)"},{value:"priority",label:"\uC6B0\uC120 (grok-4.3 + reasoning high, \uAE4A\uC740 reasoning)"}],apply:(mode,body)=>{mode==="save"?(body.model="grok-4.3",body.reasoning_effort="none"):mode==="priority"&&(body.model="grok-4.3",body.reasoning_effort="high")},help:`Grok reasoning effort \uC790\uB3D9 \uC120\uD0DD. | |
| \xB7 save: grok-4.3 + reasoning_effort=none \u2014 \uB2E8\uC21C \uC791\uC5C5, \uCD94\uB860 \uD1A0\uD070 \uC5C6\uC774 \uBE60\uB978 \uC751\uB2F5. | |
| \xB7 default: \uC0AC\uC6A9\uC790 model \uADF8\uB300\uB85C (\uAE30\uBCF8 grok-4.3). | |
| \xB7 priority: grok-4.3 + reasoning_effort=high \u2014 \uBCF5\uC7A1\uD55C reasoning. | |
| \xB7 **\uBCC0\uACBD**: 2026-05-15 mass retirement. grok-4.3 ($1.25/$2.50 per MTok, 1M context) \uB9CC \uB0A8\uC74C. grok-4-1-fast-* (\uC61B save) / grok-4-0709 / grok-code-fast-1 \uBAA8\uB450 retired. | |
| \xB7 Batch API: 50% \uC808\uAC10 (\uBAA8\uB4E0 \uD1A0\uD070 \uD0C0\uC785). Cached input: 50% off prior rates (\uAD6C\uCCB4 multiplier \uBE44\uACF5\uAC1C).`}},{id:"local-openai",name:"Local OpenAI-compat",baseUrl:"http://localhost:8080/v1",model:"local-model",keyHint:"\uC790\uCCB4 \uD638\uC2A4\uD305\uD55C OpenAI \uD638\uD658 \uC11C\uBC84 (llama.cpp, vLLM, LM Studio \uB4F1).",auth:"none"},{id:"fireworks",name:"Fireworks AI",baseUrl:"https://api.fireworks.ai/inference/v1",model:"accounts/fireworks/models/llama-v3p3-70b-instruct",keyHint:`Llama / Qwen / DeepSeek / Kimi \uB4F1 open-weight \uBAA8\uB378 \uBE60\uB978 \uC778\uD37C\uB7F0\uC2A4. | |
| \uBAA8\uB378 ID: \`accounts/fireworks/models/{model-name}\` \uD615\uC2DD (\uC608: llama-v3p3-70b-instruct). | |
| \uD0A4 \uBC1C\uAE09: https://fireworks.ai/api-keys | |
| \xB7 **\uC8FC\uC758**: llama-v3p1-70b-instruct \uB294 likely deprecated \u2014 llama-v3p3-70b-instruct ($0.90/$0.90 per MTok) \uAD8C\uC7A5.`,costModes:{options:[{value:"default",label:"\uAE30\uBCF8 (\uC0AC\uC6A9\uC790 model \uADF8\uB300\uB85C)"},{value:"save",label:"\uC808\uC57D (gpt-oss-120b, $0.15/$0.60)"},{value:"priority",label:"\uC6B0\uC120 (deepseek-v4-pro, $1.74/$3.48 reasoning)"}],apply:(mode,body)=>{mode==="save"?body.model="accounts/fireworks/models/gpt-oss-120b":mode==="priority"&&(body.model="accounts/fireworks/models/deepseek-v4-pro")},help:`Fireworks tier \uC790\uB3D9 \uC120\uD0DD. verbatim \uAC00\uACA9 \uBC18\uC601. | |
| \xB7 save: gpt-oss-120b ($0.15/$0.60 per MTok, 131k context) \u2014 open-weight \uC911 \uC800\uAC00 reasoning. | |
| \xB7 default: \uC0AC\uC6A9\uC790 model (\uAE30\uBCF8 llama-v3p3-70b-instruct, $0.90/$0.90). | |
| \xB7 priority: deepseek-v4-pro ($1.74/$3.48, 1M+ context) \uCD5C\uACE0 reasoning tier. | |
| \xB7 \uBAA8\uB4E0 \uBAA8\uB378 cached input \uAE30\uBCF8 50% off. Batch API 50% \uCD94\uAC00 \uD560\uC778. | |
| \xB7 Source: docs.fireworks.ai/serverless/pricing (2026-05-24).`}},{id:"nvidia",name:"NVIDIA NIM",baseUrl:"https://integrate.api.nvidia.com/v1",model:"meta/llama-3.1-70b-instruct",keyHint:"NVIDIA build platform \uC758 LLM inference endpoint. Llama / Nemotron / Mistral \uB4F1.\n\uBAA8\uB378 ID: `vendor/model-name` \uD615\uC2DD (\uC608: meta/llama-3.1-70b-instruct, nvidia/llama-3.1-nemotron-70b-instruct).\n\uD0A4 \uBC1C\uAE09: https://build.nvidia.com/ (NVIDIA \uACC4\uC815 \uD544\uC694)",costModes:{options:[{value:"default",label:"\uAE30\uBCF8 (\uC0AC\uC6A9\uC790 model \uADF8\uB300\uB85C)"},{value:"save",label:"\uC808\uC57D (nemotron-nano-9b-v2)"},{value:"priority",label:"\uC6B0\uC120 (llama-3.3-nemotron-super-49b-v1.5)"}],apply:(mode,body)=>{mode==="save"?body.model="nvidia/nemotron-nano-9b-v2":mode==="priority"&&(body.model="nvidia/llama-3.3-nemotron-super-49b-v1.5")},help:`NIM \uBAA8\uB378 *\uD488\uC9C8* \uC120\uD0DD (NVIDIA primary source \uC5D0 $/token \uAC00\uACA9 \uBBF8\uACF5\uAC1C \u2014 NIM \uC740 GPU \uB77C\uC774\uC120\uC2A4 \uAE30\uBC18 \uCCAD\uAD6C). \uBCF8 dropdown \uC740 model-quality picker. | |
| \xB7 save: nemotron-nano-9b-v2 \u2014 \uAC00\uC7A5 \uC791\uC740 specialized agent \uBAA8\uB378 (Nemotron Nano series). | |
| \xB7 default: \uC0AC\uC6A9\uC790 model (\uAE30\uBCF8 meta/llama-3.1-70b-instruct). | |
| \xB7 priority: llama-3.3-nemotron-super-49b-v1.5 \u2014 NVIDIA reasoning-tuned, \uCD5C\uACE0 \uC815\uD655\uB3C4. | |
| \xB7 \uC0AC\uC6A9 \uC804 NVIDIA Developer \uBB34\uB8CC credits (1,000-5,000) \uB610\uB294 NVIDIA AI Enterprise \uB77C\uC774\uC120\uC2A4 ($4,500/GPU/year ~ $1/GPU/hour cloud) \uD655\uC778 \uD544\uC694. | |
| \xB7 Source: docs.api.nvidia.com/nim/docs/product (2026-05-23).`}}];function makeOpenAICompatProvider(spec){let id=`compat-${spec.id}`,displayName=spec.name,schema=[{key:"apiKey",label:"API Key",type:"password",default:"",help:spec.keyHint||"API \uD0A4"},{key:"baseUrl",label:"Base URL",type:"text",default:spec.baseUrl,help:"\uC5D4\uB4DC\uD3EC\uC778\uD2B8 prefix. \uC608: "+spec.baseUrl},{key:"model",label:"Model",type:"text",default:spec.model,help:"\uBAA8\uB378 \uC2DD\uBCC4\uC790. \uAC8C\uC774\uD2B8\uC6E8\uC774\uB294 `provider/model` \uD615\uC2DD\uC744 \uC4F0\uB294 \uACBD\uC6B0\uAC00 \uB9CE\uC2B5\uB2C8\uB2E4."},{key:"systemPrompt",label:"System prepend (\uC120\uD0DD)",type:"textarea",default:"",help:"\uBE44\uC5B4 \uC788\uC9C0 \uC54A\uC73C\uBA74 \uCCAB \uBA54\uC2DC\uC9C0\uB85C system \uC5ED\uD560\uC744 \uCD94\uAC00\uD569\uB2C8\uB2E4."},{key:"temperature",label:"Temperature",type:"number",default:"1",help:"0 ~ 2 \uBC94\uC704 \uAD8C\uC7A5. \uBE48 \uAC12\uC774\uBA74 \uAE30\uBCF8\uAC12 1."},{key:"maxTokens",label:"max_tokens (\uC751\uB2F5 \uC0C1\uD55C, \uC120\uD0DD)",type:"number",default:"",help:`\uC751\uB2F5 \uD1A0\uD070 \uC0C1\uD55C. \uBE44\uC6CC\uB450\uBA74 \uD504\uB85C\uBC14\uC774\uB354 \uAE30\uBCF8\uAC12\uC744 \uC0AC\uC6A9\uD558\uACE0, RisuAI\uAC00 pluginRequest.max_tokens \uB97C \uBCF4\uB0B4\uC8FC\uBA74 \uADF8 \uAC12\uC774 \uC6B0\uC120\uD569\uB2C8\uB2E4. | |
| \xB7 \uC751\uB2F5\uC774 \uC790\uAFB8 \uC798\uB9AC\uBA74(\uB610\uB294 finish_reason='length'\uBA74) \uAC12\uC744 \uB298\uB9AC\uC138\uC694. | |
| \xB7 \uC77C\uBD80 \uAC8C\uC774\uD2B8\uC6E8\uC774(\uD2B9\uD788 Vercel\xB7OpenRouter)\uB294 \uB108\uBB34 \uD070 \uAC12\uC744 \uAC70\uBD80\uD569\uB2C8\uB2E4.`},{key:"reasoningEffort",label:"Reasoning Effort (\uC120\uD0DD)",type:"select",default:"none",options:[{value:"none",label:"none (\uC0AC\uC6A9 \uC548 \uD568)"},{value:"low",label:"low"},{value:"medium",label:"medium"},{value:"high",label:"high"}],help:`\uCD94\uB860 \uD1A0\uD070\uC744 \uC9C0\uC6D0\uD558\uB294 \uBAA8\uB378 \uC804\uC6A9 (o1\xB7o3\xB7deepseek-reasoner\xB7\`:thinking\` \uBCC0\uC885 \uB4F1). | |
| \xB7 OpenAI/Copilot/DeepSeek/\uC77C\uBC18 OpenAI-\uD638\uD658: body.reasoning_effort \uB85C \uC804\uB2EC. | |
| \xB7 OpenRouter/Vercel: body.reasoning.effort \uB85C \uC804\uB2EC (\uAC8C\uC774\uD2B8\uC6E8\uC774 \uAD8C\uC7A5 \uD3EC\uB9F7). | |
| \xB7 'none' \uC774\uBA74 \uD544\uB4DC\uB97C \uBCF4\uB0B4\uC9C0 \uC54A\uC73C\uBBC0\uB85C, \uCD94\uB860 \uBBF8\uC9C0\uC6D0 \uBAA8\uB378\uC5D0\uC11C\uB294 \uADF8\uB300\uB85C \uB450\uC138\uC694.`},{key:"streamingMode",label:"Streaming",type:"select",default:"auto",options:[{value:"auto",label:"auto (\uAE30\uBCF8: \uC2A4\uD2B8\uB9AC\uBC0D ON)"},{value:"off",label:"off (\uD55C \uBC88\uC5D0 \uBC1B\uAE30)"}],help:"\uC77C\uBD80 \uAC8C\uC774\uD2B8\uC6E8\uC774\uB294 SSE\uB97C \uAE68\uB728\uB9AC\uAC70\uB098 \uC911\uAC04\uC5D0 \uB04A\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uC751\uB2F5\uC774 \uC790\uAFB8 \uC798\uB9AC\uAC70\uB098 \uBE48 \uC751\uB2F5\uC774 \uC624\uBA74 off\uB85C \uB450\uACE0 \uC2DC\uB3C4\uD574 \uBCF4\uC138\uC694."},{key:"customParamsJson",label:"Custom Params (JSON, \uC120\uD0DD)",type:"textarea",default:"",help:`\uC694\uCCAD body\uC5D0 \uADF8\uB300\uB85C merge\uB418\uB294 JSON. spec \uC5D0 \uC5C6\uB294 \uC635\uC158\uC744 \uB123\uC744 \uB54C \uC4F0\uC138\uC694. | |
| \uC608\uC2DC: | |
| \xB7 OpenRouter \uB77C\uC6B0\uD305 \u2014 {"provider":{"order":["Cerebras"]}} | |
| \xB7 OpenRouter/Vercel \uCD94\uB860 \u2014 {"reasoning":{"effort":"high"}} | |
| \xB7 \uAE30\uD0C0 \u2014 {"top_k":40,"seed":42} | |
| \uC5EC\uAE30 \uAC12\uC774 \uC2DC\uC2A4\uD15C \uAE30\uBCF8\uAC12\uBCF4\uB2E4 \uC6B0\uC120\uD569\uB2C8\uB2E4 (overwrite).`}];if(spec.costModes&&Array.isArray(spec.costModes.options)){let customParamsIdx=schema.findIndex(s=>s.key==="customParamsJson"),insertAt=customParamsIdx>=0?customParamsIdx:schema.length;schema.splice(insertAt,0,{key:"costMode",label:"\uBE44\uC6A9 \uBAA8\uB4DC",type:"select",default:spec.costModes.default||"default",options:spec.costModes.options,help:spec.costModes.help||"\uAE30\uBCF8 / \uC808\uC57D / \uC6B0\uC120 \uBAA8\uB4DC \uC790\uB3D9 \uC801\uC6A9."})}if(spec.id==="openrouter"){let customParamsIdx=schema.findIndex(s=>s.key==="customParamsJson"),insertAt=customParamsIdx>=0?customParamsIdx:schema.length;schema.splice(insertAt,0,{key:"routingSort",label:"OpenRouter Routing - Sort by",type:"select",default:"none",options:[{value:"none",label:"none (\uAC00\uACA9 \uAC00\uC911 load balancing, default)"},{value:"price",label:"price (\uAC00\uC7A5 \uC800\uB834\uD55C \uBC31\uC5D4\uB4DC)"},{value:"throughput",label:"throughput (\uAC00\uC7A5 \uBE60\uB978 \uBC31\uC5D4\uB4DC)"},{value:"latency",label:"latency (p50 latency \uAC00\uC7A5 \uB0AE\uC740 \uBC31\uC5D4\uB4DC)"}],help:`OpenRouter \uC758 \uC790\uB3D9 routing \uC6B0\uC120\uC21C\uC704. \uBE44\uC5B4\uB450\uBA74 OpenRouter \uAC00 \uAC00\uACA9 \uAC00\uC911 load balancing \uC73C\uB85C \uC790\uB3D9 \uC120\uD0DD\uD569\uB2C8\uB2E4. | |
| \xB7 price: \uAC19\uC740 \uBAA8\uB378\uC744 \uC81C\uACF5\uD558\uB294 \uBC31\uC5D4\uB4DC \uC911 \uAC00\uC7A5 \uC2FC \uACF3 \uC120\uD638. | |
| \xB7 throughput: \uCD08\uB2F9 \uD1A0\uD070\uC774 \uAC00\uC7A5 \uBE60\uB978 \uBC31\uC5D4\uB4DC. | |
| \xB7 latency: p50 latency \uAC00 \uAC00\uC7A5 \uB0AE\uC740 \uBC31\uC5D4\uB4DC. | |
| \uACF5\uC2DD doc: https://openrouter.ai/docs/guides/routing/provider-selection`},{key:"routingMaxPrice",label:"OpenRouter Routing - Max price (USD/M output tokens)",type:"number",default:"",help:`Output \uD1A0\uD070 \uBC31\uB9CC \uAC1C\uB2F9 USD \uC0C1\uD55C. \uC774 \uAC00\uACA9\uC744 \uCD08\uACFC\uD558\uB294 \uBC31\uC5D4\uB4DC\uB294 \uC790\uB3D9 \uC81C\uC678\uB429\uB2C8\uB2E4. | |
| \xB7 \uC608: 2 = $2/MTok output \uC774\uD558\uB9CC \uD5C8\uC6A9. | |
| \xB7 \uBE44\uC5B4 \uB450\uBA74 \uAC00\uACA9 \uC81C\uD55C \uC5C6\uC74C. | |
| \xB7 \uB9E4\uC6B0 \uBE44\uC2FC \uBAA8\uB378 (Claude Opus, GPT-4.1 \uB4F1) \uD638\uCD9C \uC2DC \uC758\uB3C4\uCE58 \uC54A\uC740 \uACE0\uAC00 \uB77C\uC6B0\uD305 \uAC00\uB4DC.`},{key:"routingZdrOnly",label:"OpenRouter Routing - ZDR only",type:"checkbox",default:!1,help:`ZDR (Zero Data Retention) \uC9C0\uC6D0 \uBC31\uC5D4\uB4DC\uB9CC \uC0AC\uC6A9\uD569\uB2C8\uB2E4. \uBAA8\uB4E0 \uBC31\uC5D4\uB4DC\uAC00 ZDR \uBBF8\uC9C0\uC6D0\uC774\uBA74 \uD638\uCD9C \uC2E4\uD328. | |
| \xB7 \uD504\uB77C\uC774\uBC84\uC2DC \uC6B0\uC120 \uC0AC\uC6A9 \uC2DC \uAD8C\uC7A5. | |
| \xB7 \uD638\uD658 \uBC31\uC5D4\uB4DC \uD480\uC774 \uC881\uC544\uC838 fallback \uC635\uC158\uC774 \uC904\uC5B4\uB4ED\uB2C8\uB2E4.`})}(spec.extraHeaders||spec.authMode==="copilot-oauth")&&schema.push({key:"requestHeadersJson",label:"Request Headers (JSON)",type:"textarea",default:formatCompatExtraHeadersDefault(spec),help:`\uC774 \uD504\uB85C\uBC14\uC774\uB354 \uD638\uCD9C\uC5D0 \uBCF4\uB0BC \uCD94\uAC00 \uD5E4\uB354 JSON\uC785\uB2C8\uB2E4. \uAE30\uBCF8\uAC12\uC774 \uBBF8\uB9AC \uCC44\uC6CC\uC838 \uC788\uC73C\uBA70, \uD544\uC694\uD560 \uB54C\uB9CC \uC218\uC815\uD558\uC138\uC694. | |
| \xB7 Copilot\uC758 Editor-Version / User-Agent / Copilot-Integration-Id \uAC19\uC740 \uC720\uC9C0\uBCF4\uC218\uC6A9 \uD5E4\uB354\uB3C4 \uC5EC\uAE30\uC11C \uC9C1\uC811 \uBC14\uAFC0 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | |
| \xB7 JSON object\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4. \uAC12\uC740 \uBB38\uC790\uC5F4 \uD5E4\uB354\uB85C \uC804\uC1A1\uB429\uB2C8\uB2E4. | |
| \xB7 \uD0A4\uB97C \uC9C0\uC6B0\uBA74 \uD574\uB2F9 \uD5E4\uB354\uB3C4 \uD568\uAED8 \uC81C\uAC70\uB429\uB2C8\uB2E4. | |
| \xB7 JSON\uC774 \uC798\uBABB\uB418\uBA74 \uB0B4\uC7A5 \uAE30\uBCF8 \uD5E4\uB354\uB85C \uB418\uB3CC\uC544\uAC11\uB2C8\uB2E4.`});function runProvider8(pluginRequest,abortSignal,options){return createTokenStream(async ctx=>{let cfg=await loadProviderSettings(id,schema);if(spec.auth!=="none"&&!cfg.apiKey){ctx.enqueue(`[${displayName}] API Key\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. Blessing \u2192 \uC124\uC815 \u2192 ${displayName} \uC5D0\uC11C \uC785\uB825\uD558\uC138\uC694.`);return}let baseUrl=normalizeOpenAIBase(cfg.baseUrl,spec.baseUrl),overrideModel=options&&typeof options.modelOverride=="string"?options.modelOverride.trim():"",model=String(overrideModel||cfg.model||spec.model),messages=toOpenAIMessages(pluginRequest);cfg.systemPrompt&&String(cfg.systemPrompt).trim()&&messages.unshift({role:"system",content:String(cfg.systemPrompt)});let streamOn=String(cfg.streamingMode||"auto").toLowerCase()!=="off",body={model,messages,stream:streamOn};streamOn&&(body.stream_options={include_usage:!0});let temp=Number(cfg.temperature);Number.isFinite(temp)&&(body.temperature=temp);let maxTokensKey=needsMaxCompletionTokens(model)?"max_completion_tokens":"max_tokens";if(typeof pluginRequest?.max_tokens=="number")body[maxTokensKey]=pluginRequest.max_tokens;else{let userMax=parseInt(cfg.maxTokens,10);Number.isFinite(userMax)&&userMax>0&&(body[maxTokensKey]=userMax)}typeof pluginRequest?.top_p=="number"&&(body.top_p=pluginRequest.top_p),typeof pluginRequest?.frequency_penalty=="number"&&(body.frequency_penalty=pluginRequest.frequency_penalty),typeof pluginRequest?.presence_penalty=="number"&&(body.presence_penalty=pluginRequest.presence_penalty);let effort=String(cfg.reasoningEffort||"none").toLowerCase();if(effort&&effort!=="none"&&(spec.id==="openrouter"||spec.id==="vercel-gateway"?body.reasoning={effort}:body.reasoning_effort=effort),spec.costModes&&typeof spec.costModes.apply=="function"&&cfg.costMode)try{spec.costModes.apply(cfg.costMode,body)}catch(err){try{console.warn(`[Blessing/${displayName}] costMode apply \uC2E4\uD328:`,err)}catch{}}if(spec.id==="ollama"&&((!body.options||typeof body.options!="object")&&(body.options={}),shouldInjectOllamaNumCtx(spec.id,body.model,body.options.num_ctx!=null)&&(body.options.num_ctx=estimateOllamaNumCtx(body.messages))),spec.id==="openrouter"){let providerObj={},sort=String(cfg.routingSort||"none").toLowerCase();sort&&sort!=="none"&&(providerObj.sort=sort);let maxPrice=parseFloat(cfg.routingMaxPrice);Number.isFinite(maxPrice)&&maxPrice>0&&(providerObj.max_price={completion:maxPrice}),(cfg.routingZdrOnly===!0||cfg.routingZdrOnly==="true")&&(providerObj.zdr=!0),Object.keys(providerObj).length>0&&(body.provider=providerObj)}if(cfg.customParamsJson&&String(cfg.customParamsJson).trim())try{let extra=JSON.parse(String(cfg.customParamsJson));extra&&typeof extra=="object"&&!Array.isArray(extra)&&Object.assign(body,extra)}catch(err){ctx.enqueue(`[${displayName}] Custom Params (JSON) \uD30C\uC2F1 \uC2E4\uD328: ${err?.message||String(err)}. \uD574\uB2F9 \uD544\uB4DC \uAC12\uC744 \uC810\uAC80\uD558\uC138\uC694. \uC774\uBC88 \uD638\uCD9C\uC740 customParams \uC5C6\uC774 \uC9C4\uD589\uD569\uB2C8\uB2E4. | |
| `)}let headers={"Content-Type":"application/json",Accept:streamOn?"text/event-stream":"application/json"},effectiveBase=baseUrl;if(cfg.apiKey)if(spec.authMode==="copilot-oauth")try{let tok=await ensureCopilotApiToken(String(cfg.apiKey),id);headers.Authorization=`Bearer ${tok.token}`,tok.endpoint&&(effectiveBase=tok.endpoint.replace(/\/+$/,""))}catch(err){ctx.fail(`[${displayName}] Copilot \uD1A0\uD070 \uAD50\uD658 \uC2E4\uD328: ${err?.message||String(err)}`);return}else headers.Authorization=`Bearer ${cfg.apiKey}`;Object.assign(headers,resolveCompatExtraHeaders(spec,cfg,err=>{ctx.enqueue(`[${displayName}] Request Headers (JSON) \uD30C\uC2F1 \uC2E4\uD328: ${err?.message||String(err)}. \uAE30\uBCF8 \uD5E4\uB354\uB85C \uACC4\uC18D \uC9C4\uD589\uD569\uB2C8\uB2E4. | |
| `)}));let url=`${effectiveBase}/chat/completions`,response=await fetchWithFallback(url,{method:"POST",headers,body:JSON.stringify(body),signal:abortSignal,retry:!0,loggedFetch:!streamOn});if(!response.ok){let text=await response.text().catch(()=>""),c=classifyError({status:response.status,body:text});c.surface==="modal"&&surfaceClassification(c,{providerId:id,displayName}),ctx.fail(formatOpenAICompatError(displayName,response.status,response.statusText,text));return}let markerStyle=await loadMarkerStyle();if(!streamOn){let payload=null;try{payload=await response.json()}catch(err){ctx.fail(`[${displayName}] \uC751\uB2F5 JSON \uD30C\uC2F1 \uC2E4\uD328: ${err?.message||String(err)}`);return}let content=payload?.choices?.[0]?.message?.content;typeof content=="string"&&content.length>0?ctx.enqueue(content):ctx.enqueue(`[${displayName}] \uC751\uB2F5\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. (\uBAA8\uB378 ID \uD655\uC778 \uB610\uB294 Streaming=auto \uB85C \uC2DC\uB3C4)`);let finish=payload?.choices?.[0]?.finish_reason,cause=openaiCause(finish);if(cause){let marker=markerFor(cause,markerStyle);marker&&ctx.enqueue(marker)}return}let finishReason=null,compatUsage={inputTokens:0,outputTokens:0,cacheCreation:0,cacheRead:0};try{for await(let msg of parseSSE(response,abortSignal)){if(!msg?.data)continue;if(msg.data==="[DONE]")break;let payload;try{payload=JSON.parse(msg.data)}catch{continue}let choice=payload?.choices?.[0],delta=choice?.delta?.content;typeof delta=="string"&&delta.length>0&&ctx.enqueue(delta),choice?.finish_reason&&(finishReason=choice.finish_reason);let u=payload?.usage;u&&(compatUsage.inputTokens=Number(u.prompt_tokens)||0,compatUsage.outputTokens=Number(u.completion_tokens)||0,compatUsage.cacheRead=Number(u.prompt_cache_hit_tokens)||Number(u.cache_read_input_tokens)||0)}let cause=openaiCause(finishReason);if(cause){let marker=markerFor(cause,markerStyle);marker&&ctx.enqueue(marker)}}catch(err){if(err?.message==="aborted")throw err;let c=classifyError({error:err});c.surface==="modal"&&surfaceClassification(c,{providerId:id,displayName}),ctx.fail("\uC751\uB2F5 \uC218\uC2E0 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.",!1);let marker=markerFor("eof",markerStyle);marker&&ctx.enqueue(marker);try{console.warn(`[Blessing/${displayName}] stream error: ${err?.message||err}`)}catch{}}(compatUsage.inputTokens>0||compatUsage.outputTokens>0)&&recordUsage(id,compatUsage,body.model).catch(()=>{}),appendContextWarnIfNeeded(ctx,body.model,pluginRequest,markerStyle)},abortSignal)}return{PROVIDER_ID:id,DISPLAY_NAME:displayName,SETTINGS_SCHEMA:schema,runProvider:runProvider8}}var COMPAT_PROVIDER_DEFS=OPENAI_COMPAT_PRESETS.map(makeOpenAICompatProvider),COMPAT_PROVIDER_IDS=new Set(COMPAT_PROVIDER_DEFS.map(d=>d.PROVIDER_ID));var PROVIDERS=[{id:PROVIDER_ID,displayName:DISPLAY_NAME,schema:SETTINGS_SCHEMA,run:runProvider,ported:!0},{id:PROVIDER_ID2,displayName:DISPLAY_NAME2,schema:SETTINGS_SCHEMA2,run:runProvider2,ported:!0},{id:PROVIDER_ID3,displayName:DISPLAY_NAME3,schema:SETTINGS_SCHEMA3,run:runProvider3,ported:!0,notice:`Google AI Studio (https://aistudio.google.com) \uBC1C\uAE09 API \uD0A4 \uC804\uC6A9. | |
| \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uB294 generativelanguage.googleapis.com/v1beta\uB85C \uACE0\uC815\uC774\uBA70 \uC218\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. OpenRouter \uB4F1 \uB2E4\uB978 \uAC8C\uC774\uD2B8\uC6E8\uC774\uB97C \uAC70\uCE58\uAC70\uB098 Base URL\uC744 \uBC14\uAFB8\uB824\uBA74 'Custom Google'\uC744 \uC0AC\uC6A9\uD558\uC138\uC694. \uACB0\uC81C \uACC4\uC815\uC774 \uD544\uC694\uD55C Vertex AI (aiplatform.googleapis.com)\uC640\uB294 \uBCC4\uAC1C \uC11C\uBE44\uC2A4\uC785\uB2C8\uB2E4.`},{id:PROVIDER_ID4,displayName:DISPLAY_NAME4,schema:SETTINGS_SCHEMA4,run:runProvider4,ported:!0,notice:`Base URL\uC744 \uC790\uC720\uB86D\uAC8C \uC9C0\uC815\uD560 \uC218 \uC788\uB294 Gemini \uD638\uCD9C (GoogleAI\uC640 \uAC19\uC740 body \uD615\uC2DD). | |
| \uAE30\uBCF8 Base URL\uC740 generativelanguage.googleapis.com/v1beta (= AI Studio API)\uC774\uBA70, OpenRouter\xB7\uC790\uCCB4 \uD504\uB85D\uC2DC\xB7\uAE30\uC5C5 \uAC8C\uC774\uD2B8\uC6E8\uC774 \uB4F1\uC73C\uB85C \uB77C\uC6B0\uD305\uD558\uB824\uBA74 \uC774 \uAC12\uC744 \uC218\uC815\uD558\uC138\uC694. AI Studio \uD0A4\uB97C \uADF8\uB300\uB85C \uC4F0\uBA74\uC11C \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uB9CC \uC62E\uAE30\uACE0 \uC2F6\uC744 \uB54C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.`},{id:PROVIDER_ID5,displayName:DISPLAY_NAME5,schema:SETTINGS_SCHEMA5,run:runProvider5,ported:!0},{id:PROVIDER_ID7,displayName:DISPLAY_NAME7,schema:SETTINGS_SCHEMA7,run:runProvider7,ported:!0},{id:PROVIDER_ID6,displayName:DISPLAY_NAME6,schema:SETTINGS_SCHEMA6,run:runProvider6,ported:!0,notice:`\u203B Vertex AI \uB9AC\uBE0C\uB79C\uB529 (2026-04-22, Google Cloud Next \uBC1C\uD45C) | |
| Vertex AI Platform\uC740 'Gemini Enterprise Agent Platform'\uC758 \uC77C\uBD80\uB85C \uD1B5\uD569\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uD504\uB85C\uC81D\uD2B8/\uC124\uC815/\uC694\uAE08/SKU\uB294 \uBCC0\uACBD\uB418\uC9C0 \uC54A\uC73C\uBA70, \uBCF8 \uD50C\uB7EC\uADF8\uC778\uC758 Service Account \uC778\uC99D \uD750\uB984\uACFC aiplatform.googleapis.com \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uB3C4 \uADF8\uB300\uB85C \uB3D9\uC791\uD569\uB2C8\uB2E4. Google Cloud Console \uBA54\uB274 \uBA85\uCE6D\uACFC \uBD81\uB9C8\uD06C\uAC00 \uD5A5\uD6C4 \uBA87 \uB2EC \uB3D9\uC548 \uB2E8\uACC4\uC801\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uB429\uB2C8\uB2E4 (\uBD81\uB9C8\uD06C\uB294 \uC790\uB3D9 \uB9AC\uB514\uB809\uC158). | |
| \uAD00\uB828 \uB370\uC774\uD130 \uBD84\uC11D \uC81C\uD488\uB3C4 \uD568\uAED8 \uBA85\uCE6D \uBCC0\uACBD: Dataplex Universal Catalog\u2192Knowledge Catalog, BigLake\u2192Lakehouse, Dataproc\u2192Managed Service for Apache Spark, Composer\u2192Managed Service for Apache Airflow, Looker Studio\u2192Data Studio.`},...COMPAT_PROVIDER_DEFS.map(d=>({id:d.PROVIDER_ID,displayName:d.DISPLAY_NAME,schema:d.SETTINGS_SCHEMA,run:d.runProvider,ported:!0}))];var OPENAI=["gpt-5.5","gpt-5.5-pro","gpt-5.4-pro","gpt-5.4","gpt-5.4-mini","gpt-5.4-nano","gpt-5.3-chat-latest","gpt-5.2-pro","gpt-5.2","gpt-5.1","gpt-5.1-codex","gpt-5-pro","gpt-5","gpt-5-mini","gpt-5-nano","gpt-5-codex","gpt-4.1","gpt-4.1-mini","gpt-4.1-nano","gpt-4o","gpt-4o-mini","o4-mini","o3","o3-mini","o1-pro","o1"],ANTHROPIC=["claude-opus-4-7","claude-opus-4-6","claude-opus-4-5-20251101","claude-opus-4-1-20250805","claude-opus-4-20250514","claude-sonnet-4-6","claude-sonnet-4-5-20250929","claude-sonnet-4-20250514","claude-haiku-4-5-20251001"],GEMINI=["gemini-pro-latest","gemini-flash-latest","gemini-flash-lite-latest","gemini-3.1-pro-preview","gemini-3.1-flash-lite","gemini-3-pro-preview","gemini-3-flash-preview","gemini-2.5-pro","gemini-2.5-flash","gemini-2.5-flash-lite","gemini-2.0-flash","gemini-2.0-flash-lite"],DEEPSEEK=["deepseek-chat","deepseek-reasoner"],GLM=["glm-4.6","glm-4.5","glm-4.5-air","glm-4.5v","glm-4-plus","glm-4-air","glm-4-airx","glm-4-long","glm-4-flash","glm-4v-plus"],HUNYUAN=["hunyuan-turbo","hunyuan-pro","hunyuan-standard","hunyuan-large","hunyuan-lite"],GROQ=["llama-3.3-70b-versatile","llama-3.1-8b-instant","llama-3.1-70b-versatile","mixtral-8x7b-32768","gemma2-9b-it","deepseek-r1-distill-llama-70b","qwen-2.5-32b","qwen-2.5-coder-32b"],CEREBRAS=["llama-3.3-70b","llama3.1-70b","llama3.1-8b","qwen-3-32b"],TOGETHER=["deepseek-ai/DeepSeek-V3","deepseek-ai/DeepSeek-R1","meta-llama/Llama-3.3-70B-Instruct-Turbo","meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo","meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo","Qwen/Qwen2.5-72B-Instruct-Turbo","Qwen/QwQ-32B-Preview","mistralai/Mixtral-8x22B-Instruct-v0.1"],MISTRAL=["mistral-large-latest","mistral-medium-latest","mistral-small-latest","ministral-8b-latest","ministral-3b-latest","codestral-latest","pixtral-large-latest","open-mistral-nemo"],XAI=["grok-4","grok-4-mini","grok-3","grok-3-mini","grok-3-fast","grok-3-mini-fast","grok-2-latest","grok-2-vision-latest","grok-beta"],COPILOT=["gpt-4o","gpt-4o-mini","gpt-4.1","gpt-5","gpt-5-mini","o1","o3-mini","o4-mini","claude-3.5-sonnet","claude-3.7-sonnet","claude-3.7-sonnet-thought","claude-sonnet-4","claude-opus-4","gemini-2.0-flash","gemini-2.5-pro"],OPENROUTER=["openai/gpt-4o","openai/gpt-4o-mini","openai/gpt-5","openai/o3-mini","anthropic/claude-3.5-sonnet","anthropic/claude-sonnet-4","anthropic/claude-opus-4","google/gemini-2.5-pro","google/gemini-2.0-flash-001","deepseek/deepseek-chat","deepseek/deepseek-r1","meta-llama/llama-3.3-70b-instruct","x-ai/grok-2-1212","x-ai/grok-4","mistralai/mistral-large"],VERCEL_GATEWAY=["openai/gpt-4o","openai/gpt-4o-mini","openai/gpt-5","anthropic/claude-sonnet-4","anthropic/claude-opus-4","google/gemini-2.5-pro","google/gemini-2.0-flash","xai/grok-4","deepseek/deepseek-r1"],PROVIDER_CATALOGS={"custom-openai":OPENAI,anthropic:ANTHROPIC,"custom-claude":ANTHROPIC,"google-ai":GEMINI,"custom-google":GEMINI,vertex:[],"compat-deepseek":DEEPSEEK,"compat-glm":GLM,"compat-hunyuan":HUNYUAN,"compat-groq":GROQ,"compat-cerebras":CEREBRAS,"compat-together":TOGETHER,"compat-mistral":MISTRAL,"compat-xai":XAI,"compat-copilot":COPILOT,"compat-openrouter":OPENROUTER,"compat-vercel-gateway":VERCEL_GATEWAY,"compat-ollama":[],"compat-local-openai":[]};function getCatalogModels(providerId){let list=PROVIDER_CATALOGS[providerId];return Array.isArray(list)?list.slice():[]}async function resolveCompatAuth(preset,providerId,apiKey,base){if(preset&&preset.authMode==="copilot-oauth"&&apiKey){let tok=await ensureCopilotApiToken(apiKey,providerId);return{bearer:tok&&tok.token?tok.token:apiKey,base:tok&&tok.endpoint?tok.endpoint.replace(/\/+$/,""):base}}return{bearer:apiKey,base}}var COMPAT_PRESET_BY_ID=new Map(OPENAI_COMPAT_PRESETS.map(p=>[`compat-${p.id}`,p])),CACHE_PREFIX=`${STORAGE_PREFIX}discovery.`,CACHE_TTL_MS=24*60*60*1e3,REQUEST_TIMEOUT_MS=12e3,ADAPTERS={"custom-openai":fetchOpenAICompat,anthropic:fetchAnthropic,"google-ai":fetchGoogleAI,"custom-claude":fetchCustomClaude,"custom-google":fetchCustomGoogle,vertex:fetchVertex};function resolveAdapter(providerId){return ADAPTERS[providerId]?ADAPTERS[providerId]:COMPAT_PROVIDER_IDS.has(providerId)?fetchOpenAICompat:null}function supportsDiscovery(providerId){return resolveAdapter(providerId)!==null}async function discoverModels(providerId,schema,opts={}){let adapter=resolveAdapter(providerId);if(!adapter)throw new Error(`'${providerId}'\uB294 \uBAA8\uB378 \uD0D0\uC0C9\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`);if(!opts.force){let cached=await readCache(providerId);if(cached)return{models:cached.models,fromCache:!0,fetchedAt:cached.fetchedAt}}let settings=await loadProviderSettings(providerId,schema),models=await adapter(settings,providerId),fetchedAt=Date.now();return await writeCache(providerId,{models,fetchedAt}),{models,fromCache:!1,fetchedAt}}async function readCachedModels(providerId){let cached=await readCache(providerId);return cached?cached.models:null}async function pingEndpoint(providerId,schema){let settings=await loadProviderSettings(providerId,schema);if(providerId==="vertex")return await pingVertex(settings);let req=resolvePingRequest(providerId,settings);if(!req)throw new Error(`'${providerId}'\uC758 \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uB97C \uACB0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`);let ctrl=new AbortController,timer=setTimeout(()=>ctrl.abort(),8e3);try{let res=await fetchWithFallback(req.url,{method:"GET",headers:req.headers||{},signal:ctrl.signal});return{url:req.url,status:res.status,statusText:res.statusText||""}}catch(err){throw err?.name==="AbortError"?new Error("\uC694\uCCAD \uD0C0\uC784\uC544\uC6C3 (8s) \u2014 \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uAC00 \uC751\uB2F5\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4."):err}finally{clearTimeout(timer)}}var MODEL_TEST_MIN_INTERVAL_MS=6e3,_modelTestLastCall=new Map,MODEL_TEST_TIMEOUT_MS=15e3,TEST_ADAPTERS={"custom-openai":callOpenAICompat,anthropic:callAnthropic,"custom-claude":callCustomClaude,"google-ai":callGoogleAI,"custom-google":callCustomGoogle,vertex:callVertex};function resolveTestAdapter(providerId){return TEST_ADAPTERS[providerId]?TEST_ADAPTERS[providerId]:COMPAT_PROVIDER_IDS.has(providerId)?callOpenAICompat:null}function supportsModelTest(providerId){return resolveTestAdapter(providerId)!==null}async function testModelCall(providerId,schema){let adapter=resolveTestAdapter(providerId);if(!adapter)throw new Error(`'${providerId}'\uB294 \uBAA8\uB378 \uD638\uCD9C \uD14C\uC2A4\uD2B8\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`);let now=Date.now(),last=_modelTestLastCall.get(providerId)||0,wait=MODEL_TEST_MIN_INTERVAL_MS-(now-last);if(wait>0)throw new Error(`rate-limit \uBCF4\uD638: ${Math.ceil(wait/1e3)}\uCD08 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.`);_modelTestLastCall.set(providerId,now);let settings=await loadProviderSettings(providerId,schema),model=String(settings.model||"").trim();if(!model)try{let subs=await loadSubmodels(providerId);subs.length>0&&(model=String(subs[0].model||"").trim())}catch{}if(!model){let catalog=getCatalogModels(providerId);catalog.length>0&&(model=String(catalog[0]||"").trim())}if(!model)throw new Error("\uD14C\uC2A4\uD2B8\uD560 \uBAA8\uB378\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. '\uCD94\uAC00 \uBAA8\uB378' \uC139\uC158\uC5D0\uC11C \uBAA8\uB378\uC744 1\uAC1C \uC774\uC0C1 \uB4F1\uB85D\uD558\uAC70\uB098, \uD050\uB808\uC774\uC158 \uCE74\uD0C8\uB85C\uADF8\uAC00 \uC81C\uACF5\uB418\uB294 \uD504\uB85C\uBC14\uC774\uB354(\uC608: OpenAI / Anthropic / Google AI)\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.");let started=Date.now(),result=await adapter(settings,model,providerId),ms=Date.now()-started;return{ok:!0,status:result.status,model,ms,reply:result.reply||""}}async function callOpenAICompat(settings,model,providerId){let preset=COMPAT_PRESET_BY_ID.get(providerId)||null,needsKey=!preset||preset.auth!=="none",apiKey=String(settings.apiKey||"").trim();if(needsKey&&!apiKey)throw new Error("API Key\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");let base=normalizeOpenAIBase(settings.baseUrl,preset?.baseUrl||"https://api.openai.com/v1"),isReasoning=needsMaxCompletionTokens(model),body={model,messages:[{role:"user",content:"hi"}],stream:!1};isReasoning?body.max_completion_tokens=64:(body.max_tokens=1,body.temperature=0);let auth=await resolveCompatAuth(preset,providerId,apiKey,base),headers={};auth.bearer&&(headers.Authorization=`Bearer ${auth.bearer}`),Object.assign(headers,resolveCompatExtraHeaders(preset,settings));let data=await jsonPost(`${auth.base}/chat/completions`,body,headers),reply=data?.choices?.[0]?.message?.content||data?.choices?.[0]?.text||"";return{status:200,reply:truncatePreview(reply)}}async function callAnthropic(settings,model){let apiKey=String(settings.apiKey||"").trim();if(!apiKey)throw new Error("API Key\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");let data=await jsonPost("https://api.anthropic.com/v1/messages",{model,max_tokens:16,messages:[{role:"user",content:"hi"}]},{"x-api-key":apiKey,"anthropic-version":"2023-06-01","anthropic-dangerous-direct-browser-access":"true"}),block=Array.isArray(data?.content)?data.content.find(b=>b?.type==="text"):null;return{status:200,reply:truncatePreview(block?.text||"")}}async function callCustomClaude(settings,model){let apiKey=String(settings.apiKey||"").trim();if(!apiKey)throw new Error("API Key\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");let base=normalizeClaudeBase(settings.baseUrl,"https://api.anthropic.com/v1"),authStyle=String(settings.authStyle||"x-api-key").trim(),headers={"anthropic-version":String(settings.anthropicVersion||"2023-06-01"),"anthropic-dangerous-direct-browser-access":"true"};authStyle==="bearer"?headers.Authorization=`Bearer ${apiKey}`:headers["x-api-key"]=apiKey;let body={model,max_tokens:16,messages:[{role:"user",content:"hi"}]},data=await jsonPost(`${base}/messages`,body,headers),block=Array.isArray(data?.content)?data.content.find(b=>b?.type==="text"):null;return{status:200,reply:truncatePreview(block?.text||"")}}async function callGoogleAI(settings,model){let apiKey=String(settings.apiKey||"").trim();if(!apiKey)throw new Error("API Key\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");let url=`https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent?key=${encodeURIComponent(apiKey)}`,body={contents:[{role:"user",parts:[{text:"hi"}]}],generationConfig:prepareGeminiConfig({maxOutputTokens:Math.max(1,parseInt(settings.maxTokens,10)||8192),temperature:0},model,settings)},data=await jsonPost(url,body,{}),text=checkedGeminiText(data);return{status:200,reply:truncatePreview(text)}}async function callCustomGoogle(settings,model){let apiKey=String(settings.apiKey||"").trim();if(!apiKey)throw new Error("API Key\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");let url=`${normalizeGoogleBase(settings.baseUrl,"https://generativelanguage.googleapis.com/v1beta")}/models/${encodeURIComponent(model)}:generateContent?key=${encodeURIComponent(apiKey)}`,body={contents:[{role:"user",parts:[{text:"hi"}]}],generationConfig:prepareGeminiConfig({maxOutputTokens:Math.max(1,parseInt(settings.maxTokens,10)||8192),temperature:0},model,settings)},data=await jsonPost(url,body,{}),text=checkedGeminiText(data);return{status:200,reply:truncatePreview(text)}}async function callVertex(settings,model){let{token,projectId}=await getAccessTokenFromServiceAccount(settings.serviceAccountJson),region=String(settings.region||"global").trim()||"global",isClaude=String(model).toLowerCase().includes("claude"),base=region==="global"?`https://aiplatform.googleapis.com/v1/projects/${projectId}/locations/global`:`https://${region}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${region}`,url=isClaude?`${base}/publishers/anthropic/models/${encodeURIComponent(model)}:rawPredict`:`${base}/publishers/google/models/${encodeURIComponent(model)}:generateContent`,body=isClaude?{anthropic_version:"vertex-2023-10-16",messages:[{role:"user",content:"hi"}],max_tokens:1}:{contents:[{role:"user",parts:[{text:"hi"}]}],generationConfig:prepareGeminiConfig({maxOutputTokens:Math.max(1,parseInt(settings.maxTokens,10)||8192),temperature:0},model,settings)};try{let data=await jsonPost(url,body,{authorization:"Bearer "+token}),reply=isClaude?Array.isArray(data?.content)&&data.content.find(b=>b?.type==="text")?.text||"":checkedGeminiText(data);return{status:200,reply:truncatePreview(reply)}}catch(err){throw new Error(`OAuth \uD1A0\uD070 \uBC1C\uAE09\uC740 \uC131\uACF5\uD588\uC73C\uB098 \uBAA8\uB378 \uD638\uCD9C \uC2E4\uD328: ${err?.message||err}. (\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C\uB294 aiplatform.googleapis.com\uAC00 CORS\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. RisuAI \uB370\uC2A4\uD06C\uD0D1 \uB610\uB294 CORS \uD504\uB85D\uC2DC \uD658\uACBD\uC5D0\uC11C \uC0AC\uC6A9\uD558\uC138\uC694.)`)}}async function pingVertex(settings){let url="https://oauth2.googleapis.com/token";try{let{token}=await getAccessTokenFromServiceAccount(settings.serviceAccountJson);return{url,status:200,statusText:token?"OAuth \uD1A0\uD070 \uBC1C\uAE09 OK":"OK"}}catch(err){let msg=err?.message||String(err);throw new Error(`Vertex \uC778\uC99D \uAC80\uC0AC \uC2E4\uD328: ${msg}`)}}function truncatePreview(s){let str=String(s||"").trim();return str?str.length>60?str.slice(0,60)+"\u2026":str:""}async function jsonPost(url,body,headers){let ctrl=new AbortController,timer=setTimeout(()=>ctrl.abort(),MODEL_TEST_TIMEOUT_MS);try{let res=await fetchWithFallback(url,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",...headers||{}},body:JSON.stringify(body),signal:ctrl.signal,proxyRejectStatuses:[403,404,502,503,504]});if(!res.ok){let text=await safeText(res),snippet=text?": "+text.slice(0,240):"";throw res.status===429?new Error(`HTTP 429 (rate limit)${snippet}`):res.status===401||res.status===403?new Error(`HTTP ${res.status} (\uC778\uC99D \uC2E4\uD328 \u2014 API Key \uD655\uC778)${snippet}`):res.status===404?new Error(`HTTP 404 (\uBAA8\uB378/\uACBD\uB85C \uC5C6\uC74C \u2014 Model \uB610\uB294 Base URL \uD655\uC778)${snippet}`):new Error(`HTTP ${res.status}${snippet}`)}return await res.json()}catch(err){throw err?.name==="AbortError"?new Error(`\uC694\uCCAD \uD0C0\uC784\uC544\uC6C3 (${MODEL_TEST_TIMEOUT_MS/1e3}s)`):err}finally{clearTimeout(timer)}}function supportsPing(providerId){return PING_PROVIDER_IDS.has(providerId)||COMPAT_PROVIDER_IDS.has(providerId)}var PING_PROVIDER_IDS=new Set(["anthropic","google-ai","vertex","custom-claude","custom-google","custom-openai"]);function resolvePingRequest(providerId,settings){if(providerId==="anthropic")return{url:"https://api.anthropic.com/v1/models",headers:{"anthropic-version":"2023-06-01","anthropic-dangerous-direct-browser-access":"true"}};if(providerId==="google-ai")return{url:"https://generativelanguage.googleapis.com/v1beta/models"};if(providerId==="custom-claude")return{url:`${normalizeClaudeBase(settings.baseUrl,"https://api.anthropic.com/v1")}/models`,headers:{"anthropic-version":String(settings.anthropicVersion||"2023-06-01"),"anthropic-dangerous-direct-browser-access":"true"}};if(providerId==="custom-google")return{url:`${normalizeGoogleBase(settings.baseUrl,"https://generativelanguage.googleapis.com/v1beta")}/models`};if(providerId==="custom-openai"||COMPAT_PROVIDER_IDS.has(providerId)){let preset=COMPAT_PRESET_BY_ID.get(providerId);return{url:`${normalizeOpenAIBase(settings.baseUrl,preset?.baseUrl||"https://api.openai.com/v1")}/models`}}return null}async function readCache(providerId){try{let raw=await getStorage().getItem(CACHE_PREFIX+providerId);if(!raw)return null;let parsed=typeof raw=="string"?JSON.parse(raw):raw;return!parsed||!Array.isArray(parsed.models)||Date.now()-(parsed.fetchedAt||0)>CACHE_TTL_MS?null:parsed}catch{return null}}async function writeCache(providerId,payload){try{await getStorage().setItem(CACHE_PREFIX+providerId,JSON.stringify(payload))}catch{}}async function fetchOpenAICompat(settings,providerId){let preset=COMPAT_PRESET_BY_ID.get(providerId)||null,apiKey=String(settings.apiKey||"").trim(),baseUrl=normalizeOpenAIBase(settings.baseUrl,preset?.baseUrl||"https://api.openai.com/v1"),auth=await resolveCompatAuth(preset,providerId,apiKey,baseUrl);baseUrl=auth.base;let url=`${baseUrl}/models`,headers={};auth.bearer&&(headers.Authorization=`Bearer ${auth.bearer}`),Object.assign(headers,resolveCompatExtraHeaders(preset,settings));let data=await jsonFetch(url,{headers});return extractIds(data?.data||data?.models||data||[])}async function fetchAnthropic(settings){let apiKey=String(settings.apiKey||"").trim();if(!apiKey)throw new Error("API Key\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");let data=await jsonFetch("https://api.anthropic.com/v1/models",{headers:{"x-api-key":apiKey,"anthropic-version":"2023-06-01","anthropic-dangerous-direct-browser-access":"true"}});return extractIds(data?.data||data?.models||[])}async function fetchCustomClaude(settings){let apiKey=String(settings.apiKey||"").trim();if(!apiKey)throw new Error("API Key\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");let baseUrl=normalizeClaudeBase(settings.baseUrl,"https://api.anthropic.com/v1"),authStyle=String(settings.authStyle||"x-api-key").trim(),headers={"anthropic-version":String(settings.anthropicVersion||"2023-06-01"),"anthropic-dangerous-direct-browser-access":"true"};authStyle==="bearer"?headers.Authorization=`Bearer ${apiKey}`:headers["x-api-key"]=apiKey;let data=await jsonFetch(`${baseUrl}/models`,{headers});return extractIds(data?.data||data?.models||[])}async function fetchGoogleAI(settings){let apiKey=String(settings.apiKey||"").trim();if(!apiKey)throw new Error("API Key\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");let url=`https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(apiKey)}`,data=await jsonFetch(url,{});return(Array.isArray(data?.models)?data.models:[]).map(m=>String(m?.name||"").replace(/^models\//,"")).filter(Boolean)}async function fetchCustomGoogle(settings){let apiKey=String(settings.apiKey||"").trim();if(!apiKey)throw new Error("API Key\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");let url=`${normalizeGoogleBase(settings.baseUrl,"https://generativelanguage.googleapis.com/v1beta")}/models?key=${encodeURIComponent(apiKey)}`,data=await jsonFetch(url,{});return(Array.isArray(data?.models)?data.models:[]).map(m=>String(m?.name||"").replace(/^models\//,"")).filter(Boolean)}async function fetchVertex(settings){let sa=String(settings.serviceAccountJson||"").trim();if(!sa)throw new Error("Service Account JSON\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");let token="";try{token=(await getAccessTokenFromServiceAccount(sa)).token}catch(err){throw new Error(`OAuth \uD1A0\uD070 \uBC1C\uAE09 \uC2E4\uD328: ${err?.message||err}`)}let region=String(settings.region||"global").trim()||"global",base=region==="global"?"https://aiplatform.googleapis.com/v1":`https://${region}-aiplatform.googleapis.com/v1`,publishers=["google","anthropic"],results=await Promise.allSettled(publishers.map(pub=>jsonFetch(`${base}/publishers/${pub}/models`,{headers:{Authorization:`Bearer ${token}`}}))),ids=[],errors=[];if(results.forEach((r,i)=>{if(r.status==="fulfilled"){let arr=Array.isArray(r.value?.publisherModels)?r.value.publisherModels:Array.isArray(r.value?.models)?r.value.models:[];for(let m of arr){let id=String(m?.name||m?.publisherModel||"").trim().replace(/^publishers\/[^/]+\/models\//,"");id&&ids.push(id)}}else errors.push(`${publishers[i]}: ${r.reason?.message||r.reason}`)}),ids.length===0){let detail=errors.length?` (${errors.join(" / ")})`:"";throw new Error(`Vertex \uBAA8\uB378 \uBAA9\uB85D\uC744 \uAC00\uC838\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4${detail}. aiplatform.googleapis.com\uC740 \uBE0C\uB77C\uC6B0\uC800 CORS\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC73C\uBBC0\uB85C, RisuAI \uB370\uC2A4\uD06C\uD0D1 \uB610\uB294 nativeFetch/\uD504\uB85D\uC2DC \uD658\uACBD\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.`)}return Array.from(new Set(ids)).sort()}async function jsonFetch(url,opts){let ctrl=new AbortController,timer=setTimeout(()=>ctrl.abort(),REQUEST_TIMEOUT_MS);try{let res=await fetchWithFallback(url,{method:"GET",headers:{Accept:"application/json",...opts.headers||{}},signal:ctrl.signal,proxyRejectStatuses:[403,404,502,503,504]});if(!res.ok){let body=await safeText(res);throw new Error(`HTTP ${res.status}${body?": "+body.slice(0,200):""}`)}return await res.json()}finally{clearTimeout(timer)}}async function safeText(res){try{return await res.text()}catch{return""}}function extractIds(list){return Array.isArray(list)?list.map(m=>typeof m=="string"?m:m?.id||m?.name||m?.model||"").map(s=>String(s).trim()).filter(Boolean):[]}function checkedGeminiText(data){let candidate=data?.candidates?.[0];if(data?.error||data?.promptFeedback?.blockReason)throw Error("Gemini \uD638\uCD9C\uC774 \uAC70\uC808\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");let text=candidate?.content?.parts?.filter(p=>p?.thought!==!0).map(p=>p?.text||"").join("")||"";if(!text.trim())throw Error("Gemini \uAC00\uC2DC \uC751\uB2F5\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");if(candidate.finishReason!=="STOP")throw Error(`Gemini \uC751\uB2F5\uC774 \uC644\uACB0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 (${candidate.finishReason||"\uC885\uB8CC \uC0AC\uC720 \uC5C6\uC74C"}).`);return text}init_styles();function createSpinner(size){injectStyles();let el=document.createElement("span");return el.className="blessing-spinner"+(size==="sm"?" blessing-spinner--sm":size==="lg"?" blessing-spinner--lg":""),el.setAttribute("role","status"),el.setAttribute("aria-label","\uB85C\uB529 \uC911"),el}function inlineBusy(text){let wrap=document.createElement("span");wrap.className="blessing-inline-busy",wrap.appendChild(createSpinner("sm"));let span=document.createElement("span");return span.textContent=text,wrap.appendChild(span),wrap}function busyRegion(target,msg="\uB85C\uB529 \uC911..."){if(!target)return()=>{};injectStyles();let prevPos=target.style.position,cs=(target.ownerDocument||document).defaultView?.getComputedStyle(target);cs&&cs.position==="static"&&(target.style.position="relative");let overlay=document.createElement("div");if(overlay.className="blessing-busy__overlay",overlay.appendChild(createSpinner()),msg){let m=document.createElement("span");m.className="blessing-busy__msg",m.textContent=msg,overlay.appendChild(m)}return target.appendChild(overlay),()=>{overlay.parentNode===target&&target.removeChild(overlay),cs&&cs.position==="static"&&(target.style.position=prevPos)}}async function withRegion(target,fn,msg){let hide=busyRegion(target,msg);try{return await fn()}finally{hide()}}async function withButtonBusy(btn,fn,busyText){if(!btn)return fn();let prevDisabled=btn.disabled,previousNodes=[...btn.childNodes],previousText=btn.textContent;btn.disabled=!0;let spinner=createSpinner("sm");if(btn.textContent="",btn.appendChild(spinner),busyText){let space=document.createElement("span");space.style.marginLeft="8px",space.textContent=busyText,btn.appendChild(space)}else{let space=document.createElement("span");space.style.marginLeft="8px",space.textContent=previousText,btn.appendChild(space)}try{return await fn()}finally{btn.disabled=prevDisabled,btn.replaceChildren(...previousNodes)}}async function renderProvidersTab(root){root.innerHTML="";let h2=document.createElement("h2");h2.textContent="\uD504\uB85C\uBC14\uC774\uB354 \uC124\uC815",root.appendChild(h2);let intro=document.createElement("p");intro.textContent="\uC11C\uBE44\uC2A4 \uC120\uD0DD \u2192 \uC5F0\uACB0 \uC815\uBCF4 \uC785\uB825 \u2192 \uBAA8\uB378 \uD638\uCD9C \uD655\uC778. \uC785\uB825\uD55C \uAC12\uC740 \uC790\uB3D9 \uC800\uC7A5\uB429\uB2C8\uB2E4.",root.appendChild(intro),root.appendChild(createExplanation("\uD504\uB85C\uBC14\uC774\uB354\uB294 \uBB34\uC5C7\uC778\uAC00\uC694?","AI \uBAA8\uB378\uC744 \uC81C\uACF5\uD558\uB294 \uC11C\uBE44\uC2A4\uC785\uB2C8\uB2E4. API \uD0A4\uB97C \uBC1B\uC740 \uC11C\uBE44\uC2A4\uC640 \uAC19\uC740 \uD56D\uBAA9\uC744 \uC120\uD0DD\uD558\uC138\uC694. \uD0A4\uC640 \uC124\uC815\uC740 \uD604\uC7AC \uD638\uC2A4\uD2B8\uC758 \uD50C\uB7EC\uADF8\uC778 \uC800\uC7A5\uC18C\uC5D0 \uC800\uC7A5\uB429\uB2C8\uB2E4."));let maintenance=createSection("\uC804\uCCB4 \uC124\uC815\uC744 \uCD08\uAE30\uD654\uD558\uB824\uBA74",{maintenance:!0}),toolbar=document.createElement("div");toolbar.style.display="flex",toolbar.style.gap="var(--blessing-gap-sm)",toolbar.style.flexWrap="wrap",toolbar.style.margin="var(--blessing-gap-sm) 0 var(--blessing-gap) 0";let resetAllBtn=document.createElement("button");resetAllBtn.type="button",resetAllBtn.textContent="\uBAA8\uB4E0 \uD504\uB85C\uBC14\uC774\uB354 \uC124\uC815 \uC0AD\uC81C",styleAsDangerButton(resetAllBtn),resetAllBtn.title="\uC785\uB825\uD55C \uBAA8\uB4E0 \uAC12\uC744 \uC644\uC804\uD788 \uC0AD\uC81C\uD569\uB2C8\uB2E4. \uAE30\uBCF8\uAC12\uB3C4 \uC801\uC6A9\uB418\uC9C0 \uC54A\uACE0 \uBE48 \uC0C1\uD0DC\uB85C \uBCF5\uADC0\uB429\uB2C8\uB2E4.",resetAllBtn.addEventListener("click",async()=>{(typeof confirm!="function"||confirm(`\uBAA8\uB4E0 \uD504\uB85C\uBC14\uC774\uB354\uC758 \uC124\uC815\uC744 \uC644\uC804\uD788 \uC0AD\uC81C\uD569\uB2C8\uB2E4. | |
| API \uD0A4, \uBAA8\uB378, \uBAA8\uB4E0 \uC635\uC158\uC774 \uC0AC\uB77C\uC9D1\uB2C8\uB2E4. \uACC4\uC18D\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?`))&&await withButtonBusy(resetAllBtn,async()=>{for(let p of PROVIDERS){for(let f of p.schema)await setProviderSetting(p.id,f.key,"");await saveSubmodels(p.id,[])}await renderProvidersTab(root)},"\uC0AD\uC81C \uC911...")}),toolbar.appendChild(resetAllBtn);let defaultAllBtn=document.createElement("button");defaultAllBtn.type="button",defaultAllBtn.textContent="\uBAA8\uB4E0 \uD504\uB85C\uBC14\uC774\uB354\uB97C \uAE30\uBCF8\uAC12\uC73C\uB85C",styleAsSecondaryButton(defaultAllBtn),defaultAllBtn.title="\uBAA8\uB4E0 \uD544\uB4DC\uB97C \uCC98\uC74C\uC758 \uAE30\uBCF8\uAC12\uC73C\uB85C \uB418\uB3CC\uB9BD\uB2C8\uB2E4. API \uD0A4\uB3C4 \uBE44\uC6CC\uC9D1\uB2C8\uB2E4.",defaultAllBtn.addEventListener("click",async()=>{(typeof confirm!="function"||confirm(`\uBAA8\uB4E0 \uD504\uB85C\uBC14\uC774\uB354\uB97C \uAE30\uBCF8\uAC12\uC73C\uB85C \uB418\uB3CC\uB9BD\uB2C8\uB2E4. | |
| API \uD0A4\uB3C4 \uBE44\uC6CC\uC9D1\uB2C8\uB2E4. \uACC4\uC18D\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?`))&&await withButtonBusy(defaultAllBtn,async()=>{for(let p of PROVIDERS)for(let f of p.schema)await setProviderSetting(p.id,f.key,f.default??"");await renderProvidersTab(root)},"\uC801\uC6A9 \uC911...")}),toolbar.appendChild(defaultAllBtn),maintenance.appendChild(toolbar);let valuesPromise=loadAllProviderSettings().catch(()=>new Map),visibleContainer=document.createElement("div");root.appendChild(visibleContainer);let accRefs=[],visibleByProvider=new Map,hiddenRowByProvider=new Map,hiddenSection=null,hiddenList=null,hiddenSummary=null,emptyState=null,filterText="",filterRows=()=>{let matches=0;for(let[id,row]of hiddenRowByProvider){let p=PROVIDERS.find(p2=>p2.id===id),match=`${p.displayName} ${id} ${providerHint(p)}`.toLowerCase().includes(filterText);row.style.display=match?"flex":"none",match&&matches++}hiddenSummary&&(hiddenSummary.textContent=`\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD504\uB85C\uBC14\uC774\uB354 (${matches})`),hiddenSection&&(hiddenSection.querySelector(".blessing-search-empty").hidden=matches>0)},renderEmptyState=()=>{let card=document.createElement("div");card.className="blessing-card",card.style.padding="var(--blessing-gap)",card.style.textAlign="left",card.style.border="1px dashed var(--blessing-border-soft)",card.style.color="var(--blessing-fg-muted)",card.style.fontSize="13px",card.style.lineHeight="1.6";let title=document.createElement("div");title.textContent="\uC0AC\uC6A9 \uC911\uC778 \uD504\uB85C\uBC14\uC774\uB354\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4",title.style.fontSize="14px",title.style.fontWeight="600",title.style.color="var(--blessing-fg)",title.style.marginBottom="6px",card.appendChild(title);let body=document.createElement("div");return body.textContent="\uCC98\uC74C\uC774\uB77C\uBA74 API \uD0A4\uB97C \uBC1B\uC740 \uC11C\uBE44\uC2A4\uB97C \uC544\uB798\uC5D0\uC11C \uCC3E\uC544 \u2018+ \uC0AC\uC6A9\u2019\uC744 \uB20C\uB7EC \uC8FC\uC138\uC694.",card.appendChild(body),card},updateEmptyState=()=>{visibleByProvider.size===0?emptyState||(emptyState=renderEmptyState(),visibleContainer.appendChild(emptyState)):emptyState&&(emptyState.remove(),emptyState=null)},ensureHiddenSection=()=>{if(hiddenSection)return;hiddenSection=document.createElement("details"),hiddenSection.open=!0,hiddenSection.style.marginTop="var(--blessing-gap)",hiddenSection.style.padding="0",hiddenSection.style.border="1px solid var(--blessing-border-soft)",hiddenSection.style.borderRadius="var(--blessing-radius-sm)",hiddenSection.style.background="var(--blessing-bg-soft, transparent)",hiddenSummary=document.createElement("summary"),hiddenSummary.style.padding="10px 14px",hiddenSummary.style.cursor="pointer",hiddenSummary.style.fontSize="13px",hiddenSummary.style.fontWeight="600",hiddenSummary.style.color="var(--blessing-fg)",hiddenSummary.style.userSelect="none",hiddenSummary.textContent="\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD504\uB85C\uBC14\uC774\uB354 (0)",hiddenSection.appendChild(hiddenSummary);let search=document.createElement("input");search.type="search",search.className="blessing-input blessing-provider-search",search.placeholder="\uC11C\uBE44\uC2A4 \uC774\uB984 \uB610\uB294 \uD0A4\uB97C \uBC1B\uC740 \uACF3 \uAC80\uC0C9",search.setAttribute("aria-label","\uD504\uB85C\uBC14\uC774\uB354 \uAC80\uC0C9"),search.value=filterText,search.addEventListener("input",()=>{filterText=search.value.trim().toLowerCase(),filterRows()}),hiddenSection.appendChild(search);let noMatches=document.createElement("p");noMatches.className="blessing-search-empty",noMatches.textContent="\uC77C\uCE58\uD558\uB294 \uC11C\uBE44\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uAC80\uC0C9\uC5B4\uB97C \uC904\uC5EC \uBCF4\uC138\uC694.",noMatches.hidden=!0,hiddenSection.appendChild(noMatches),hiddenList=document.createElement("div"),hiddenList.style.padding="0 8px 8px",hiddenSection.appendChild(hiddenList),root.insertBefore(hiddenSection,maintenance.parentNode===root?maintenance:null)},updateHiddenSummary=()=>{let n=hiddenRowByProvider.size;hiddenSection&&(n===0?(hiddenSection.remove(),hiddenSection=null,hiddenList=null,hiddenSummary=null):hiddenSummary&&filterRows())},moveToHidden=(provider,persist=!0)=>{let entry=visibleByProvider.get(provider.id);if(entry&&(entry.wrapper.remove(),visibleByProvider.delete(provider.id)),!hiddenRowByProvider.has(provider.id)){ensureHiddenSection();let row=renderHiddenProviderRow(provider,moveToVisible),idx=PROVIDERS.findIndex(p=>p.id===provider.id),inserted=!1;for(let i=idx+1;i<PROVIDERS.length;i++){let next=hiddenRowByProvider.get(PROVIDERS[i].id);if(next){hiddenList.insertBefore(row,next),inserted=!0;break}}inserted||hiddenList.appendChild(row),hiddenRowByProvider.set(provider.id,row)}updateHiddenSummary(),updateEmptyState(),persist&&setProviderVisible(provider.id,!1).catch(()=>{})},moveToVisible=(provider,persist=!0)=>{let row=hiddenRowByProvider.get(provider.id);if(row&&(row.remove(),hiddenRowByProvider.delete(provider.id)),!visibleByProvider.has(provider.id)){let acc=createProviderAccordion(provider,valuesPromise,moveToHidden,root),idx=PROVIDERS.findIndex(p=>p.id===provider.id),inserted=!1;for(let i=idx+1;i<PROVIDERS.length;i++){let next=visibleByProvider.get(PROVIDERS[i].id);if(next){visibleContainer.insertBefore(acc.wrapper,next.wrapper),inserted=!0;break}}inserted||(emptyState&&emptyState.parentNode===visibleContainer?visibleContainer.insertBefore(acc.wrapper,emptyState):visibleContainer.appendChild(acc.wrapper));let ref={provider,configBadge:acc.configBadge,expand:acc.expand,wrapper:acc.wrapper};visibleByProvider.set(provider.id,ref),accRefs.push(ref),valuesPromise.then(allValues=>{applyConfigBadge(ref.configBadge,providerHasStoredValue(provider,allValues))}).catch(()=>{})}if(updateHiddenSummary(),updateEmptyState(),persist){setProviderVisible(provider.id,!0).catch(()=>{});let entry=visibleByProvider.get(provider.id);entry.expand(),entry.wrapper.scrollIntoView({block:"start"}),entry.wrapper.querySelector("button[aria-expanded]").focus({preventScroll:!0})}};ensureHiddenSection();let hiddenFrag=document.createDocumentFragment();for(let p of PROVIDERS){let row=renderHiddenProviderRow(p,moveToVisible);hiddenFrag.appendChild(row),hiddenRowByProvider.set(p.id,row)}hiddenList.appendChild(hiddenFrag),updateHiddenSummary(),updateEmptyState();let responseSettings=createSection("\uC751\uB2F5 \uC218\uC2E0 \uBC29\uC2DD\uC744 \uBC14\uAFB8\uB824\uBA74");await renderStreamingToggle(responseSettings),root.append(responseSettings,maintenance),loadVisibleProviderIds().then(visibleIds=>{for(let id of visibleIds){let p=PROVIDERS.find(x=>x.id===id);p&&moveToVisible(p,!1)}}).catch(()=>{}),valuesPromise.then(allValues=>{for(let ref of accRefs)applyConfigBadge(ref.configBadge,providerHasStoredValue(ref.provider,allValues))}).catch(()=>{})}function applyConfigBadge(badge,hasValue){hasValue?(badge.textContent="\uC124\uC815\uB428",badge.style.color="var(--blessing-accent, inherit)",badge.style.background="var(--blessing-accent-soft, var(--blessing-border-soft))"):badge.textContent="\uBE44\uC5B4 \uC788\uC74C"}function createProviderAccordion(provider,valuesPromise,onHide,rootForBodyActions){let wrapper=document.createElement("div");wrapper.className="blessing-card blessing-provider-card",wrapper.dataset.providerId=provider.id,wrapper.style.padding="0",wrapper.style.overflow="hidden",wrapper.style.position="relative";let head=document.createElement("button");head.type="button",head.style.width="100%",head.style.display="flex",head.style.alignItems="center",head.style.justifyContent="space-between",head.style.gap="10px",head.style.padding="12px 14px",head.style.background="transparent",head.style.color="var(--blessing-fg)",head.style.border="none",head.style.cursor="pointer",head.style.font="inherit",head.style.fontSize="13px",head.style.textAlign="left",head.setAttribute("aria-expanded","false");let left=document.createElement("span");left.style.display="inline-flex",left.style.alignItems="center",left.style.gap="10px",left.style.minWidth="0";let chevron=document.createElement("span");chevron.textContent=">",chevron.style.fontSize="9px",chevron.style.color="var(--blessing-fg-muted)",chevron.style.transition="transform 120ms",chevron.style.display="inline-block",chevron.style.width="10px",left.appendChild(chevron);let titleEl=document.createElement("span");titleEl.style.fontWeight="600",titleEl.textContent=provider.displayName,titleEl.style.whiteSpace="nowrap",titleEl.style.overflow="hidden",titleEl.style.textOverflow="ellipsis",left.appendChild(titleEl),head.appendChild(left);let right=document.createElement("span");right.style.display="inline-flex",right.style.alignItems="center",right.style.gap="6px",right.style.flexShrink="0";let configBadge=document.createElement("span");configBadge.textContent="\u2026",configBadge.style.fontSize="11px",configBadge.style.padding="2px 8px",configBadge.style.borderRadius="999px",configBadge.style.background="var(--blessing-border-soft)",configBadge.style.color="var(--blessing-fg-muted)",right.appendChild(configBadge),head.appendChild(right),wrapper.appendChild(head);let HIDE_ICON_SVG='<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="14" height="14" style="display:block"><path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88"/></svg>',hideOverlayBtn=document.createElement("button");hideOverlayBtn.type="button",hideOverlayBtn.setAttribute("aria-label","\uC774 \uD504\uB85C\uBC14\uC774\uB354 \uC228\uAE30\uAE30"),hideOverlayBtn.innerHTML=HIDE_ICON_SVG,hideOverlayBtn.title="\uC774 \uD504\uB85C\uBC14\uC774\uB354\uB97C \uBAA8\uB378 picker\uC5D0\uC11C \uC228\uAE30\uACE0, \uD558\uB2E8 '\uC228\uAE40 \uD504\uB85C\uBC14\uC774\uB354' \uC139\uC158\uC73C\uB85C \uC774\uB3D9\uC2DC\uD0B5\uB2C8\uB2E4. \uC800\uC7A5\uB41C \uAC12\uC740 \uC720\uC9C0\uB418\uBA70 '\uD45C\uC2DC'\uB97C \uB204\uB974\uBA74 \uBCF5\uC6D0\uB429\uB2C8\uB2E4. picker \uBC18\uC601\uC740 \uB2E4\uC74C \uC0C8\uB85C\uACE0\uCE68 \uD6C4\uC785\uB2C8\uB2E4.",hideOverlayBtn.style.position="absolute",hideOverlayBtn.style.top="2px",hideOverlayBtn.style.right="10px",hideOverlayBtn.style.display="inline-flex",hideOverlayBtn.style.alignItems="center",hideOverlayBtn.style.justifyContent="center",hideOverlayBtn.style.width="44px",hideOverlayBtn.style.height="44px",hideOverlayBtn.style.padding="0",hideOverlayBtn.style.background="transparent",hideOverlayBtn.style.color="var(--blessing-fg-muted)",hideOverlayBtn.style.border="1px solid var(--blessing-border-soft)",hideOverlayBtn.style.borderRadius="var(--blessing-radius-sm)",hideOverlayBtn.style.cursor="pointer",hideOverlayBtn.style.opacity="0.7",hideOverlayBtn.style.zIndex="2",hideOverlayBtn.addEventListener("mouseenter",()=>{hideOverlayBtn.style.opacity="1",hideOverlayBtn.style.color="var(--blessing-danger, var(--blessing-fg))",hideOverlayBtn.style.borderColor="var(--blessing-danger, var(--blessing-border))"}),hideOverlayBtn.addEventListener("mouseleave",()=>{hideOverlayBtn.style.opacity="0.7",hideOverlayBtn.style.color="var(--blessing-fg-muted)",hideOverlayBtn.style.borderColor="var(--blessing-border-soft)"}),hideOverlayBtn.addEventListener("click",ev=>{if(ev.stopPropagation(),!hideOverlayBtn.disabled){hideOverlayBtn.disabled=!0;try{typeof onHide=="function"&&onHide(provider)}catch{hideOverlayBtn.disabled=!1,hideOverlayBtn.innerHTML=HIDE_ICON_SVG}}}),right.style.marginRight="46px",wrapper.appendChild(hideOverlayBtn);let bodyHost=document.createElement("div");bodyHost.style.display="none",bodyHost.style.padding="0 14px 14px",wrapper.appendChild(bodyHost);let built=!1,bodyEl=null,ensureBuilt=()=>{built||(built=!0,bodyEl=renderProviderCardBody(provider,rootForBodyActions),bodyHost.appendChild(bodyEl),(async()=>{try{let allValues=await valuesPromise;hydrateCardBody(bodyEl,provider,allValues)}catch{}})())},setOpen=open=>{open?(ensureBuilt(),bodyHost.style.display="",chevron.style.transform="rotate(90deg)",head.setAttribute("aria-expanded","true")):(bodyHost.style.display="none",chevron.style.transform="rotate(0deg)",head.setAttribute("aria-expanded","false"))};return head.addEventListener("click",()=>{let isOpen=bodyHost.style.display!=="none";setOpen(!isOpen)}),{wrapper,configBadge,expand:()=>setOpen(!0)}}function providerHasStoredValue(provider,allValues){for(let f of provider.schema){let v=allValues.get(`${provider.id}/${f.key}`);if(v!=null&&!(typeof v=="string"&&v==="")&&!(typeof v=="boolean"&&v===!1))return!0}return!1}function hydrateCardBody(body,provider,allValues){for(let field of provider.schema){let stored=allValues.get(`${provider.id}/${field.key}`);if(stored==null)continue;let el=body.querySelector(`[data-blessing-field="${cssEscape(field.key)}"]`);el&&el.dataset.blessingEdited!=="true"&&(field.type==="checkbox"?el.checked=stored===!0||stored==="true":"value"in el&&(el.value=stringValue(stored)))}}function cssEscape(s){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(s):String(s).replace(/[^a-zA-Z0-9_-]/g,m=>"\\"+m)}function isLikelyCorsOrProxyError(err){let m=String(err?.message||err||"").toLowerCase();return m?m.includes("failed to fetch")||m.includes("networkerror")||m.includes("load failed")||m.includes("api method fetch not found")||m.includes("\uC2DC\uB3C4:")&&m.includes("nativefetch"):!1}function isLikelyProxyRejectError(err){return String(err?.message||err||"").toLowerCase().includes("proxy-reject")}function setStatusError(statusEl,headline,err){statusEl.innerHTML="";let top=document.createElement("div");if(top.textContent=headline,top.style.color="var(--blessing-danger, #d96666)",statusEl.appendChild(top),isLikelyProxyRejectError(err)){let hint=document.createElement("div");hint.style.marginTop="6px",hint.style.fontSize="12.5px",hint.style.lineHeight="1.55",hint.style.color="var(--blessing-fg-muted)",hint.innerHTML='\u21B3 RisuAI \uC11C\uBC84 \uD504\uB85D\uC2DC(<strong>sv.risuai.xyz/proxy2</strong>)\uAC00 \uC774 \uB3C4\uBA54\uC778\uC744 <strong>\uCC28\uB2E8</strong>\uD588\uC2B5\uB2C8\uB2E4 (HTTP 403/404/5xx). \uD574\uACB0\uCC45:<br>\u2460 RisuAI \uC124\uC815 \u2192 \uACE0\uAE09\uC124\uC815 \u2192 <strong>"Use plain fetch instead of server(\uC9C1\uC811 \uC694\uCCAD \uBCF4\uB0B4\uAE30)" \uB97C ON</strong> \uC73C\uB85C \uB450\uACE0 \uC7AC\uC2DC\uB3C4. (\uB2E8, \uC774 \uBAA8\uB4DC\uB294 \uBE0C\uB77C\uC6B0\uC800 CORS \uC81C\uC57D\uC744 \uBC1B\uC73C\uBA70 risuai.xyz \uC6F9\uC5D0\uC11C\uB294 \uB300\uBD80\uBD84 \uC81C\uACF5\uC790\uC5D0\uC11C \uB9C9\uD799\uB2C8\uB2E4.)<br>\u2461 \uADFC\uBCF8 \uD574\uACB0\uC740 \uB370\uC2A4\uD06C\uD0D1(Tauri) \uB610\uB294 noderisu-only(PocketRisu) \uBE4C\uB4DC \uC0AC\uC6A9 \u2014 \uC11C\uBC84\uB97C \uAC70\uCE58\uC9C0 \uC54A\uACE0 \uC9C1\uC811 \uD638\uCD9C\uD569\uB2C8\uB2E4.',statusEl.appendChild(hint);return}if(isLikelyCorsOrProxyError(err)){let hint=document.createElement("div");hint.style.marginTop="6px",hint.style.fontSize="12.5px",hint.style.lineHeight="1.55",hint.style.color="var(--blessing-fg-muted)",hint.innerHTML='\u21B3 <strong>"Failed to fetch"</strong> \uB958 \uC624\uB958\uB294 \uB300\uBD80\uBD84 \uBE0C\uB77C\uC6B0\uC800 CORS \uCC28\uB2E8\uC785\uB2C8\uB2E4. risuai.xyz \uC6F9\uC5D0\uC11C \uC0AC\uC6A9 \uC2DC RisuAI \uC124\uC815 \u2192 \uACE0\uAE09\uC124\uC815 \u2192 <strong>"Use plain fetch instead of server(\uC9C1\uC811 \uC694\uCCAD \uBCF4\uB0B4\uAE30)" \uB97C OFF</strong> \uB85C \uB450\uC5B4\uC57C \uD569\uB2C8\uB2E4 (RisuAI \uC758 /proxy2 \uC11C\uBC84 \uD504\uB85D\uC2DC\uAC00 CORS \uB97C \uC6B0\uD68C). \uB370\uC2A4\uD06C\uD0D1(Tauri) / noderisu-only(PocketRisu) \uBE4C\uB4DC \uC0AC\uC6A9\uC790\uB294 \uD574\uB2F9 \uC5C6\uC74C.',statusEl.appendChild(hint)}}function renderProviderCardBody(provider,rootForRerender){let body=document.createElement("div"),hint=document.createElement("p");hint.className="blessing-provider-hint",hint.textContent=providerHint(provider),body.appendChild(hint);let datalistId=`blessing-suggestions-${provider.id}`,datalistEl=document.createElement("datalist");datalistEl.id=datalistId,body.appendChild(datalistEl);function applySuggestions(models){if(datalistEl.innerHTML="",!!Array.isArray(models))for(let m of models){let opt=document.createElement("option");opt.value=String(m),datalistEl.appendChild(opt)}}let status=createStatusBlock(),fieldRows=new Map,primaryFields=[],advancedFields=[];for(let field of provider.schema)field.key!=="model"&&field.key!=="thinkingLevel"&&(isPrimaryProviderField(field)?primaryFields.push(field):advancedFields.push(field));for(let field of primaryFields){let fieldRow=renderField(provider.id,field);body.appendChild(fieldRow),fieldRows.set(field.key,fieldRow)}if(provider.id==="compat-copilot"){let loginStatus=createStatusBlock(),copilotLoginSection=renderCopilotLoginSection(provider,loginStatus,rootForRerender),apiKeyRow=fieldRows.get("apiKey");apiKeyRow?.nextSibling?body.insertBefore(copilotLoginSection,apiKeyRow.nextSibling):body.appendChild(copilotLoginSection)}advancedFields.length&&body.appendChild(renderAdvancedSettingsSection(provider.id,advancedFields));let unifiedPicker=renderUnifiedModelPicker(provider);body.appendChild(unifiedPicker.element);let thinkingField=provider.schema.find(field=>field.key==="thinkingLevel");if(thinkingField){body.appendChild(renderField(provider.id,thinkingField));let thinkingNote=document.createElement("p");thinkingNote.className="blessing-provider-hint",thinkingNote.textContent="Gemini 3\uC758 \uCC44\uD305\uACFC \uD638\uCD9C \uD14C\uC2A4\uD2B8\uC5D0 \uACF5\uD1B5 \uC801\uC6A9\uB429\uB2C8\uB2E4. Gemini 2.5\uB294 \uD1A0\uD070 \uC608\uC0B0\uC744 \uC0AC\uC6A9\uD569\uB2C8\uB2E4.",provider.id==="vertex"&&(thinkingNote.textContent+=" \uBE44\uC6A9 \uBAA8\uB4DC\uAC00 \uBAA8\uB378\uC744 \uBC14\uAFB8\uBA74 \uC2E4\uC81C \uC804\uC1A1\uB418\uB294 \uBAA8\uB378\uC744 \uAE30\uC900\uC73C\uB85C \uC801\uC6A9\uB429\uB2C8\uB2E4."),body.appendChild(thinkingNote)}let subSection=renderSubmodelSection(provider,{datalistId,onChange:()=>unifiedPicker.refresh()});subSection.style.border="0",subSection.style.padding="0";let actions=document.createElement("div");if(actions.style.display="flex",actions.style.gap="var(--blessing-gap-sm)",actions.style.flexWrap="wrap",actions.style.marginTop="var(--blessing-gap-sm)",actions.style.paddingTop="var(--blessing-gap-sm)",actions.style.borderTop="1px solid var(--blessing-border-soft)",supportsPing(provider.id)){let testBtn=document.createElement("button");testBtn.type="button",testBtn.textContent="\uC11C\uBC84 \uC5F0\uACB0 \uD655\uC778",styleAsSecondaryButton(testBtn),testBtn.title="\uC774 \uD504\uB85C\uBC14\uC774\uB354\uC758 \uBAA8\uB378 \uBAA9\uB85D \uC5D4\uB4DC\uD3EC\uC778\uD2B8(/models)\uC5D0 \uC778\uC99D \uC5C6\uC774 GET\uC744 \uBCF4\uB0B4 \uB124\uD2B8\uC6CC\uD06C \uB3C4\uB2EC\uC131\uB9CC \uD655\uC778\uD569\uB2C8\uB2E4. \uCD94\uB860\uC744 \uC2E4\uD589\uD558\uC9C0 \uC54A\uC73C\uBA70 \uD1A0\uD070\uC744 \uC18C\uBE44\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",testBtn.addEventListener("click",async()=>{await withButtonBusy(testBtn,async()=>{status.textContent="";let started=Date.now();try{let res=await pingEndpoint(provider.id,provider.schema),ms=Date.now()-started;status.textContent=`\uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uB3C4\uB2EC \uC131\uACF5 (${ms}ms) \u2014 HTTP ${res.status}`+(res.statusText?` ${res.statusText}`:"")+` | |
| \u2192 ${res.url}`}catch(err){let ms=Date.now()-started;setStatusError(status,`\uB3C4\uB2EC \uC2E4\uD328 (${ms}ms): ${err?.message||String(err)}`,err)}},"\uD14C\uC2A4\uD2B8 \uC911...")}),actions.appendChild(testBtn)}if(supportsModelTest(provider.id)){let modelTestBtn=document.createElement("button");modelTestBtn.type="button",modelTestBtn.textContent="\uBAA8\uB378 \uD638\uCD9C \uD14C\uC2A4\uD2B8 (\uD1A0\uD070 \uC0AC\uC6A9)",styleAsSecondaryButton(modelTestBtn),modelTestBtn.classList.add("blessing-primary-action"),modelTestBtn.title="\uC120\uD0DD\uD55C \uBAA8\uB378\uB85C \uC2E4\uC81C \uC694\uCCAD\uC744 \uBCF4\uB0C5\uB2C8\uB2E4. \uC785\uB825\xB7\uCD9C\uB825\xB7\uCD94\uB860 \uD1A0\uD070\uC5D0 \uB530\uB77C \uBE44\uC6A9\uC774 \uBC1C\uC0DD\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",modelTestBtn.addEventListener("click",async()=>{await withButtonBusy(modelTestBtn,async()=>{status.textContent="";let started=Date.now();try{let res=await testModelCall(provider.id,provider.schema);status.textContent=`\uBAA8\uB378 \uD638\uCD9C \uC131\uACF5 (${res.ms}ms) \u2014 model: ${res.model}`+(res.reply?` | |
| \uC751\uB2F5 \uBBF8\uB9AC\uBCF4\uAE30: ${res.reply}`:` | |
| \uD45C\uC2DC\uD560 \uC751\uB2F5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`)+` | |
| \uC774\uC81C \uD638\uC2A4\uD2B8\uC758 \uCC44\uD305 \uBAA8\uB378 \uC120\uD0DD\uC5D0\uC11C [Blessing] \uBAA8\uB378\uC744 \uACE8\uB77C \uC8FC\uC138\uC694. \uCC98\uC74C \uCD94\uAC00\uD588\uB2E4\uBA74 \uBA3C\uC800 \uC0C8\uB85C\uACE0\uCE68\uD558\uC138\uC694.`}catch(err){let ms=Date.now()-started;setStatusError(status,`\uBAA8\uB378 \uD638\uCD9C \uC2E4\uD328 (${ms}ms): ${err?.message||String(err)}`,err)}},"\uD638\uCD9C \uC911...")}),actions.prepend(modelTestBtn)}if(supportsDiscovery(provider.id)){let discoverBtn=document.createElement("button");discoverBtn.type="button",discoverBtn.textContent="\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uBAA8\uB378 \uAC00\uC838\uC624\uAE30",styleAsSecondaryButton(discoverBtn),discoverBtn.addEventListener("click",async()=>{await withButtonBusy(discoverBtn,async()=>{status.textContent="";try{let res=await discoverModels(provider.id,provider.schema,{force:!0});applySuggestions(res.models),status.textContent=`${res.models.length}\uAC1C \uBAA8\uB378 \uD655\uC778. '\uCD94\uAC00 \uBAA8\uB378' \uC785\uB825\uCE78 \uD074\uB9AD \uC2DC \uC790\uB3D9\uC644\uC131\uC73C\uB85C \uB178\uCD9C\uB429\uB2C8\uB2E4.`}catch(err){setStatusError(status,"\uAC00\uC838\uC624\uAE30 \uC2E4\uD328: "+(err?.message||String(err)),err)}},"\uC870\uD68C \uC911..."),status.scrollIntoView({block:"nearest"})}),subSection.appendChild(discoverBtn),provider.id==="vertex"&&hasReliableGetProxy().then(ok=>{ok||(discoverBtn.disabled=!0,discoverBtn.title="\uC774 \uBE4C\uB4DC\uC5D0\uC11C\uB294 Vertex \uBAA8\uB378 \uAC80\uC0C9\uC774 \uB3D9\uC791\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (aiplatform.googleapis.com\uC774 \uBE0C\uB77C\uC6B0\uC800 CORS\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uACE0, \uACF5\uC2DD \uC6F9 \uBE4C\uB4DC\uC758 nativeFetch\uB294 GET \uD504\uB85D\uC2DC\uB97C \uAC70\uCE58\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4). \uC544\uB798 '\uCD94\uAC00 \uBAA8\uB378' \uCEEC\uC5D0 \uBAA8\uB378 ID\uB97C \uC9C1\uC811 \uC785\uB825\uD574 \uC8FC\uC138\uC694. \uC608: gemini-2.5-pro / claude-sonnet-4@20250514.",status.textContent||(status.textContent="Vertex \uBAA8\uB378 \uAC80\uC0C9\uC740 \uB370\uC2A4\uD06C\uD0D1/Node \uD638\uC2A4\uD2B8\uC5D0\uC11C\uB9CC \uB3D9\uC791\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD658\uACBD\uC5D0\uC11C\uB294 '\uCD94\uAC00 \uBAA8\uB378' \uCE78\uC5D0 \uBAA8\uB378 ID\uB97C \uC9C1\uC811 \uC785\uB825\uD574 \uC8FC\uC138\uC694."))}).catch(()=>{}),readCachedModels(provider.id).then(cached=>{cached&&cached.length&&applySuggestions(cached)}).catch(()=>{})}let providerMaintenance=createSection("\uC774 \uD504\uB85C\uBC14\uC774\uB354\uC758 \uC124\uC815\uC744 \uCD08\uAE30\uD654\uD558\uB824\uBA74",{maintenance:!0}),resetNote=document.createElement("p");resetNote.textContent="\uAE30\uBCF8\uAC12\uC73C\uB85C \uB418\uB3CC\uB824\uB3C4 API \uD0A4\uB294 \uBE44\uC6CC\uC9D1\uB2C8\uB2E4. \uD544\uC694\uD55C \uC124\uC815\uC740 \uBA3C\uC800 \uBC31\uC5C5\uD574 \uC8FC\uC138\uC694.",providerMaintenance.appendChild(resetNote);let resetBtn=document.createElement("button");resetBtn.type="button",resetBtn.textContent="\uAE30\uBCF8\uAC12\uC73C\uB85C",styleAsSecondaryButton(resetBtn),resetBtn.title="\uC774 \uD504\uB85C\uBC14\uC774\uB354\uC758 \uBAA8\uB4E0 \uD544\uB4DC\uB97C \uCC98\uC74C\uC758 \uAE30\uBCF8\uAC12\uC73C\uB85C \uB418\uB3CC\uB9BD\uB2C8\uB2E4.",resetBtn.addEventListener("click",async()=>{await withButtonBusy(resetBtn,async()=>{for(let f of provider.schema)await setProviderSetting(provider.id,f.key,f.default??"");rootForRerender&&await renderProvidersTab(rootForRerender)},"\uC801\uC6A9 \uC911...")}),providerMaintenance.appendChild(resetBtn);let clearBtn=document.createElement("button");clearBtn.type="button",clearBtn.textContent="\uC124\uC815 \uC0AD\uC81C",styleAsDangerButton(clearBtn),clearBtn.title="\uC774 \uD504\uB85C\uBC14\uC774\uB354\uC758 \uBAA8\uB4E0 \uD544\uB4DC\uB97C \uBE44\uC6C1\uB2C8\uB2E4.",clearBtn.addEventListener("click",async()=>{await withButtonBusy(clearBtn,async()=>{for(let f of provider.schema)await setProviderSetting(provider.id,f.key,"");await saveSubmodels(provider.id,[]),rootForRerender&&await renderProvidersTab(rootForRerender)},"\uC0AD\uC81C \uC911...")}),providerMaintenance.appendChild(clearBtn),body.appendChild(actions),body.appendChild(status),body.appendChild(createExplanation("\uC11C\uBC84 \uC5F0\uACB0 \uD655\uC778\uACFC \uBAA8\uB378 \uD638\uCD9C \uD14C\uC2A4\uD2B8\uB294 \uC5B4\uB5BB\uAC8C \uB2E4\uB978\uAC00\uC694?",`\uC11C\uBC84 \uC5F0\uACB0 \uD655\uC778\uC740 \uC694\uCCAD\uC774 \uC11C\uBC84\uC5D0 \uB3C4\uCC29\uD558\uB294\uC9C0 \uD655\uC778\uD569\uB2C8\uB2E4. HTTP 401\xB7403\uB3C4 \uC11C\uBC84\uC5D0\uB294 \uB3C4\uCC29\uD55C \uAC83\uC774\uBA70, \uD0A4\uAC00 \uC720\uD6A8\uD558\uB2E4\uB294 \uB73B\uC740 \uC544\uB2D9\uB2C8\uB2E4. Vertex\uB294 \uC11C\uBE44\uC2A4 \uACC4\uC815 \uC778\uC99D\uB3C4 \uD655\uC778\uD569\uB2C8\uB2E4. | |
| \uBAA8\uB378 \uD638\uCD9C \uD14C\uC2A4\uD2B8\uB294 \uC704\uC5D0\uC11C \uC120\uD0DD\uD55C \uBAA8\uB378\uC5D0 \u2018hi\u2019\uB97C \uBCF4\uB0B4 \uC2E4\uC81C \uC751\uB2F5\uC744 \uD655\uC778\uD569\uB2C8\uB2E4. \uBE44\uC6A9\uACFC \uD560\uB2F9\uB7C9\uC744 \uC0AC\uC6A9\uD558\uBA70, \uAC19\uC740 \uC11C\uBE44\uC2A4\uC758 \uD14C\uC2A4\uD2B8\uB294 6\uCD08 \uAC04\uACA9\uC73C\uB85C \uC81C\uD55C\uB429\uB2C8\uB2E4. Gemini\uB294 \uC800\uC7A5\uD55C \uCD5C\uB300 \uCD9C\uB825 \uD1A0\uD070(\uC5C6\uC73C\uBA74 8192)\uC744 \uC0C1\uD55C\uC73C\uB85C \uC0AC\uC6A9\uD569\uB2C8\uB2E4. \uCD94\uB860 \uD1A0\uD070\uB3C4 \uBE44\uC6A9\uC5D0 \uD3EC\uD568\uB420 \uC218 \uC788\uC73C\uBBC0\uB85C \u2018\uBA87 \uD1A0\uD070\uB9CC \uC0AC\uC6A9\u2019\uD558\uB294 \uD14C\uC2A4\uD2B8\uAC00 \uC544\uB2D9\uB2C8\uB2E4.`));let next=document.createElement("p");next.className="blessing-next-step",next.textContent="\uCC44\uD305\uC5D0 \uC4F0\uB824\uBA74: \uCC98\uC74C \uCD94\uAC00\uD55C \uB4A4 \uD638\uC2A4\uD2B8\uB97C \uC0C8\uB85C\uACE0\uCE68\uD558\uACE0, \uCC44\uD305 \uBAA8\uB378 \uC120\uD0DD\uC5D0\uC11C [Blessing] "+provider.displayName+" \uBAA8\uB378\uC744 \uACE0\uB974\uC138\uC694.",body.appendChild(next);let additionalModels=createSection("\uBAA9\uB85D\uC5D0 \uC5C6\uB294 \uBAA8\uB378\uC744 \uCD94\uAC00\uD558\uB824\uBA74");additionalModels.appendChild(subSection),body.appendChild(additionalModels),provider.notice&&body.appendChild(createExplanation("\uC774 \uC11C\uBE44\uC2A4\uC5D0\uC11C \uC8FC\uC758\uD560 \uC810\uC740 \uBB34\uC5C7\uC778\uAC00\uC694?",provider.notice));let privacy=getPrivacyFor(provider.id);return privacy&&body.appendChild(createExplanation("\uB300\uD654\uB294 \uC5B4\uB514\uB85C \uC804\uC1A1\uB418\uACE0 \uBCF4\uAD00\uB418\uB098\uC694?",formatPrivacyDetail(privacy))),body.appendChild(providerMaintenance),body}function renderCopilotLoginSection(provider,status,rootForRerender){let section=document.createElement("div");section.style.display="flex",section.style.flexDirection="column",section.style.gap="8px",section.style.margin="4px 0 10px";let corsNotice=document.createElement("div");corsNotice.style.padding="10px 12px",corsNotice.style.borderRadius="var(--blessing-radius-sm, 6px)",corsNotice.style.border="1px solid var(--blessing-warn-border, #d4a93a)",corsNotice.style.background="var(--blessing-warn-bg, rgba(212, 169, 58, 0.12))",corsNotice.style.color="var(--blessing-fg, inherit)",corsNotice.style.fontSize="12.5px",corsNotice.style.lineHeight="1.55",corsNotice.innerHTML='<strong>\uB85C\uADF8\uC778 \uC804\uC5D0 \uAF2D \uD655\uC778:</strong> risuai.xyz \uC6F9\uC5D0\uC11C \uC4F0\uB294 \uACBD\uC6B0, <strong>RisuAI \uC124\uC815 \u2192 \uACE0\uAE09\uC124\uC815 \u2192 "Use plain fetch instead of server(\uC9C1\uC811 \uC694\uCCAD \uBCF4\uB0B4\uAE30)" \uB97C OFF</strong> \uB85C \uB450\uC5B4\uC57C \uD569\uB2C8\uB2E4. ON \uC774\uBA74 GitHub OAuth \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uAC00 \uBE0C\uB77C\uC6B0\uC800 CORS\uC5D0 \uB9C9\uD600 "Failed to fetch" \uC624\uB958\uB85C \uC2E4\uD328\uD569\uB2C8\uB2E4. \uB370\uC2A4\uD06C\uD0D1(Tauri) / noderisu-only \uBE4C\uB4DC\uB294 \uBB34\uAD00.',section.appendChild(corsNotice);let loginBtn=document.createElement("button");return loginBtn.type="button",loginBtn.textContent="GitHub\uB85C \uB85C\uADF8\uC778",styleAsSecondaryButton(loginBtn),loginBtn.title="GitHub Copilot Device Code \uD750\uB984\uC744 \uC2DC\uC791\uD569\uB2C8\uB2E4. \uBC84\uD2BC\uC744 \uB204\uB974\uBA74 user_code\uC640 \uC778\uC99D URL\uC774 \uD45C\uC2DC\uB418\uACE0, \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC2B9\uC778\uD558\uBA74 API Key \uCE78\uC5D0 gho_ \uD1A0\uD070\uC774 \uC790\uB3D9 \uC800\uC7A5\uB429\uB2C8\uB2E4.",loginBtn.addEventListener("click",async()=>{let ackKey="blessing.copilot.corsAck",acked=!1;try{acked=localStorage.getItem(ackKey)==="1"}catch{}if(!acked){if(!window.confirm(`[GitHub Copilot \uB85C\uADF8\uC778 \uC0AC\uC804 \uD655\uC778] | |
| risuai.xyz \uC6F9\uC5D0\uC11C \uC4F0\uB294 \uACBD\uC6B0 RisuAI \uC124\uC815\uC5D0\uC11C | |
| \uACE0\uAE09\uC124\uC815 \u2192 "Use plain fetch instead of server(\uC9C1\uC811 \uC694\uCCAD \uBCF4\uB0B4\uAE30)" | |
| \uB97C OFF \uB85C \uB450\uC5B4\uC57C GitHub OAuth\uAC00 \uB3D9\uC791\uD569\uB2C8\uB2E4 (CORS \uC6B0\uD68C). | |
| ON \uC774\uBA74 "Failed to fetch" \uB85C \uC2E4\uD328\uD569\uB2C8\uB2E4. | |
| \uC774\uBBF8 OFF \uB85C \uB418\uC5B4 \uC788\uB2E4\uBA74 [\uD655\uC778] \uC744 \uB20C\uB7EC \uC9C4\uD589\uD558\uC138\uC694. | |
| (\uC774 \uC548\uB0B4\uB294 \uB2E4\uC2DC \uD45C\uC2DC\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.)`))return;try{localStorage.setItem(ackKey,"1")}catch{}}await withButtonBusy(loginBtn,async()=>{status.textContent="";let started;try{started=await startCopilotDeviceFlow()}catch(err){setStatusError(status,"device code \uC694\uCCAD \uC2E4\uD328: "+(err?.message||String(err)),err);return}status.innerHTML="";let wrap=document.createElement("div");wrap.style.lineHeight="1.6";let line1=document.createElement("div");line1.textContent="1) \uC544\uB798 URL\uC744 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uACE0(\uAE38\uAC8C \uB204\uB974\uAC70\uB098 \uC6B0\uD074\uB9AD\uD558\uC5EC \uC0C8 \uD0ED\uC5D0\uC11C \uC5F4\uAE30) \u2192 GitHub \uACC4\uC815\uC73C\uB85C \uB85C\uADF8\uC778:";let linkRow=document.createElement("div"),link=document.createElement("a");link.href=started.verification_uri,link.target="_blank",link.rel="noopener noreferrer",link.textContent=started.verification_uri,link.style.color="var(--blessing-accent, #6aa9ff)",linkRow.appendChild(link);let line2=document.createElement("div");line2.textContent="2) \uC774 user_code\uB97C \uC785\uB825 \u2192 'Authorize' \uD074\uB9AD:";let codeBox=document.createElement("div");codeBox.textContent=started.user_code,codeBox.style.fontFamily="monospace",codeBox.style.fontSize="20px",codeBox.style.fontWeight="700",codeBox.style.letterSpacing="2px",codeBox.style.padding="6px 10px",codeBox.style.margin="6px 0",codeBox.style.display="inline-block",codeBox.style.background="var(--blessing-surface-soft, rgba(255,255,255,0.06))",codeBox.style.border="1px solid var(--blessing-border-soft)",codeBox.style.borderRadius="var(--blessing-radius-sm)",codeBox.style.cursor="pointer",codeBox.title="\uD074\uB9AD\uD574 \uBCF5\uC0AC",codeBox.addEventListener("click",()=>{try{navigator.clipboard?.writeText(started.user_code)}catch{}});let progress=document.createElement("div");progress.style.marginTop="4px",progress.style.color="var(--blessing-fg-muted)",progress.textContent="\uB300\uAE30 \uC911... (\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC2B9\uC778\uD558\uBA74 \uC790\uB3D9\uC73C\uB85C \uC800\uC7A5\uB429\uB2C8\uB2E4)",wrap.appendChild(line1),wrap.appendChild(linkRow),wrap.appendChild(line2),wrap.appendChild(codeBox),wrap.appendChild(progress),status.appendChild(wrap);try{window.open(started.verification_uri,"_blank","noopener,noreferrer")}catch{}try{let result=await pollCopilotDeviceFlow(started.device_code,started.interval,void 0,({elapsedSec,lastError})=>{progress.textContent=`\uB300\uAE30 \uC911... ${elapsedSec}s`+(lastError?` (${lastError})`:"")});await setProviderSetting(provider.id,"apiKey",result.access_token),await clearCopilotApiTokenCache(provider.id),progress.textContent="\uB85C\uADF8\uC778 \uC644\uB8CC. API Key \uCE78\uC5D0 gho_ \uD1A0\uD070\uC774 \uC800\uC7A5\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCE74\uB4DC\uB97C \uB2E4\uC2DC \uD3BC\uCE58\uBA74 \uC785\uB825\uAC12\uC5D0 \uBC18\uC601\uB429\uB2C8\uB2E4.",progress.style.color="var(--blessing-ok, #5fbf5f)",rootForRerender&&await renderProvidersTab(rootForRerender)}catch(err){progress.textContent="\uB85C\uADF8\uC778 \uC2E4\uD328: "+(err?.message||String(err)),progress.style.color="var(--blessing-danger, #d96666)"}},"\uC778\uC99D \uB300\uAE30 \uC911...")}),section.appendChild(loginBtn),section.appendChild(status),section}function isPrimaryProviderField(field){let key=String(field?.key||"").toLowerCase(),label=String(field?.label||"").toLowerCase();return key.includes("apikey")||key.includes("api_key")||key.includes("token")||key.includes("secret")||key.includes("password")||key.includes("baseurl")||key.includes("base_url")||key.includes("url")||key.includes("uri")||key.includes("endpoint")||key.includes("host")||key.includes("serviceaccount")||label.includes("api key")||label.includes("token")||label.includes("secret")||label.includes("password")||label.includes("base url")||label.includes("url")||label.includes("uri")||label.includes("endpoint")||label.includes("host")||label.includes("service account")}function renderAdvancedSettingsSection(providerId,fields){let details=document.createElement("details");details.style.margin="10px 0",details.style.border="1px solid var(--blessing-border-soft)",details.style.borderRadius="var(--blessing-radius-sm)",details.style.background="var(--blessing-bg-soft, transparent)";let summary=document.createElement("summary");summary.textContent="\uACE0\uAE09 \uC124\uC815",summary.style.cursor="pointer",summary.style.padding="10px 12px",summary.style.fontSize="12px",summary.style.fontWeight="600",summary.style.color="var(--blessing-fg)",summary.style.userSelect="none",details.appendChild(summary);let content=document.createElement("div");content.style.padding="0 12px 10px";for(let field of fields)content.appendChild(renderField(providerId,field));return details.appendChild(content),details}function createStatusBlock(){let status=document.createElement("div");return status.setAttribute("role","status"),status.setAttribute("aria-live","polite"),status.style.fontSize="12px",status.style.color="var(--blessing-fg-muted)",status.style.marginTop="var(--blessing-gap-sm)",status.style.whiteSpace="pre-wrap",status}function renderHiddenProviderRow(provider,onShow){let row=document.createElement("div");row.style.display="flex",row.style.alignItems="center",row.style.justifyContent="space-between",row.style.gap="10px",row.style.padding="8px 10px",row.style.fontSize="12px",row.style.color="var(--blessing-fg-muted)";let name=document.createElement("span");name.textContent=provider.displayName;let hint=document.createElement("small");hint.className="blessing-service-hint",hint.textContent=providerHint(provider),name.appendChild(hint),name.style.minWidth="0",name.style.overflow="hidden",name.style.textOverflow="ellipsis",name.style.whiteSpace="normal",row.appendChild(name);let showBtn=document.createElement("button");return showBtn.type="button",showBtn.textContent="+ \uC0AC\uC6A9",styleAsSecondaryButton(showBtn),showBtn.title="\uC774 \uD504\uB85C\uBC14\uC774\uB354\uB97C \uCE74\uB4DC \uC601\uC5ED\uC73C\uB85C \uB04C\uC5B4\uC62C\uB9BD\uB2C8\uB2E4. \uC800\uC7A5\uB41C \uAC12\uC740 \uADF8\uB300\uB85C\uC774\uBA70, picker \uBC18\uC601\uC740 \uB2E4\uC74C \uC0C8\uB85C\uACE0\uCE68 \uD6C4\uC785\uB2C8\uB2E4.",showBtn.addEventListener("click",()=>{if(!showBtn.disabled){showBtn.disabled=!0;try{typeof onShow=="function"&&onShow(provider)}catch{showBtn.disabled=!1}}}),row.appendChild(showBtn),row}function styleAsSecondaryButton(btn){btn.style.padding="4px 10px",btn.style.background="transparent",btn.style.color="var(--blessing-fg)",btn.style.border="1px solid var(--blessing-border)",btn.style.borderRadius="var(--blessing-radius-sm)",btn.style.cursor="pointer",btn.style.font="inherit",btn.style.fontSize="12px"}function styleAsDangerButton(btn){styleAsSecondaryButton(btn),btn.style.color="var(--blessing-danger)",btn.style.borderColor="var(--blessing-danger)"}function renderUnifiedModelPicker(provider){let section=document.createElement("div");section.style.marginTop="var(--blessing-gap-sm)",section.style.padding="10px 12px",section.style.border="1px solid var(--blessing-border-soft)",section.style.borderRadius="var(--blessing-radius-sm)",section.style.background="var(--blessing-surface-soft, transparent)";let header=document.createElement("div");header.style.display="flex",header.style.alignItems="center",header.style.gap="6px",header.style.marginBottom="8px",header.style.fontSize="12px",header.style.fontWeight="600",header.style.color="var(--blessing-fg)";let titleText=document.createElement("span");titleText.textContent="\uD14C\uC2A4\uD2B8\uD560 \uBAA8\uB378",header.appendChild(titleText);let selectedLabel=document.createElement("span");selectedLabel.style.marginLeft="auto",selectedLabel.style.fontSize="11px",selectedLabel.style.fontWeight="400",selectedLabel.style.color="var(--blessing-fg-muted)",selectedLabel.style.fontFamily="var(--blessing-font-mono, monospace)",selectedLabel.style.maxWidth="55%",selectedLabel.style.overflowWrap="anywhere",selectedLabel.style.whiteSpace="normal",selectedLabel.style.textAlign="right",header.appendChild(selectedLabel),section.appendChild(header);let grid=document.createElement("div");grid.style.display="flex",grid.style.flexWrap="wrap",grid.style.gap="6px";let catalogSection=createSection("\uB2E4\uB978 \uBAA8\uB378\uB85C \uD14C\uC2A4\uD2B8\uD558\uACE0 \uC2F6\uC5B4\uC694");catalogSection.className="blessing-explanation",catalogSection.appendChild(grid),section.appendChild(catalogSection),section.appendChild(createExplanation("\uC5EC\uAE30\uC11C \uBAA8\uB378\uC744 \uACE0\uB974\uBA74 \uCC44\uD305\uC5D0\uB3C4 \uC801\uC6A9\uB418\uB098\uC694?","\uC5EC\uAE30\uC11C\uB294 \uC5F0\uACB0\uC744 \uD14C\uC2A4\uD2B8\uD560 \uBAA8\uB378\uB9CC \uACE0\uB985\uB2C8\uB2E4. \uCC44\uD305 \uBAA8\uB378\uC740 \uD638\uC2A4\uD2B8\uC758 \uBAA8\uB378 \uC120\uD0DD \uD654\uBA74\uC5D0\uC11C [Blessing] \uD56D\uBAA9\uC744 \uACE8\uB77C \uC8FC\uC138\uC694."));let emptyEl=document.createElement("div");emptyEl.style.fontSize="11px",emptyEl.style.color="var(--blessing-fg-muted)",emptyEl.style.padding="2px 0",emptyEl.textContent="\uC120\uD0DD \uAC00\uB2A5\uD55C \uBAA8\uB378\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC544\uB798 '\uCD94\uAC00 \uBAA8\uB378' \uC139\uC158\uC5D0\uC11C \uB4F1\uB85D\uD558\uC138\uC694.",section.appendChild(emptyEl);function renderBadge(modelId,source,label){let badge=document.createElement("button");badge.type="button",badge.dataset.modelId=modelId,badge.title=`${source==="catalog"?"\uAE30\uBCF8 \uC81C\uACF5":"\uCD94\uAC00 \uBAA8\uB378"}: ${modelId}`+(label&&label!==modelId?` | |
| \uD45C\uC2DC\uBA85: ${label}`:""),badge.style.display="inline-flex",badge.style.alignItems="center",badge.style.gap="6px",badge.style.padding="4px 10px",badge.style.fontSize="11px",badge.style.fontFamily="var(--blessing-font-mono, monospace)",badge.style.color="var(--blessing-fg)",badge.style.background="var(--blessing-surface, transparent)",badge.style.border="1px solid var(--blessing-border)",badge.style.borderRadius="9999px",badge.style.cursor="pointer",badge.style.font="inherit",badge.style.fontSize="11px";let labelText=document.createElement("span");if(labelText.textContent=label||modelId,badge.appendChild(labelText),source!=="catalog"&&label&&label!==modelId){let sub=document.createElement("span");sub.textContent=modelId,sub.style.color="var(--blessing-fg-muted)",sub.style.fontSize="10px",sub.style.opacity="0.75",badge.appendChild(sub)}if(source==="catalog"){let tag=document.createElement("span");tag.textContent="\uAE30\uBCF8",tag.style.fontSize="9px",tag.style.padding="1px 5px",tag.style.borderRadius="9999px",tag.style.background="var(--blessing-border-soft)",tag.style.color="var(--blessing-fg-muted)",tag.style.fontFamily="var(--blessing-font, inherit)",badge.appendChild(tag)}return badge}function applyActiveStyle(badge,isActive){badge.setAttribute("aria-pressed",String(isActive)),isActive?(badge.style.background="var(--blessing-accent, #4ade80)",badge.style.color="var(--blessing-bg, #111)",badge.style.borderColor="var(--blessing-accent, #4ade80)",badge.style.fontWeight="600"):(badge.style.background="var(--blessing-surface, transparent)",badge.style.color="var(--blessing-fg)",badge.style.borderColor="var(--blessing-border)",badge.style.fontWeight="400")}async function refresh(){grid.innerHTML="";let catalog=getCatalogModels(provider.id),subs=await loadSubmodels(provider.id).catch(()=>[]),currentSelection=String(await getProviderSetting(provider.id,"model",provider.schema.find(f=>f.key==="model")?.default??"")||"").trim(),entries=[],seen=new Set;for(let m of catalog){let id=String(m).trim();!id||seen.has(id)||(seen.add(id),entries.push({id,source:"catalog",label:id}))}for(let s of subs){let id=String(s.model||"").trim();if(!id)continue;let label=String(s.label||"").trim()||id;if(seen.has(id)){let existing=entries.find(e=>e.id===id);existing&&label!==id&&(existing.label=label,existing.source="submodel");continue}seen.add(id),entries.push({id,source:"submodel",label})}if(catalogSection.hidden=entries.length===0,entries.length===0){emptyEl.textContent=currentSelection?"\uB2E4\uB978 \uBAA8\uB378\uC774 \uD544\uC694\uD558\uBA74 \uC544\uB798\uC5D0\uC11C \uBAA8\uB378\uC744 \uCD94\uAC00\uD574 \uC8FC\uC138\uC694.":"\uC544\uB798\uC5D0\uC11C \uBAA8\uB378\uC744 \uCD94\uAC00\uD558\uBA74 \uD14C\uC2A4\uD2B8\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",emptyEl.style.display="",selectedLabel.textContent=currentSelection?`\uD14C\uC2A4\uD2B8 \uC120\uD0DD: ${currentSelection} (\uBAA9\uB85D \uBC16)`:"\uC120\uD0DD\uD560 \uBAA8\uB378\uC774 \uC5C6\uC2B5\uB2C8\uB2E4";return}emptyEl.style.display="none";let activeFound=!1;for(let e of entries){let badge=renderBadge(e.id,e.source,e.label),isActive=e.id===currentSelection;isActive&&(activeFound=!0),applyActiveStyle(badge,isActive),badge.addEventListener("click",async()=>{await setProviderSetting(provider.id,"model",e.id),grid.querySelectorAll("button[data-model-id]").forEach(b=>{applyActiveStyle(b,b.dataset.modelId===e.id)}),selectedLabel.textContent=`\uD14C\uC2A4\uD2B8 \uC120\uD0DD: ${e.id}`}),grid.appendChild(badge)}selectedLabel.textContent=activeFound?`\uD14C\uC2A4\uD2B8 \uC120\uD0DD: ${currentSelection}`:currentSelection?`\uD14C\uC2A4\uD2B8 \uC120\uD0DD: ${currentSelection} (\uBAA9\uB85D \uBC16)`:`\uC790\uB3D9 \uC120\uD0DD: ${subs[0]?.model||catalog[0]||"\uC5C6\uC74C"}`}return refresh().catch(()=>{}),{element:section,refresh}}function renderSubmodelSection(provider,options={}){let datalistId=options.datalistId||"",onChange=typeof options.onChange=="function"?options.onChange:()=>{},section=document.createElement("div");section.style.marginTop="var(--blessing-gap-sm)",section.style.padding="10px 12px",section.style.border="1px solid var(--blessing-border-soft)",section.style.borderRadius="var(--blessing-radius-sm)",section.style.background="var(--blessing-surface-soft, transparent)";let header=document.createElement("div");header.style.display="flex",header.style.alignItems="center",header.style.justifyContent="space-between",header.style.gap="8px",header.style.marginBottom="6px";let titleWrap=document.createElement("div");titleWrap.style.display="flex",titleWrap.style.alignItems="center",titleWrap.style.gap="6px",titleWrap.style.fontSize="12px",titleWrap.style.fontWeight="600",titleWrap.style.color="var(--blessing-fg)";let titleText=document.createElement("span");titleText.textContent="\uCD94\uAC00 \uBAA8\uB378",titleWrap.appendChild(titleText),header.appendChild(titleWrap);let statusEl=document.createElement("span");statusEl.style.fontSize="11px",statusEl.style.color="var(--blessing-fg-muted)",statusEl.style.marginLeft="auto",statusEl.style.flex="0 1 auto",statusEl.style.minWidth="0",statusEl.style.overflow="hidden",statusEl.style.textOverflow="ellipsis",statusEl.style.whiteSpace="nowrap",statusEl.textContent="",header.appendChild(statusEl);let addBtn=document.createElement("button");addBtn.type="button",addBtn.textContent="\uFF0B \uBAA8\uB378 \uCD94\uAC00",styleAsSecondaryButton(addBtn),header.appendChild(addBtn),section.appendChild(header);let listEl=document.createElement("div");listEl.style.display="flex",listEl.style.flexDirection="column",listEl.style.gap="6px",section.appendChild(createExplanation("\uBAA8\uB378 ID\uC640 \uD45C\uC2DC\uBA85\uC740 \uBB34\uC5C7\uC778\uAC00\uC694?","\uBAA8\uB378 ID\uB294 \uC11C\uBE44\uC2A4\uC5D0\uC11C \uC548\uB0B4\uD55C \uC774\uB984\uC744 \uADF8\uB300\uB85C \uC785\uB825\uD569\uB2C8\uB2E4. \uD45C\uC2DC\uBA85\uC740 \uCC44\uD305 \uBAA8\uB378 \uBAA9\uB85D\uC5D0 \uBCF4\uC5EC \uC904 \uC774\uB984\uC774\uBA70 \uC0DD\uB7B5\uD574\uB3C4 \uB429\uB2C8\uB2E4. \u2018\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uBAA8\uB378 \uAC00\uC838\uC624\uAE30\u2019\uB97C \uB204\uB974\uBA74 \uBAA8\uB378 ID \uC785\uB825\uCE78\uC5D0\uC11C \uC790\uB3D9\uC644\uC131\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.")),section.appendChild(listEl);let emptyEl=document.createElement("div");emptyEl.style.fontSize="11px",emptyEl.style.color="var(--blessing-fg-muted)",emptyEl.style.padding="4px 0",emptyEl.textContent="\uCD94\uAC00\uB85C \uB4F1\uB85D\uB41C \uBAA8\uB378\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uFF0B \uBAA8\uB378 \uCD94\uAC00 \uBC84\uD2BC\uC73C\uB85C \uC0C8 \uBAA8\uB378\uC744 \uB4F1\uB85D\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",section.appendChild(emptyEl);let reloadHint=document.createElement("div");reloadHint.style.fontSize="11px",reloadHint.style.color="var(--blessing-fg-muted)",reloadHint.style.marginTop="6px",reloadHint.textContent="\uC800\uC7A5\uC774 \uB05D\uB098\uBA74 \uD638\uC2A4\uD2B8\uB97C \uC0C8\uB85C\uACE0\uCE68\uD558\uACE0 \uCC44\uD305 \uBAA8\uB378 \uC120\uD0DD\uC5D0\uC11C \uCD94\uAC00\uD55C [Blessing] \uBAA8\uB378\uC744 \uACE8\uB77C \uC8FC\uC138\uC694.",section.appendChild(reloadHint);let state=[],savedTimer=0,debounceTimer=0,inFlight=0;function setStatus(kind,text){statusEl.textContent=text,kind==="saved"?statusEl.style.color="var(--blessing-accent, #4ade80)":kind==="error"?statusEl.style.color="#ef4444":statusEl.style.color="var(--blessing-fg-muted)"}function render(){listEl.innerHTML="",state.length===0?emptyEl.style.display="":emptyEl.style.display="none",state.forEach((entry,idx)=>{listEl.appendChild(renderRow(entry,idx))})}function renderRow(entry,idx){let row=document.createElement("div");row.className="blessing-model-row";let labelInput=document.createElement("input");labelInput.type="text",labelInput.placeholder="\uD45C\uC2DC\uBA85 (\uC120\uD0DD)",labelInput.value=entry.label||"",labelInput.style.flex="1 1 30%",labelInput.style.minWidth="0",labelInput.className="blessing-input",styleSubmodelInput(labelInput),labelInput.addEventListener("input",()=>{state[idx].label=labelInput.value,schedulePersist()});let modelInput=document.createElement("input");modelInput.type="text",modelInput.placeholder=["google-ai","custom-google","vertex"].includes(provider.id)?"\uC608: gemini-2.5-flash":"\uC11C\uBE44\uC2A4\uC5D0\uC11C \uC548\uB0B4\uD55C \uBAA8\uB378 ID",modelInput.value=entry.model||"",modelInput.style.flex="1 1 45%",modelInput.style.minWidth="0",modelInput.className="blessing-input",styleSubmodelInput(modelInput),datalistId&&modelInput.setAttribute("list",datalistId),modelInput.addEventListener("input",()=>{state[idx].model=modelInput.value,schedulePersist()});let delBtn=document.createElement("button");delBtn.type="button",delBtn.textContent="\uC0AD\uC81C",styleAsDangerButton(delBtn),delBtn.style.flex="0 0 auto",delBtn.addEventListener("click",()=>{state.splice(idx,1),render(),persistNow()});for(let[input,caption]of[[labelInput,"\uD45C\uC2DC\uBA85 (\uC120\uD0DD)"],[modelInput,"\uBAA8\uB378 ID"]]){let label=document.createElement("label");label.textContent=caption,label.appendChild(input),row.appendChild(label)}return row.appendChild(delBtn),row}function schedulePersist(){debounceTimer&&clearTimeout(debounceTimer),setStatus("saving","\uC785\uB825 \uC911..."),debounceTimer=setTimeout(()=>{debounceTimer=0,persistNow()},400)}function persistNow(){debounceTimer&&(clearTimeout(debounceTimer),debounceTimer=0),savedTimer&&(clearTimeout(savedTimer),savedTimer=0);let snapshot=state.map(it=>({label:it.label||"",model:it.model||""})),validCount=snapshot.filter(it=>(it.model||"").trim()!=="").length;setStatus("saving","\uC800\uC7A5 \uC911...");let myToken=++inFlight;saveSubmodels(provider.id,snapshot).then(clean=>{if(myToken!==inFlight)return;let stored=Array.isArray(clean)?clean.length:validCount;setStatus("saved",`\uC800\uC7A5\uB428 (${stored}\uAC1C)`),savedTimer=setTimeout(()=>{myToken===inFlight&&setStatus("idle","")},1500);try{onChange()}catch{}}).catch(err=>{if(myToken!==inFlight)return;let msg=err&&err.message?err.message:String(err||"unknown");setStatus("error",`\uC800\uC7A5 \uC2E4\uD328: ${msg}`);try{console.warn("[Blessing] saveSubmodels \uC2E4\uD328",provider.id,err)}catch{}})}return addBtn.addEventListener("click",()=>{state.push({label:"",model:""}),render(),setStatus("idle","")}),loadSubmodels(provider.id).then(list=>{state=list.map(it=>({label:it.label||"",model:it.model||""})),render()}).catch(()=>{render()}),render(),section}function styleSubmodelInput(input){input.style.background="var(--blessing-surface, transparent)",input.style.color="var(--blessing-fg)",input.style.border="1px solid var(--blessing-border)",input.style.borderRadius="var(--blessing-radius-sm)",input.style.padding="4px 8px",input.style.fontSize="12px",input.style.font="inherit",input.style.fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace"}function renderField(providerId,field){let initial=field.default??"";if(field.type==="checkbox"){let row2=document.createElement("div"),label2=document.createElement("label");label2.className="blessing-field-switch";let caption=document.createElement("span");caption.textContent=field.label;let cb=document.createElement("input");return cb.type="checkbox",cb.checked=!!initial,cb.dataset.blessingField=field.key,label2.append(caption,cb),row2.appendChild(label2),field.help&&row2.appendChild(createExplanation(fieldQuestion(field),field.help)),cb.addEventListener("change",async()=>{cb.dataset.blessingEdited="true",await setProviderSetting(providerId,field.key,cb.checked)}),row2}let row=document.createElement("div");row.style.display="flex",row.style.flexDirection="column",row.style.gap="4px",row.style.margin="8px 0";let labelWrap=document.createElement("div");labelWrap.style.display="flex",labelWrap.style.alignItems="center",labelWrap.style.gap="6px",labelWrap.style.fontSize="12px",labelWrap.style.color="var(--blessing-fg-muted)";let label=document.createElement("label");label.htmlFor=`blessing-${providerId}-${field.key}`,label.textContent=field.label,labelWrap.appendChild(label),row.appendChild(labelWrap);let input;switch(field.type){case"textarea":{let ta=document.createElement("textarea");ta.rows=4,ta.value=stringValue(initial),applyInputStyle(ta),input=ta;break}case"number":{let num=document.createElement("input");num.type="number",num.value=stringValue(initial),applyInputStyle(num),input=num;break}case"password":{let pw=document.createElement("input");pw.type="password",pw.autocomplete="off",pw.value=stringValue(initial),applyInputStyle(pw),input=pw;break}case"select":{let sel=document.createElement("select"),opts=Array.isArray(field.options)?field.options:[],cur=stringValue(initial);for(let o of opts){let opt=document.createElement("option");opt.value=String(o?.value??""),opt.textContent=String(o?.label??o?.value??""),opt.value===cur&&(opt.selected=!0),sel.appendChild(opt)}applyInputStyle(sel),input=sel;break}default:{let tx=document.createElement("input");tx.type="text",tx.value=stringValue(initial),applyInputStyle(tx),input=tx}}let save=async()=>{let value;if(field.type==="number"){let v=input.value;value=v===""?"":v}else value=input.value;await setProviderSetting(providerId,field.key,value)},markEdited=()=>{input.dataset.blessingEdited="true"};return input.addEventListener("input",markEdited),input.addEventListener("change",()=>(markEdited(),save())),input.addEventListener("blur",()=>{if(input.dataset.blessingEdited==="true")return save()}),input.setAttribute("data-blessing-field",String(field.key)),input.id=`blessing-${providerId}-${field.key}`,row.appendChild(input),field.help&&row.appendChild(createExplanation(fieldQuestion(field),field.help)),row}function applyInputStyle(el){el.style.padding="6px 8px",el.style.background="var(--blessing-bg)",el.style.color="var(--blessing-fg)",el.style.border="1px solid var(--blessing-border)",el.style.borderRadius="var(--blessing-radius-sm)",el.style.font="inherit",el.style.outline="none"}function stringValue(v){return v==null?"":typeof v=="string"?v:String(v)}async function renderStreamingToggle(root){let card=document.createElement("div");card.className="blessing-card",card.style.marginBottom="var(--blessing-gap)";let row=document.createElement("label");row.style.display="flex",row.style.alignItems="center",row.style.gap="var(--blessing-gap-sm)",row.style.cursor="pointer",row.style.font="inherit";let checkbox=document.createElement("input");checkbox.type="checkbox",checkbox.checked=await loadStreamingPreference(),row.appendChild(checkbox);let labelText=document.createElement("span");labelText.style.fontSize="13px",labelText.style.fontWeight="600",labelText.style.color="var(--blessing-fg)",labelText.textContent="API \uC751\uB2F5\uC744 \uC2A4\uD2B8\uB9AC\uBC0D\uC73C\uB85C \uBC1B\uAE30",row.appendChild(labelText),card.appendChild(createExplanation("\uC2A4\uD2B8\uB9AC\uBC0D\uC73C\uB85C \uBC1B\uAE30\uB294 \uBB34\uC5C7\uC778\uAC00\uC694?",`\uBAA8\uB378 API\uC758 \uC751\uB2F5\uC744 \uB098\uB204\uC5B4 \uBC1B\uC744\uC9C0, \uC644\uB8CC\uB41C \uC751\uB2F5\uC73C\uB85C \uBC1B\uC744\uC9C0 \uC120\uD0DD\uD569\uB2C8\uB2E4. | |
| \uCF1C\uAE30: API\uAC00 \uBCF4\uB0B4\uB294 \uC751\uB2F5 \uC870\uAC01\uC744 \uCC28\uB840\uB85C \uBC1B\uC2B5\uB2C8\uB2E4. \uD604\uC7AC \uBE14\uB808\uC2F1\uC740 \uBAA8\uB450 \uBAA8\uC740 \uB4A4 \uCC44\uD305\uC5D0 \uD55C \uBC88\uC5D0 \uD45C\uC2DC\uD569\uB2C8\uB2E4. | |
| \uB044\uAE30: \uC644\uB8CC\uB41C \uC751\uB2F5\uC744 \uBC1B\uC2B5\uB2C8\uB2E4. \uD638\uC2A4\uD2B8\uC758 \uC694\uCCAD \uB85C\uADF8\uC5D0\uC11C \uC804\uCCB4 \uC751\uB2F5\uC744 \uD655\uC778\uD560 \uB54C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | |
| \uD734\uB300\uD3F0\uC774 \uBC31\uADF8\uB77C\uC6B4\uB4DC\uB85C \uAC00\uAC70\uB098 \uC5F0\uACB0\uC774 \uB04A\uAE30\uBA74 \uC5B4\uB290 \uBC29\uC2DD\uB3C4 \uC644\uB8CC\uB97C \uBCF4\uC7A5\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. | |
| OpenAI \uD638\uD658 \uD504\uB9AC\uC14B\uC740 \uC81C\uACF5\uC790\uBCC4 Streaming \uC124\uC815\uC744 \uB530\uB985\uB2C8\uB2E4. \uBCC0\uACBD\uC740 \uB2E4\uC74C \uC694\uCCAD\uBD80\uD130 \uC801\uC6A9\uB429\uB2C8\uB2E4.`)),card.prepend(row);let note=document.createElement("div");note.style.fontSize="11px",note.style.color="var(--blessing-fg-muted)",note.style.marginTop="var(--blessing-gap-xs)",note.style.marginLeft="22px",note.textContent="\uD604\uC7AC \uCC44\uD305\uC5D0\uB294 \uC751\uB2F5\uC774 \uB05D\uB09C \uB4A4 \uD55C \uBC88\uC5D0 \uD45C\uC2DC\uB429\uB2C8\uB2E4.",card.appendChild(note),checkbox.addEventListener("change",async()=>{await saveStreamingPreference(!!checkbox.checked)}),root.appendChild(card)}function providerHint(provider){return{"google-ai":"Google AI Studio\uC5D0\uC11C API \uD0A4\uB97C \uBC1B\uC740 \uACBD\uC6B0","custom-google":"Gemini\uC6A9 \uBCC4\uB3C4 \uC11C\uBC84 \uC8FC\uC18C\uC640 \uD0A4\uB97C \uD568\uAED8 \uBC1B\uC740 \uACBD\uC6B0",vertex:"Google Cloud \uC11C\uBE44\uC2A4 \uACC4\uC815\uC73C\uB85C \uC5F0\uACB0\uD558\uB294 \uACBD\uC6B0","custom-openai":"OpenAI \uD638\uD658 \uC11C\uBC84\uC758 \uC8FC\uC18C\uC640 \uD0A4\uB97C \uD568\uAED8 \uBC1B\uC740 \uACBD\uC6B0","custom-claude":"Claude\uC6A9 \uBCC4\uB3C4 \uC11C\uBC84 \uC8FC\uC18C\uC640 \uD0A4\uB97C \uD568\uAED8 \uBC1B\uC740 \uACBD\uC6B0",anthropic:"Anthropic\uC5D0\uC11C Claude API \uD0A4\uB97C \uBC1B\uC740 \uACBD\uC6B0","compat-ollama":"\uB0B4 \uAE30\uAE30\uC5D0\uC11C \uC2E4\uD589 \uC911\uC778 Ollama\uC5D0 \uC5F0\uACB0","compat-copilot":"GitHub \uACC4\uC815\uC73C\uB85C \uB85C\uADF8\uC778\uD574 \uC5F0\uACB0"}[provider.id]||`${provider.displayName}\uC5D0\uC11C \uBC1B\uC740 \uC5F0\uACB0 \uC815\uBCF4 \uC0AC\uC6A9`}function fieldQuestion(field){let questions={apiKey:"API \uD0A4\uB294 \uC5B4\uB5BB\uAC8C \uC785\uB825\uD558\uB098\uC694?",baseUrl:"\uC8FC\uC18C\uB97C \uC5B4\uB5BB\uAC8C \uC785\uB825\uD574\uC57C \uD560\uC9C0 \uBAA8\uB974\uACA0\uC5B4\uC694",maxTokens:"\uC751\uB2F5 \uAE38\uC774\uB294 \uC5B4\uB5BB\uAC8C \uC815\uD558\uB098\uC694?",thinkingLevel:"\uCD94\uB860 \uC218\uC900\uC740 \uC5B4\uB5A4 \uBAA8\uB378\uC5D0 \uC801\uC6A9\uB418\uB098\uC694?",thinkingBudget:"\uCD94\uB860 \uD1A0\uD070 \uC608\uC0B0\uC740 \uC5B4\uB5A4 \uBAA8\uB378\uC5D0 \uC801\uC6A9\uB418\uB098\uC694?",customGenConfig:"\uCD94\uAC00 \uC0DD\uC131 \uC124\uC815\uC740 \uBB34\uC5C7\uC778\uAC00\uC694?",serviceAccountJson:"\uC11C\uBE44\uC2A4 \uACC4\uC815 \uC815\uBCF4\uB294 \uC5B4\uB5BB\uAC8C \uC900\uBE44\uD558\uB098\uC694?"};return questions[field.key]?questions[field.key]:/url|endpoint|host/i.test(field.key)?"\uC8FC\uC18C\uB97C \uC5B4\uB5BB\uAC8C \uC785\uB825\uD574\uC57C \uD560\uC9C0 \uBAA8\uB974\uACA0\uC5B4\uC694":`${field.label} \uC124\uC815\uC740 \uC5B4\uB5BB\uAC8C \uC4F0\uB098\uC694?`}async function renderInsightsTab(root){root.innerHTML="";let h2=document.createElement("h2");h2.textContent="\uC778\uC0AC\uC774\uD2B8",root.appendChild(h2),root.appendChild(createExplanation("\uC5B4\uB5A4 \uD638\uCD9C\uC774 \uAE30\uB85D\uB418\uB098\uC694?",`Blessing\uC774 \uCC98\uB9AC\uD55C LLM \uD638\uCD9C\uC758 \uACB0\uACFC\uB97C \uC9D1\uACC4\uD569\uB2C8\uB2E4. | |
| \xB7 \uCD5C\uADFC 500\uAC74\uAE4C\uC9C0\uB9CC \uBCF4\uAD00\uB429\uB2C8\uB2E4 (\uC624\uB798\uB41C \uAC83\uBD80\uD130 \uC790\uB3D9 \uD3D0\uAE30). | |
| \xB7 \uBAA8\uB4E0 \uB370\uC774\uD130\uB294 \uC0AC\uC6A9\uC790\uC758 pluginStorage\uC5D0\uB9CC \uC800\uC7A5\uB418\uBA70 \uC678\uBD80\uB85C \uC804\uC1A1\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. | |
| \xB7 \uD638\uC2A4\uD2B8\uC758 \uB2E4\uB978 \uD504\uB85C\uBC14\uC774\uB354(LBI \uB4F1) \uD638\uCD9C\uC740 \uC9D1\uACC4\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));let refreshBtn=document.createElement("button");refreshBtn.type="button",refreshBtn.textContent="\uC0C8\uB85C \uACE0\uCE68",styleAsSecondaryButton2(refreshBtn);let clearBtn=document.createElement("button");clearBtn.type="button",clearBtn.textContent="\uAE30\uB85D \uBE44\uC6B0\uAE30",styleAsSecondaryButton2(clearBtn);let actionRow=document.createElement("div");actionRow.style.display="flex",actionRow.style.gap="var(--blessing-gap-sm)",actionRow.style.marginBottom="var(--blessing-gap)",actionRow.appendChild(refreshBtn),root.appendChild(actionRow);let summaryCard=document.createElement("div");summaryCard.className="blessing-card",root.appendChild(summaryCard);let providersTitle=document.createElement("div");providersTitle.textContent="\uD504\uB85C\uBC14\uC774\uB354\uBCC4",providersTitle.style.fontWeight="600",providersTitle.style.margin="var(--blessing-gap) 0 var(--blessing-gap-sm) 0",root.appendChild(providersTitle);let providersGrid=document.createElement("div");providersGrid.style.display="grid",providersGrid.style.gridTemplateColumns="repeat(auto-fill, minmax(220px, 1fr))",providersGrid.style.gap="var(--blessing-gap-sm)",providersGrid.style.marginBottom="var(--blessing-gap)",root.appendChild(providersGrid);let usageTitle=document.createElement("div");usageTitle.textContent="\uD1A0\uD070\xB7\uCE90\uC2DC \uB204\uC801",usageTitle.style.fontWeight="600",usageTitle.style.margin="var(--blessing-gap) 0 var(--blessing-gap-sm) 0",root.appendChild(usageTitle),root.appendChild(createExplanation("\uD1A0\uD070\uACFC \uCE90\uC2DC\uB294 \uBB34\uC5C7\uC778\uAC00\uC694?",`\uC11C\uBE44\uC2A4\uAC00 \uBCF4\uB0B4 \uC900 \uC0AC\uC6A9\uB7C9\uC744 \uBAA8\uC544 \uBCF4\uC5EC \uC90D\uB2C8\uB2E4. | |
| \xB7 Input / Output / Cache write / Cache read \uD1A0\uD070 \uBCC4\uB3C4 \uD569\uACC4. | |
| \xB7 \uCE90\uC2DC\uB294 \uC774\uBBF8 \uCC98\uB9AC\uD55C \uC785\uB825\uC744 \uC7AC\uC0AC\uC6A9\uD55C \uAE30\uB85D\uC785\uB2C8\uB2E4. \uD560\uC778\uC728\uC740 \uBAA8\uB378\uB9C8\uB2E4 \uB2E4\uB985\uB2C8\uB2E4. | |
| \xB7 \uBAA8\uB4E0 \uB370\uC774\uD130\uB294 pluginStorage \uC5D0\uB9CC \uC800\uC7A5\uB418\uBA70 \uC678\uBD80 \uC804\uC1A1\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. | |
| \xB7 \uC9C0\uC6D0 provider: Anthropic / Custom Claude / Cohere / Google AI / Custom Google / Vertex (Gemini + Claude path) / Custom OpenAI / 13 OpenAI-compat presets. | |
| \xB7 OpenAI Chat Completions / Responses API \uC591\uCABD\uC5D0\uC11C cache hit (cached_tokens) \uC790\uB3D9 \uCEA1\uCCD0. | |
| \xB7 DeepSeek \uC758 prompt_cache_hit_tokens, Anthropic \uC758 cache_creation/read, Gemini \uC758 cachedContentTokenCount \uB4F1 provider-specific cache \uD544\uB4DC \uBAA8\uB450 \uC815\uADDC\uD654.`));let usageGrid=document.createElement("div");usageGrid.style.display="grid",usageGrid.style.gridTemplateColumns="repeat(auto-fill, minmax(240px, 1fr))",usageGrid.style.gap="var(--blessing-gap-sm)",usageGrid.style.marginBottom="var(--blessing-gap)",root.appendChild(usageGrid);let budgetTitle=document.createElement("div");budgetTitle.textContent="\uC6D4\uAC04 \uC608\uC0B0 (USD)",budgetTitle.style.fontWeight="600",budgetTitle.style.margin="var(--blessing-gap) 0 var(--blessing-gap-sm) 0",root.appendChild(budgetTitle),root.appendChild(createExplanation("\uC608\uC0B0 \uC54C\uB9BC\uC740 \uC5B4\uB5BB\uAC8C \uC791\uB3D9\uD558\uB098\uC694?",`\uD504\uB85C\uBC14\uC774\uB354\uBCC4 \uC6D4\uAC04 \uC54C\uB9BC \uAE30\uC900 \uC744 USD \uB85C \uC124\uC815\uD569\uB2C8\uB2E4. | |
| \xB7 0 \uB610\uB294 \uBE48 \uAC12 = \uC608\uC0B0 \uBE44\uD65C\uC131 (\uC54C\uB9BC \uC548 \uB744\uC6C0). | |
| \xB7 80% \uB3C4\uB2EC \uC2DC 1 \uD68C \uC548\uB0B4 \uBAA8\uB2EC. | |
| \xB7 100% \uB3C4\uB2EC \uC2DC 1 \uD68C \uACBD\uACE0 \uBAA8\uB2EC \u2014 \uD638\uCD9C\uC740 \uACC4\uC18D \uC9C4\uD589\uB429\uB2C8\uB2E4 (\uC790\uB3D9 \uCC28\uB2E8 \uC5C6\uC74C). | |
| \xB7 \uC784\uACC4 \uB3C4\uB2EC \uC54C\uB9BC\uC740 \uC774\uBC88 \uB2EC 1 \uD68C\uC529\uB9CC. \uB2E4\uC74C \uB2EC\uC774 \uB418\uBA74 \uC790\uB3D9 \uC7AC\uD65C\uC131. | |
| \xB7 \uC608\uC0B0\uC744 \uBCC0\uACBD\uD558\uAC70\uB098 [\uC54C\uB9BC \uC7AC\uD65C\uC131] \uB204\uB974\uBA74 \uC989\uC2DC \uB2E4\uC2DC \uC54C\uB9BC \uBC1B\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | |
| \xB7 \uB4F1\uB85D\uB41C \uB2E8\uAC00\uB85C \uACC4\uC0B0\uD55C \uCD94\uC815\uCE58\uC785\uB2C8\uB2E4. \uB2E8\uAC00\uAC00 \uC5C6\uB294 \uBAA8\uB378\uC740 \uBE44\uC6A9 \uD569\uACC4\uC5D0\uC11C \uBE60\uC9C0\uBA70, \uC2E4\uC81C \uCCAD\uAD6C\uC561\uC740 \uC11C\uBE44\uC2A4\uC5D0\uC11C \uD655\uC778\uD574 \uC8FC\uC138\uC694.`));let budgetNote=document.createElement("p");budgetNote.textContent="\uC608\uC0B0\uC740 \uC54C\uB9BC \uAE30\uC900\uC785\uB2C8\uB2E4. \uD55C\uB3C4\uB97C \uB118\uC5B4\uB3C4 \uD638\uCD9C\uC740 \uCC28\uB2E8\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uBE44\uC6A9\uC740 \uCD94\uC815\uCE58\uC774\uBA70 \uC2E4\uC81C \uCCAD\uAD6C\uC561\uACFC \uB2E4\uB97C \uC218 \uC788\uC2B5\uB2C8\uB2E4.",root.appendChild(budgetNote);let extraBudgets=new Set,budgetSelect=document.createElement("select");budgetSelect.className="blessing-input",budgetSelect.setAttribute("aria-label","\uC608\uC0B0\uC744 \uC124\uC815\uD560 \uD504\uB85C\uBC14\uC774\uB354");let placeholder=document.createElement("option");placeholder.value="",placeholder.textContent="\uC0AC\uC6A9 \uC804\uC5D0\uB3C4 \uC608\uC0B0\uC744 \uC124\uC815\uD560 \uC218 \uC788\uC5B4\uC694",budgetSelect.appendChild(placeholder);for(let provider of PROVIDERS){let option=document.createElement("option");option.value=provider.id,option.textContent=provider.displayName,budgetSelect.appendChild(option)}budgetSelect.addEventListener("change",async()=>{budgetSelect.value&&(extraBudgets.add(budgetSelect.value),await refresh(),budgetGrid.querySelector(`[data-budget-provider="${budgetSelect.value}"] input`)?.focus(),budgetSelect.value="")}),root.appendChild(budgetSelect);let budgetGrid=document.createElement("div");budgetGrid.style.display="grid",budgetGrid.style.gridTemplateColumns="repeat(auto-fill, minmax(280px, 1fr))",budgetGrid.style.gap="var(--blessing-gap-sm)",budgetGrid.style.marginBottom="var(--blessing-gap)",root.appendChild(budgetGrid);let recentTitle=document.createElement("div");recentTitle.textContent="\uCD5C\uADFC \uD638\uCD9C (\uCD5C\uB300 25\uAC74)",recentTitle.style.fontWeight="600",recentTitle.style.margin="var(--blessing-gap) 0 var(--blessing-gap-sm) 0",root.appendChild(recentTitle);let recentCard=document.createElement("div");recentCard.className="blessing-card",recentCard.style.fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace",recentCard.style.fontSize="12px",recentCard.style.lineHeight="1.6",recentCard.style.maxHeight="240px",recentCard.style.overflow="auto",root.appendChild(recentCard);async function refresh(){await withRegion(summaryCard,async()=>{let[sum,events,usage,budgets,visible]=await Promise.all([summarize(),loadEvents(),summarizeUsage(),getBudgets(),loadVisibleProviderIds()]);renderSummary(summaryCard,sum),providersTitle.hidden=sum.perProvider.length===0,renderProviders(providersGrid,sum.perProvider),renderUsage(usageGrid,usage);let ids=new Set([...usage.map(u=>u.providerId),...Object.keys(budgets),...visible,...extraBudgets]),monthKey2=new Date().toISOString().slice(0,7);renderBudget(budgetGrid,[...ids].map(providerId=>usage.find(u=>u.providerId===providerId)||{providerId,monthUsd:0,monthKey:monthKey2}),budgets,refresh),renderRecent(recentCard,events)},"\uBD88\uB7EC\uC624\uB294 \uC911...")}refreshBtn.addEventListener("click",()=>withButtonBusy(refreshBtn,refresh,"\uC0C8\uB85C\uACE0\uCE68 \uC911...")),clearBtn.addEventListener("click",()=>withButtonBusy(clearBtn,async()=>{await clearEvents(),await clearUsage(),await refresh()},"\uC9C0\uC6B0\uB294 \uC911..."));let maintenance=createSection("\uC0AC\uC6A9 \uAE30\uB85D\uC744 \uBE44\uC6B0\uB824\uBA74",{maintenance:!0});maintenance.appendChild(clearBtn),root.appendChild(maintenance),await refresh()}function renderSummary(card,sum){if(card.innerHTML="",sum.total===0){let p=document.createElement("p");p.textContent="\uC544\uC9C1 \uAE30\uB85D\uB41C \uD638\uCD9C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. Blessing \uD504\uB85C\uBC14\uC774\uB354\uB97C \uC0AC\uC6A9\uD574 \uCC44\uD305\uC744 \uC2DC\uB3C4\uD574 \uBCF4\uC138\uC694.",p.style.margin="0",card.appendChild(p);return}let row=document.createElement("div");row.style.display="grid",row.style.gridTemplateColumns="repeat(auto-fit, minmax(120px, 1fr))",row.style.gap="var(--blessing-gap)",row.appendChild(stat("\uCD1D \uD638\uCD9C",String(sum.total)));let okRate=sum.total>0?Math.round(sum.ok/sum.total*100):0;row.appendChild(stat("\uC131\uACF5",`${sum.ok} (${okRate}%)`)),row.appendChild(stat("\uC2E4\uD328",String(sum.failed))),sum.sinceMs&&row.appendChild(stat("\uAE30\uB85D \uC2DC\uC791",formatTime(sum.sinceMs))),card.appendChild(row)}function renderProviders(grid,list){if(grid.innerHTML="",list.length!==0)for(let p of list){let card=document.createElement("div");card.className="blessing-card",card.style.margin="0";let name=document.createElement("div");name.textContent=providerName(p.providerId),name.style.fontWeight="600",name.style.marginBottom="var(--blessing-gap-xs)",card.appendChild(name);let line1=document.createElement("div");line1.style.fontSize="12px",line1.style.color="var(--blessing-fg-muted)";let okRate=p.count>0?Math.round(p.ok/p.count*100):0;line1.textContent=`\uD638\uCD9C ${p.count} \xB7 \uC131\uACF5\uB960 ${okRate}% \xB7 \uD3C9\uADE0 ${p.avgMs}ms`,card.appendChild(line1);let line2=document.createElement("div");line2.style.fontSize="12px",line2.style.color="var(--blessing-fg-muted)",line2.textContent=`\uB204\uC801 ${formatBytes(p.totalBytes)} \xB7 \uB9C8\uC9C0\uB9C9 ${formatTime(p.lastAt)}`,card.appendChild(line2),grid.appendChild(card)}}function renderUsage(grid,list){if(grid.innerHTML="",!list||list.length===0){let empty=document.createElement("div");empty.style.fontSize="12px",empty.style.color="var(--blessing-fg-muted)",empty.style.gridColumn="1 / -1",empty.textContent="\uBAA8\uB378\uC774 \uC0AC\uC6A9\uB7C9\uC744 \uBCF4\uB0B4\uBA74 \uC785\uB825\xB7\uCD9C\uB825\xB7\uCE90\uC2DC \uD1A0\uD070\uC774 \uC5EC\uAE30\uC5D0 \uC313\uC785\uB2C8\uB2E4. Gemini\uB97C \uD3EC\uD568\uD55C \uAC01 \uC11C\uBE44\uC2A4\uC758 \uC751\uB2F5 \uC815\uBCF4\uC5D0 \uB530\uB77C \uC9D1\uACC4\uB429\uB2C8\uB2E4.",grid.appendChild(empty);return}for(let u of list){let card=document.createElement("div");card.className="blessing-card",card.style.margin="0";let name=document.createElement("div");name.textContent=providerName(u.providerId),name.style.fontWeight="600",name.style.marginBottom="var(--blessing-gap-xs)",card.appendChild(name);let lineIO=document.createElement("div");if(lineIO.style.fontSize="12px",lineIO.style.color="var(--blessing-fg-muted)",lineIO.textContent=`\uC785\uB825 ${formatTokens(u.input)} \xB7 \uCD9C\uB825 ${formatTokens(u.output)} \xB7 ${u.calls}\uD68C \uD638\uCD9C`,card.appendChild(lineIO),u.cacheCreation>0||u.cacheRead>0){let lineCache=document.createElement("div");lineCache.style.fontSize="12px",lineCache.style.color="var(--blessing-accent, var(--blessing-fg-muted))",lineCache.textContent=`\uCE90\uC2DC \uC800\uC7A5 ${formatTokens(u.cacheCreation)} \xB7 \uC7AC\uC0AC\uC6A9 ${formatTokens(u.cacheRead)}`,card.appendChild(lineCache)}if(u.usd>0){let lineCost=document.createElement("div");if(lineCost.style.fontSize="12px",lineCost.style.color="var(--blessing-fg)",lineCost.style.marginTop="var(--blessing-gap-xs)",lineCost.textContent=`\uC608\uC0C1 \uBE44\uC6A9: $${formatUsd(u.usd)} (\uC804\uCCB4) \xB7 $${formatUsd(u.monthUsd)} (${u.monthKey})`,card.appendChild(lineCost),u.lastModel){let lineModel=document.createElement("div");lineModel.style.fontSize="11px",lineModel.style.color="var(--blessing-fg-muted)",lineModel.textContent=`\uB9C8\uC9C0\uB9C9 \uBAA8\uB378: ${u.lastModel}`,card.appendChild(lineModel);let hint=formatPricingHint(u.lastModel);if(hint&&hint!=="\uB2E8\uAC00 \uC815\uBCF4 \uC5C6\uC74C"){let lineRate=document.createElement("div");lineRate.style.fontSize="11px",lineRate.style.color="var(--blessing-fg-muted)",lineRate.textContent=`\xB7 ${hint}`,card.appendChild(lineRate)}}}else if(u.lastModel){let lineUnknown=document.createElement("div");lineUnknown.style.fontSize="11px",lineUnknown.style.color="var(--blessing-fg-muted)",lineUnknown.style.marginTop="var(--blessing-gap-xs)",lineUnknown.textContent=`\uC608\uC0C1 \uBE44\uC6A9: \uB2E8\uAC00 \uC815\uBCF4 \uC5C6\uC74C (${u.lastModel})`,card.appendChild(lineUnknown)}let lineTime=document.createElement("div");lineTime.style.fontSize="11px",lineTime.style.color="var(--blessing-fg-muted)",lineTime.style.marginTop="var(--blessing-gap-xs)",lineTime.textContent=`\uB9C8\uC9C0\uB9C9 ${formatTime(u.lastAt)}`,card.appendChild(lineTime),grid.appendChild(card)}}function formatUsd(n){return n=Number(n)||0,n===0?"0.00":n<.01?n.toFixed(4):n<1?n.toFixed(3):n.toFixed(2)}function renderBudget(grid,usageList,budgets,refresh){if(grid.innerHTML="",!usageList||usageList.length===0){let empty=document.createElement("div");empty.style.fontSize="12px",empty.style.color="var(--blessing-fg-muted)",empty.style.gridColumn="1 / -1",empty.textContent="\uC704 \uBAA9\uB85D\uC5D0\uC11C \uC11C\uBE44\uC2A4\uB97C \uACE8\uB77C \uC6D4\uAC04 \uC54C\uB9BC \uAE30\uC900\uC744 \uC124\uC815\uD574 \uBCF4\uC138\uC694.",grid.appendChild(empty);return}for(let u of usageList){let card=document.createElement("div");card.className="blessing-card",card.dataset.budgetProvider=u.providerId,card.style.margin="0";let name=document.createElement("div");name.textContent=providerName(u.providerId),name.style.fontWeight="600",name.style.marginBottom="var(--blessing-gap-xs)",card.appendChild(name);let limit=Number(budgets[u.providerId])||0,used=Number(u.monthUsd)||0,pct=limit>0?Math.min(100,used/limit*100):0;if(limit>0){let bar=document.createElement("div");bar.style.height="8px",bar.style.background="var(--blessing-border-soft, rgba(255,255,255,0.12))",bar.style.borderRadius="var(--blessing-radius-sm)",bar.style.overflow="hidden",bar.style.marginBottom="var(--blessing-gap-xs)";let fill=document.createElement("div");fill.style.height="100%",fill.style.width=`${pct}%`,fill.style.background=pct>=100?"var(--blessing-danger)":pct>=80?"var(--blessing-warning, var(--blessing-accent))":"var(--blessing-accent)",fill.style.transition="width 240ms ease",bar.appendChild(fill),card.appendChild(bar);let usedLine=document.createElement("div");usedLine.style.fontSize="12px",usedLine.style.color="var(--blessing-fg-muted)",usedLine.textContent=`$${formatUsd(used)} / $${limit.toFixed(2)} (${pct.toFixed(1)}%) \xB7 ${u.monthKey}`,card.appendChild(usedLine)}else{let noBudget=document.createElement("div");noBudget.style.fontSize="12px",noBudget.style.color="var(--blessing-fg-muted)",noBudget.style.marginBottom="var(--blessing-gap-xs)",noBudget.textContent=`\uC608\uC0B0 \uBBF8\uC124\uC815 \xB7 \uC774\uBC88 \uB2EC \uC0AC\uC6A9: $${formatUsd(used)} (${u.monthKey})`,card.appendChild(noBudget)}let inputRow=document.createElement("div");inputRow.style.display="flex",inputRow.style.gap="var(--blessing-gap-xs)",inputRow.style.alignItems="center",inputRow.style.marginTop="var(--blessing-gap-xs)";let input=document.createElement("input");input.type="number",input.setAttribute("aria-label",`${providerName(u.providerId)} \uC6D4\uAC04 \uC608\uC0B0 (USD)`),input.min="0",input.step="0.01",input.placeholder="\uC608\uC0B0 (USD)",input.value=limit>0?String(limit):"",input.className="blessing-input",input.style.flex="1",input.style.minWidth="0",input.style.padding="4px 8px",input.style.fontSize="12px";let saveBtn=document.createElement("button");if(saveBtn.type="button",saveBtn.textContent="\uC800\uC7A5",styleAsSecondaryButton2(saveBtn),saveBtn.style.padding="4px 10px",saveBtn.style.fontSize="12px",saveBtn.addEventListener("click",()=>withButtonBusy(saveBtn,async()=>{let v=Number(input.value);await setBudget(u.providerId,Number.isFinite(v)&&v>0?v:0),await refresh()},"\uC800\uC7A5 \uC911...")),inputRow.appendChild(input),inputRow.appendChild(saveBtn),card.appendChild(inputRow),limit>0&&pct>=80){let resetRow=document.createElement("div");resetRow.style.marginTop="var(--blessing-gap-xs)";let resetBtn=document.createElement("button");resetBtn.type="button",resetBtn.textContent="\uC784\uACC4 \uC54C\uB9BC \uC7AC\uD65C\uC131",styleAsSecondaryButton2(resetBtn),resetBtn.style.padding="4px 10px",resetBtn.style.fontSize="11px",resetBtn.addEventListener("click",()=>withButtonBusy(resetBtn,async()=>{await resetBudgetAlerts(u.providerId),await refresh()},"\uC7AC\uD65C\uC131 \uC911...")),resetRow.appendChild(resetBtn),card.appendChild(resetRow)}grid.appendChild(card)}}function formatTokens(n){return n=Number(n)||0,n<1e3?`${n}`:n<1e6?`${(n/1e3).toFixed(1)}K`:`${(n/1e6).toFixed(2)}M`}function renderRecent(card,events){if(card.innerHTML="",events.length===0){card.textContent="(\uC774\uBCA4\uD2B8 \uC5C6\uC74C)";return}let recent=events.slice(-25).reverse();for(let e of recent){let line=document.createElement("div"),mark=e.ok?"\uC131\uACF5":"\uC2E4\uD328",dur=Math.max(0,(e.endedAt||0)-(e.startedAt||0)),msg=e.error?` \u2014 ${e.error}`:"";line.textContent=`${mark} ${formatTime(e.endedAt)} ${providerName(e.providerId)} ${dur}ms ${formatBytes(e.bytes)}${msg}`,line.style.color=e.ok?"var(--blessing-fg)":"var(--blessing-danger)",card.appendChild(line)}}function stat(label,value){let wrap=document.createElement("div"),v=document.createElement("div");v.textContent=value,v.style.fontSize="20px",v.style.fontWeight="600";let l=document.createElement("div");return l.textContent=label,l.style.fontSize="12px",l.style.color="var(--blessing-fg-muted)",wrap.appendChild(v),wrap.appendChild(l),wrap}function formatTime(ms){if(!ms)return"-";try{let d=new Date(ms),pad=n=>String(n).padStart(2,"0");return`${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`}catch{return String(ms)}}function formatBytes(n){return n=Number(n)||0,n<1024?`${n}B`:n<1024*1024?`${(n/1024).toFixed(1)}KB`:`${(n/(1024*1024)).toFixed(2)}MB`}function styleAsSecondaryButton2(btn){btn.style.padding="6px 14px",btn.style.borderRadius="var(--blessing-radius-sm)",btn.style.border="1px solid var(--blessing-border-soft)",btn.style.background="transparent",btn.style.color="var(--blessing-fg-muted)",btn.style.cursor="pointer",btn.style.font="inherit"}function providerName(id){return PROVIDERS.find(p=>p.id===id)?.displayName||id}var LBI_SKIP_PATTERNS=[/^lbi:idb:LBI:LBI:/];var CLEANUP_TARGETS=[{id:"external-idb-prefix-1",label:"\uC794\uC874 \uC678\uBD80 \uB370\uC774\uD130 \uC815\uB9AC \u2014 IDB \uC601\uC5ED",shortName:"\uC774 \uC601\uC5ED",patterns:LBI_SKIP_PATTERNS,description:"storage \uC758 `lbi:idb:LBI:LBI:` prefix \uD0A4 \u2014 \uB2E8\uC77C ~289 MB \uC124\uC815 dump + ~134 KB+ \uD638\uCD9C \uB85C\uADF8 \uB204\uC801 + \uADF8 \uC678 \uAC19\uC740 prefix \uC758 internal \uD0A4",helpText:`\uC678\uBD80 plugin \uC774 \uC81C\uAC70\uB418\uC5B4\uB3C4 storage \uC5D0 \uB0A8\uC740 internal \uB370\uC774\uD130 (\uC608: \uB2E8\uC77C ~289 MB \uC124\uC815 dump, ~134 KB+ \uD638\uCD9C \uB85C\uADF8 \uB204\uC801 \uB4F1) \uAC00 *\uB2E4\uB978 \uD50C\uB7EC\uADF8\uC778\uC774 storage \uB97C \uC2A4\uCE94\uD560 \uB54C* peak memory \uD3ED\uC99D\uC744 \uC720\uBC1C\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | |
| \xB7 \uBCF8 \uC815\uB9AC\uB294 \`lbi:idb:LBI:LBI:\` prefix \uC758 *\uBAA8\uB4E0 \uD0A4* \uB97C \uC0AD\uC81C\uD569\uB2C8\uB2E4 (IDB-emulation \uC601\uC5ED \uC804\uCCB4). | |
| \xB7 \uC0AC\uC6A9\uC790\uAC00 \uB9C8\uC774\uADF8\uB808\uC774\uC158\uD560 \uAC00\uCE58 \uC788\uB294 \uC2AC\uB86F \uB370\uC774\uD130\uB294 *\uBCC4\uB3C4 \uC601\uC5ED* \uC774\uB77C \uC190\uB300\uC9C0 \uC54A\uC74C. | |
| \xB7 *\uC601\uAD6C \uC0AD\uC81C* \u2014 \uBCF5\uAD6C \uBD88\uAC00. \uD574\uB2F9 \uC678\uBD80 plugin \uC744 \uB2E4\uC2DC \uC124\uCE58\uD574\uB3C4 \uADF8 \uB370\uC774\uD130\uB294 \uBE48 \uC0C1\uD0DC\uB85C \uC2DC\uC791. | |
| \xB7 \uBCF8 \uC791\uC5C5\uC740 \uD574\uB2F9 \uC678\uBD80 plugin \uC774 \uC81C\uAC70\uB41C \uC0C1\uD669 \uD55C\uC815 \u2014 \uD65C\uC131 \uC0AC\uC6A9 \uC911\uC774\uBA74 \uC815\uB9AC \uD6C4 \uADF8 plugin \uC758 \uB3D9\uC791\uC774 \uAE68\uC9C8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`}];async function purgeCleanupTarget(target,opts={}){let{dryRun=!1}=opts,out={ok:!0,removed:0,failed:0,errors:[],removedKeys:[]};if(!target||!Array.isArray(target.patterns)||target.patterns.length===0)return out.ok=!1,out.errors.push("invalid target \u2014 patterns \uD544\uC218"),out;try{let storage=getStorage();if(!storage||typeof storage.keys!="function")return out.ok=!1,out.errors.push("pluginStorage.keys() \uBBF8\uC9C0\uC6D0 \u2014 \uC815\uB9AC \uBD88\uAC00"),out;let all=await storage.keys();if(!Array.isArray(all))return out;let targets=all.filter(k=>typeof k=="string"&&target.patterns.some(p=>p.test(k)));if(dryRun)return out.removedKeys=targets,out;for(let k of targets)try{typeof storage.removeItem=="function"?await storage.removeItem(k):typeof storage.deleteItem=="function"?await storage.deleteItem(k):await storage.setItem(k,""),out.removed+=1,out.removedKeys.push(k)}catch(err){out.failed+=1,out.errors.push(`${k}: ${err?.message||String(err)}`)}return out.failed>0&&(out.ok=!1),out}catch(err){return out.ok=!1,out.errors.push(`scan \uC2E4\uD328: ${err?.message||String(err)}`),out}}async function purgeLargeLegacyInternals(){let firstTarget=CLEANUP_TARGETS.find(t=>t.id==="external-idb-prefix-1");return purgeCleanupTarget(firstTarget)}var MIN_PATTERN_LENGTH=3;function validateCustomCleanupPattern(mode,pattern){let p=String(pattern||"").trim();if(!p)return{ok:!1,error:"\uD328\uD134\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4."};if(mode==="prefix")return p.length<MIN_PATTERN_LENGTH?{ok:!1,error:`prefix \uB294 \uCD5C\uC18C ${MIN_PATTERN_LENGTH} \uAE00\uC790 \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4 (\uC624\uC6A9 \uCC28\uB2E8).`}:p.startsWith("blessing")||"blessing.".startsWith(p)?{ok:!1,error:"blessing.* \uD0A4\uB294 Blessing \uC790\uCCB4 \uB370\uC774\uD130\uC785\uB2C8\uB2E4. \uC790\uCCB4 \uB370\uC774\uD130\uB294 \uBCF8 UI \uC5D0\uC11C \uC815\uB9AC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."}:{ok:!0};if(mode==="regex"){let re;try{re=new RegExp(p)}catch(err){return{ok:!1,error:`\uC815\uADDC\uC2DD \uC624\uB958: ${err?.message||String(err)}`}}let guardSamples=["blessing.provider.test.apiKey","blessing.insights.events","blessing.budget.providers"];for(let sample of guardSamples)if(re.test(sample))return{ok:!1,error:"\uBCF8 \uC815\uADDC\uC2DD\uC774 blessing.* \uC790\uCCB4 \uB370\uC774\uD130\uB97C \uB9E4\uCE58\uD569\uB2C8\uB2E4. \uD328\uD134\uC744 \uC881\uD600 \uC8FC\uC138\uC694."};return{ok:!0}}return{ok:!1,error:`\uC54C \uC218 \uC5C6\uB294 mode: ${mode} (prefix / regex \uC911 \uD558\uB098)`}}function buildCustomCleanupTarget(mode,pattern){let p=String(pattern||"").trim(),re;if(mode==="prefix"){let escaped=p.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");re=new RegExp("^"+escaped)}else re=new RegExp(p);return{id:`custom-${mode}-${Date.now()}`,label:`\uC0AC\uC6A9\uC790 \uC815\uC758 cleanup (${mode}: ${p})`,shortName:"\uC0AC\uC6A9\uC790 \uC815\uC758",patterns:[re],description:`\uC0AC\uC6A9\uC790 \uC785\uB825 \uD328\uD134 ${mode==="prefix"?"prefix":"regex"}: ${p}`,helpText:`\uC0AC\uC6A9\uC790\uAC00 \uC9C1\uC811 \uC785\uB825\uD55C ${mode} \uD328\uD134\uC73C\uB85C \uB9E4\uCE58\uB418\uB294 \uD0A4\uB9CC \uC0AD\uC81C\uD569\uB2C8\uB2E4.`}}var LBI_ARG_DEST_PREFIX=`${STORAGE_PREFIX}legacy.lbi_arg.`,LBI_ARG_MAP={common_customProvider_url:`${STORAGE_PREFIX}provider.custom-openai.baseUrl`,common_customProvider_apiKey:`${STORAGE_PREFIX}provider.custom-openai.apiKey`,common_customProvider_model:`${STORAGE_PREFIX}provider.custom-openai.model`,common_openaiProvider_apiKey:`${STORAGE_PREFIX}provider.custom-openai.apiKey`,common_anthropicProvider_apiKey:`${STORAGE_PREFIX}provider.anthropic.apiKey`,common_googleAIProvider_apiKey:`${STORAGE_PREFIX}provider.google-ai.apiKey`,common_vertexAIProvider_credentials:`${STORAGE_PREFIX}provider.vertex.serviceAccountJson`,common_vertexAIProvider_customLocation:`${STORAGE_PREFIX}provider.vertex.region`};function mapLbiArgKey(argKey){return LBI_ARG_MAP[argKey]||null}function sortLbiArgKeys(keys){return keys.slice().sort((a,b)=>{let ap=a.startsWith("common_customProvider_")?0:1,bp=b.startsWith("common_customProvider_")?0:1;return ap-bp})}function parseLbiExportFile(text){let json;try{json=JSON.parse(text)}catch(e){throw new Error("JSON \uD30C\uC2F1\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4: "+(e?.message||String(e)))}if(!json||typeof json!="object")throw new Error("LBI \uB0B4\uBCF4\uB0B4\uAE30 \uD30C\uC77C \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4 (\uAC1D\uCCB4\uAC00 \uC544\uB2D8).");return json.lbiSettings&&typeof json.lbiSettings=="object"?{entries:json.lbiSettings,format:"v1"}:{entries:json,format:"legacy"}}async function planFileMigration(entries){let storage=getStorage(),keys=sortLbiArgKeys(Object.keys(entries||{})),rows=[],conflicts=0,unmapped=0;for(let src of keys){let dst=mapLbiArgKey(src);if(!dst){unmapped+=1;continue}let existing=null;try{existing=await storage.getItem(dst)}catch{}let conflict=existing!=null;conflict&&(conflicts+=1),rows.push({src,dst,conflict,mapped:!0})}return{keys:rows,total:rows.length,conflicts,unmapped}}async function migrateFromFile(entries,mode="skip",selected=null){let storage=getStorage(),plan=await planFileMigration(entries),filter=selected?selected instanceof Set?selected:new Set(selected):null,copied=0,skipped=0,failed=0,errors=[];for(let row of plan.keys){if(filter&&!filter.has(row.src)){skipped+=1;continue}if(row.conflict&&mode==="skip"){skipped+=1;continue}try{let raw=entries[row.src],value=typeof raw=="string"?raw:JSON.stringify(raw);await storage.setItem(row.dst,value),copied+=1}catch(err){failed+=1,errors.push(`${row.src} \u2192 ${row.dst}: ${err?.message||String(err)}`)}}return{ok:failed===0,copied,skipped,failed,errors}}function renderImportTab(root){root.innerHTML="",renderFileSection(root);let maintenance=createSection("\uC774\uC804 \uD50C\uB7EC\uADF8\uC778\uC758 \uB370\uC774\uD130\uB97C \uC815\uB9AC\uD558\uB824\uBA74",{maintenance:!0});renderCleanupSection(maintenance),root.appendChild(maintenance)}function makeRadio(name,value,label){let wrap=document.createElement("label");wrap.style.display="inline-flex",wrap.style.alignItems="center",wrap.style.gap="6px",wrap.style.cursor="pointer";let input=document.createElement("input");input.type="radio",input.name=name,input.value=value,wrap.appendChild(input);let span=document.createElement("span");return span.textContent=label,wrap.appendChild(span),wrap}function styleAsPrimaryButton(btn){btn.style.padding="8px 14px",btn.style.border="1px solid var(--blessing-border)",btn.style.background="var(--blessing-accent-soft)",btn.style.color="var(--blessing-fg)",btn.style.borderRadius="var(--blessing-radius-sm)",btn.style.cursor="pointer",btn.style.font="inherit"}function styleAsGhostButton(btn){btn.style.padding="4px 10px",btn.style.border="1px solid var(--blessing-border-soft)",btn.style.background="transparent",btn.style.color="var(--blessing-fg-muted)",btn.style.borderRadius="var(--blessing-radius-sm)",btn.style.cursor="pointer",btn.style.font="inherit",btn.style.fontSize="12px"}function renderFileSection(root){let h3=document.createElement("h2");h3.textContent="\uD30C\uC77C\uC5D0\uC11C \uAC00\uC838\uC624\uAE30",root.appendChild(h3),root.appendChild(createExplanation("LBI \uC124\uC815\uC740 \uC5B4\uB5BB\uAC8C \uAC00\uC838\uC624\uB098\uC694?",`LBI\uC5D0\uC11C '\uC124\uC815 \uB0B4\uBCF4\uB0B4\uAE30'\uB85C \uB9CC\uB4E0 JSON \uD30C\uC77C\uC744 \uC5C5\uB85C\uB4DC\uD574 \uD0A4-\uAC12\uC744 Blessing \uCE21\uC73C\uB85C \uBCF5\uC0AC\uD569\uB2C8\uB2E4. | |
| \xB7 \uC54C\uB824\uC9C4 \uD504\uB85C\uBC14\uC774\uB354 \uC2AC\uB86F(API Key / Base URL / Model \uB4F1)\uB9CC \uC790\uB3D9 \uB9E4\uD551\uB418\uC5B4 \uC815\uC2DD \uACBD\uB85C(blessing.provider.<id>.<field>)\uC5D0 \uC800\uC7A5\uB429\uB2C8\uB2E4. | |
| \xB7 \uB9E4\uD551\uB418\uC9C0 \uC54A\uB294 LBI \uACE0\uC720 \uC2AC\uB86F(chat sampling, hypa, translation, tools \uB4F1)\uC740 \uBB34\uC2DC\uB429\uB2C8\uB2E4. | |
| \xB7 \uAC01 \uD0A4\uB9C8\uB2E4 \uCCB4\uD06C\uBC15\uC2A4\uB85C \uAC00\uC838\uC62C\uC9C0 \uC5EC\uBD80\uB97C \uC120\uD0DD\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | |
| \xB7 \uCDA9\uB3CC \uCC98\uB9AC: | |
| - \uAC74\uB108\uB6F0\uAE30: Blessing \uCE21\uC5D0 \uC774\uBBF8 \uAC19\uC740 \uD0A4\uAC00 \uC788\uC73C\uBA74 \uADF8\uB300\uB85C \uB460. | |
| - \uB36E\uC5B4\uC4F0\uAE30: \uD30C\uC77C \uAC12\uC73C\uB85C \uAD50\uCCB4. | |
| \uC9C0\uC6D0 \uD3EC\uB9F7: | |
| \xB7 { "lbiSettings": { ... } } \u2014 \uD604\uC7AC LBI \uB0B4\uBCF4\uB0B4\uAE30 \uD3EC\uB9F7 | |
| \xB7 { ... } \u2014 \uAD6C\uBC84\uC804 (\uB8E8\uD2B8\uAC00 \uACE7 \uD0A4\uB9F5)`));let pickerCard=document.createElement("div");pickerCard.className="blessing-card",root.appendChild(pickerCard);let fileInput=document.createElement("input");fileInput.type="file",fileInput.accept=".json,application/json",fileInput.style.display="none";let pickBtn=document.createElement("button");pickBtn.type="button",pickBtn.textContent="JSON \uD30C\uC77C \uC120\uD0DD",styleAsPrimaryButton(pickBtn),pickBtn.addEventListener("click",()=>fileInput.click());let pickedLabel=document.createElement("span");pickedLabel.style.marginLeft="var(--blessing-gap-sm)",pickedLabel.style.color="var(--blessing-fg-muted)",pickedLabel.style.fontSize="12px",pickedLabel.textContent="\uC120\uD0DD\uB41C \uD30C\uC77C \uC5C6\uC74C",pickerCard.appendChild(pickBtn),pickerCard.appendChild(pickedLabel),pickerCard.appendChild(fileInput);let fileSummary=document.createElement("p");fileSummary.style.display="none",root.appendChild(fileSummary);let fileListCard=document.createElement("div");fileListCard.className="blessing-card",fileListCard.style.display="none",root.appendChild(fileListCard);let fileControls=document.createElement("div");fileControls.className="blessing-card",fileControls.style.display="none",root.appendChild(fileControls);let fileResult=document.createElement("div");fileResult.className="blessing-card",fileResult.style.display="none",root.appendChild(fileResult),fileInput.addEventListener("change",async()=>{let f=fileInput.files&&fileInput.files[0];if(f){pickedLabel.textContent=f.name,fileSummary.style.display="block",fileSummary.style.color="",fileSummary.textContent="",fileSummary.appendChild(inlineBusy("\uD30C\uC77C\uC744 \uC77D\uB294 \uC911...")),fileListCard.style.display="none",fileControls.style.display="none";try{let text=await f.text(),parsed=parseLbiExportFile(text),plan=await planFileMigration(parsed.entries);renderFilePlan(parsed,plan)}catch{fileSummary.style.display="block",fileSummary.style.color="var(--blessing-danger)",fileSummary.textContent="\uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. LBI\uC758 \uC124\uC815 \uB0B4\uBCF4\uB0B4\uAE30\uB85C \uB9CC\uB4E0 JSON \uD30C\uC77C\uC778\uC9C0 \uD655\uC778\uD574 \uC8FC\uC138\uC694.",fileListCard.style.display="none",fileControls.style.display="none"}}});function renderFilePlan(parsed,plan){fileResult.style.display="none";let skippedCount=typeof plan.unmapped=="number"?plan.unmapped:0;if(plan.total===0){fileSummary.style.display="block",fileSummary.style.color="var(--blessing-fg-muted)",fileSummary.textContent=skippedCount>0?`\uD30C\uC77C\uC5D0 ${skippedCount}\uAC1C \uD0A4\uAC00 \uC788\uC9C0\uB9CC, Blessing\uC774 \uC0AC\uC6A9\uD558\uB294 \uC2AC\uB86F\uC5D0 \uB9E4\uD551\uB418\uB294 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`:"\uD30C\uC77C\uC5D0 \uAC00\uC838\uC62C \uD0A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",fileListCard.style.display="none",fileControls.style.display="none";return}fileSummary.style.display="block",fileSummary.style.color="",fileSummary.textContent=`\uD30C\uC77C\uC5D0\uC11C \uB9E4\uD551 \uAC00\uB2A5\uD55C ${plan.total}\uAC1C \uD0A4 \uBC1C\uACAC (\uD3EC\uB9F7: ${parsed.format}). \uADF8\uC911 ${plan.conflicts}\uAC1C\uAC00 Blessing \uCE21\uACFC \uCDA9\uB3CC`+(skippedCount>0?`. \uB9E4\uD551\uB418\uC9C0 \uC54A\uB294 ${skippedCount}\uAC1C LBI \uACE0\uC720 \uC2AC\uB86F\uC740 \uBB34\uC2DC\uB429\uB2C8\uB2E4.`:"."),fileListCard.innerHTML="",fileListCard.style.display="block";let checkboxes=renderKeyList(fileListCard,plan.keys);fileControls.innerHTML="",fileControls.style.display="block";let modeWrap=document.createElement("div");modeWrap.style.display="flex",modeWrap.style.gap="var(--blessing-gap)",modeWrap.style.marginBottom="var(--blessing-gap-sm)";let skipLabel=makeRadio("blessing-file-mode","skip","\uAC74\uB108\uB6F0\uAE30 (\uAE30\uBCF8)"),overLabel=makeRadio("blessing-file-mode","overwrite","\uB36E\uC5B4\uC4F0\uAE30");skipLabel.querySelector("input").checked=!0,modeWrap.appendChild(skipLabel),modeWrap.appendChild(overLabel),fileControls.appendChild(modeWrap);let runBtn=document.createElement("button");runBtn.type="button",runBtn.textContent="\uD30C\uC77C\uC5D0\uC11C \uAC00\uC838\uC624\uAE30",styleAsPrimaryButton(runBtn),fileControls.appendChild(runBtn),runBtn.addEventListener("click",async()=>{let mode=fileControls.querySelector('input[name="blessing-file-mode"]:checked')?.value||"skip",selected=checkboxes.filter(c=>c.checked).map(c=>c.dataset.src||"");if(selected.length===0){renderFileResult({ok:!1,copied:0,skipped:0,failed:0,errors:["\uC120\uD0DD\uB41C \uD0A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uAC00\uC838\uC62C \uD56D\uBAA9\uC744 \uD55C \uAC1C \uC774\uC0C1 \uCCB4\uD06C\uD574 \uC8FC\uC138\uC694."]});return}await withButtonBusy(runBtn,async()=>{try{let res=await migrateFromFile(parsed.entries,mode,selected);renderFileResult(res);let replan=await planFileMigration(parsed.entries);renderFilePlan(parsed,replan)}catch(err){renderFileResult({ok:!1,copied:0,skipped:0,failed:1,errors:[err?.message||String(err)]})}},"\uBCF5\uC0AC \uC911...")})}function renderFileResult(res){fileResult.style.display="block",fileResult.innerHTML="";let head=document.createElement("div");head.style.fontWeight="600",head.textContent=res.ok?"\uD30C\uC77C \uAC00\uC838\uC624\uAE30 \uC644\uB8CC":"\uD30C\uC77C \uAC00\uC838\uC624\uAE30\uAC00 \uC77C\uBD80 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",head.style.color=res.ok?"var(--blessing-fg)":"var(--blessing-danger)",fileResult.appendChild(head);let stats=document.createElement("div");if(stats.style.marginTop="var(--blessing-gap-xs)",stats.style.color="var(--blessing-fg-muted)",stats.textContent=`\uBCF5\uC0AC ${res.copied} \xB7 \uAC74\uB108\uB700 ${res.skipped} \xB7 \uC2E4\uD328 ${res.failed}`,fileResult.appendChild(stats),res.errors?.length){let errBox=document.createElement("pre");errBox.style.marginTop="var(--blessing-gap-sm)",errBox.style.fontSize="11px",errBox.style.whiteSpace="pre-wrap",errBox.style.color="var(--blessing-danger)",errBox.textContent=res.errors.slice(0,20).join(` | |
| `),fileResult.appendChild(errBox)}}}function renderKeyList(root,rows){let toolbar=document.createElement("div");toolbar.style.display="flex",toolbar.style.alignItems="center",toolbar.style.gap="var(--blessing-gap-sm)",toolbar.style.marginBottom="var(--blessing-gap-sm)";let selectAllBtn=document.createElement("button");selectAllBtn.type="button",selectAllBtn.textContent="\uC804\uCCB4 \uC120\uD0DD",styleAsGhostButton(selectAllBtn);let clearAllBtn=document.createElement("button");clearAllBtn.type="button",clearAllBtn.textContent="\uC804\uCCB4 \uD574\uC81C",styleAsGhostButton(clearAllBtn);let countLabel=document.createElement("span");countLabel.style.marginLeft="auto",countLabel.style.fontSize="12px",countLabel.style.color="var(--blessing-fg-muted)",toolbar.appendChild(selectAllBtn),toolbar.appendChild(clearAllBtn),toolbar.appendChild(countLabel),root.appendChild(toolbar);let list=document.createElement("div");list.style.fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace",list.style.fontSize="12px",list.style.lineHeight="1.6",list.style.maxHeight="220px",list.style.overflow="auto",list.style.display="flex",list.style.flexDirection="column",list.style.gap="2px";let checkboxes=[];for(let row of rows){let line=document.createElement("label");line.style.display="flex",line.style.alignItems="center",line.style.gap="8px",line.style.padding="4px 6px",line.style.borderRadius="var(--blessing-radius-sm)",line.style.cursor="pointer",line.addEventListener("mouseenter",()=>{line.style.background="var(--blessing-bg-hover)"}),line.addEventListener("mouseleave",()=>{line.style.background="transparent"});let cb=document.createElement("input");cb.type="checkbox",cb.checked=!0,cb.dataset.src=row.src,cb.style.flex="0 0 auto",cb.style.accentColor="var(--blessing-accent)",cb.addEventListener("change",updateCount),checkboxes.push(cb),line.appendChild(cb);let mark=document.createElement("span");mark.textContent=row.conflict?"!":"\xB7",mark.style.flex="0 0 auto",mark.style.width="12px",mark.style.textAlign="center",mark.style.color=row.conflict?"var(--blessing-danger)":"var(--blessing-fg-muted)",line.appendChild(mark);let text=document.createElement("span"),match=/^blessing\.provider\.([^.]+)\.(.+)$/.exec(row.dst),provider=PROVIDERS.find(p=>p.id===match?.[1]),field=provider?.schema.find(f=>f.key===match?.[2]);text.textContent=provider?`${provider.displayName} \xB7 ${field?.label||match[2]}${row.conflict?" (\uC774\uBBF8 \uC800\uC7A5\uB41C \uAC12 \uC788\uC74C)":""}`:row.dst,text.style.color=row.conflict?"var(--blessing-danger)":"var(--blessing-fg)",text.style.flex="1 1 auto",text.style.overflow="hidden",text.style.textOverflow="ellipsis",text.style.whiteSpace="nowrap",line.appendChild(text),list.appendChild(line)}root.appendChild(list),root.appendChild(createExplanation("\uC5B4\uB5A4 \uC800\uC7A5 \uD56D\uBAA9\uC73C\uB85C \uC62E\uACA8\uC9C0\uB098\uC694?",rows.map(row=>`${row.src} \u2192 ${row.dst}`).join(` | |
| `)));function updateCount(){let sel=checkboxes.filter(c=>c.checked).length;countLabel.textContent=`${sel} / ${checkboxes.length} \uC120\uD0DD\uB428`}return updateCount(),selectAllBtn.addEventListener("click",()=>{for(let c of checkboxes)c.checked=!0;updateCount()}),clearAllBtn.addEventListener("click",()=>{for(let c of checkboxes)c.checked=!1;updateCount()}),checkboxes}function renderCleanupSection(root){let h2=document.createElement("h2");h2.textContent="LBI \uC794\uC874 \uB370\uC774\uD130 \uC815\uB9AC \u2014 IDB \uC601\uC5ED",h2.style.marginTop="var(--blessing-gap-lg, 24px)",root.appendChild(h2),root.appendChild(createExplanation("\uB0A8\uC740 \uB370\uC774\uD130\uB97C \uC815\uB9AC\uD558\uBA74 \uBB34\uC5C7\uC774 \uC9C0\uC6CC\uC9C0\uB098\uC694?",`\uC678\uBD80 plugin \uC774 \uC81C\uAC70\uB418\uC5B4\uB3C4 storage \uC5D0 \uB0A8\uC740 internal \uB370\uC774\uD130 (\uC608: \uB2E8\uC77C ~289 MB \uC124\uC815 dump, ~134 KB+ \uD638\uCD9C \uB85C\uADF8 \uB204\uC801 \uB4F1) \uAC00 *\uB2E4\uB978 \uD50C\uB7EC\uADF8\uC778\uC774 storage \uB97C \uC2A4\uCE94\uD560 \uB54C* peak memory \uD3ED\uC99D\uC744 \uC720\uBC1C\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | |
| \xB7 \uBCF8 \uC815\uB9AC\uB294 \`lbi:idb:LBI:LBI:\` prefix \uC758 *\uBAA8\uB4E0 \uD0A4* \uB97C \uC0AD\uC81C\uD569\uB2C8\uB2E4 (IDB-emulation \uC601\uC5ED \uC804\uCCB4). | |
| \xB7 \uC0AC\uC6A9\uC790\uAC00 \uB9C8\uC774\uADF8\uB808\uC774\uC158\uD560 \uAC00\uCE58 \uC788\uB294 \uC2AC\uB86F \uB370\uC774\uD130\uB294 *\uBCC4\uB3C4 \uC601\uC5ED* \uC774\uB77C \uC190\uB300\uC9C0 \uC54A\uC74C. | |
| \xB7 *\uC601\uAD6C \uC0AD\uC81C* \u2014 \uBCF5\uAD6C \uBD88\uAC00. \uD574\uB2F9 \uC678\uBD80 plugin \uC744 \uB2E4\uC2DC \uC124\uCE58\uD574\uB3C4 \uADF8 \uB370\uC774\uD130\uB294 \uBE48 \uC0C1\uD0DC\uB85C \uC2DC\uC791. | |
| \xB7 \uBCF8 \uC791\uC5C5\uC740 \uD574\uB2F9 \uC678\uBD80 plugin \uC774 \uC81C\uAC70\uB41C \uC0C1\uD669 \uD55C\uC815 \u2014 \uD65C\uC131 \uC0AC\uC6A9 \uC911\uC774\uBA74 \uC815\uB9AC \uD6C4 \uADF8 plugin \uC758 \uB3D9\uC791\uC774 \uAE68\uC9C8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`));let card=document.createElement("div");card.className="blessing-card",root.appendChild(card);let warn=document.createElement("div");warn.style.fontSize="12px",warn.style.color="var(--blessing-fg-muted)",warn.style.lineHeight="1.7",warn.style.marginBottom="var(--blessing-gap)",warn.innerHTML="\uC678\uBD80 plugin \uC774 \uB0A8\uAE34 internal \uB370\uC774\uD130 \u2014 <code>lbi:idb:LBI:LBI:</code> prefix \uC758 \uBAA8\uB4E0 \uD0A4 (IDB-emulation \uC601\uC5ED \uC804\uCCB4) \uB97C \uC601\uAD6C \uC0AD\uC81C\uD569\uB2C8\uB2E4.<br>\uD574\uB2F9 \uC678\uBD80 plugin \uC758 \uCF54\uB4DC\uAC00 \uC2E4\uD589\uB418\uC9C0 \uC54A\uC73C\uB2C8 \uC790\uCCB4 \uC815\uB9AC \uBD88\uAC00 \u2014 \uD544\uC694\uD55C \uB370\uC774\uD130\uB294 \uBA3C\uC800 \uBC31\uC5C5\uD574 \uC8FC\uC138\uC694.<br><strong>\uD574\uB2F9 \uC678\uBD80 plugin \uC744 \uD65C\uC131 \uC0AC\uC6A9 \uC911\uC774\uBA74 \uBCF8 \uC815\uB9AC \uD6C4 \uADF8 plugin \uC774 \uAE68\uC9C8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.</strong> \uC644\uC804\uD788 \uC81C\uAC70\uB41C \uC0C1\uD0DC\uC5D0\uC11C\uB9CC \uC9C4\uD589.",card.appendChild(warn);let confirmRow=document.createElement("div");confirmRow.style.display="flex",confirmRow.style.alignItems="center",confirmRow.style.gap="var(--blessing-gap-sm)",confirmRow.style.marginBottom="var(--blessing-gap-sm)";let confirmLabel=document.createElement("span");confirmLabel.textContent="\uD655\uC778:",confirmLabel.style.fontSize="12px",confirmLabel.style.color="var(--blessing-fg-muted)",confirmRow.appendChild(confirmLabel);let confirmInput=document.createElement("input");confirmInput.type="text",confirmInput.placeholder="\uC0AD\uC81C \uB77C\uACE0 \uC785\uB825",confirmInput.style.flex="1",confirmInput.style.minWidth="0",confirmInput.style.padding="4px 8px",confirmInput.style.fontSize="12px",confirmInput.style.border="1px solid var(--blessing-border-soft)",confirmInput.style.borderRadius="var(--blessing-radius-sm)",confirmInput.style.background="transparent",confirmInput.style.color="var(--blessing-fg)",confirmInput.style.font="inherit",confirmRow.appendChild(confirmInput),card.appendChild(confirmRow);let purgeBtn=document.createElement("button");purgeBtn.type="button",purgeBtn.textContent="\uC601\uAD6C \uC0AD\uC81C",purgeBtn.disabled=!0,purgeBtn.style.padding="6px 14px",purgeBtn.style.borderRadius="var(--blessing-radius-sm)",purgeBtn.style.border="1px solid var(--blessing-danger)",purgeBtn.style.background="transparent",purgeBtn.style.color="var(--blessing-danger)",purgeBtn.style.cursor="pointer",purgeBtn.style.font="inherit",purgeBtn.style.opacity="0.4",confirmInput.addEventListener("input",()=>{let ready=confirmInput.value.trim()==="\uC0AD\uC81C";purgeBtn.disabled=!ready,purgeBtn.style.opacity=ready?"1":"0.4",purgeBtn.style.cursor=ready?"pointer":"not-allowed"});let status=document.createElement("div");status.style.marginTop="var(--blessing-gap-sm)",status.style.fontSize="12px",status.style.color="var(--blessing-fg-muted)",status.style.lineHeight="1.7",status.style.whiteSpace="pre-wrap",purgeBtn.addEventListener("click",()=>withButtonBusy(purgeBtn,async()=>{status.textContent="";let result=await purgeLargeLegacyInternals();result.ok&&result.removed===0?(status.textContent="\uC815\uB9AC \uB300\uC0C1\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC774\uBBF8 \uAE68\uB057\uD558\uAC70\uB098 \uB9E4\uCE58 \uD0A4\uAC00 \uCC98\uC74C\uBD80\uD130 \uC5C6\uC5C8\uC2B5\uB2C8\uB2E4).",status.style.color="var(--blessing-fg-muted)"):result.ok?(status.textContent=`\uC815\uB9AC \uC644\uB8CC: ${result.removed} \uAC1C \uD0A4 \uC0AD\uC81C. | |
| ${result.removedKeys.map(k=>`\xB7 ${k}`).join(` | |
| `)}`,status.style.color="var(--blessing-fg)",confirmInput.value="",purgeBtn.disabled=!0,purgeBtn.style.opacity="0.4"):(status.textContent=`\uC815\uB9AC \uC77C\uBD80 \uC2E4\uD328: \uC0AD\uC81C ${result.removed} \xB7 \uC2E4\uD328 ${result.failed} | |
| ${(result.errors||[]).map(e=>`\xB7 ${e}`).join(` | |
| `)}`,status.style.color="var(--blessing-danger)")},"\uC0AD\uC81C \uC911...").finally(()=>{let ready=!confirmInput.disabled&&confirmInput.value.trim()==="\uC0AD\uC81C";purgeBtn.disabled=!ready,purgeBtn.style.opacity=ready?"1":"0.4",purgeBtn.style.cursor=ready?"pointer":"not-allowed"})),card.appendChild(purgeBtn),card.appendChild(status),renderCustomCleanupCard(root)}function renderCustomCleanupCard(root){let subTitle=document.createElement("h2");subTitle.textContent="\uC9C1\uC811 \uC9C0\uC815\uD55C \uB370\uC774\uD130 \uC815\uB9AC",subTitle.style.marginTop="var(--blessing-gap-lg, 24px)",root.appendChild(subTitle),root.appendChild(createExplanation("\uC0AD\uC81C\uD560 \uD56D\uBAA9\uC744 \uC9C1\uC811 \uC9C0\uC815\uD558\uB824\uBA74 \uC5B4\uB5BB\uAC8C \uD558\uB098\uC694?",`\uC704 LBI \uC815\uB9AC \uB300\uC0C1 \uC678\uC758 \uC800\uC7A5 \uD56D\uBAA9\uC740 \uD328\uD134\uC744 \uC9C1\uC811 \uC785\uB825\uD558\uACE0 \uBBF8\uB9AC\uBCF4\uAE30\uB85C \uD655\uC778\uD55C \uB4A4 \uC815\uB9AC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. | |
| \xB7 \uBAA8\uB4DC: prefix (\uB2E8\uC21C \uC2DC\uC791 \uB9E4\uCE58) \uB610\uB294 regex (\uC815\uADDC\uC2DD). | |
| \xB7 prefix mode: \uCD5C\uC18C 3 \uAE00\uC790 \uC774\uC0C1. \uC608: \`xyz_\`, \`OLD_PLUGIN_\`. | |
| \xB7 regex mode: JavaScript \uC815\uADDC\uC2DD. \uC608: \`^foo_(v\\d+)?_\`. | |
| \xB7 \uC548\uC804\uD310: blessing.* \uC790\uCCB4 \uB370\uC774\uD130 \uB9E4\uCE58 \uCC28\uB2E8 (Blessing \uC790\uCCB4 \uB370\uC774\uD130 \uBCF4\uD638). | |
| \xB7 *\uBC18\uB4DC\uC2DC* [\uB9E4\uCE58 \uD0A4 \uBBF8\uB9AC\uBCF4\uAE30] \uB85C \uC5B4\uB5A4 \uD0A4\uAC00 \uC0AD\uC81C\uB420\uC9C0 \uD655\uC778 \uD6C4 \uC9C4\uD589. \uB108\uBB34 broad \uD55C \uD328\uD134\uC740 \uC0AC\uC6A9\uC790 \uCC45\uC784. | |
| \xB7 *\uC601\uAD6C \uC0AD\uC81C* \u2014 \uBCF5\uAD6C \uBD88\uAC00.`));let card=document.createElement("div");card.className="blessing-card",root.appendChild(card);let modeRow=document.createElement("div");modeRow.style.display="flex",modeRow.style.gap="var(--blessing-gap-sm)",modeRow.style.alignItems="center",modeRow.style.marginBottom="var(--blessing-gap-sm)";let modeLabel=document.createElement("span");modeLabel.textContent="\uBAA8\uB4DC:",modeLabel.style.fontSize="12px",modeLabel.style.color="var(--blessing-fg-muted)",modeRow.appendChild(modeLabel);let modeSelect=document.createElement("select");modeSelect.style.padding="4px 8px",modeSelect.style.fontSize="12px",modeSelect.style.border="1px solid var(--blessing-border-soft)",modeSelect.style.borderRadius="var(--blessing-radius-sm)",modeSelect.style.background="transparent",modeSelect.style.color="var(--blessing-fg)",modeSelect.style.font="inherit";let optPrefix=document.createElement("option");optPrefix.value="prefix",optPrefix.textContent="prefix (\uB2E8\uC21C \uC2DC\uC791 \uB9E4\uCE58, \uAD8C\uC7A5)";let optRegex=document.createElement("option");optRegex.value="regex",optRegex.textContent="regex (\uC815\uADDC\uC2DD, \uACE0\uAE09)",modeSelect.appendChild(optPrefix),modeSelect.appendChild(optRegex),modeRow.appendChild(modeSelect),card.appendChild(modeRow);let patternRow=document.createElement("div");patternRow.style.display="flex",patternRow.style.gap="var(--blessing-gap-sm)",patternRow.style.alignItems="center",patternRow.style.marginBottom="var(--blessing-gap-sm)";let patternLabel=document.createElement("span");patternLabel.textContent="\uD328\uD134:",patternLabel.style.fontSize="12px",patternLabel.style.color="var(--blessing-fg-muted)",patternRow.appendChild(patternLabel);let patternInput=document.createElement("input");patternInput.type="text",patternInput.placeholder="\uC608: OLD_PLUGIN_ (prefix) \uB610\uB294 ^foo_v\\d+_ (regex)",patternInput.style.flex="1",patternInput.style.minWidth="0",patternInput.style.padding="4px 8px",patternInput.style.fontSize="12px",patternInput.style.border="1px solid var(--blessing-border-soft)",patternInput.style.borderRadius="var(--blessing-radius-sm)",patternInput.style.background="transparent",patternInput.style.color="var(--blessing-fg)",patternInput.style.font="inherit",patternRow.appendChild(patternInput),card.appendChild(patternRow);let previewBtn=document.createElement("button");previewBtn.type="button",previewBtn.textContent="\uB9E4\uCE58 \uD0A4 \uBBF8\uB9AC\uBCF4\uAE30",previewBtn.style.padding="6px 14px",previewBtn.style.borderRadius="var(--blessing-radius-sm)",previewBtn.style.border="1px solid var(--blessing-border-soft)",previewBtn.style.background="transparent",previewBtn.style.color="var(--blessing-fg-muted)",previewBtn.style.cursor="pointer",previewBtn.style.font="inherit",previewBtn.style.marginRight="var(--blessing-gap-sm)",card.appendChild(previewBtn);let confirmRow=document.createElement("div");confirmRow.style.display="flex",confirmRow.style.alignItems="center",confirmRow.style.gap="var(--blessing-gap-sm)",confirmRow.style.marginTop="var(--blessing-gap-sm)";let confirmLabel=document.createElement("span");confirmLabel.textContent="\uD655\uC778:",confirmLabel.style.fontSize="12px",confirmLabel.style.color="var(--blessing-fg-muted)",confirmRow.appendChild(confirmLabel);let confirmInput=document.createElement("input");confirmInput.type="text",confirmInput.placeholder="\uC0AD\uC81C \uB77C\uACE0 \uC785\uB825",confirmInput.disabled=!0,confirmInput.style.flex="1",confirmInput.style.minWidth="0",confirmInput.style.padding="4px 8px",confirmInput.style.fontSize="12px",confirmInput.style.border="1px solid var(--blessing-border-soft)",confirmInput.style.borderRadius="var(--blessing-radius-sm)",confirmInput.style.background="transparent",confirmInput.style.color="var(--blessing-fg)",confirmInput.style.font="inherit",confirmInput.style.opacity="0.4",confirmRow.appendChild(confirmInput);let purgeBtn=document.createElement("button");purgeBtn.type="button",purgeBtn.textContent="\uC601\uAD6C \uC0AD\uC81C",purgeBtn.disabled=!0,purgeBtn.style.padding="6px 14px",purgeBtn.style.borderRadius="var(--blessing-radius-sm)",purgeBtn.style.border="1px solid var(--blessing-danger)",purgeBtn.style.background="transparent",purgeBtn.style.color="var(--blessing-danger)",purgeBtn.style.cursor="not-allowed",purgeBtn.style.font="inherit",purgeBtn.style.opacity="0.4",confirmRow.appendChild(purgeBtn),card.appendChild(confirmRow);let status=document.createElement("div");status.style.marginTop="var(--blessing-gap-sm)",status.style.fontSize="12px",status.style.color="var(--blessing-fg-muted)",status.style.lineHeight="1.7",status.style.whiteSpace="pre-wrap",status.style.wordBreak="break-all",card.appendChild(status);let lastValidatedTarget=null;function resetConfirmState(){confirmInput.value="",confirmInput.disabled=!0,confirmInput.style.opacity="0.4",purgeBtn.disabled=!0,purgeBtn.style.opacity="0.4",purgeBtn.style.cursor="not-allowed",lastValidatedTarget=null}modeSelect.addEventListener("change",resetConfirmState),patternInput.addEventListener("input",resetConfirmState),previewBtn.addEventListener("click",()=>withButtonBusy(previewBtn,async()=>{resetConfirmState(),status.textContent="";let mode=String(modeSelect.value||"prefix"),pattern=String(patternInput.value||"").trim(),validation=validateCustomCleanupPattern(mode,pattern);if(!validation.ok){status.textContent=`\uC548\uC804\uD310 \uAC70\uBD80: ${validation.error}`,status.style.color="var(--blessing-danger)";return}let target=buildCustomCleanupTarget(mode,pattern),result=await purgeCleanupTarget(target,{dryRun:!0});if(!result.ok){status.textContent=`\uBBF8\uB9AC\uBCF4\uAE30 \uC2E4\uD328: ${(result.errors||[]).join("; ")}`,status.style.color="var(--blessing-danger)";return}if(result.removedKeys.length===0){status.textContent="\uB9E4\uCE58\uB418\uB294 \uD0A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",status.style.color="var(--blessing-fg-muted)";return}status.textContent=`\uB9E4\uCE58 ${result.removedKeys.length} \uAC1C \uD0A4 (\uC544\uB798 \uD0A4\uB4E4\uC774 \uC0AD\uC81C \uB300\uC0C1): | |
| ${result.removedKeys.map(k=>`\xB7 ${k}`).join(` | |
| `)} | |
| \uD655\uC778 \uD6C4 "\uC0AD\uC81C" \uC785\uB825 + [\uC601\uAD6C \uC0AD\uC81C] \uC9C4\uD589.`,status.style.color="var(--blessing-fg)",lastValidatedTarget=target,confirmInput.disabled=!1,confirmInput.style.opacity="1"},"\uBBF8\uB9AC\uBCF4\uAE30 \uC911...")),confirmInput.addEventListener("input",()=>{let ready=lastValidatedTarget&&confirmInput.value.trim()==="\uC0AD\uC81C";purgeBtn.disabled=!ready,purgeBtn.style.opacity=ready?"1":"0.4",purgeBtn.style.cursor=ready?"pointer":"not-allowed"}),purgeBtn.addEventListener("click",()=>withButtonBusy(purgeBtn,async()=>{if(!lastValidatedTarget){status.textContent="\uBA3C\uC800 [\uB9E4\uCE58 \uD0A4 \uBBF8\uB9AC\uBCF4\uAE30] \uB97C \uB20C\uB7EC \uAC80\uC99D\uD574 \uC8FC\uC138\uC694.",status.style.color="var(--blessing-danger)";return}status.textContent="";let result=await purgeCleanupTarget(lastValidatedTarget);result.ok&&result.removed===0?(status.textContent="\uC815\uB9AC \uB300\uC0C1\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (preview \uC774\uD6C4 storage \uAC00 \uBC14\uB00C\uC5C8\uC744 \uC218 \uC788\uC74C).",status.style.color="var(--blessing-fg-muted)"):result.ok?(status.textContent=`\uC815\uB9AC \uC644\uB8CC: ${result.removed} \uAC1C \uD0A4 \uC0AD\uC81C. | |
| ${result.removedKeys.map(k=>`\xB7 ${k}`).join(` | |
| `)}`,status.style.color="var(--blessing-fg)",resetConfirmState()):(status.textContent=`\uC815\uB9AC \uC77C\uBD80 \uC2E4\uD328: \uC0AD\uC81C ${result.removed} \xB7 \uC2E4\uD328 ${result.failed} | |
| ${(result.errors||[]).map(e=>`\xB7 ${e}`).join(` | |
| `)}`,status.style.color="var(--blessing-danger)")},"\uC0AD\uC81C \uC911...").finally(()=>{let ready=!confirmInput.disabled&&confirmInput.value.trim()==="\uC0AD\uC81C";purgeBtn.disabled=!ready,purgeBtn.style.opacity=ready?"1":"0.4",purgeBtn.style.cursor=ready?"pointer":"not-allowed"}))}var LEGACY_SUBPREFIX=`${STORAGE_PREFIX}legacy.`,SECRET_KEY_HINTS=["apiKey","apikey","serviceAccountJson","credentials","token","secret"];function isLegacyKey(k){return k.startsWith(LEGACY_SUBPREFIX)}function looksLikeSecret(k){let lower=k.toLowerCase();return SECRET_KEY_HINTS.some(s=>lower.includes(s.toLowerCase()))}async function renderBackupTab(root){root.innerHTML="";let h2=document.createElement("h2");h2.textContent="\uBC31\uC5C5",root.appendChild(h2),root.appendChild(createExplanation("\uBC31\uC5C5 \uD30C\uC77C\uB85C \uBB34\uC5C7\uC744 \uC62E\uAE38 \uC218 \uC788\uB098\uC694?",`Blessing\uC774 \uC800\uC7A5\uD55C \uBAA8\uB4E0 \uC124\uC815\uC744 JSON\uC73C\uB85C \uB0B4\uBCF4\uB0B4\uAC70\uB098 \uBD88\uB7EC\uC635\uB2C8\uB2E4. | |
| \xB7 API \uD0A4 \uD3EC\uD568\uC744 \uCF1C\uBA74 \uD30C\uC77C\uC5D0\uC11C \uD0A4\uB97C \uC77D\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uC774 \uD30C\uC77C\uC740 \uAC1C\uC778 \uBCF4\uAD00\uC6A9\uC73C\uB85C \uC0AC\uC6A9\uD558\uC138\uC694. | |
| \xB7 \uBD88\uB7EC\uC624\uAE30\uB294 \uAE30\uBCF8\uC801\uC73C\uB85C \uCDA9\uB3CC \uD0A4\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uAE30 \uBAA8\uB4DC\uB85C \uBC14\uAFB8\uBA74 \uAE30\uC874 \uAC12\uC744 \uAD50\uCCB4\uD569\uB2C8\uB2E4. | |
| `));let expCard=document.createElement("div");expCard.className="blessing-card";{let title=document.createElement("div");title.style.fontWeight="600",title.style.marginBottom="var(--blessing-gap-sm)",title.textContent="\uB0B4\uBCF4\uB0B4\uAE30",expCard.appendChild(title);let opts=document.createElement("div");opts.style.display="flex",opts.style.flexDirection="column",opts.style.gap="4px",opts.style.marginBottom="var(--blessing-gap-sm)";let inclSecrets=mkCheckbox("API \uD0A4/\uC2DC\uD06C\uB9BF \uD3EC\uD568",!0,"\uCCB4\uD06C \uD574\uC81C \uC2DC apiKey/serviceAccountJson \uAC19\uC740 \uC2DC\uD06C\uB9BF \uD544\uB4DC\uB294 \uBE48 \uAC12\uC73C\uB85C \uB0B4\uBCF4\uB0C5\uB2C8\uB2E4. \uD0A4 \uC5C6\uC774 \uC124\uC815 \uAD6C\uC870\uB9CC \uACF5\uC720\uD560 \uB54C \uC720\uC6A9\uD569\uB2C8\uB2E4.");opts.appendChild(inclSecrets);let inclLegacy=mkCheckbox("\uAD6C\uBC84\uC804 \uD638\uD658 \uC124\uC815 \uD3EC\uD568",!1,"\uAD6C\uBC84\uC804 \uD638\uD658\uC6A9 blessing.legacy.* \uD0A4\uAE4C\uC9C0 \uD568\uAED8 \uB0B4\uBCF4\uB0C5\uB2C8\uB2E4. \uD3C9\uC18C\uC5D0\uB294 \uAEBC \uB450\uB294 \uD3B8\uC774 \uC548\uC804\uD569\uB2C8\uB2E4.");opts.appendChild(inclLegacy);let purpose=document.createElement("label");purpose.className="blessing-purpose",purpose.textContent="\uBC31\uC5C5 \uC6A9\uB3C4";let select=document.createElement("select");select.className="blessing-input",select.setAttribute("aria-label","\uBC31\uC5C5 \uC6A9\uB3C4");for(let[value,text]of[["personal","\uB0B4 \uAE30\uAE30\uB85C \uC62E\uAE30\uAE30 (API \uD0A4 \uD3EC\uD568)"],["share","\uC124\uC815\uB9CC \uACF5\uC720\uD558\uAE30 (API \uD0A4 \uC81C\uC678)"]]){let option=document.createElement("option");option.value=value,option.textContent=text,select.appendChild(option)}let purposeNote=document.createElement("p"),updatePurpose=()=>{select.value=inclSecrets.checked?"personal":"share",purposeNote.textContent=inclSecrets.checked?"API \uD0A4\uAC00 \uC77D\uC744 \uC218 \uC788\uB294 \uC0C1\uD0DC\uB85C \uD3EC\uD568\uB429\uB2C8\uB2E4. \uC774 \uD30C\uC77C\uC740 \uB2E4\uB978 \uC0AC\uB78C\uC5D0\uAC8C \uBCF4\uB0B4\uC9C0 \uB9C8\uC138\uC694.":"\uC54C\uB824\uC9C4 API \uD0A4\xB7\uC778\uC99D \uC815\uBCF4\uB294 \uBE44\uC6C1\uB2C8\uB2E4. \uC9C1\uC811 \uC785\uB825\uD55C \uC8FC\uC18C\uB098 \uCD94\uAC00 \uC124\uC815\uC5D0 \uBE44\uBC00\uAC12\uC774 \uC788\uB2E4\uBA74 \uACF5\uC720 \uC804\uC5D0 \uD30C\uC77C\uC744 \uD655\uC778\uD558\uC138\uC694."};select.addEventListener("change",()=>{inclSecrets.querySelector("input").checked=select.value==="personal",updatePurpose()}),inclSecrets.addEventListener("change",updatePurpose),updatePurpose(),purpose.appendChild(select),expCard.append(purpose,purposeNote);let options=createSection("\uD3EC\uD568\uD560 \uD56D\uBAA9\uC744 \uC9C1\uC811 \uACE0\uB974\uB824\uBA74");options.appendChild(opts),expCard.appendChild(options);let btnRow=document.createElement("div");btnRow.style.display="flex",btnRow.style.gap="var(--blessing-gap-sm)",btnRow.style.flexWrap="wrap";let dlBtn=document.createElement("button");dlBtn.type="button",dlBtn.textContent="JSON \uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC",styleAsPrimaryButton2(dlBtn),dlBtn.addEventListener("click",async()=>{await withButtonBusy(dlBtn,async()=>{let payload=await buildExport({includeSecrets:inclSecrets.checked,includeLegacy:inclLegacy.checked});downloadJson(payload),expStatus.textContent=`${Object.keys(payload.data).length}\uAC1C \uD0A4\uB97C JSON \uD30C\uC77C\uB85C \uB2E4\uC6B4\uB85C\uB4DC\uD588\uC2B5\uB2C8\uB2E4.`},"\uC218\uC9D1 \uC911...")}),btnRow.appendChild(dlBtn);let copyBtn=document.createElement("button");copyBtn.type="button",copyBtn.textContent="\uD074\uB9BD\uBCF4\uB4DC\uB85C \uBCF5\uC0AC",styleAsSecondaryButton3(copyBtn),copyBtn.addEventListener("click",async()=>{await withButtonBusy(copyBtn,async()=>{let payload=await buildExport({includeSecrets:inclSecrets.checked,includeLegacy:inclLegacy.checked}),text=JSON.stringify(payload,null,2);try{await navigator.clipboard.writeText(text),expStatus.textContent=`${Object.keys(payload.data).length}\uAC1C \uD0A4\uB97C \uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uD588\uC2B5\uB2C8\uB2E4 (${text.length} bytes).`}catch(err){expStatus.textContent="\uD074\uB9BD\uBCF4\uB4DC \uBCF5\uC0AC \uC2E4\uD328: "+(err?.message||String(err))+` | |
| \uC544\uB798 \uD14D\uC2A4\uD2B8 \uC0C1\uC790\uC5D0 \uCD9C\uB825\uD588\uC2B5\uB2C8\uB2E4.`,expPreview.style.display="",expPreview.value=text}},"\uC218\uC9D1 \uC911...")}),btnRow.appendChild(copyBtn),expCard.appendChild(btnRow);let expStatus=document.createElement("div");expStatus.style.fontSize="12px",expStatus.style.color="var(--blessing-fg-muted)",expStatus.style.marginTop="var(--blessing-gap-sm)",expStatus.style.whiteSpace="pre-wrap",expCard.appendChild(expStatus);let expPreview=document.createElement("textarea");styleAsCodeTextarea(expPreview),expPreview.style.display="none",expPreview.style.marginTop="var(--blessing-gap-sm)",expPreview.readOnly=!0,expCard.appendChild(expPreview)}root.appendChild(expCard);let impCard=document.createElement("div");impCard.className="blessing-card";{let title=document.createElement("div");title.style.fontWeight="600",title.style.marginBottom="var(--blessing-gap-sm)",title.textContent="\uBD88\uB7EC\uC624\uAE30",impCard.appendChild(title);let modeRow=document.createElement("div");modeRow.style.display="flex",modeRow.style.gap="var(--blessing-gap-sm)",modeRow.style.alignItems="center",modeRow.style.flexWrap="wrap",modeRow.style.marginBottom="var(--blessing-gap-sm)";let modeSkip=mkRadio("blessing-imp-mode","skip","\uAC74\uB108\uB6F0\uAE30 (\uAE30\uC874 \uAC12 \uBCF4\uC874)",!0),modeOver=mkRadio("blessing-imp-mode","overwrite","\uB36E\uC5B4\uC4F0\uAE30 (\uAE30\uC874 \uAC12 \uAD50\uCCB4)",!1);modeRow.appendChild(modeSkip.wrap),modeRow.appendChild(modeOver.wrap),impCard.appendChild(modeRow);let fileRow=document.createElement("div");fileRow.style.display="flex",fileRow.style.flexDirection="column",fileRow.style.gap="var(--blessing-gap-sm)";let fileInput=document.createElement("input");fileInput.type="file",fileInput.accept="application/json,.json,.txt",fileInput.style.display="none",fileRow.appendChild(fileInput);let dropZone=document.createElement("div");dropZone.setAttribute("role","button"),dropZone.setAttribute("tabindex","0"),dropZone.style.border="1.5px dashed var(--blessing-border)",dropZone.style.borderRadius="var(--blessing-radius-sm)",dropZone.style.padding="18px 14px",dropZone.style.textAlign="center",dropZone.style.cursor="pointer",dropZone.style.background="var(--blessing-bg-soft, transparent)",dropZone.style.transition="border-color 120ms, background 120ms",dropZone.style.userSelect="none";let dzTitle=document.createElement("div");dzTitle.style.fontSize="13px",dzTitle.style.fontWeight="500",dzTitle.textContent="JSON \uD30C\uC77C\uC744 \uB04C\uC5B4\uB2E4 \uB193\uAC70\uB098 \uD074\uB9AD\uD574\uC11C \uC120\uD0DD",dropZone.appendChild(dzTitle);let dzHint=document.createElement("div");dzHint.style.fontSize="11px",dzHint.style.color="var(--blessing-fg-muted)",dzHint.style.marginTop="4px",dzHint.textContent="blessing-config-*.json \xB7 \uCD5C\uB300 ~\uC218 MB",dropZone.appendChild(dzHint);let dzFile=document.createElement("div");dzFile.style.fontSize="12px",dzFile.style.marginTop="8px",dzFile.style.color="var(--blessing-accent, inherit)",dzFile.style.fontFamily="ui-monospace, monospace",dzFile.style.wordBreak="break-all",dropZone.appendChild(dzFile);let clearFileBtn=document.createElement("button");clearFileBtn.type="button",clearFileBtn.textContent="\uC120\uD0DD \uD574\uC81C",clearFileBtn.style.display="none",clearFileBtn.style.marginTop="8px",clearFileBtn.style.padding="3px 10px",clearFileBtn.style.fontSize="11px",clearFileBtn.style.background="transparent",clearFileBtn.style.color="var(--blessing-fg-muted)",clearFileBtn.style.border="1px solid var(--blessing-border-soft)",clearFileBtn.style.borderRadius="var(--blessing-radius-sm)",clearFileBtn.style.cursor="pointer",dropZone.appendChild(clearFileBtn);let updateFileLabel=()=>{let f=fileInput.files&&fileInput.files[0];f?(dzFile.textContent=`${f.name} (${formatBytes2(f.size)})`,clearFileBtn.style.display="inline-block",dzTitle.style.display="none",dzHint.style.display="none"):(dzFile.textContent="",clearFileBtn.style.display="none",dzTitle.style.display="",dzHint.style.display="")};dropZone.addEventListener("click",e=>{e.target!==clearFileBtn&&fileInput.click()}),dropZone.addEventListener("keydown",e=>{(e.key==="Enter"||e.key===" ")&&(e.preventDefault(),fileInput.click())});let onDragOver=e=>{e.preventDefault(),dropZone.style.borderColor="var(--blessing-accent)",dropZone.style.background="var(--blessing-accent-soft, var(--blessing-bg-soft, transparent))"},onDragLeave=()=>{dropZone.style.borderColor="var(--blessing-border)",dropZone.style.background="var(--blessing-bg-soft, transparent)"};dropZone.addEventListener("dragover",onDragOver),dropZone.addEventListener("dragenter",onDragOver),dropZone.addEventListener("dragleave",onDragLeave),dropZone.addEventListener("drop",e=>{e.preventDefault(),onDragLeave();let f=e.dataTransfer?.files?.[0];if(f){try{let dt=new DataTransfer;dt.items.add(f),fileInput.files=dt.files}catch{}updateFileLabel()}}),fileInput.addEventListener("change",updateFileLabel),clearFileBtn.addEventListener("click",e=>{e.stopPropagation(),fileInput.value="",updateFileLabel()}),fileRow.appendChild(dropZone),impCard.appendChild(fileRow);let orLabel=document.createElement("div");orLabel.textContent="\uB610\uB294 JSON \uD14D\uC2A4\uD2B8 \uC9C1\uC811 \uBD99\uC5EC\uB123\uAE30",orLabel.style.fontSize="12px",orLabel.style.color="var(--blessing-fg-muted)",orLabel.style.margin="var(--blessing-gap) 0 4px 0",impCard.appendChild(orLabel);let ta=document.createElement("textarea");ta.setAttribute("aria-label","\uBC31\uC5C5 JSON \uBD99\uC5EC\uB123\uAE30"),ta.placeholder='{ "version":"1.2.2", "data": { "blessing.provider...": "..." } }',styleAsCodeTextarea(ta),impCard.appendChild(ta);let runBtn=document.createElement("button");runBtn.type="button",runBtn.textContent="\uBD88\uB7EC\uC624\uAE30 \uC2E4\uD589",styleAsPrimaryButton2(runBtn),runBtn.style.marginTop="var(--blessing-gap-sm)";let impStatus=document.createElement("div");impStatus.style.fontSize="12px",impStatus.style.color="var(--blessing-fg-muted)",impStatus.style.marginTop="var(--blessing-gap-sm)",impStatus.style.whiteSpace="pre-wrap",runBtn.addEventListener("click",async()=>{await withButtonBusy(runBtn,async()=>{impStatus.textContent="";let text=ta.value.trim();if(!text&&fileInput.files&&fileInput.files[0]&&(text=await fileInput.files[0].text()),!text){impStatus.textContent="\uD30C\uC77C\uC744 \uC120\uD0DD\uD558\uAC70\uB098 JSON\uC744 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.";return}let parsed;try{parsed=JSON.parse(text)}catch{impStatus.textContent="\uBC31\uC5C5 \uB0B4\uC6A9\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. Blessing\uC5D0\uC11C \uB0B4\uBCF4\uB0B8 JSON \uD30C\uC77C\uC778\uC9C0, \uBCF5\uC0AC\uD55C \uB0B4\uC6A9\uC774 \uC798\uB9AC\uC9C0 \uC54A\uC558\uB294\uC9C0 \uD655\uC778\uD574 \uC8FC\uC138\uC694.";return}let data=parsed?.data;if(!data||typeof data!="object"||Array.isArray(data)){impStatus.textContent="Blessing \uBC31\uC5C5 \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4. LBI\uC5D0\uC11C \uB0B4\uBCF4\uB0B8 \uD30C\uC77C\uC740 \u2018\uB9C8\uC774\uADF8\uB808\uC774\uC158\u2019 \uD0ED\uC5D0\uC11C \uAC00\uC838\uC640 \uC8FC\uC138\uC694.";return}let mode=modeOver.input.checked?"overwrite":"skip",res=await applyImport(data,mode);impStatus.textContent=`\uC801\uC6A9 ${res.applied}\uAC1C, \uAC74\uB108\uB700 ${res.skipped}\uAC1C, \uBB34\uC2DC ${res.ignored}\uAC1C. | |
| \uC124\uC815 \uD0ED\uC744 \uB2E4\uC2DC \uC5F4\uBA74 \uC0C8 \uAC12\uC774 \uBCF4\uC785\uB2C8\uB2E4. \uCC44\uD305 \uBAA8\uB378 \uBAA9\uB85D\uC740 \uD638\uC2A4\uD2B8\uB97C \uC0C8\uB85C\uACE0\uCE68\uD558\uBA74 \uBC18\uC601\uB429\uB2C8\uB2E4.`},"\uC801\uC6A9 \uC911...")}),impCard.appendChild(runBtn),impCard.appendChild(impStatus)}root.appendChild(impCard)}async function buildExport({includeSecrets,includeLegacy}){let storage=getStorage(),allKeys=await storage.keys()||[],data={};for(let k of allKeys){if(!String(k).startsWith(STORAGE_PREFIX)||!includeLegacy&&isLegacyKey(k))continue;let v=await storage.getItem(k);if(!includeSecrets&&looksLikeSecret(k)){data[k]="";continue}data[k]=v}return{kind:"blessing-config",version:"1.2.2",exportedAt:new Date().toISOString(),data}}async function applyImport(data,mode){let storage=getStorage(),applied=0,skipped=0,ignored=0;for(let[k,v]of Object.entries(data)){if(!String(k).startsWith(STORAGE_PREFIX)){ignored++;continue}if(mode==="skip"){let existing=await storage.getItem(k);if(existing!=null&&existing!==""){skipped++;continue}}try{await storage.setItem(k,v),applied++}catch{ignored++}}return{applied,skipped,ignored}}function downloadJson(payload){let text=JSON.stringify(payload,null,2),blob=new Blob([text],{type:"application/json"}),url=URL.createObjectURL(blob),a=document.createElement("a"),stamp=new Date().toISOString().slice(0,10).replace(/-/g,"");a.href=url,a.download=`blessing-config-${stamp}.json`,document.body.appendChild(a),a.click(),setTimeout(()=>{document.body.removeChild(a),URL.revokeObjectURL(url)},0)}function mkCheckbox(label,checked,helpText){let wrap=document.createElement("div"),row=document.createElement("label");row.className="blessing-check-row";let cb=document.createElement("input");cb.type="checkbox",cb.checked=!!checked;let span=document.createElement("span");return span.textContent=label,row.append(cb,span),wrap.appendChild(row),helpText&&wrap.appendChild(createExplanation(label==="API \uD0A4/\uC2DC\uD06C\uB9BF \uD3EC\uD568"?"API \uD0A4\uB97C \uBE7C\uACE0 \uACF5\uC720\uD558\uB824\uBA74 \uC5B4\uB5BB\uAC8C \uD558\uB098\uC694?":"\uAD6C\uBC84\uC804 \uD638\uD658 \uC124\uC815\uB3C4 \uD544\uC694\uD55C\uAC00\uC694?",helpText)),Object.defineProperty(wrap,"checked",{get:()=>cb.checked}),wrap}function mkRadio(name,value,label,checked){let wrap=document.createElement("label");wrap.style.display="inline-flex",wrap.style.alignItems="center",wrap.style.gap="4px",wrap.style.fontSize="12px";let inp=document.createElement("input");inp.type="radio",inp.name=name,inp.value=value,inp.checked=!!checked,wrap.appendChild(inp);let span=document.createElement("span");return span.textContent=label,wrap.appendChild(span),{wrap,input:inp}}function styleAsPrimaryButton2(btn){btn.style.padding="6px 14px",btn.style.background="var(--blessing-accent)",btn.style.color="#fff",btn.style.border="1px solid var(--blessing-accent)",btn.style.borderRadius="var(--blessing-radius-sm)",btn.style.cursor="pointer",btn.style.font="inherit",btn.style.fontSize="12px"}function styleAsSecondaryButton3(btn){btn.style.padding="6px 14px",btn.style.background="transparent",btn.style.color="var(--blessing-fg)",btn.style.border="1px solid var(--blessing-border)",btn.style.borderRadius="var(--blessing-radius-sm)",btn.style.cursor="pointer",btn.style.font="inherit",btn.style.fontSize="12px"}function styleAsCodeTextarea(ta){ta.style.width="100%",ta.style.boxSizing="border-box",ta.style.minHeight="140px",ta.style.padding="10px 12px",ta.style.font="12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",ta.style.lineHeight="1.55",ta.style.color="var(--blessing-fg)",ta.style.background="var(--blessing-bg-soft, transparent)",ta.style.border="1px solid var(--blessing-border)",ta.style.borderRadius="var(--blessing-radius-sm)",ta.style.outline="none",ta.style.resize="vertical",ta.style.tabSize="2",ta.spellcheck=!1,ta.addEventListener("focus",()=>{ta.style.borderColor="var(--blessing-accent)"}),ta.addEventListener("blur",()=>{ta.style.borderColor="var(--blessing-border)"})}function formatBytes2(n){return!Number.isFinite(n)||n<0?"":n<1024?n+" B":n<1024*1024?(n/1024).toFixed(1)+" KB":(n/1024/1024).toFixed(2)+" MB"}function renderAboutTab(root){root.innerHTML="";let h2=document.createElement("h2");h2.textContent="\uC815\uBCF4",root.appendChild(h2);let p=document.createElement("p");p.textContent=`${BLESSING_DISPLAY_NAME} (\uBE14\uB808\uC2F1) v${BLESSING_VERSION}`,root.appendChild(p);let caps=getCapabilities(),card=document.createElement("div");card.className="blessing-card";let rows=[["\uC124\uCE58 \uB300\uC0C1",BUILD_TARGET_LABEL],["\uCC44\uD305 \uC751\uB2F5 \uD45C\uC2DC",caps.supportsStreamingReturn?"\uBC1B\uB294 \uC911 \uD45C\uC2DC":"\uC644\uB8CC \uD6C4 \uD55C \uBC88\uC5D0 \uD45C\uC2DC"],["\uC785\uB825 \uD658\uACBD",caps.isMobileLike?"\uBAA8\uBC14\uC77C/\uD130\uCE58":"\uB370\uC2A4\uD06C\uD1B1"]];for(let[k,v]of rows){let row=document.createElement("div");row.style.display="flex",row.style.justifyContent="space-between",row.style.padding="4px 0";let left=document.createElement("span");left.textContent=k,left.style.color="var(--blessing-fg-muted)";let right=document.createElement("span");right.textContent=v,row.appendChild(left),row.appendChild(right),card.appendChild(row)}root.appendChild(card),root.appendChild(createExplanation("\uBB38\uC81C\uB97C \uBB38\uC758\uD560 \uB54C \uC5B4\uB5A4 \uC815\uBCF4\uB97C \uC54C\uB824 \uB4DC\uB9AC\uBA74 \uB418\uB098\uC694?",`\uC124\uCE58 \uB300\uC0C1\uACFC \uC704 \uBC84\uC804, \uBB38\uC81C\uAC00 \uC0DD\uAE34 \uC11C\uBE44\uC2A4 \uC774\uB984\uC744 \uC54C\uB824 \uC8FC\uC138\uC694. API \uD0A4\uB294 \uBCF4\uB0B4\uC9C0 \uB9C8\uC138\uC694. | |
| \uD638\uC2A4\uD2B8 \uC9C4\uB2E8: V3 API ${caps.hasV3?"\uC0AC\uC6A9 \uAC00\uB2A5":"\uAC10\uC9C0\uB418\uC9C0 \uC54A\uC74C"} \xB7 V2 API ${caps.hasV2?"\uAC10\uC9C0\uB428":"\uC5C6\uC74C"}`))}var openOverlay=null,TABS=[{id:"providers",label:"\uC124\uC815",iconName:"cog",render:renderProvidersTab},{id:"insights",label:"\uC778\uC0AC\uC774\uD2B8",iconName:"chart",render:renderInsightsTab},{id:"import",label:"\uB9C8\uC774\uADF8\uB808\uC774\uC158",iconName:"arrowDownTray",render:renderImportTab},{id:"backup",label:"\uBC31\uC5C5",iconName:"archiveBox",render:renderBackupTab},{id:"about",label:"\uC815\uBCF4",iconName:"info",render:renderAboutTab}];function openBlessingPanel(initialTab="providers"){if(!injectStyles()){try{let host=globalThis.risuai;host&&typeof host.log=="function"&&host.log("[Blessing] DOM\uC774 \uC0AC\uC6A9 \uBD88\uAC00\uB2A5\uD55C \uD658\uACBD\uC785\uB2C8\uB2E4. \uD328\uB110\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.")}catch{}return}try{let host=getHost();host&&typeof host.showContainer=="function"&&host.showContainer("fullscreen")}catch{}if(openOverlay)return;let doc=document,overlay=doc.createElement("div");overlay.className="blessing-overlay",overlay.addEventListener("click",e=>{e.target===overlay&&closePanel()});let panel=doc.createElement("div");panel.className="blessing-panel",panel.setAttribute("role","dialog"),panel.setAttribute("aria-modal","true"),panel.setAttribute("aria-label",BLESSING_DISPLAY_NAME);let header=doc.createElement("div");header.className="blessing-header";let brand=doc.createElement("div");brand.className="blessing-header__brand",brand.innerHTML=icon("sparkles");let brandText=doc.createElement("span");brandText.textContent=BLESSING_DISPLAY_NAME,brand.appendChild(brandText);let subtitle=doc.createElement("span");subtitle.className="blessing-header__subtitle",subtitle.textContent=BLESSING_SUBTITLE,brand.appendChild(subtitle),header.appendChild(brand);let spacer=doc.createElement("div");spacer.className="blessing-header__spacer",header.appendChild(spacer);let closeBtn=doc.createElement("button");closeBtn.type="button",closeBtn.className="blessing-iconbtn",closeBtn.setAttribute("aria-label","\uB2EB\uAE30"),closeBtn.innerHTML=icon("xMark"),closeBtn.addEventListener("click",closePanel),header.appendChild(closeBtn),panel.appendChild(header);let body=doc.createElement("div");body.className="blessing-body";let tabs=doc.createElement("nav");tabs.className="blessing-tabs",tabs.setAttribute("aria-label","Blessing \uD0ED");let content=doc.createElement("section");content.className="blessing-content";let tabButtons={},tabRoots={},tabBuilt={},renderGen=0,currentTabId="",activateTab=id=>{renderGen+=1;let myGen=renderGen,t=TABS.find(x=>x.id===id)||TABS[0];for(let tid of Object.keys(tabButtons))tabButtons[tid].classList.toggle("is-active",tid===t.id);for(let tid of Object.keys(tabRoots))tabRoots[tid].style.display="none";currentTabId=t.id;let subroot=tabRoots[t.id];if(subroot){subroot.style.display="";return}subroot=doc.createElement("div"),tabRoots[t.id]=subroot,content.appendChild(subroot);let loadingHost=doc.createElement("div");loadingHost.style.padding="var(--blessing-gap-lg)",loadingHost.appendChild(inlineBusy("\uD0ED\uC744 \uBD88\uB7EC\uC624\uB294 \uC911...")),subroot.appendChild(loadingHost);let clearLoading=()=>{loadingHost.parentNode===subroot&&subroot.removeChild(loadingHost)},finish=()=>{tabBuilt[t.id]=!0,clearLoading()};try{let ret=t.render(subroot);ret&&typeof ret.then=="function"?ret.then(finish,err=>{clearLoading(),myGen===renderGen&¤tTabId===t.id&&renderTabError(subroot,err)}):finish()}catch(err){clearLoading(),myGen===renderGen&¤tTabId===t.id&&renderTabError(subroot,err)}};for(let t of TABS){let btn=doc.createElement("button");btn.type="button",btn.className="blessing-tab",btn.dataset.tab=t.id,btn.innerHTML=icon(t.iconName);let lbl=doc.createElement("span");lbl.textContent=t.label,btn.appendChild(lbl),btn.addEventListener("click",()=>activateTab(t.id)),tabs.appendChild(btn),tabButtons[t.id]=btn}body.appendChild(tabs),body.appendChild(content),panel.appendChild(body),overlay.appendChild(panel),doc.body.appendChild(overlay),openOverlay=overlay,doc.addEventListener("keydown",escClose,!0),activateTab(initialTab)}function escClose(e){e.key==="Escape"&&closePanel()}function renderTabError(content,err){content.textContent="";let msg=document.createElement("p");msg.textContent="\uD0ED\uC744 \uADF8\uB9AC\uB294 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4: "+(err&&err.message?err.message:String(err)),content.appendChild(msg)}function closePanel(){openOverlay&&openOverlay.parentNode&&openOverlay.parentNode.removeChild(openOverlay),document.removeEventListener("keydown",escClose,!0),openOverlay=null;try{let host=getHost();host&&typeof host.hideContainer=="function"&&host.hideContainer()}catch{}}function classifyResponse(text,outcome){return outcome?.ok===!1?{ok:!1,error:outcome.error||"\uC751\uB2F5 \uCC98\uB9AC \uC2E4\uD328"}:String(text??"").trim()?{ok:!0}:{ok:!1,error:"\uBE48 \uC751\uB2F5"}}(async()=>{let host=getHost();if(!host)return;let caps=getCapabilities();if(!caps.hasV3){try{host&&typeof host.log=="function"&&host.log(`[Blessing ${BLESSING_VERSION}] V3 API \uD45C\uBA74\uC774 \uAC10\uC9C0\uB418\uC9C0 \uC54A\uC544 \uD65C\uC131\uD654\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`)}catch{}return}try{await host.registerSetting(BLESSING_DISPLAY_NAME,()=>openBlessingPanel("providers"),buttonIcon("sparkles"),"html",BLESSING_SETTINGS_ID)}catch(err){try{host.log&&host.log("[Blessing] registerSetting \uC2E4\uD328:",err)}catch{}}let visibleIds=await loadVisibleProviderIds().catch(()=>new Set),submodelLists=await Promise.all(PROVIDERS.map(p=>visibleIds.has(p.id)?loadSubmodels(p.id).catch(()=>[]):Promise.resolve([]))),baseModelNames=await Promise.all(PROVIDERS.map(p=>{if(!visibleIds.has(p.id))return Promise.resolve("");let modelField=p.schema&&p.schema.find(f=>f.key==="model"),fallback=modelField?String(modelField.default??""):"";return getProviderSetting(p.id,"model",fallback).then(v=>String(v||"").trim()||fallback,()=>fallback)})),registrationTasks=[];for(let i=0;i<PROVIDERS.length;i++){let provider=PROVIDERS[i];if(!visibleIds.has(provider.id))continue;let baseModel=baseModelNames[i],userSubmodels=submodelLists[i]||[],catalogModels=getCatalogModels(provider.id),seen=new Set,merged=[];for(let sm of userSubmodels)!sm||typeof sm.model!="string"||!sm.model||seen.has(sm.model)||(seen.add(sm.model),merged.push({label:sm.label,model:sm.model}));for(let model of catalogModels)seen.has(model)||(seen.add(model),merged.push({label:void 0,model}));if(merged.length===0){let baseDisplay=baseModel?`[Blessing] ${provider.displayName} \xB7 ${baseModel}`:`[Blessing] ${provider.displayName}`;registrationTasks.push(registerProviderEntry(host,caps,provider,baseDisplay,void 0));continue}for(let sm of merged){let labelText=sm.label&&sm.label.trim()?sm.label.trim():sm.model,subDisplay=`[Blessing] ${provider.displayName} \xB7 ${labelText}`;registrationTasks.push(registerProviderEntry(host,caps,provider,subDisplay,sm.model))}}await Promise.all(registrationTasks);try{await host.onUnload(()=>{try{closePanel()}catch{}try{flushNow()}catch{}})}catch{}})();async function registerProviderEntry(host,caps,provider,displayName,modelOverride){try{await host.addProvider(displayName,async(pluginRequest,abortSignal)=>{let startedAt=Date.now(),bytes=0;try{let stream=provider.run(pluginRequest,abortSignal,modelOverride?{modelOverride}:void 0),counted=countingStream(stream,n=>{bytes+=n});if(caps.supportsStreamingReturn)return{success:!0,content:withCompletion(counted,(okFlag,errMsg)=>{recordEvent({providerId:provider.id,startedAt,endedAt:Date.now(),ok:okFlag,bytes,error:errMsg})},()=>getStreamOutcome(stream))};let text=await collectStreamToString(counted,abortSignal),cls=classifyResponse(text,getStreamOutcome(stream));recordEvent({providerId:provider.id,startedAt,endedAt:Date.now(),ok:cls.ok,bytes,error:cls.error});let content=!cls.ok&&!text.includes(cls.error)?[text,cls.error].filter(Boolean).join(` | |
| `):text;return{success:cls.ok,content}}catch(err){let msg=err?.message||String(err);return recordEvent({providerId:provider.id,startedAt,endedAt:Date.now(),ok:!1,bytes,error:msg}),{success:!1,content:`[Blessing/${provider.id}] ${msg}`}}})}catch(err){try{host.log&&host.log(`[Blessing] addProvider \uC2E4\uD328 (${displayName}):`,err)}catch{}}}function countingStream(src,onBytes){let reader=src.getReader();return new ReadableStream({async pull(controller){let{value,done}=await reader.read();if(done){controller.close();return}if(typeof value=="string")try{onBytes(value.length)}catch{}else if(value&&value.byteLength!=null)try{onBytes(value.byteLength)}catch{}controller.enqueue(value)},cancel(reason){try{reader.cancel(reason)}catch{}}})}function withCompletion(src,onComplete,outcome){let reader=src.getReader(),HEAD_LIMIT=512,headAccumulated="";return new ReadableStream({async pull(controller){try{let{value,done}=await reader.read();if(done){controller.close();let cls=classifyResponse(headAccumulated,outcome?.());try{onComplete(cls.ok,cls.error)}catch{}return}headAccumulated.length<HEAD_LIMIT&&typeof value=="string"&&(headAccumulated+=value,headAccumulated.length>HEAD_LIMIT&&(headAccumulated=headAccumulated.slice(0,HEAD_LIMIT))),controller.enqueue(value)}catch(err){try{onComplete(!1,err?.message||String(err))}catch{}controller.error(err)}},cancel(reason){try{reader.cancel(reason)}catch{}try{onComplete(!1,"cancelled")}catch{}}})}async function collectStreamToString(stream,abortSignal){let reader=stream.getReader(),buf="";try{for(;;){if(abortSignal?.aborted)throw new Error("aborted");let{value,done}=await reader.read();if(done)break;typeof value=="string"?buf+=value:value!=null&&(buf+=String(value))}}finally{try{reader.releaseLock()}catch{}}return buf}})(); | |