@aituber-onair/chat
Version:
Chat and LLM API integration library for AITuber OnAir
20 lines (16 loc) • 39.7 kB
JavaScript
var AITuberOnAirChat=(()=>{var fe=Object.defineProperty;var Ne=Object.getOwnPropertyDescriptor;var Ae=Object.getOwnPropertyNames;var ke=Object.prototype.hasOwnProperty;var Ge=(d,e)=>{for(var t in e)fe(d,t,{get:e[t],enumerable:!0})},Ue=(d,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Ae(e))!ke.call(d,s)&&s!==t&&fe(d,s,{get:()=>e[s],enumerable:!(o=Ne(e,s))||o.enumerable});return d};var Ve=d=>Ue(fe({},"__esModule",{value:!0}),d);var Ke={};Ge(Ke,{CHAT_RESPONSE_LENGTH:()=>b,CLAUDE_VISION_SUPPORTED_MODELS:()=>J,ChatServiceFactory:()=>x,ChatServiceHttpClient:()=>y,ClaudeChatService:()=>U,ClaudeChatServiceProvider:()=>V,DEFAULT_MAX_TOKENS:()=>xe,DEFAULT_SUMMARY_PROMPT_TEMPLATE:()=>We,DEFAULT_VISION_PROMPT:()=>He,EMOTION_TAG_CLEANUP_REGEX:()=>Oe,EMOTION_TAG_REGEX:()=>De,ENDPOINT_CLAUDE_API:()=>Ce,ENDPOINT_GEMINI_API:()=>ve,ENDPOINT_OPENAI_CHAT_COMPLETIONS_API:()=>R,ENDPOINT_OPENAI_RESPONSES_API:()=>S,ENDPOINT_OPENROUTER_API:()=>Se,EmotionParser:()=>j,GEMINI_VISION_SUPPORTED_MODELS:()=>z,GPT5_PRESETS:()=>Te,GPT_5_MODELS:()=>we,GeminiChatService:()=>k,GeminiChatServiceProvider:()=>G,HttpError:()=>P,MAX_TOKENS_BY_LENGTH:()=>Re,MODEL_CLAUDE_3_5_HAIKU:()=>ce,MODEL_CLAUDE_3_5_SONNET:()=>pe,MODEL_CLAUDE_3_7_SONNET:()=>he,MODEL_CLAUDE_3_HAIKU:()=>M,MODEL_CLAUDE_4_OPUS:()=>me,MODEL_CLAUDE_4_SONNET:()=>ue,MODEL_GEMINI_1_5_FLASH:()=>ae,MODEL_GEMINI_1_5_PRO:()=>le,MODEL_GEMINI_2_0_FLASH:()=>ie,MODEL_GEMINI_2_0_FLASH_LITE:()=>T,MODEL_GEMINI_2_5_FLASH:()=>se,MODEL_GEMINI_2_5_FLASH_LITE:()=>re,MODEL_GEMINI_2_5_FLASH_LITE_PREVIEW_06_17:()=>ne,MODEL_GEMINI_2_5_PRO:()=>oe,MODEL_GPT_4O:()=>ee,MODEL_GPT_4O_MINI:()=>w,MODEL_GPT_4_1:()=>X,MODEL_GPT_4_1_MINI:()=>Q,MODEL_GPT_4_1_NANO:()=>Z,MODEL_GPT_4_5_PREVIEW:()=>te,MODEL_GPT_5:()=>W,MODEL_GPT_5_CHAT_LATEST:()=>$,MODEL_GPT_5_MINI:()=>H,MODEL_GPT_5_NANO:()=>D,MODEL_GPT_OSS_20B_FREE:()=>O,MODEL_O1:()=>ye,MODEL_O1_MINI:()=>_e,MODEL_O3_MINI:()=>ge,OPENROUTER_CREDITS_THRESHOLD:()=>je,OPENROUTER_FREE_DAILY_LIMIT_HIGH_CREDITS:()=>Be,OPENROUTER_FREE_DAILY_LIMIT_LOW_CREDITS:()=>Fe,OPENROUTER_FREE_MODELS:()=>K,OPENROUTER_FREE_RATE_LIMIT_PER_MINUTE:()=>Me,OPENROUTER_VISION_SUPPORTED_MODELS:()=>be,OpenAIChatService:()=>N,OpenAIChatServiceProvider:()=>A,OpenRouterChatService:()=>F,OpenRouterChatServiceProvider:()=>B,StreamTextAccumulator:()=>v,VISION_SUPPORTED_MODELS:()=>q,getMaxTokensForResponseLength:()=>E,installGASFetch:()=>Le,isGPT5Model:()=>I,isOpenRouterFreeModel:()=>Ee,isOpenRouterVisionModel:()=>L,runOnceText:()=>Je,screenplayToText:()=>ze,textToScreenplay:()=>Ie,textsToScreenplay:()=>qe});var R="https://api.openai.com/v1/chat/completions",S="https://api.openai.com/v1/responses",D="gpt-5-nano",H="gpt-5-mini",W="gpt-5",$="gpt-5-chat-latest",X="gpt-4.1",Q="gpt-4.1-mini",Z="gpt-4.1-nano",w="gpt-4o-mini",ee="gpt-4o",ge="o3-mini",_e="o1-mini",ye="o1",te="gpt-4.5-preview",q=[D,H,W,$,X,Q,Z,w,ee,te,"o1"],we=[D,H,W,$];function I(d){return we.includes(d)}var ve="https://generativelanguage.googleapis.com",oe="gemini-2.5-pro",se="gemini-2.5-flash",re="gemini-2.5-flash-lite",ne="gemini-2.5-flash-lite-preview-06-17",ie="gemini-2.0-flash",T="gemini-2.0-flash-lite",ae="gemini-1.5-flash",le="gemini-1.5-pro",z=[oe,se,re,ne,ie,T,ae,le];var Ce="https://api.anthropic.com/v1/messages",M="claude-3-haiku-20240307",ce="claude-3-5-haiku-20241022",pe="claude-3-5-sonnet-20241022",he="claude-3-7-sonnet-20250219",ue="claude-4-sonnet-20250514",me="claude-4-opus-20250514",J=[M,ce,pe,he,ue,me];var Se="https://openrouter.ai/api/v1/chat/completions",O="openai/gpt-oss-20b:free",K=[O],be=[],Me=20,Fe=50,Be=1e3,je=10;function Ee(d){return K.some(e=>d.includes(e))}function L(d){return be.some(e=>d.includes(e))}var b={VERY_SHORT:"veryShort",SHORT:"short",MEDIUM:"medium",LONG:"long",VERY_LONG:"veryLong",DEEP:"deep"},Re={[b.VERY_SHORT]:40,[b.SHORT]:100,[b.MEDIUM]:200,[b.LONG]:300,[b.VERY_LONG]:1e3,[b.DEEP]:5e3},xe=5e3,Te={casual:{reasoning_effort:"minimal",verbosity:"low",description:"Fast responses for casual chat, quick questions (GPT-4 like experience)"},balanced:{reasoning_effort:"medium",verbosity:"medium",description:"Balanced reasoning for business tasks, learning, general problem solving"},expert:{reasoning_effort:"high",verbosity:"high",description:"Deep reasoning for research, complex analysis, expert-level tasks"}};function E(d){return d?Re[d]??xe:xe}var He="You are a friendly AI avatar. Comment on the situation based on the broadcast screen.",We=`You are a skilled summarizing assistant.
Analyze the following conversation and produce a summary in the **same language** as the majority of the conversation:
- Summaries should highlight key points
- Stay concise (around {maxLength} characters if possible)
- No redundant expressions
If the conversation is in Japanese, summarize in Japanese.
If it's in English, summarize in English.
If it's in another language, summarize in that language.
`;var v=class{static append(e,t){if(!t)return;let o=e[e.length-1];o&&o.type==="text"?o.text+=t:e.push({type:"text",text:t})}static getFullText(e){return e.filter(t=>t.type==="text").map(t=>t.text).join("")}static addTextBlock(e,t){t&&e.push({type:"text",text:t})}};var P=class extends Error{constructor(t,o,s){super(`HTTP ${t}: ${o}`);this.status=t;this.statusText=o;this.body=s;this.name="HttpError"}},Y=class Y{static setFetch(e){this.fetchImpl=e}static async post(e,t,o={},s={}){let{timeout:r=3e4,retries:n=0,retryDelay:i=1e3}=s,h={...{"Content-Type":"application/json"},...o},p=null;for(let c=0;c<=n;c++)try{let u=typeof AbortController<"u",a=u?new AbortController:void 0,m=u?setTimeout(()=>a.abort(),r):void 0,f=await Y.fetchImpl(e,{method:"POST",headers:h,body:typeof t=="string"?t:JSON.stringify(t),...a?{signal:a.signal}:{}});if(m&&clearTimeout(m),!f.ok){let g=await f.text();throw new P(f.status,f.statusText,g)}return f}catch(u){if(p=u,u instanceof P&&u.status>=400&&u.status<500)throw u;if(u instanceof Error&&u.name==="AbortError")throw new Error(`Request timeout after ${r}ms`);c<n&&await new Promise(a=>setTimeout(a,i*(c+1)))}throw p||new Error("Request failed")}static async handleErrorResponse(e){let t=await e.text();throw new P(e.status,e.statusText,t)}static async get(e,t={},o={}){let{timeout:s=3e4,retries:r=0,retryDelay:n=1e3}=o,i=null;for(let l=0;l<=r;l++)try{let h=typeof AbortController<"u",p=h?new AbortController:void 0,c=h?setTimeout(()=>p.abort(),s):void 0,u=await Y.fetchImpl(e,{method:"GET",headers:t,...p?{signal:p.signal}:{}});if(c&&clearTimeout(c),!u.ok){let a=await u.text();throw new P(u.status,u.statusText,a)}return u}catch(h){if(i=h,h instanceof P&&h.status>=400&&h.status<500)throw h;if(h instanceof Error&&h.name==="AbortError")throw new Error(`Request timeout after ${s}ms`);l<r&&await new Promise(p=>setTimeout(p,n*(l+1)))}throw i||new Error("Request failed")}};Y.fetchImpl=(e,t)=>fetch(e,t);var y=Y;var N=class{constructor(e,t=w,o=w,s,r=R,n=[],i,l,h,p=!1){this.provider="openai";if(this.apiKey=e,this.model=t,this.tools=s||[],this.endpoint=r,this.mcpServers=n,this.responseLength=i,this.verbosity=l,this.reasoning_effort=h,this.enableReasoningSummary=p,!q.includes(o))throw new Error(`Model ${o} does not support vision capabilities.`);this.visionModel=o}getModel(){return this.model}getVisionModel(){return this.visionModel}async processChat(e,t,o){if(this.tools.length===0){let n=await this.callOpenAI(e,this.model,!0),i=this.endpoint===S;try{if(i){let h=(await this.parseResponsesStream(n,t)).blocks.filter(p=>p.type==="text").map(p=>p.text).join("");await o(h)}else{let l=await this.handleStream(n,t);await o(l)}}catch(l){throw console.error("[processChat] Error in streaming/completion:",l),l}return}let{blocks:s,stop_reason:r}=await this.chatOnce(e);if(r==="end"){let n=s.filter(i=>i.type==="text").map(i=>i.text).join("");await o(n);return}throw new Error("processChat received tool_calls. ChatProcessor must use chatOnce() loop when tools are enabled.")}async processVisionChat(e,t,o){try{if(this.tools.length===0){let n=await this.callOpenAI(e,this.visionModel,!0),i=this.endpoint===S;try{if(i){let h=(await this.parseResponsesStream(n,t)).blocks.filter(p=>p.type==="text").map(p=>p.text).join("");await o(h)}else{let l=await this.handleStream(n,t);await o(l)}}catch(l){throw console.error("[processVisionChat] Error in streaming/completion:",l),l}return}let{blocks:s,stop_reason:r}=await this.visionChatOnce(e,!0,t);if(r==="end"){let n=s.filter(i=>i.type==="text").map(i=>i.text).join("");await o(n);return}throw new Error("processVisionChat received tool_calls. ChatProcessor must use visionChatOnce() loop when tools are enabled.")}catch(s){throw console.error("Error in processVisionChat:",s),s}}async chatOnce(e,t=!0,o=()=>{},s){let r=await this.callOpenAI(e,this.model,t,s);return this.parseResponse(r,t,o)}async visionChatOnce(e,t=!1,o=()=>{},s){let r=await this.callOpenAI(e,this.visionModel,t,s);return this.parseResponse(r,t,o)}async parseResponse(e,t,o){return this.endpoint===S?t?this.parseResponsesStream(e,o):this.parseResponsesOneShot(await e.json()):t?this.parseStream(e,o):this.parseOneShot(await e.json())}async callOpenAI(e,t,o=!1,s){let r=this.buildRequestBody(e,t,o,s);return await y.post(this.endpoint,r,{Authorization:`Bearer ${this.apiKey}`})}buildRequestBody(e,t,o,s){let r=this.endpoint===S;this.validateMCPCompatibility();let n={model:t,stream:o},i=s!==void 0?s:E(this.responseLength);r?n.max_output_tokens=i:n.max_completion_tokens=i,r?n.input=this.cleanMessagesForResponsesAPI(e):n.messages=e,I(t)&&(r?(this.reasoning_effort&&(n.reasoning={...n.reasoning,effort:this.reasoning_effort},this.enableReasoningSummary&&(n.reasoning.summary="auto")),this.verbosity&&(n.text={...n.text,format:{type:"text"},verbosity:this.verbosity})):(this.reasoning_effort&&(n.reasoning_effort=this.reasoning_effort),this.verbosity&&(n.verbosity=this.verbosity)));let l=this.buildToolsDefinition();return l.length>0&&(n.tools=l,r||(n.tool_choice="auto")),n}validateMCPCompatibility(){if(this.mcpServers.length>0&&this.endpoint===R)throw new Error(`MCP servers are not supported with Chat Completions API. Current endpoint: ${this.endpoint}. Please use OpenAI Responses API endpoint: ${S}. MCP tools are only available in the Responses API endpoint.`)}cleanMessagesForResponsesAPI(e){return e.map(t=>{let s={role:t.role==="tool"?"user":t.role};return typeof t.content=="string"?s.content=t.content:Array.isArray(t.content)?s.content=t.content.map(r=>r.type==="text"?{type:"input_text",text:r.text}:r.type==="image_url"?{type:"input_image",image_url:r.image_url.url}:r):s.content=t.content,s})}buildToolsDefinition(){let e=this.endpoint===S,t=[];return this.tools.length>0&&(e?t.push(...this.tools.map(o=>({type:"function",name:o.name,description:o.description,parameters:o.parameters}))):t.push(...this.tools.map(o=>({type:"function",function:{name:o.name,description:o.description,parameters:o.parameters}})))),this.mcpServers.length>0&&e&&t.push(...this.buildMCPToolsDefinition()),t}buildMCPToolsDefinition(){return this.mcpServers.map(e=>{let t={type:"mcp",server_label:e.name,server_url:e.url};return e.tool_configuration?.allowed_tools&&(t.allowed_tools=e.tool_configuration.allowed_tools),e.authorization_token&&(t.headers={Authorization:`Bearer ${e.authorization_token}`}),t})}async handleStream(e,t){let o=e.body.getReader(),s=new TextDecoder,r="",n="";for(;;){let{done:i,value:l}=await o.read();if(i)break;r+=s.decode(l,{stream:!0});let h;for(;(h=r.indexOf(`
`))!==-1;){let p=r.slice(0,h).trim();if(r=r.slice(h+2),!p.startsWith("data:"))continue;let c=p.slice(5).trim();if(c==="[DONE]"){r="";break}let a=JSON.parse(c).choices[0]?.delta?.content||"";a&&(t(a),n+=a)}}return n}async parseStream(e,t){let o=e.body.getReader(),s=new TextDecoder,r=[],n=new Map,i="";for(;;){let{done:p,value:c}=await o.read();if(p)break;i+=s.decode(c,{stream:!0});let u;for(;(u=i.indexOf(`
`))!==-1;){let a=i.slice(0,u).trim();if(i=i.slice(u+2),!a.startsWith("data:"))continue;let m=a.slice(5).trim();if(m==="[DONE]"){i="";break}let g=JSON.parse(m).choices[0].delta;g.content&&(t(g.content),r.push({type:"text",text:g.content})),g.tool_calls&&g.tool_calls.forEach(_=>{let C=n.get(_.index)??{id:_.id,name:_.function.name,args:""};C.args+=_.function.arguments||"",n.set(_.index,C)})}}let l=Array.from(n.entries()).sort((p,c)=>p[0]-c[0]).map(([p,c])=>({type:"tool_use",id:c.id,name:c.name,input:JSON.parse(c.args||"{}")}));return{blocks:[...r,...l],stop_reason:l.length?"tool_use":"end"}}parseOneShot(e){let t=e.choices[0],o=[];return t.finish_reason==="tool_calls"?t.message.tool_calls.forEach(s=>o.push({type:"tool_use",id:s.id,name:s.function.name,input:JSON.parse(s.function.arguments||"{}")})):o.push({type:"text",text:t.message.content}),{blocks:o,stop_reason:t.finish_reason==="tool_calls"?"tool_use":"end"}}async parseResponsesStream(e,t){let o=e.body.getReader(),s=new TextDecoder,r=[],n=new Map,i="";for(;;){let{done:p,value:c}=await o.read();if(p)break;i+=s.decode(c,{stream:!0});let u="",a="",m=i.split(`
`);i=m.pop()||"";for(let f=0;f<m.length;f++){let g=m[f].trim();if(g.startsWith("event:"))u=g.slice(6).trim();else if(g.startsWith("data:"))a=g.slice(5).trim();else if(g===""&&u&&a){try{let _=JSON.parse(a),C=this.handleResponsesSSEEvent(u,_,t,r,n)}catch{console.warn("Failed to parse SSE data:",a)}u="",a=""}}}let l=Array.from(n.values()).map(p=>({type:"tool_use",id:p.id,name:p.name,input:p.input||{}}));return{blocks:[...r,...l],stop_reason:l.length?"tool_use":"end"}}handleResponsesSSEEvent(e,t,o,s,r){switch(e){case"response.output_item.added":t.item?.type==="message"&&Array.isArray(t.item.content)?t.item.content.forEach(n=>{n.type==="output_text"&&n.text&&(o(n.text),v.append(s,n.text))}):t.item?.type==="function_call"&&r.set(t.item.id,{id:t.item.id,name:t.item.name,input:t.item.arguments?JSON.parse(t.item.arguments):{}});break;case"response.content_part.added":t.part?.type==="output_text"&&typeof t.part.text=="string"&&(o(t.part.text),v.append(s,t.part.text));break;case"response.output_text.delta":case"response.content_part.delta":{let n=typeof t.delta=="string"?t.delta:t.delta?.text??"";n&&(o(n),v.append(s,n))}break;case"response.output_text.done":case"response.content_part.done":break;case"response.completed":return"completed";case"response.reasoning.started":case"response.reasoning.delta":case"response.reasoning.done":break;default:break}}parseResponsesOneShot(e){let t=[];return e.output&&Array.isArray(e.output)&&e.output.forEach(o=>{o.type==="message"&&o.content&&o.content.forEach(s=>{s.type==="output_text"&&s.text&&t.push({type:"text",text:s.text})}),o.type==="function_call"&&t.push({type:"tool_use",id:o.id,name:o.name,input:o.arguments?JSON.parse(o.arguments):{}})}),{blocks:t,stop_reason:t.some(o=>o.type==="tool_use")?"tool_use":"end"}}};var A=class{createChatService(e){let t=this.optimizeGPT5Options(e),o=t.visionModel||(this.supportsVisionForModel(t.model||this.getDefaultModel())?t.model:this.getDefaultModel()),s=t.tools,r=t.mcpServers??[],n=t.model||this.getDefaultModel(),i=!1;r.length>0?i=!0:I(n)&&(i=(t.gpt5EndpointPreference||"chat")==="responses");let l=t.endpoint||(i?S:R);return new N(t.apiKey,n,o,s,l,r,t.responseLength,t.verbosity,t.reasoning_effort,t.enableReasoningSummary)}getProviderName(){return"openai"}getSupportedModels(){return[D,H,W,$,X,Q,Z,w,ee,ge,_e,"o1",te]}getDefaultModel(){return D}supportsVision(){return!0}supportsVisionForModel(e){return q.includes(e)}optimizeGPT5Options(e){let t=e.model||this.getDefaultModel();if(!I(t))return e;let o={...e};if(e.gpt5Preset){let s=Te[e.gpt5Preset];o.reasoning_effort=s.reasoning_effort,o.verbosity=s.verbosity}else e.reasoning_effort||(o.reasoning_effort="medium");return o}};var de=class{static async fetchToolSchemas(e){try{let t={"Content-Type":"application/json"};e.authorization_token&&(t.Authorization=`Bearer ${e.authorization_token}`);let s=await(await y.post(`${e.url}/tools`,{},t)).json();return Array.isArray(s.tools)?s.tools.map(r=>({name:`mcp_${e.name}_${r.name}`,description:r.description||`Tool from ${e.name} MCP server`,parameters:r.inputSchema||{type:"object",properties:{},required:[]}})):[{name:`mcp_${e.name}_search`,description:`Search using ${e.name} MCP server`,parameters:{type:"object",properties:{query:{type:"string",description:"Search query"}},required:["query"]}}]}catch(t){return console.warn(`Failed to fetch MCP schemas from ${e.name}:`,t),[{name:`mcp_${e.name}_search`,description:`Search using ${e.name} MCP server (schema fetch failed)`,parameters:{type:"object",properties:{query:{type:"string",description:"Search query"}},required:["query"]}}]}}static async fetchAllToolSchemas(e){let t=[];for(let o of e)try{let s=await this.fetchToolSchemas(o);t.push(...s)}catch(s){console.error(`Failed to fetch schemas from ${o.name}:`,s)}return t}};var k=class{constructor(e,t=T,o=T,s=[],r=[],n){this.provider="gemini";this.mcpToolSchemas=[];this.mcpSchemasInitialized=!1;this.callIdMap=new Map;if(this.apiKey=e,this.model=t,this.responseLength=n,!z.includes(o))throw new Error(`Model ${o} does not support vision capabilities.`);this.visionModel=o,this.tools=s,this.mcpServers=r}safeJsonParse(e){try{return JSON.parse(e)}catch{return e}}normalizeToolResult(e){return e===null?{content:null}:typeof e=="object"?e:{content:e}}adaptKeysForApi(e){let t={toolConfig:"tool_config",functionCallingConfig:"function_calling_config",functionDeclarations:"function_declarations",functionCall:"function_call",functionResponse:"function_response"};return Array.isArray(e)?e.map(o=>this.adaptKeysForApi(o)):e&&typeof e=="object"?Object.fromEntries(Object.entries(e).map(([o,s])=>[t[o]??o,this.adaptKeysForApi(s)])):e}getModel(){return this.model}getVisionModel(){return this.visionModel}getMCPServers(){return this.mcpServers}addMCPServer(e){this.mcpServers.push(e),this.mcpSchemasInitialized=!1}removeMCPServer(e){this.mcpServers=this.mcpServers.filter(t=>t.name!==e),this.mcpSchemasInitialized=!1}hasMCPServers(){return this.mcpServers.length>0}async initializeMCPSchemas(){if(!(this.mcpSchemasInitialized||this.mcpServers.length===0))try{let e=new Promise((o,s)=>setTimeout(()=>s(new Error("MCP schema fetch timeout")),5e3)),t=de.fetchAllToolSchemas(this.mcpServers);this.mcpToolSchemas=await Promise.race([t,e]),this.mcpSchemasInitialized=!0}catch(e){console.warn("Failed to initialize MCP schemas, using fallback:",e),this.mcpToolSchemas=this.mcpServers.map(t=>({name:`mcp_${t.name}_search`,description:`Search using ${t.name} MCP server (fallback)`,parameters:{type:"object",properties:{query:{type:"string",description:"Search query"}},required:["query"]}})),this.mcpSchemasInitialized=!0}}async processChat(e,t,o){try{if(this.tools.length===0&&this.mcpServers.length===0){let n=await this.callGemini(e,this.model,!0),{blocks:i}=await this.parseStream(n,t),l=i.filter(h=>h.type==="text").map(h=>h.text).join("");await o(l);return}let{blocks:s,stop_reason:r}=await this.chatOnce(e,!0,t);if(r==="end"){let n=s.filter(i=>i.type==="text").map(i=>i.text).join("");await o(n);return}throw new Error("Received functionCall. Use chatOnce() loop when tools are enabled.")}catch(s){throw console.error("Error in processChat:",s),s}}async processVisionChat(e,t,o){try{if(this.tools.length===0&&this.mcpServers.length===0){let n=await this.callGemini(e,this.visionModel,!0),{blocks:i}=await this.parseStream(n,t),l=i.filter(h=>h.type==="text").map(h=>h.text).join("");await o(l);return}let{blocks:s,stop_reason:r}=await this.visionChatOnce(e);if(s.filter(n=>n.type==="text").forEach(n=>t(n.text)),r==="end"){let n=s.filter(i=>i.type==="text").map(i=>i.text).join("");await o(n);return}throw new Error("Received functionCall. Use visionChatOnce() loop when tools are enabled.")}catch(s){throw console.error("Error in processVisionChat:",s),s}}convertMessagesToGeminiFormat(e){let t=[],o=null,s=[],r=()=>{o&&s.length&&(t.push({role:o,parts:[...s]}),s=[])};for(let n of e){let i=this.mapRoleToGemini(n.role);if(n.tool_calls){r();for(let l of n.tool_calls)this.callIdMap.set(l.id,l.function.name),t.push({role:"model",parts:[{functionCall:{name:l.function.name,args:JSON.parse(l.function.arguments||"{}")}}]});continue}if(n.role==="tool"){r();let l=n.name??this.callIdMap.get(n.tool_call_id)??"result";t.push({role:"user",parts:[{functionResponse:{name:l,response:this.normalizeToolResult(this.safeJsonParse(n.content))}}]});continue}i!==o&&r(),o=i,s.push({text:n.content})}return r(),t}async callGemini(e,t,o=!1,s){let i={contents:e.some(m=>Array.isArray(m.content)&&m.content.some(f=>f?.type==="image_url"||f?.inlineData))?await this.convertVisionMessagesToGeminiFormat(e):this.convertMessagesToGeminiFormat(e),generationConfig:{maxOutputTokens:s!==void 0?s:E(this.responseLength)}},l=[];if(this.tools.length>0&&l.push(...this.tools.map(m=>({name:m.name,description:m.description,parameters:m.parameters}))),this.mcpServers.length>0)try{await this.initializeMCPSchemas(),l.push(...this.mcpToolSchemas.map(m=>({name:m.name,description:m.description,parameters:m.parameters})))}catch(m){console.warn("MCP initialization failed, skipping MCP tools:",m)}l.length>0&&(i.tools=[{functionDeclarations:l}],i.toolConfig={functionCallingConfig:{mode:"AUTO"}});let h=async(m,f)=>{let g=o?"streamGenerateContent":"generateContent",_=o?"?alt=sse":"",C=`${ve}/${m}/models/${t}:${g}${_}${_?"&":"?"}key=${this.apiKey}`;return y.post(C,f)},p=/flash[-_]lite/.test(t),c=/gemini-2\.5/.test(t),u=p||c?"v1beta":"v1",a=async()=>{try{let m=u==="v1"?i:this.adaptKeysForApi(i);return await h(u,m)}catch(m){if(!(p||c)&&/Unknown name|Cannot find field|404/.test(m.message))return await h("v1beta",this.adaptKeysForApi(i));throw m}};try{return await a()}catch(m){throw m.body&&(console.error("Gemini API Error Details:",m.body),console.error("Request Body:",JSON.stringify(i,null,2))),m}}async convertVisionMessagesToGeminiFormat(e){let t=[],o=null,s=[];for(let r of e){let n=this.mapRoleToGemini(r.role);if(r.tool_calls){for(let i of r.tool_calls)t.push({role:"model",parts:[{functionCall:{name:i.function.name,args:JSON.parse(i.function.arguments||"{}")}}]});continue}if(r.role==="tool"){let i=r.name??this.callIdMap.get(r.tool_call_id)??"result";t.push({role:"user",parts:[{functionResponse:{name:i,response:this.normalizeToolResult(this.safeJsonParse(r.content))}}]});continue}if(n!==o&&s.length>0&&(t.push({role:o,parts:[...s]}),s=[]),o=n,typeof r.content=="string")s.push({text:r.content});else if(Array.isArray(r.content)){for(let i of r.content)if(i.type==="text")s.push({text:i.text});else if(i.type==="image_url")try{let h=await(await y.get(i.image_url.url)).blob(),p=await this.blobToBase64(h);s.push({inlineData:{mimeType:h.type||"image/jpeg",data:p.split(",")[1]}})}catch(l){throw console.error("Error processing image:",l),new Error(`Failed to process image: ${l.message}`)}}}return o&&s.length>0&&t.push({role:o,parts:[...s]}),t}blobToBase64(e){return new Promise((t,o)=>{let s=new FileReader;s.onloadend=()=>t(s.result),s.onerror=o,s.readAsDataURL(e)})}mapRoleToGemini(e){switch(e){case"system":return"model";case"user":return"user";case"assistant":return"model";default:return"user"}}async parseStream(e,t){let o=e.body.getReader(),s=new TextDecoder,r=[],n=[],i="",l=p=>{if(!p||p==="[DONE]")return;let c;try{c=JSON.parse(p)}catch{return}for(let u of c.candidates??[])for(let a of u.content?.parts??[])a.text&&(t(a.text),v.addTextBlock(r,a.text)),a.functionCall&&n.push({type:"tool_use",id:this.genUUID(),name:a.functionCall.name,input:a.functionCall.args??{}}),a.functionResponse&&n.push({type:"tool_result",tool_use_id:a.functionResponse.name,content:JSON.stringify(a.functionResponse.response)})};for(;;){let{done:p,value:c}=await o.read();if(p)break;i+=s.decode(c,{stream:!0});let u;for(;(u=i.indexOf(`
`))!==-1;){let a=i.slice(0,u);if(i=i.slice(u+1),a.endsWith("\r")&&(a=a.slice(0,-1)),!a.trim()){l("");continue}a.startsWith("data:")&&(a=a.slice(5).trim()),a&&l(a)}}return i&&l(i),{blocks:[...r,...n],stop_reason:n.some(p=>p.type==="tool_use")?"tool_use":"end"}}parseOneShot(e){let t=[],o=[];for(let r of e.candidates??[])for(let n of r.content?.parts??[])n.text&&t.push({type:"text",text:n.text}),n.functionCall&&o.push({type:"tool_use",id:this.genUUID(),name:n.functionCall.name,input:n.functionCall.args??{}}),n.functionResponse&&o.push({type:"tool_result",tool_use_id:n.functionResponse.name,content:JSON.stringify(n.functionResponse.response)});return{blocks:[...t,...o],stop_reason:o.some(r=>r.type==="tool_use")?"tool_use":"end"}}async chatOnce(e,t=!0,o=()=>{},s){let r=await this.callGemini(e,this.model,t,s);return t?this.parseStream(r,o):this.parseOneShot(await r.json())}async visionChatOnce(e,t=!1,o=()=>{},s){let r=await this.callGemini(e,this.visionModel,t,s);return t?this.parseStream(r,o):this.parseOneShot(await r.json())}genUUID(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}};var G=class{createChatService(e){let t=e.visionModel||(this.supportsVisionForModel(e.model||this.getDefaultModel())?e.model:this.getDefaultModel());return new k(e.apiKey,e.model||this.getDefaultModel(),t,e.tools||[],e.mcpServers||[],e.responseLength)}getProviderName(){return"gemini"}getSupportedModels(){return[oe,se,re,ne,ie,T,ae,le]}getDefaultModel(){return T}supportsVision(){return!0}supportsVisionForModel(e){return z.includes(e)}};var U=class{constructor(e,t=M,o=M,s=[],r=[],n){this.provider="claude";if(this.apiKey=e,this.model=t||M,this.visionModel=o||M,this.tools=s,this.mcpServers=r,this.responseLength=n,!J.includes(this.visionModel))throw new Error(`Model ${this.visionModel} does not support vision capabilities.`)}getModel(){return this.model}getVisionModel(){return this.visionModel}getMCPServers(){return this.mcpServers}addMCPServer(e){this.mcpServers.push(e)}removeMCPServer(e){this.mcpServers=this.mcpServers.filter(t=>t.name!==e)}hasMCPServers(){return this.mcpServers.length>0}async processChat(e,t,o){if(this.tools.length===0&&this.mcpServers.length===0){let r=await this.callClaude(e,this.model,!0),n=await this.parsePureStream(r,t);await o(n);return}let s=await this.chatOnce(e,!0,t);if(s.stop_reason==="end"){let r=s.blocks.filter(n=>n.type==="text").map(n=>n.text).join("");await o(r);return}throw new Error("processChat received tool_calls. ChatProcessor must use chatOnce() loop when tools are enabled.")}async processVisionChat(e,t,o){if(this.tools.length===0&&this.mcpServers.length===0){let r=await this.callClaude(e,this.visionModel,!0),n=await this.parsePureStream(r,t);await o(n);return}let s=await this.visionChatOnce(e);if(s.stop_reason==="end"){let r=s.blocks.filter(n=>n.type==="text").map(n=>n.text).join("");await o(r);return}throw new Error("processVisionChat received tool_calls. ChatProcessor must use chatOnce() loop when tools are enabled.")}convertMessagesToClaudeFormat(e){return e.map(t=>({role:this.mapRoleToClaude(t.role),content:t.content}))}convertVisionMessagesToClaudeFormat(e){return e.map(t=>{if(typeof t.content=="string")return{role:this.mapRoleToClaude(t.role),content:[{type:"text",text:t.content}]};if(Array.isArray(t.content)){let o=t.content.map(s=>{if(s.type==="image_url"){if(s.image_url.url.startsWith("data:")){let r=s.image_url.url.match(/^data:([^;]+);base64,(.+)$/);return r?{type:"image",source:{type:"base64",media_type:r[1],data:r[2]}}:null}return{type:"image",source:{type:"url",url:s.image_url.url,media_type:this.getMimeTypeFromUrl(s.image_url.url)}}}return s}).filter(s=>s);return{role:this.mapRoleToClaude(t.role),content:o}}return{role:this.mapRoleToClaude(t.role),content:[]}})}mapRoleToClaude(e){switch(e){case"system":return"system";case"user":return"user";case"assistant":return"assistant";default:return"user"}}getMimeTypeFromUrl(e){switch(e.split(".").pop()?.toLowerCase()){case"jpg":case"jpeg":return"image/jpeg";case"png":return"image/png";case"gif":return"image/gif";case"webp":return"image/webp";default:return"image/jpeg"}}async callClaude(e,t,o,s){let r=e.find(c=>c.role==="system")?.content??"",n=e.filter(c=>c.role!=="system"),i=n.some(c=>Array.isArray(c.content)&&c.content.some(u=>u.type==="image_url"||u.type==="image")),l={model:t,system:r,messages:i?this.convertVisionMessagesToClaudeFormat(n):this.convertMessagesToClaudeFormat(n),stream:o,max_tokens:s!==void 0?s:E(this.responseLength)};this.tools.length&&(l.tools=this.tools.map(c=>({name:c.name,description:c.description,input_schema:c.parameters})),l.tool_choice={type:"auto"}),this.mcpServers.length>0&&(l.mcp_servers=this.mcpServers);let h={"Content-Type":"application/json","x-api-key":this.apiKey,"anthropic-version":"2023-06-01","anthropic-dangerous-direct-browser-access":"true"};return this.mcpServers.length>0&&(h["anthropic-beta"]="mcp-client-2025-04-04"),await y.post(Ce,l,h)}async parseStream(e,t){let o=e.body.getReader(),s=new TextDecoder,r=[],n=new Map,i="";for(;;){let{done:l,value:h}=await o.read();if(l)break;i+=s.decode(h,{stream:!0});let p;for(;(p=i.indexOf(`
`))!==-1;){let c=i.slice(0,p).trim();if(i=i.slice(p+1),!c.startsWith("data:"))continue;let u=c.slice(5).trim();if(u==="[DONE]")break;let a=JSON.parse(u);if(a.type==="content_block_delta"&&a.delta?.text&&(t(a.delta.text),r.push({type:"text",text:a.delta.text})),a.type==="content_block_start"&&a.content_block?.type==="tool_use"?n.set(a.index,{id:a.content_block.id,name:a.content_block.name,args:""}):a.type==="content_block_start"&&a.content_block?.type==="mcp_tool_use"?n.set(a.index,{id:a.content_block.id,name:a.content_block.name,args:"",server_name:a.content_block.server_name}):a.type==="content_block_start"&&a.content_block?.type==="tool_result"?r.push({type:"tool_result",tool_use_id:a.content_block.tool_use_id,content:a.content_block.content??""}):a.type==="content_block_start"&&a.content_block?.type==="mcp_tool_result"&&r.push({type:"mcp_tool_result",tool_use_id:a.content_block.tool_use_id,is_error:a.content_block.is_error??!1,content:a.content_block.content??[]}),a.type==="content_block_delta"&&a.delta?.type==="input_json_delta"){let m=n.get(a.index);m&&(m.args+=a.delta.partial_json||"")}if(a.type==="content_block_stop"&&n.has(a.index)){let{id:m,name:f,args:g,server_name:_}=n.get(a.index);_?r.push({type:"mcp_tool_use",id:m,name:f,server_name:_,input:JSON.parse(g||"{}")}):r.push({type:"tool_use",id:m,name:f,input:JSON.parse(g||"{}")}),n.delete(a.index)}}}return{blocks:r,stop_reason:r.some(l=>l.type==="tool_use"||l.type==="mcp_tool_use")?"tool_use":"end"}}async parsePureStream(e,t){let{blocks:o}=await this.parseStream(e,t);return o.filter(s=>s.type==="text").map(s=>s.text).join("")}parseOneShot(e){let t=[];return(e.content??[]).forEach(o=>{o.type==="text"?t.push({type:"text",text:o.text}):o.type==="tool_use"?t.push({type:"tool_use",id:o.id,name:o.name,input:o.input??{}}):o.type==="mcp_tool_use"?t.push({type:"mcp_tool_use",id:o.id,name:o.name,server_name:o.server_name,input:o.input??{}}):o.type==="tool_result"?t.push({type:"tool_result",tool_use_id:o.tool_use_id,content:o.content??""}):o.type==="mcp_tool_result"&&t.push({type:"mcp_tool_result",tool_use_id:o.tool_use_id,is_error:o.is_error??!1,content:o.content??[]})}),{blocks:t,stop_reason:t.some(o=>o.type==="tool_use"||o.type==="mcp_tool_use")?"tool_use":"end"}}async chatOnce(e,t=!0,o=()=>{},s){let r=await this.callClaude(e,this.model,t,s),n=t?await this.parseStream(r,o):this.parseOneShot(await r.json());return this.convertToStandardCompletion(n)}async visionChatOnce(e,t=!1,o=()=>{},s){let r=await this.callClaude(e,this.visionModel,t,s),n=t?await this.parseStream(r,o):this.parseOneShot(await r.json());return this.convertToStandardCompletion(n)}convertToStandardCompletion(e){return{blocks:e.blocks.filter(o=>o.type==="text"||o.type==="tool_use"||o.type==="tool_result"),stop_reason:e.stop_reason}}};var V=class{createChatService(e){let t=e.visionModel||(this.supportsVisionForModel(e.model||this.getDefaultModel())?e.model:this.getDefaultModel());return new U(e.apiKey,e.model||this.getDefaultModel(),t,e.tools??[],e.mcpServers??[],e.responseLength)}getProviderName(){return"claude"}getSupportedModels(){return[M,ce,pe,he,ue,me]}getDefaultModel(){return M}supportsVision(){return!0}supportsVisionForModel(e){return J.includes(e)}};var F=class{constructor(e,t=O,o=O,s,r=Se,n,i,l,h,p,c){this.provider="openrouter";this.lastRequestTime=0;this.requestCount=0;this.apiKey=e,this.model=t,this.tools=s||[],this.endpoint=r,this.responseLength=n,this.appName=i,this.appUrl=l,this.reasoning_effort=h,this.includeReasoning=p,this.reasoningMaxTokens=c,this.visionModel=o}getModel(){return this.model}getVisionModel(){return this.visionModel}async applyRateLimiting(){if(!Ee(this.model))return;let e=Date.now(),t=e-this.lastRequestTime;if(t>6e4&&(this.requestCount=0),this.requestCount>=Me){let o=6e4-t;o>0&&(console.log(`Rate limit reached for free tier. Waiting ${o}ms...`),await new Promise(s=>setTimeout(s,o)),this.requestCount=0)}this.lastRequestTime=e,this.requestCount++}async processChat(e,t,o){if(await this.applyRateLimiting(),this.tools.length===0){let n=await this.callOpenRouter(e,this.model,!0),i=await this.handleStream(n,t);await o(i);return}let{blocks:s,stop_reason:r}=await this.chatOnce(e);if(r==="end"){let n=s.filter(i=>i.type==="text").map(i=>i.text).join("");await o(n);return}throw new Error("processChat received tool_calls. ChatProcessor must use chatOnce() loop when tools are enabled.")}async processVisionChat(e,t,o){if(!L(this.visionModel))throw new Error(`Model ${this.visionModel} does not support vision capabilities.`);await this.applyRateLimiting();try{if(this.tools.length===0){let n=await this.callOpenRouter(e,this.visionModel,!0),i=await this.handleStream(n,t);await o(i);return}let{blocks:s,stop_reason:r}=await this.visionChatOnce(e,!0,t);if(r==="end"){let n=s.filter(i=>i.type==="text").map(i=>i.text).join("");await o(n);return}throw new Error("processVisionChat received tool_calls. ChatProcessor must use visionChatOnce() loop when tools are enabled.")}catch(s){throw console.error("Error in processVisionChat:",s),s}}async chatOnce(e,t=!0,o=()=>{},s){await this.applyRateLimiting();let r=await this.callOpenRouter(e,this.model,t,s);return t?this.parseStream(r,o):this.parseOneShot(await r.json())}async visionChatOnce(e,t=!1,o=()=>{},s){if(!L(this.visionModel))throw new Error(`Model ${this.visionModel} does not support vision capabilities.`);await this.applyRateLimiting();let r=await this.callOpenRouter(e,this.visionModel,t,s);return t?this.parseStream(r,o):this.parseOneShot(await r.json())}async callOpenRouter(e,t,o=!1,s){let r=this.buildRequestBody(e,t,o,s),n={Authorization:`Bearer ${this.apiKey}`};return this.appUrl&&(n["HTTP-Referer"]=this.appUrl),this.appName&&(n["X-Title"]=this.appName),await y.post(this.endpoint,r,n)}buildRequestBody(e,t,o,s){let r={model:t,messages:e,stream:o};if((s!==void 0?s:E(this.responseLength))&&console.warn("OpenRouter: Token limits are not supported for gpt-oss-20b model due to known issues. Using unlimited tokens instead."),this.reasoning_effort||this.includeReasoning!==void 0||this.reasoningMaxTokens){if(r.reasoning={},this.reasoning_effort){let i=this.reasoning_effort==="minimal"?"low":this.reasoning_effort;r.reasoning.effort=i}this.includeReasoning!==!0&&(r.reasoning.exclude=!0),this.reasoningMaxTokens&&(r.reasoning.max_tokens=this.reasoningMaxTokens)}else r.reasoning={exclude:!0};return this.tools.length>0&&(r.tools=this.tools.map(i=>({type:"function",function:{name:i.name,description:i.description,parameters:i.parameters}})),r.tool_choice="auto"),r}async handleStream(e,t){let o=e.body.getReader(),s=new TextDecoder,r="",n="";for(;;){let{done:i,value:l}=await o.read();if(i)break;r+=s.decode(l,{stream:!0});let h=r.split(`
`);r=h.pop()||"";for(let p of h){let c=p.trim();if(!c||c.startsWith(":")||!c.startsWith("data:"))continue;let u=c.slice(5).trim();if(u==="[DONE]")return n;try{let m=JSON.parse(u).choices?.[0]?.delta?.content||"";m&&(t(m),n+=m)}catch{console.debug("Failed to parse SSE data:",u)}}}return n}async parseStream(e,t){let o=e.body.getReader(),s=new TextDecoder,r=[],n=new Map,i="";for(;;){let{done:p,value:c}=await o.read();if(p)break;i+=s.decode(c,{stream:!0});let u=i.split(`
`);i=u.pop()||"";for(let a of u){let m=a.trim();if(!m||m.startsWith(":")||!m.startsWith("data:"))continue;let f=m.slice(5).trim();if(f==="[DONE]")break;try{let _=JSON.parse(f).choices?.[0]?.delta;_?.content&&(t(_.content),v.append(r,_.content)),_?.tool_calls&&_.tool_calls.forEach(C=>{let Pe=n.get(C.index)??{id:C.id,name:C.function?.name,args:""};Pe.args+=C.function?.arguments||"",n.set(C.index,Pe)})}catch{console.debug("Failed to parse SSE data:",f)}}}let l=Array.from(n.entries()).sort((p,c)=>p[0]-c[0]).map(([p,c])=>({type:"tool_use",id:c.id,name:c.name,input:JSON.parse(c.args||"{}")}));return{blocks:[...r,...l],stop_reason:l.length?"tool_use":"end"}}parseOneShot(e){let t=e.choices?.[0],o=[];return t?.finish_reason==="tool_calls"&&t?.message?.tool_calls?t.message.tool_calls.forEach(s=>o.push({type:"tool_use",id:s.id,name:s.function?.name,input:JSON.parse(s.function?.arguments||"{}")})):t?.message?.content&&o.push({type:"text",text:t.message.content}),{blocks:o,stop_reason:t?.finish_reason==="tool_calls"?"tool_use":"end"}}};var B=class{createChatService(e){let t=e.visionModel||e.model||this.getDefaultModel();if(e.visionModel&&!this.supportsVisionForModel(e.visionModel))throw new Error(`Model ${e.visionModel} does not support vision capabilities.`);let o=e.tools,s=e.appName,r=e.appUrl;return new F(e.apiKey,e.model||this.getDefaultModel(),t,o,e.endpoint,e.responseLength,s,r,e.reasoning_effort,e.includeReasoning,e.reasoningMaxTokens)}getProviderName(){return"openrouter"}getSupportedModels(){return[O]}getDefaultModel(){return O}supportsVision(){return!1}supportsVisionForModel(e){return L(e)}getFreeModels(){return K}isModelFree(e){return K.includes(e)||e.endsWith(":free")}};var x=class{static registerProvider(e){this.providers.set(e.getProviderName(),e)}static createChatService(e,t){let o=this.providers.get(e);if(!o)throw new Error(`Unknown chat provider: ${e}`);return o.createChatService(t)}static getProviders(){return this.providers}static getAvailableProviders(){return Array.from(this.providers.keys())}static getSupportedModels(e){let t=this.providers.get(e);return t?t.getSupportedModels():[]}};x.providers=new Map;x.registerProvider(new A);x.registerProvider(new G);x.registerProvider(new V);x.registerProvider(new B);var $e=["happy","sad","angry","surprised","neutral"],De=/\[([a-z]+)\]/i,Oe=/\[[a-z]+\]\s*/gi,j=class{static extractEmotion(e){let t=e.match(De);if(t){let o=t[1].toLowerCase(),s=e.replace(Oe,"").trim();return{emotion:o,cleanText:s}}return{cleanText:e}}static isValidEmotion(e){return $e.includes(e)}static cleanEmotionTags(e){return e.replace(Oe,"").trim()}static addEmotionTag(e,t){return`[${e}] ${t}`}};function Ie(d){let{emotion:e,cleanText:t}=j.extractEmotion(d);return e?{emotion:e,text:t}:{text:t}}function qe(d){return d.map(e=>Ie(e))}function ze(d){return d.emotion?j.addEmotionTag(d.emotion,d.text):d.text}async function Je(d,e){let{blocks:t}=await d.chatOnce(e,!1,()=>{});return v.getFullText(t)}function Le(){y.setFetch(async(d,e={})=>{let t=(e.method||"GET").toString().toUpperCase(),o=e.headers,s={};if(Array.isArray(o))for(let[c,u]of o)s[c]=String(u);else if(o&&typeof o=="object")for(let[c,u]of Object.entries(o))s[c]=String(u);let r={method:t,headers:s,muteHttpExceptions:!0},n=e.body;typeof n=="string"?r.payload=n:n!=null&&(s["Content-Type"]||(s["Content-Type"]="application/json"),r.payload=JSON.stringify(n));let i=UrlFetchApp.fetch(d,r),l=i.getResponseCode(),h=i.getContentText();return{ok:l>=200&&l<300,status:l,statusText:String(l),text:async()=>h,json:async()=>h?JSON.parse(h):null}})}return Ve(Ke);})();
;