@@ -101,6 +101,177 @@ async function callOllama(
101101 return data . message ?. content ?? '' ;
102102}
103103
104+ /**
105+ * Send a prompt to the Google Gemini API.
106+ * Uses the generativelanguage.googleapis.com REST endpoint.
107+ * Config: apiKey = Google API key, model = e.g. gemini-2.0-flash
108+ * Host is ignored (uses Google's endpoint directly).
109+ */
110+ async function callGemini (
111+ config : EasyAIConfig ,
112+ systemPrompt : string ,
113+ userPrompt : string ,
114+ ) : Promise < string > {
115+ if ( ! config . apiKey ) {
116+ throw new Error ( 'Gemini requires an API key. Set it in Settings > About > EasyAI API Hosting.' ) ;
117+ }
118+
119+ const model = config . model || 'gemini-2.0-flash' ;
120+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${ model } :generateContent?key=${ config . apiKey } ` ;
121+
122+ const body = {
123+ system_instruction : {
124+ parts : [ { text : systemPrompt } ] ,
125+ } ,
126+ contents : [
127+ {
128+ role : 'user' ,
129+ parts : [ { text : userPrompt } ] ,
130+ } ,
131+ ] ,
132+ generationConfig : {
133+ temperature : 0.7 ,
134+ maxOutputTokens : 8192 ,
135+ } ,
136+ } ;
137+
138+ const res = await fetch ( url , {
139+ method : 'POST' ,
140+ headers : { 'Content-Type' : 'application/json' } ,
141+ body : JSON . stringify ( body ) ,
142+ } ) ;
143+
144+ if ( ! res . ok ) {
145+ const text = await res . text ( ) ;
146+ throw new Error ( `Gemini returned ${ res . status } : ${ text } ` ) ;
147+ }
148+
149+ const data = await res . json ( ) ;
150+ return data . candidates ?. [ 0 ] ?. content ?. parts ?. [ 0 ] ?. text ?? '' ;
151+ }
152+
153+ /**
154+ * Send a prompt to the Anthropic Claude API.
155+ * Uses the api.anthropic.com Messages endpoint.
156+ * Config: apiKey = Anthropic API key, model = e.g. claude-sonnet-4-20250514
157+ * Host can override the base URL for proxied setups.
158+ */
159+ async function callClaude (
160+ config : EasyAIConfig ,
161+ systemPrompt : string ,
162+ userPrompt : string ,
163+ ) : Promise < string > {
164+ if ( ! config . apiKey ) {
165+ throw new Error ( 'Claude requires an API key. Set it in Settings > About > EasyAI API Hosting.' ) ;
166+ }
167+
168+ const baseUrl = config . host && ! config . host . includes ( 'localhost' )
169+ ? config . host . replace ( / \/ + $ / , '' )
170+ : 'https://api.anthropic.com' ;
171+ const url = `${ baseUrl } /v1/messages` ;
172+ const model = config . model || 'claude-sonnet-4-20250514' ;
173+
174+ const body = {
175+ model,
176+ max_tokens : 8192 ,
177+ system : systemPrompt ,
178+ messages : [
179+ { role : 'user' , content : userPrompt } ,
180+ ] ,
181+ } ;
182+
183+ const res = await fetch ( url , {
184+ method : 'POST' ,
185+ headers : {
186+ 'Content-Type' : 'application/json' ,
187+ 'x-api-key' : config . apiKey ,
188+ 'anthropic-version' : '2023-06-01' ,
189+ 'anthropic-dangerous-direct-browser-access' : 'true' ,
190+ } ,
191+ body : JSON . stringify ( body ) ,
192+ } ) ;
193+
194+ if ( ! res . ok ) {
195+ const text = await res . text ( ) ;
196+ throw new Error ( `Claude returned ${ res . status } : ${ text } ` ) ;
197+ }
198+
199+ const data = await res . json ( ) ;
200+ // Claude returns content as an array of blocks
201+ const blocks = data . content ?? [ ] ;
202+ return blocks
203+ . filter ( ( b : any ) => b . type === 'text' )
204+ . map ( ( b : any ) => b . text )
205+ . join ( '' ) ;
206+ }
207+
208+ /**
209+ * Send a prompt to AWS Bedrock via the invoke-model REST endpoint.
210+ * Config: host = full Bedrock endpoint URL (e.g. https://bedrock-runtime.us-east-1.amazonaws.com),
211+ * model = model ID (e.g. anthropic.claude-3-haiku-20240307-v1:0),
212+ * apiKey = format "ACCESS_KEY_ID:SECRET_ACCESS_KEY" or a session token.
213+ *
214+ * NOTE: For browser-based usage, Bedrock typically requires a proxy/gateway
215+ * since direct AWS SigV4 signing from the browser is complex.
216+ * This implementation supports a proxy that accepts the Bedrock converse API format
217+ * and forwards to AWS with proper signing.
218+ */
219+ async function callBedrock (
220+ config : EasyAIConfig ,
221+ systemPrompt : string ,
222+ userPrompt : string ,
223+ ) : Promise < string > {
224+ if ( ! config . host ) {
225+ throw new Error ( 'Bedrock requires a host URL (e.g. your Bedrock proxy endpoint). Set it in Settings > About > EasyAI API Hosting.' ) ;
226+ }
227+
228+ const baseUrl = config . host . replace ( / \/ + $ / , '' ) ;
229+ const model = config . model || 'anthropic.claude-3-haiku-20240307-v1:0' ;
230+ const url = `${ baseUrl } /model/${ encodeURIComponent ( model ) } /converse` ;
231+
232+ const body = {
233+ system : [ { text : systemPrompt } ] ,
234+ messages : [
235+ {
236+ role : 'user' ,
237+ content : [ { text : userPrompt } ] ,
238+ } ,
239+ ] ,
240+ inferenceConfig : {
241+ maxTokens : 8192 ,
242+ temperature : 0.7 ,
243+ } ,
244+ } ;
245+
246+ const headers : Record < string , string > = {
247+ 'Content-Type' : 'application/json' ,
248+ } ;
249+
250+ // If an API key is provided, pass it as Authorization header for proxy auth
251+ if ( config . apiKey ) {
252+ headers [ 'Authorization' ] = `Bearer ${ config . apiKey } ` ;
253+ }
254+
255+ const res = await fetch ( url , {
256+ method : 'POST' ,
257+ headers,
258+ body : JSON . stringify ( body ) ,
259+ } ) ;
260+
261+ if ( ! res . ok ) {
262+ const text = await res . text ( ) ;
263+ throw new Error ( `Bedrock returned ${ res . status } : ${ text } ` ) ;
264+ }
265+
266+ const data = await res . json ( ) ;
267+ // Bedrock Converse API response format
268+ const output = data . output ?. message ?. content ?? [ ] ;
269+ return output
270+ . filter ( ( b : any ) => b . text )
271+ . map ( ( b : any ) => b . text )
272+ . join ( '' ) ;
273+ }
274+
104275/**
105276 * Main entry point — dispatches to the correct backend based on config.agent.
106277 */
@@ -115,12 +286,12 @@ export async function queryEasyAI(
115286 switch ( config . agent ) {
116287 case 'Ollama' :
117288 return callOllama ( config , systemPrompt , userPrompt ) ;
118-
119- // Future agents can be added here:
120- // case 'Gemini': return callGemini(config, systemPrompt, userPrompt);
121- // case 'Claude': return callClaude(config, systemPrompt, userPrompt);
122- // case 'Bedrock': return callBedrock(config, systemPrompt, userPrompt);
123-
289+ case 'Gemini' :
290+ return callGemini ( config , systemPrompt , userPrompt ) ;
291+ case 'Claude' :
292+ return callClaude ( config , systemPrompt , userPrompt ) ;
293+ case 'Bedrock' :
294+ return callBedrock ( config , systemPrompt , userPrompt ) ;
124295 default :
125296 throw new Error ( `Unsupported EasyAI agent: "${ config . agent } ". Configure a supported agent in Settings > About > EasyAI API Hosting.` ) ;
126297 }
0 commit comments