actualNoteContentPart: # Calling an LLM (OpenAI, Gemini, Anthropic) from a plugin
  The [AmpleAI plugin](https://www.amplenote.com/help/using_default_ai_plugin)  offers a live prototype to see how an AI response. The challenges & decisions to make when you're building a plugin that will interface with AI include "will response be streamed?" (usually advisable) and "how will the plugin show streamed progress if so?" Here is the general idea:
  
  ```
  // --------------------------------------------------------------------------// Make a single request attempt to the AI provider// From https://github.com/alloy-org/ai-plugin/blob/main/lib/providers/fetch-ai-provider.js//// @param {string} apiKey - API key for the provider// @param {number} attemptNumber - The attempt number (0-indexed)// @param {boolean} jsonResponseExpected - Whether a JSON response is expected// @param {Array} messages - Array of message objects// @param {string} model - Model name// @param {string} promptKey - Key identifying the prompt type// @param {boolean} stream - Whether streaming is enabled// @param {number} timeoutSeconds - Request timeout in seconds// @returns {Promise<Response>} The fetch responseasync function makeRequest(app, messages, model, { attemptNumber = 1, promptKey = null,    stream = null, timeoutSeconds = 10 } = {}) {  const providerEm = "openai"; // See a `providerFromModel(model)` implementation in AmpleAI  if (attemptNumber > 0) console.debug(`Attempt #${ attemptNumber }: Trying ${ model } with ${ promptKey || "no promptKey" }`);   const apiKey = app.settings["OpenAI Key"]; // From table of settings for model  const body = { model: "gpt-5.2", messages: ["To be, or not to be?"], response_format: { type: "json_object" }}; // See `requestBodyForProvider(messages, model, stream, tools, { promptKey })` in AmpleAI for live example of interpreting a request body for various providers  const endpoint = "https://api.openai.com/v1/chat/completions"; // See `providerEndpointUrl(model, apiKey)` implementation in AmpleAI for provider-specific URLs  console.debug(`Calling ${ providerEm } at ${ endpoint } with body ${ JSON.stringify(body) } at ${ new Date() }`);  const headers = { "Content-Type": "application/json", "Authorization": `Bearer ${ apiKey }` }; // See `headersForProvider(providerEm, apiKey)` in AmpleAI   // Use Promise.race to implement request timeout: the fetch and a timeout promise race,  // and if the timeout resolves first, the request is rejected with a timeout error  const fetchResponse = await Promise.race([    fetch(endpoint, {      method: "POST", headers, body: JSON.stringify(body),    }),    new Promise((_, reject) =>      setTimeout(() => reject(new Error('Timeout')), timeoutSeconds * 1000)    )  ]);   if (!fetchResponse.ok) {    const err = new Error(`Request failed with status ${ fetchResponse.status }`);    err.response = fetchResponse;    throw err;  }   return fetchResponse;} 
  ```
processedNoteContent: ---
  title: Plugin API Examples
  headers: # Calling an LLM (OpenAI, Gemini, Anthropic) from a plugin
  ---
  Calling an LLM (OpenAI, Gemini, Anthropic) from a plugin
  The [AmpleAI plugin](https://www.amplenote.com)`
    offers a live prototype to see how an AI response. The challenges & decisions to make when you're building a plugin that will interface with AI include "will response be streamed?" (usually advisable) and "how will the plugin show streamed progress if so?" Here is the general idea: // --------------------------------------------------------------------------// Make a single request attempt to the AI provider// From https://github.com/alloy-org/ai-plugin/blob/main/lib/providers/fetch-ai-provider.js//// @param {string} apiKey - API key for the provider// @param {number} attemptNumber - The attempt number (0-indexed)// @param {boolean} jsonResponseExpected - Whether a JSON response is expected// @param {Array} messages - Array of message objects// @param {string} model - Model name// @param {string} promptKey - Key identifying the prompt type// @param {boolean} stream - Whether streaming is enabled// @param {number} timeoutSeconds - Request timeout in seconds// @returns {Promise<Response>} The fetch responseasync function makeRequest(app, messages, model, { attemptNumber = 1, promptKey = null,    stream = null, timeoutSeconds = 10 } = {}) {  const providerEm = "openai"; // See a `providerFromModel(model)` implementation in AmpleAI  if (attemptNumber > 0) console.debug(`Attempt #${ attemptNumber }: Trying ${ model } with ${ promptKey || "no promptKey" }`);   const apiKey = app.settings["OpenAI Key"]; // From table of settings for model  const body = { model: "gpt-5.2", messages: ["To be, or not to be?"], response_format: { type: "json_object" }}; // See `requestBodyForProvider(messages, model, stream, tools, { promptKey })` in AmpleAI for live example of interpreting a request body for various providers  const endpoint = "https://api.openai.com/v1/chat/completions"; // See `providerEndpointUrl(model, apiKey)` implementation in AmpleAI for provider-specific URLs  console.debug(`Calling ${ providerEm } at ${ endpoint } with body ${ JSON.stringify(body) } at ${ new Date() }`);  const headers = { "Content-Type": "application/json", "Authorization": `Bearer ${ apiKey }` }; // See `headersForProvider(providerEm, apiKey)` in AmpleAI   // Use Promise.race to implement request timeout: the fetch and a timeout promise race,  // and if the timeout resolves first, the request is rejected with a timeout error  const fetchResponse = await Promise.race([    fetch(endpoint, {      method: "POST", headers, body: JSON.stringify(body),    }),    new Promise((_, reject) =>      setTimeout(() => reject(new Error('Timeout')), timeoutSeconds * 1000)    )  ]);   if (!fetchResponse.ok) {    const err = new Error(`Request failed with status ${ fetchResponse.status }`);    err.response = fetchResponse;    throw err;  }   return fetchResponse;}
noteTitle: Plugin API Examples