Polyfill da Prompt API: como dar fallback para Firefox e Safari
Polyfill da Prompt API: como dar fallback para Firefox e Safari
O prompt-api-polyfill é uma biblioteca do Google Chrome Labs (maio de 2026) que implementa a interface completa da Prompt API em browsers que não têm IA on-device. Firefox, Safari, Chrome velho — o polyfill redireciona a inferência para backends cloud (Gemini API, OpenAI) ou modelos locais via Transformers.js. Você escreve código contra a API padrão e funciona em qualquer browser moderno. Sem if/else por browser. Sem gambiarras.
TL;DR
- O polyfill oficial implementa
window.LanguageModelem Firefox, Safari e Chrome antigo via backends cloud ou Transformers.js - Estratégia: on-device quando disponível (privacidade, offline), cloud como fallback (compatibilidade)
- Código idêntico funciona cross-browser — progressive enhancement com feature detection
Se tem uma lição que eu levo de anos fazendo web: nunca aposte tudo em feature exclusiva de um browser. Já vi esse filme — ActiveX, prefixed CSS, APIs que morreram na praia. O polyfill resolve exatamente isso.

O problema
A Prompt API funciona no Chrome. E só no Chrome. O cenário real:
| Browser | Status | Posição |
|---|---|---|
| Chrome 148+ | ✅ Estável com Gemini Nano | Propositor |
| Edge | ✅ Developer Preview com Phi-4-mini / Aion | Suporte ativo |
| Firefox | ❌ Não implementa | Posição contrária |
| Safari | ❌ Não implementa | Posição contrária |
Isso significa ~30% dos usuários web (Firefox + Safari) ficam de fora. Para aplicação em produção, ignorar um terço dos usuários não é opção.
Instalação e setup
npm install prompt-api-polyfill
O polyfill funciona em duas camadas: a interface (implementa window.LanguageModel) e o backend (direciona inferência para um provedor).
Backends disponíveis
| Backend | Processamento | Custo | Privacidade | Offline |
|---|---|---|---|---|
| Gemini API (cloud) | Servidor Google | Pay-per-token | Dados saem | ❌ |
| OpenAI API (cloud) | Servidor OpenAI | Pay-per-token | Dados saem | ❌ |
| Transformers.js (local) | Browser do usuário | Grátis | Total | ✅ |
Com Gemini (cloud)
import config from './.env.json' with { type: 'json' };
window.GEMINI_CONFIG = config;
// Carregar APENAS quando necessário
if (!('LanguageModel' in window)) {
await import('prompt-api-polyfill');
}
// A partir daqui, mesma API
const session = await LanguageModel.create();
const resposta = await session.prompt("Explique recursão em uma frase");
Com Transformers.js (100% local)
// .env.json
{
"apiKey": "dummy",
"device": "webgpu",
"dtype": "q4f16",
"modelName": "onnx-community/gemma-3-1b-it-ONNX-GQA"
}
window.TRANSFORMERSJS_CONFIG = config;
if (!('LanguageModel' in window)) {
await import('prompt-api-polyfill');
}
// Mesma API — mas roda Transformers.js por baixo
const session = await LanguageModel.create();
const resposta = await session.prompt("Olá, mundo!");
O backend Transformers.js não suporta responseConstraint (structured output). Os backends cloud suportam.
Progressive enhancement: o padrão correto
O approach certo é começar com funcionalidade básica e melhorar quando IA está disponível:
class AIAssistant {
#session = null;
#modo = 'sem-ia';
async inicializar() {
// Tentar nativo primeiro
if ('LanguageModel' in window) {
const disponibilidade = await LanguageModel.availability({
expectedInputs: [{ type: "text" }],
expectedOutputs: [{ type: "text" }]
});
if (disponibilidade !== 'unavailable') {
this.#session = await LanguageModel.create();
this.#modo = 'nativo';
return;
}
}
// Tentar polyfill
try {
const config = await fetch('/api/ai-config').then(r => r.json());
window.GEMINI_CONFIG = config;
await import('prompt-api-polyfill');
this.#session = await LanguageModel.create();
this.#modo = 'polyfill';
} catch (e) {
this.#modo = 'sem-ia';
}
}
async processar(texto) {
switch (this.#modo) {
case 'nativo':
case 'polyfill':
return await this.#session.prompt(texto);
case 'sem-ia':
return "IA não disponível neste browser.";
}
}
get modo() { return this.#modo; }
}
Sem polyfill: fallback manual
Se preferir controle total:
async function criarMotorIA() {
// 1. Prompt API nativa
if ('LanguageModel' in window) {
const status = await LanguageModel.availability();
if (status === 'available') {
const session = await LanguageModel.create();
return {
tipo: 'on-device',
prompt: (texto) => session.prompt(texto),
stream: (texto) => session.promptStreaming(texto),
destroy: () => session.destroy()
};
}
}
// 2. Fallback para API cloud própria
return {
tipo: 'cloud',
prompt: async (texto) => {
const res = await fetch('/api/ia/prompt', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt: texto })
});
return (await res.json()).resposta;
},
stream: (texto) => {
const eventSource = new EventSource(
`/api/ia/stream?prompt=${encodeURIComponent(texto)}`
);
return new ReadableStream({
start(controller) {
eventSource.onmessage = (e) => {
if (e.data === '[DONE]') {
controller.close();
eventSource.close();
} else {
controller.enqueue(e.data);
}
};
}
});
},
destroy: () => {}
};
}
const motor = await criarMotorIA();
console.log(`Usando: ${motor.tipo}`);
Essa abordagem dá controle completo sobre o fallback. Mais código, mas nenhuma dependência extra.
Feature detection robusta
Ir além de checar se LanguageModel existe:
async function detectarCapacidadesIA() {
const resultado = {
apiDisponivel: false,
modeloDisponivel: false,
multimodal: false,
structuredOutput: false,
offline: !navigator.onLine
};
if (!('LanguageModel' in window)) return resultado;
resultado.apiDisponivel = true;
try {
const status = await LanguageModel.availability({
expectedInputs: [{ type: "text" }],
expectedOutputs: [{ type: "text" }]
});
resultado.modeloDisponivel = status === 'available';
if (status === 'unavailable') return resultado;
} catch (e) {
return resultado;
}
// Checar multimodal
try {
const imgStatus = await LanguageModel.availability({
expectedInputs: [{ type: "text" }, { type: "image" }],
expectedOutputs: [{ type: "text" }]
});
resultado.multimodal = imgStatus !== 'unavailable';
} catch (e) {}
resultado.structuredOutput = true; // desde Chrome 137
return resultado;
}
Diferenças nativo vs. polyfill
Importante comunicar ao usuário:
| Aspecto | Nativo (on-device) | Polyfill (cloud) |
|---|---|---|
| Privacidade | Dados nunca saem | Dados enviados ao provedor |
| Latência | ~100-500ms | ~200-1000ms (rede) |
| Offline | ✅ | ❌ |
| Custo | Grátis | Pay-per-token |
| Qualidade | Gemini Nano (limitado) | Modelo cloud (superior) |
| Download | ~4.27GB inicial | Nenhum |
| Context window | ~4K tokens | 32K-128K |
Transparência com o usuário
As pessoas devem saber onde seus dados são processados:
function mostrarStatusIA(motor) {
const badge = document.getElementById('ia-status');
switch (motor.tipo) {
case 'on-device':
badge.textContent = '🟢 IA Local';
badge.title = 'Processamento no seu dispositivo. Dados não saem do browser.';
break;
case 'cloud':
badge.textContent = '☁️ IA Cloud';
badge.title = 'Processamento via servidor. Dados enviados de forma segura.';
break;
case 'sem-ia':
badge.textContent = '⚪ Sem IA';
badge.title = 'IA não disponível neste browser.';
break;
}
}
Segurança da API key
Cuidado sério: o polyfill cloud expõe sua API key no frontend. Em produção:
// ❌ NUNCA em produção: API key exposta
window.GEMINI_CONFIG = { apiKey: "AIzaSy..." };
// ✅ Proxy via backend com rate limiting
const resposta = await fetch('/api/ai/prompt', {
method: 'POST',
body: JSON.stringify({ prompt: texto }),
headers: { 'Authorization': `Bearer ${sessionToken}` }
});
Use Firebase AI Logic com App Check ou proxy próprio. API key no frontend é convite para abuso.
Tratando offline
O polyfill cloud não funciona sem rede. Trate isso:
window.addEventListener('offline', () => {
if (motorAtual.tipo === 'cloud') {
mostrarAviso('Offline. IA indisponível.');
desabilitarBotoesIA();
}
// On-device continua funcionando
});
window.addEventListener('online', () => {
if (motorAtual.tipo === 'cloud') {
esconderAviso();
habilitarBotoesIA();
}
});
FAQ
O polyfill funciona em todos os browsers?
Sim, em qualquer browser com ES modules e dynamic imports. Firefox, Safari, Chrome antigo, Edge.
Preciso de API key para backend Transformers.js?
Não. Use "apiKey": "dummy". Processamento local. Mas um modelo ONNX precisa ser baixado (~100-500MB dependendo).
O polyfill suporta multimodal?
Sim, exceto: backend OpenAI não suporta áudio+imagem juntos, backend Transformers.js não suporta structured output. Backend Gemini suporta tudo.
Diferença de qualidade entre on-device e cloud?
Grande. Gemini Nano (~4B params) é otimizado para velocidade. Backends cloud usam modelos maiores com qualidade muito superior para tarefas complexas.
O polyfill simula downloadprogress?
Sim. Para backends cloud (sem download de modelo), emite dois eventos: loaded: 0 e loaded: 1. Sua aplicação não precisa de lógica especial.
Conclusão
O polyfill resolve o maior empecilho de adoção da Prompt API: rodar em todo lugar. Você codifica contra window.LanguageModel e funciona em Chrome (nativo), Edge (nativo), Firefox (polyfill), Safari (polyfill) — mesma interface, sem branch por browser.
A estratégia que eu recomendo: on-device quando disponível (privacidade, offline, sem custo), cloud via polyfill como fallback (compatibilidade, qualidade superior pra tarefas pesadas). Dá uma olhada nas limitações do Gemini Nano em benchmarks pra saber quando o modelo local basta e quando vale apelar pro cloud.
Próximo artigo: Performance e limitações do Gemini Nano em benchmarks reais