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 ;
}
Bearer token with user’s OpenAI API key: Bearer sk-...
Request Body
OpenAI model to use. Extension uses gpt-5-nano
Array of message objects with role and content One of: system, user, assistant
Maximum tokens to generate in the response
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"
}
Success Response
Array of completion choices (typically contains 1 element) The generated message Always assistant for responses
The AI-generated text response
Reason generation stopped: stop, length, content_filter
Choice index (0 for single responses)
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 Code Error Constant Meaning User Action 401INVALID_API_KEYAPI key is invalid or expired Update API key in settings 429RATE_LIMITEDToo many requests Wait and retry 402INSUFFICIENT_QUOTAAccount has no credits Add credits to OpenAI account 400INVALID_REQUESTMalformed request Check message format 500+Generic error OpenAI server error Retry 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 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
Limit max tokens : Use only what’s needed (150-300 for definitions)
Concise prompts : Shorter system messages save tokens
Cache responses : Store AI responses locally to avoid repeat calls
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' }
];