Skip to main content

Overview

The Knowledge Tooltip extension integrates with the OpenAI Chat Completions API to provide AI-generated explanations, summaries, and contextual information. This powers the AI tab in the tooltip.
Requires user-provided API key. The extension stores the key securely in Chrome’s local storage and never syncs it across devices.

Features Using This API

  • AI Tab: Generates contextual explanations using GPT models
  • Smart Summaries: Creates concise explanations tailored to user queries
  • Multi-language Support: Responds in English or Arabic based on user preference
  • Secure Key Storage: API key stored locally, never synced to cloud

Chat Completions Endpoint

Sends messages to OpenAI’s Chat Completions API and receives AI-generated responses.

Endpoint

POST https://api.openai.com/v1/chat/completions

Implementation

From background.js:251-305:
async function callOpenAI(messages, language, maxTokens = 500) {
  const result = await chrome.storage.local.get(['openaiKey']);
  const apiKey = result.openaiKey;

  if (!apiKey) {
    throw new Error('NO_API_KEY');
  }

  const response = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${apiKey}`
    },
    body: JSON.stringify({
      model: 'gpt-5-nano',
      messages: messages,
      max_completion_tokens: maxTokens,
      reasoning_effort: 'minimal'
    })
  });

  if (!response.ok) {
    const status = response.status;
    let errorDetail = '';
    try {
      const errorData = await response.json();
      errorDetail = errorData.error?.message || JSON.stringify(errorData);
    } catch (e) {
      errorDetail = await response.text();
    }

    console.error('OpenAI API error:', status, errorDetail);

    if (status === 401) {
      throw new Error('INVALID_API_KEY');
    } else if (status === 429) {
      throw new Error('RATE_LIMITED');
    } else if (status === 402) {
      throw new Error('INSUFFICIENT_QUOTA');
    } else if (status === 400) {
      throw new Error(`INVALID_REQUEST: ${errorDetail}`);
    }
    throw new Error(`HTTP ${status}: ${errorDetail}`);
  }

  const data = await response.json();

  if (!data.choices || data.choices.length === 0) {
    throw new Error('No response from AI');
  }

  return data.choices[0].message.content;
}

Request Format

Headers

Content-Type
string
required
Always application/json
Authorization
string
required
Bearer token with user’s OpenAI API key: Bearer sk-...

Request Body

model
string
required
OpenAI model to use. Extension uses gpt-5-nano
messages
array
required
Array of message objects with role and content
max_completion_tokens
number
default:"500"
Maximum tokens to generate in the response
reasoning_effort
string
default:"minimal"
Effort level for model reasoning (specific to certain GPT models)

Request Example

chrome.runtime.sendMessage({
  action: 'callOpenAI',
  messages: [
    {
      role: 'system',
      content: 'You are a helpful assistant that explains concepts clearly and concisely.'
    },
    {
      role: 'user',
      content: 'Explain quantum entanglement in simple terms.'
    }
  ],
  language: 'en',
  maxTokens: 300
}, (response) => {
  if (response.success) {
    console.log(response.data);
  } else {
    console.error(response.error);
  }
});

Request Payload Example

{
  "model": "gpt-5-nano",
  "messages": [
    {
      "role": "system",
      "content": "You are a helpful assistant."
    },
    {
      "role": "user",
      "content": "What is machine learning?"
    }
  ],
  "max_completion_tokens": 500,
  "reasoning_effort": "minimal"
}

Response Format

Success Response

choices
array
Array of completion choices (typically contains 1 element)
usage
object
Token usage statistics
model
string
Model used for generation

Response Example

{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 1677652288,
  "model": "gpt-5-nano",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Machine learning is a branch of artificial intelligence that enables computers to learn from data and improve their performance without being explicitly programmed."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 25,
    "completion_tokens": 35,
    "total_tokens": 60
  }
}

Extension Response Processing

The extension extracts just the content:
const data = await response.json();

if (!data.choices || data.choices.length === 0) {
  throw new Error('No response from AI');
}

return data.choices[0].message.content;
Returned to content script:
{
  success: true,
  data: "Machine learning is a branch of artificial intelligence..."
}

Error Handling

The extension implements comprehensive error handling for various API failure scenarios.
From background.js:274-296:
if (!response.ok) {
  const status = response.status;
  let errorDetail = '';
  try {
    const errorData = await response.json();
    errorDetail = errorData.error?.message || JSON.stringify(errorData);
  } catch (e) {
    errorDetail = await response.text();
  }

  console.error('OpenAI API error:', status, errorDetail);

  if (status === 401) {
    throw new Error('INVALID_API_KEY');
  } else if (status === 429) {
    throw new Error('RATE_LIMITED');
  } else if (status === 402) {
    throw new Error('INSUFFICIENT_QUOTA');
  } else if (status === 400) {
    throw new Error(`INVALID_REQUEST: ${errorDetail}`);
  }
  throw new Error(`HTTP ${status}: ${errorDetail}`);
}

Error Codes

Status CodeError ConstantMeaningUser Action
401INVALID_API_KEYAPI key is invalid or expiredUpdate API key in settings
429RATE_LIMITEDToo many requestsWait and retry
402INSUFFICIENT_QUOTAAccount has no creditsAdd credits to OpenAI account
400INVALID_REQUESTMalformed requestCheck message format
500+Generic errorOpenAI server errorRetry later

Special Error: No API Key

From background.js:256-258:
if (!apiKey) {
  throw new Error('NO_API_KEY');
}
This occurs when the user hasn’t configured their API key yet.

API Key Management

The extension securely manages the OpenAI API key using Chrome’s storage API.

Key Storage Migration

From background.js:5-12:
// One-time migration: move API key from sync to local storage
chrome.runtime.onInstalled.addListener(async () => {
  const sync = await chrome.storage.sync.get(['openaiKey']);
  if (sync.openaiKey) {
    await chrome.storage.local.set({ openaiKey: sync.openaiKey });
    await chrome.storage.sync.remove('openaiKey');
  }
});
API keys are stored in local storage (not synced) to prevent accidental exposure across devices.

Check API Key Presence

From background.js:71-76:
if (message.action === 'checkOpenAIKey') {
  chrome.storage.local.get(['openaiKey'], (result) => {
    sendResponse({ hasKey: !!result.openaiKey });
  });
  return true;
}
Usage:
chrome.runtime.sendMessage(
  { action: 'checkOpenAIKey' },
  (response) => {
    if (response.hasKey) {
      // Enable AI tab
    } else {
      // Show "Configure API key" message
    }
  }
);

Setting API Key

From the options page or popup:
// Save API key
chrome.storage.local.set({ openaiKey: userInput }, () => {
  console.log('API key saved');
});

// Remove API key
chrome.storage.local.remove('openaiKey', () => {
  console.log('API key removed');
});

Model Selection

The extension uses gpt-5-nano for optimal performance and cost:
model: 'gpt-5-nano'

Model Characteristics

  • Speed: Fast response times for real-time tooltips
  • Cost: Economical for high-frequency requests
  • Quality: Sufficient for concise explanations
  • Token limit: Supports up to 500 completion tokens
To use a different model, modify the model parameter in background.js:267. Available models include gpt-4, gpt-4-turbo, gpt-3.5-turbo, etc.

Token Management

Max Completion Tokens

From background.js:252:
async function callOpenAI(messages, language, maxTokens = 500)
Default: 500 tokens (~375 words) Adjust based on use case:
// Short definition
chrome.runtime.sendMessage({
  action: 'callOpenAI',
  messages: [...],
  language: 'en',
  maxTokens: 150
});

// Detailed explanation
chrome.runtime.sendMessage({
  action: 'callOpenAI',
  messages: [...],
  language: 'en',
  maxTokens: 800
});

Token Estimation

Rule of thumb: 1 token ≈ 0.75 words
  • 100 tokens ≈ 75 words (short paragraph)
  • 500 tokens ≈ 375 words (medium explanation)
  • 1000 tokens ≈ 750 words (detailed response)

Rate Limits

OpenAI API rate limits vary by account tier. Free tier has strict limits; paid tiers increase capacity.

Typical Limits (Tier 1)

  • Requests per minute: 3,500
  • Tokens per minute: 200,000
  • Requests per day: 200

Handling Rate Limits

From background.js:289-290:
else if (status === 429) {
  throw new Error('RATE_LIMITED');
}
Implement retry logic in the content script:
async function callOpenAIWithRetry(messages, language, retries = 3) {
  for (let i = 0; i < retries; i++) {
    const response = await new Promise(resolve => {
      chrome.runtime.sendMessage({
        action: 'callOpenAI',
        messages,
        language
      }, resolve);
    });
    
    if (response.success) return response.data;
    
    if (response.error === 'RATE_LIMITED' && i < retries - 1) {
      await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
      continue;
    }
    
    throw new Error(response.error);
  }
}

Message Patterns

System Prompt

Define the AI’s behavior:
{
  role: 'system',
  content: 'You are a concise educational assistant. Explain concepts clearly in 2-3 sentences. Respond in the user\'s language.'
}

User Query

The actual question or request:
{
  role: 'user',
  content: `Explain "${selectedText}" in simple terms.`
}

Multi-turn Conversations

Include conversation history:
[
  { role: 'system', content: 'You are a helpful assistant.' },
  { role: 'user', content: 'What is photosynthesis?' },
  { role: 'assistant', content: 'Photosynthesis is...' },
  { role: 'user', content: 'How does chlorophyll work?' }
]

Language Support

The extension passes language context to influence the AI’s response language:
// English response
chrome.runtime.sendMessage({
  action: 'callOpenAI',
  messages: [
    { role: 'system', content: 'Respond in English.' },
    { role: 'user', content: 'Explain gravity.' }
  ],
  language: 'en'
});

// Arabic response
chrome.runtime.sendMessage({
  action: 'callOpenAI',
  messages: [
    { role: 'system', content: 'Respond in Arabic.' },
    { role: 'user', content: 'اشرح الجاذبية' }
  ],
  language: 'ar'
});
While the language parameter is passed to callOpenAI(), the actual language control comes from the system message. The parameter may be used for logging or future features.

Security Considerations

API Key Storage

Secure practices:
  • Stored in chrome.storage.local (not synced)
  • Never logged or transmitted except to OpenAI
  • Migrated from sync storage on install
Avoid:
  • Hardcoding API keys in source code
  • Storing in chrome.storage.sync
  • Exposing keys in console logs

Request Validation

Always validate user input before sending to API:
function sanitizeInput(text) {
  // Remove potential injection attempts
  return text.trim().slice(0, 1000); // Max 1000 characters
}

const userQuery = sanitizeInput(selectedText);

Cost Optimization

Token Usage Strategies

  1. Limit max tokens: Use only what’s needed (150-300 for definitions)
  2. Concise prompts: Shorter system messages save tokens
  3. Cache responses: Store AI responses locally to avoid repeat calls
  4. Debounce requests: Wait for user to finish selecting text

Example: Efficient Prompting

// Inefficient (wastes tokens)
const messages = [
  {
    role: 'system',
    content: 'You are an extremely helpful and knowledgeable assistant with expertise in many domains including science, history, mathematics, and more. Please provide detailed explanations...'
  },
  { role: 'user', content: 'Define photosynthesis' }
];

// Efficient
const messages = [
  { role: 'system', content: 'Explain concepts in 2-3 sentences.' },
  { role: 'user', content: 'Define photosynthesis' }
];

Build docs developers (and LLMs) love