ADR-0025 SDK de IA
SDK de IA padrão da empresa — TanStack AI, fixado em versões exatas pré-1.0 e agnóstico do fornecedor; cada produto escolhe o seu fornecedor de modelos num ADR próprio.
- Decisão
- TanStack AI, fixado; fornecedor por produtoConstruir a app
- Estado
- Aceite
- Data
- Âmbito
- Predefinição da empresa para todas as novas apps
Ligações
Apoia-se em
Contexto
Seção intitulada “Contexto”Os produtos vão acrescentar funcionalidades de IA: um assistente de conversa, resumos, extração de dados de documentos, ações que o modelo executa através de tools. As decisões à volta estão tomadas, e cada uma impõe uma regra a esta:
- Framework: TanStack Start com server functions e SSR com streaming (ADR-0002); server routes em
/api/*para tudo o que é consumido como API HTTP (ADR-0036) - Validação: Zod 4 em todas as fronteiras (ADR-0005). Os argumentos de uma tool e a resposta estruturada de um modelo também são fronteiras — o modelo é um chamador que não controlamos
- Autenticação: sessões do Better Auth, verificadas no servidor (ADR-0009)
- Rate limiting: um só limitador no middleware global de pedidos do Start, indexado pelo ID do utilizador (ADR-0010). O seu limite inicial de escrita é de 300 pedidos por utilizador por minuto — dimensionado para formulários, não para chamadas que custam dinheiro
- Estado do cliente: dados do servidor no Query, estado de UI no URL e no React (ADR-0012)
- Observabilidade: OpenTelemetry, filtrado na app antes de exportar; a telemetria já transporta IDs de utilizadores, pelo que o que registamos é uma decisão de privacidade (ADR-0015)
- Testes: sem chamadas reais a terceiros (ADR-0014) — um modelo é uma delas
- Renderização: o texto do modelo é Markdown (ADR-0021), com realce de sintaxe no código (ADR-0022)
O que está em aberto: o SDK que fala com os modelos e as regras para o usar. O fornecedor de modelos não está em aberto aqui. Qual o fornecedor, o modelo e a região é uma decisão por produto, porque depende dos dados que o produto envia e dos termos contratuais que consegue obter. Vai para o ADR próprio do produto (Como decidimos, regra 5), e a secção Decisão diz o que esse ADR tem de cobrir.
Três factos moldam a resposta:
- Uma chamada a um modelo é um pedido pago a um terceiro. Custa dinheiro por token, pode durar um minuto e tudo o que pomos no prompt sai da nossa infraestrutura. O rate limiting (ADR-0010) e a proteção de dados (RGPD; somos uma empresa da UE) aplicam-se desde a primeira chamada
- A saída do modelo é input não confiável. Tudo o que o modelo lê — uma mensagem de um utilizador, um documento, o resultado de uma tool, uma página web — pode influenciar o que escreve e que tools chama. O OWASP Top 10 for LLM Applications (2025) lista prompt injection, improper output handling, excessive agency e unbounded consumption. Tratamos o texto do modelo, os argumentos das tools e as respostas estruturadas como o payload de um webhook (ADR-0005)
- O SDK é pré-1.0 e evolui depressa. O
@tanstack/aiestá na 0.70.0. A questão é como o adotar com segurança, não se está terminado
Requisitos:
- Chat com streaming em React, com estados de paragem e de erro, que se encaixe nas server functions do Start e nas rotas da API
- Agnóstico do fornecedor: trocar ou acrescentar um fornecedor altera um adapter, num único sítio do servidor
- Tools tipadas cujos schemas são Zod, e saída estruturada validada
- Corre em Bun no nosso contentor, sem gateway alojado nem plataforma no caminho (ADR-0003)
- Spans OpenTelemetry e métricas de tokens, com o conteúdo dos prompts desligado por predefinição
- Um modelo falso com respostas definidas no teste, para que os testes não precisem de rede nem de chave
Opções consideradas
Seção intitulada “Opções consideradas”A. TanStack AI
Seção intitulada “A. TanStack AI”O @tanstack/ai contém o núcleo: chat(), tools, saída estruturada, middleware e helpers de servidor. O @tanstack/ai-react contém os hooks. Cada fornecedor é um pacote adapter: OpenAI (@tanstack/ai-openai 0.30.1), Anthropic (@tanstack/ai-anthropic 0.22.1), Gemini, Vertex, Bedrock, Mistral, Ollama, OpenRouter, Groq, Grok e mais — além de openaiCompatible para qualquer endpoint compatível com a OpenAI.
Prós
- Mesmos mantenedores e mesma organização do Router, do Query, do Start e do Form; MIT.
@tanstack/ai0.70.0 (2026-10-19) e@tanstack/ai-react0.31.0. Cerca de 594 mil downloads por semana (2026-10-12 a 2026-10-18) - O
chat()devolve um async iterable de eventos; otoServerSentEventsResponse()transforma-o numaResponseSSE. No React, ouseChat()lê-a através defetchServerSentEvents()para uma rota HTTP, ou de umfetcherpara uma server function do Start. Ostop()aborta o pedido, e umAbortControllerpassado aochat()pára também a chamada ao modelo - As tools definem-se uma só vez com
toolDefinition()e um schema Zod, e implementam-se depois com.server()ou.client(). O SDK valida os argumentos de cada chamada antes de a tool correr; uma falha volta ao modelo como erro de tool. OneedsApprovalpausa uma tool até o utilizador aprovar - Saída estruturada através de
outputSchema(Zod 4.2+, ArkType ou Valibot) - O
contextconstruído no servidor é passado às tools e ao middleware, e nunca é enviado ao modelo — um ponto pronto para o utilizador autenticado e para as dependências injetadas - Um middleware OpenTelemetry (
@tanstack/ai/middlewares/otel) emite um span por chamada achat(), por volta do modelo e por chamada a uma tool, com atributos GenAI e histogramas de tokens e de duração. A captura de prompts e de respostas está desligada, a menos que se usecaptureContent: true— lemos a predefinição no código-fonte da 0.70.0 - O
fakeText()de@tanstack/ai/testingé um adapter com guião: respostas em fila, chamadas a tools, erros, uso de tokens estimado - Quando a chamada seguinte vai para outro fornecedor, o SDK reescreve o histórico guardado (assinaturas de raciocínio, IDs de chamadas a tools) para o adaptar — o que torna realista um fornecedor de reserva
- Um painel de devtools para mensagens e chamadas a tools (
@tanstack/react-ai-devtools). Biblioteca pura: sem camada de plataforma, funciona em qualquer host que devolva umaResponseweb
Contras
- Pré-1.0 e em mudança constante. 102 versões em cerca de 10 meses e meio; 25 novas linhas minor entre a 0.43 (2026-08-04) e a 0.70 (2026-10-19). Já saíram alterações incompatíveis dentro da 0.x — o formato do protocolo HTTP, as opções de amostragem movidas para
modelOptions, as props doTextPart, os pacotes do MCP SDK — cada uma com um guia de migração - Cada adapter e o
@tanstack/ai-reacttêm o@tanstack/ai^0.70.0como peer dependency, o que numa versão 0.x significa apenas a mesma minor - Mais jovem e mais pequeno do que o AI SDK: cerca de 594 mil downloads por semana contra cerca de 18,4 M, e menos respostas online. A documentação no GitHub acompanha o
main - A documentação mostra padrões que proibimos por predefinição: uma chave guardada no browser (BYOK), um fornecedor escolhido pelo cliente (
forwardedProps) e uma server function cujo validador é apenas um cast de TypeScript - Os SDKs dos fornecedores vêm como dependências dos adapters, com intervalos que não controlamos
- O
@tanstack/ai-react0.31.0 tem o@tanstack/markdown^0.0.13como dependência obrigatória (não opcional), usada pelo seuTextPartpré-construído. O renderizador de Markdown dele é uma segunda cópia, mais antiga, da biblioteca por trás do ADR-0021
B. Vercel AI SDK
Seção intitulada “B. Vercel AI SDK”ai 7.0.148 (Apache-2.0, publicado em 2026-10-21), @ai-sdk/react 4.0.152 e pacotes de fornecedores como @ai-sdk/openai 4.0.99.
Prós
- Uma major estável e a maior base de utilizadores: cerca de 18,4 M de downloads por semana para o
aie 5,7 M para o@ai-sdk/react - Dezenas de pacotes de fornecedores oficiais;
useChat; tool calling; um modelo simulado emai/test(MockLanguageModelV4); OpenTelemetry através do@ai-sdk/otel1.0.148 - Uma abstração de agente pronta, o
ToolLoopAgent
Contras
- Depois de registada a sua integração OpenTelemetry, as entradas e as saídas são registadas por predefinição; a privacidade exige
recordInputs: falseerecordOutputs: false. A opção segura é a exceção — o oposto da nossa - Mantido pela Vercel, a empresa de plataforma que recusámos como destino de deploy (ADR-0003) e que agora é dona do Better Auth (ADR-0009); a sua documentação lista o Vercel AI Gateway em primeiro lugar entre os fornecedores. A licença e correr no nosso próprio contentor mantêm a saída em aberto, e nada no SDK exige a plataforma
- Um segundo ciclo de lançamentos ao lado do da TanStack, e 236 versões do
aino npm nos últimos 90 dias: semver estável, mas ruído constante de dependências
C. SDKs dos fornecedores, diretamente
Seção intitulada “C. SDKs dos fornecedores, diretamente”openai 7.39.0 (Apache-2.0) e @anthropic-ai/sdk 0.137.0 (MIT).
Prós: nada entre nós e o fornecedor; acesso imediato às novidades do fornecedor; o menor conjunto de dependências; cerca de 25,3 M e 28,3 M de downloads por semana.
Contras: cada um tem as suas próprias formas de mensagens, tools e streams, que se espalhariam pelas nossas funcionalidades — trocar de fornecedor passa a ser uma reescrita. Sem hook de React, sem ciclo de tools, sem modelo falso entre fornecedores: cada produto reconstruiria o transporte SSE, o abort, o uso, a validação de tools e os spans OTel. O @anthropic-ai/sdk é, ele próprio, pré-1.0. (Os adapters da opção A envolvem estes mesmos SDKs.)
D. LangChain.js
Seção intitulada “D. LangChain.js”langchain 1.5.18, @langchain/core 1.2.20 e @langchain/langgraph 1.5.1, todos MIT.
Prós: 1.x estável; integrações vastas; o LangGraph para workflows com estado e vários passos; cerca de 1,7 M de downloads por semana para o langchain.
Contras: uma framework de agentes em camadas — um harness create_agent, a orquestração do LangGraph, o tracing do LangSmith — quando precisamos de um SDK fino. As suas abstrações ficariam entre o nosso código e o modelo, e o langchain depende tanto do LangGraph como do cliente langsmith.
E. Mastra
Seção intitulada “E. Mastra”@mastra/core 1.78.0 e @mastra/react 1.11.0, Apache-2.0 exceto o código em diretórios ee/, que tem licença própria.
Prós: 1.x estável; agentes, workflows e memória numa só framework TypeScript; cerca de 1,3 M de downloads por semana para o @mastra/core.
Contras: uma framework que quer ser dona do runtime dos agentes — o @mastra/core tem 30 dependências, entre as quais hono e ws. Adotaríamos o seu runtime, não um SDK, e verificaríamos a licença ee/ funcionalidade a funcionalidade.
Onde vive a chamada ao modelo
Seção intitulada “Onde vive a chamada ao modelo”- Server function do Start:
createServerFn({ method: "POST" })a devolvertoServerSentEventsResponse(...), chamada a partir deuseChat({ fetcher }). Para a nossa própria UI, como diz a convenção do ADR-0036. O mesmo caminho de qualquer escrita: limitador, verificação CSRF, validação - Server route em
/api/*, lida comfetchServerSentEvents(): para consumidores fora da nossa app React — mobile, parceiros, scripts. O limitador cobre-a; a verificação CSRF das server functions não, pelo que não pode aceitar o cookie de sessão
Decisão
Seção intitulada “Decisão”O TanStack AI é o SDK de IA padrão da empresa para todas as novas apps: o @tanstack/ai e um adapter de fornecedor no servidor, o @tanstack/ai-react no browser. É aceite enquanto pré-1.0, fixado em versões exatas. Este ADR escolhe o SDK e as regras para o usar — não um fornecedor de modelos. Cada produto escolhe o seu fornecedor num ADR próprio.
Em concreto:
- Pré-1.0, aceite e fixado — como o ADR-0008 fez com o Drizzle v1. Sem
^nem~em nenhum pacote@tanstack/ai*nem no@tanstack/react-ai-devtools. Hoje:@tanstack/ai0.70.0,@tanstack/ai-react0.31.0 e os adapters que o ADR de um produto indicar (por exemplo@tanstack/ai-openai0.30.1 ou@tanstack/ai-anthropic0.22.1). Avançam em conjunto, num só grupo do Renovate (ADR-0016), porque cada adapter e o@tanstack/ai-reacttêm como peer dependency a mesma minor do@tanstack/ai. Os pacotes@tanstack/ai-*transitivos (ai-client,ai-utils,ai-event-client) ficam presos pelo lockfile, que o CI instala congelado. As atualizações são deliberadas: ler as notas de lançamento e os guias de migração, correr os testes sobre o modelo falso e fazer deploy do servidor e do bundle do browser em conjunto. Reavaliar quando a 1.0 sair, num novo ADR - As chamadas a modelos correm só no servidor. O
chat()e os adapters são importados apenas por código de servidor; o browser importa o@tanstack/ai-reacte nunca um pacote adapter. A chave do fornecedor é uma variável de ambiente do servidor, validada no arranque com Zod (ADR-0005) e nunca exposta ao cliente (sem prefixoVITE_). Sem BYOK (@tanstack/ai-react/byok): uma chave guardada no browser muda quem é o cliente do fornecedor, pelo que um produto que a queira escreve o seu próprio ADR - Uma só fronteira de adapter. Exatamente um módulo,
src/server/ai/adapter.ts, importa um pacote de fornecedor e lê a sua chave. Devolve o tipo de adapter do SDK (AnyTextAdapter) construído a partir de configuração validada. O código das funcionalidades, as server functions e as rotas recebem o adapter como parâmetro e nunca importam um pacote de fornecedor; os testes passam ofakeText()no seu lugar, e um produto que acrescente um fornecedor de reserva acrescenta aqui uma segunda factory e mais nada. O fornecedor e o modelo vêm da configuração, nunca do pedido — o exemplo de troca em runtime do SDK lêforwardedProps.providerdo corpo do pedido, o que deixaria um cliente escolher um fornecedor que nenhum ADR aprovou. Um produto que ofereça escolha de modelo mapeia um enum Zod para uma lista de permitidos no servidor - Handlers finos, regras em módulos simples. Uma server function ou um handler de rota da API apenas faz parse, autoriza, limita e chama uma função da funcionalidade. Um módulo,
src/server/ai/chat.ts, é o único que chama ochat(): recebe como parâmetros o adapter, as mensagens validadas, o utilizador e um sinal de abort, e liga o middleware (OpenTelemetry, uso, orçamento de tools, mapeamento de erros). Os prompts e o conjunto de tools vivem em módulos simples que recebem as suas dependências — adapter, relógio, acesso a dados através docontext— como parâmetros (ADR-0014). O prompt de sistema é definido no servidor comsystemPrompts; o texto do utilizador nunca se torna uma mensagem de sistema - Server function para a nossa UI, uma server route para clientes externos (ADR-0036). A nossa UI chama uma server function
POST—createServerFn({ method: "POST" }).validator(schema)— que devolvetoServerSentEventsResponse(chat(...)), lida poruseChat({ fetcher }). NuncaGET: o limitador ignora leituras (ADR-0012). Uma server route em/api/v1/ai/*serve apenas consumidores que não são browsers. Faz parse comchatParamsFromRequestBody(await request.json())dentro de try/catch, respondendo ela própria400, e rejeita qualquer corpo que não sejaapplication/json. Autentica-se através doapiCaller, com um token e nunca com o cookie de sessão, pelo que não precisa de verificação CSRF — a do ADR-0013 cobre apenas server functions. (Alterado em 2026-11-13: este ADR usou primeiro uma rota Hono com ocsrf()do Hono; o ADR-0036 substituiu o Hono pelas server routes do Start.) - Primeiro autenticado, depois limitado por utilizador. Todo o ponto de entrada de IA identifica o caller através do Better Auth antes de tocar no SDK — a sessão para a server function, o token para a rota da API — e responde
401caso contrário; não há endpoint de IA anónimo. O limite de escrita genérico do ADR-0010 é demasiado folgado para chamadas a modelos, pelo que as rotas de IA consomem também um âmbitoaimais estrito do mesmo limitador, indexado pelo ID do utilizador — a começar em 10 pedidos por utilizador por 60 s, ajustado por produto. Um pedido é uma execução inteira dechat(), que pode chamar o modelo várias vezes. Acima do limite:429comRetry-After, como no ADR-0010 - Limitar o que um pedido pode custar. O limitador conta pedidos, não tokens, pelo que cada execução é também limitada:
- limites Zod para o número de mensagens e o tamanho do texto na fronteira
- um
agentLoopStrategyexplícito, comomaxIterations(n)— a predefinição são cinco voltas ao modelo, e uma volta pode emitir várias chamadas a tools — mais um orçamento de chamadas a tools no middleware (onBeforeToolCall) - um teto de tokens de saída nos
modelOptionsnativos do fornecedor do adapter - um orçamento diário de tokens por utilizador, contado a partir de
onUsageno mesmo Valkey (ADR-0010); o valor é definido no ADR do produto - um só
AbortControllerpassado aochat()e aotoServerSentEventsResponse(), para que o Stop ou um separador fechado pare também a chamada ao modelo - um prazo de execução abaixo do timeout de escrita da frente: o
respondingTimeouts.writeTimeoutdo Traefik “covers the time from the end of the request header read to the end of the response write” (cobre o tempo desde o fim da leitura dos cabeçalhos do pedido até ao fim da escrita da resposta), e o ADR-0017 começa-o em 60 s, pelo que um stream mais longo é cortado na frente. Abortar as execuções aos 50 s (valor inicial); um produto que precise de execuções mais longas altera o valor da frente de forma deliberada
- Zod em todas as fronteiras que o SDK abre:
- Pedido: o
chatParamsFromRequestverifica apenas a forma do pedido AG-UI. Validamos com o nosso próprio schema Zod, descartamos as mensagenssystemedeveloperenviadas pelo cliente e nunca passamos aochat()astoolsdo cliente — o servidor constrói a lista de tools. OsforwardedPropssão controlados pelo cliente: pôr cada campo numa lista de permitidos antes de o usar - Tools:
toolDefinition()cominputSchemaeoutputSchemaZod, limitados com.max()e enums. Não JSON Schema em bruto, que tipaargscomounknown. O SDK valida cada chamada; o handler continua a autorizar - Saída estruturada:
outputSchemacom Zod 4.2+. O caminho sem streaming valida; no caminho com streaming (stream: true) o SDK deixa a validação para nós — fazer parse com o mesmo schema (parseWithStandardSchema) antes de usar o objeto
- Pedido: o
- As tools correm no servidor, a menos que só toquem na UI.
.server()para dados, segredos e efeitos secundários, obtendo as dependências e o utilizador autenticado dechat({ context })— construído no servidor, nunca a partir do corpo do pedido..client()só para efeitos de UI, como um toast ou uma navegação: corre no browser e o seu resultado volta do cliente, logo é input não confiável. Os handlers autorizam pelo utilizador da sessão, não pela conversa: o modelo não é uma autoridade, e uma aprovação é um passo de UX, porque as aprovações retomam a partir do histórico de mensagens que o browser devolve. O resultado de uma tool também chega ao browser em stream (TOOL_CALL_RESULT), pelo que só devolve o que o utilizador autenticado pode ver. Privilégio mínimo: o menor conjunto de tools por rota, só de leitura por predefinição; escritas, envios, remoções e despesas usamneedsApproval: truee voltam a verificar a permissão no handler - Estado vem dos hooks, o texto passa pelo nosso
Markdown. O browser importa ouseChate os restantes hooks apenas da entrada raiz do@tanstack/ai-react— nunca de@tanstack/ai-react/ui(oChat, oChatMessagee oTextPartpré-construídos), e nunca oTextPartnem qualquer outro renderizador de Markdown do SDK. OTextPartrenderiza com a sua própria cópia do@tanstack/markdown, fixada de forma exata na linha 0.0.x, o que poria um segundo renderizador de Markdown, com configuração própria, ao lado do nosso (ver Consequências). Os hooks tratam do estado e do streaming; cada partetexté renderizada pelo componenteMarkdownpartilhado do ADR-0021 (src/components/markdown/markdown.tsx, configurado emsrc/lib/markdown.ts). O chat não tem configuração própria: oallowHtmlnunca é definido, as imagens remotas são descartadas a menos que a sua origem esteja na lista de permitidos do ADR-0021, e a extensão de streaming está sempre ligada - A saída do modelo é input não confiável. As partes de texto renderizam-se apenas pelo caminho seguro de Markdown do ADR-0021 — nunca HTML em bruto, nunca
dangerouslySetInnerHTML— e os blocos de código pelo ADR-0022. As regras do ADR-0021 para ligações e imagens aplicam-se, porque um modelo pode ser levado a emitir uma ligação ou imagem cujo URL leva dados da conversa para fora. As respostas estruturadas passam por parse com Zod antes de serem usadas. O texto do modelo nunca se torna um URL, um caminho de ficheiro, SQL ou um argumento de shell sem validação - Sem dados pessoais em logs ou traces por predefinição.
- Ligar o
otelMiddleware({ tracer, meter })de@tanstack/ai/middlewares/oteluma só vez, emchat.ts. O ADR-0015 já instala o@opentelemetry/api; o intervalo de peers do SDK é>=1.9.0 - Deixar o
captureContentemfalse, a sua predefinição: os spans passam então a transportar o modelo, as contagens de tokens, o motivo de fim e os nomes e resultados das tools — nunca prompts, respostas, nem argumentos e resultados de tools - Nunca definir
debug: truefora do desenvolvimento (imprime chunks em bruto do fornecedor e chamadas a tools), e nunca escrever prompts, respostas, nem entrada e saída de tools nos logs do pino. Registar IDs de utilizadores, nunca emails - Um produto que tenha de capturar conteúdo para depuração ou avaliação di-lo no seu ADR de fornecedor, com uma função
redacte uma retenção mais curta do que a dos traces no backend. Se oredactlançar uma exceção, o SDK regista[redaction_failed]em vez do conteúdo - Os erros também deixam escapar dados. O SDK põe a mensagem da exceção — e, em alguns adapters, o corpo do erro do fornecedor — no chunk
RUN_ERRORque segue em stream para o browser, e regista as exceções das tools nos spans mesmo com a captura desligada. Os handlers das tools lançam erros genéricos sem dados pessoais, e o servidor substitui a mensagem doRUN_ERRORpor uma genérica mais o ID do pedido antes de o stream sair (ADR-0013, ADR-0015)
- Ligar o
- O estado do chat é estado do cliente. O
useChaté dono da conversa em curso — mensagens, streaming, paragem, erros. Não é nem dados do servidor nem estado do URL (ADR-0012): não copiar mensagens para o Query nem para o Zustand. As conversas guardadas, se um produto as mantiver, são estado do servidor (Query com server functionsGET, escritas porPOST) e uma decisão do produto, com as suas próprias regras de retenção e de eliminação - Os testes usam um modelo falso, nunca um real. Os testes de servidor e de integração injetam o
fakeText()através da fronteira do adapter — respostas, chamadas a tools e erros definidos no teste, comstate.callCountpara fazer asserções. Os testes de componentes dão aouseChatuma ligaçãostream()sobre ochat()com um adapter falso. Nenhuma chamada real a um modelo no CI; oonUnhandledFrame: "error"do MSW (ADR-0014) apanha uma fuga. Toda a app com funcionalidades de IA acrescenta testes de guarda a par dos quatro do ADR-0014: um pedido sem sessão é recusado e o modelo falso nunca é chamado; um pedido acima do limite recebe429comRetry-After; um corpo demasiado grande é rejeitado antes do modelo; uma mensagemsystemdo cliente e uma lista detoolsdo cliente nunca chegam ao adapter. A avaliação da qualidade das respostas pertence ao produto - Devtools só em desenvolvimento:
@tanstack/react-ai-devtoolse@tanstack/react-devtoolscomo devDependencies. O painel mostra mensagens e entradas e saídas de tools; não vai para produção - Fora do âmbito, cada um a precisar do seu próprio ADR: o fornecedor de modelos; a persistência de conversas (
@tanstack/ai-persistence), a memória e os streams retomáveis; o MCP; o code mode e as sandboxes; a geração de media e a voz em tempo real; os embeddings e a recuperação de informação
O que o ADR de fornecedor de cada produto tem de cobrir:
- Fornecedor, modelo e região. IDs de modelo exatos, não aliases flutuantes. Onde os prompts são processados e guardados — residência dos dados: uma região ou endpoint da UE, ou alojamento próprio com
@tanstack/ai-ollamaouopenaiCompatible. As transferências para fora do EEE precisam de um mecanismo do capítulo V do RGPD. Um gateway (OpenRouter, Vercel AI Gateway) põe mais um subcontratante no caminho - Dados pessoais nos prompts. Que categorias chegam ao fornecedor; o que removemos ou pseudonimizamos antes (minimização de dados, artigo 5.º, n.º 1, alínea c), do RGPD); o fundamento de licitude; um acordo de tratamento de dados com o fornecedor e os seus subcontratantes (artigo 28.º)
- Retenção e termos de treino. Durante quanto tempo o fornecedor guarda prompts e saídas, incluindo as cópias de monitorização de abusos; se as entradas treinam modelos; opções de retenção zero; e onde isto está escrito — o contrato, não uma página de marketing
- Custo. Preço por token, uso esperado por utilizador, um orçamento mensal com alerta, e os limites de pedidos e de tokens diários por utilizador deste ADR
- Alternativa de reserva. O que acontece quando o fornecedor está em baixo, aplica limites ou retira o modelo: um segundo fornecedor por trás da fronteira do adapter, ou a funcionalidade degrada-se e di-lo. Timeouts e retries — um retry é uma segunda cobrança
- Utilizadores. A UI diz que o utilizador está a falar com um sistema de IA (artigo 50.º, n.º 1, do Regulamento da IA da UE, aplicável desde 2026-08-02); o que fica guardado e como um utilizador o elimina
- Alcance. As tools e os dados em que o modelo pode tocar
Os fatores decisivos: os mesmos mantenedores e o mesmo ciclo de lançamentos do Router, do Query, do Start e do Form; schemas Zod que tipam tools e respostas e verificam cada chamada; captura de conteúdo desligada por predefinição no seu middleware OpenTelemetry; um modelo falso com respostas definidas no teste para testes offline; e nenhuma camada de plataforma entre nós e o fornecedor. Aceitamos uma API 0.x que muda de poucos em poucos dias, em troca de versões fixadas e atualizações no nosso calendário — a mesma troca do Drizzle v1 no ADR-0008.
Consequências
Seção intitulada “Consequências”Mais fácil:
- Um fornecedor é um adapter numa só fronteira: mudar, acrescentar ou fazer failover de um fornecedor toca num só módulo, e o SDK reescreve o histórico guardado quando a chamada seguinte vai para outro fornecedor
- Uma só linguagem de schemas: o Zod tipa os argumentos e os resultados de uma tool e uma resposta estruturada, e o SDK verifica cada chamada
- Todo o pedido de IA segue o mesmo caminho de qualquer escrita:
useChat→ server functionPOST→ ID do pedido → limitador → verificação CSRF → sessão → validação Zod → módulo da funcionalidade → adapter → fornecedor - Spans de cada execução, volta ao modelo e chamada a uma tool — com contagens de tokens, e custo quando o fornecedor o reporta — aparecem com o resto da telemetria da app (ADR-0015) sem uma palavra de prompt
- Os testes correm offline e dão sempre a mesma resposta
Mais difícil:
- Risco de pré-1.0, dito às claras. Qualquer versão minor pode quebrar tipos ou comportamento: o formato do protocolo, os nomes de opções e as props de componentes já mudaram dentro da 0.x. Versões fixadas e um pull request agrupado do Renovate tornam cada atualização uma coisa que agendamos, mas não gratuita — cada uma exige notas de lançamento, uma execução de testes e um deploy conjunto. Se as alterações incompatíveis continuarem a este ritmo depois da 1.0, esta decisão está errada e um novo ADR a substitui
- Uma subida de minor do núcleo obriga a subidas correspondentes em todos os adapters e no
@tanstack/ai-react: não se pode aplicar sozinha uma correção do núcleo - Uma segunda cópia do
@tanstack/markdowncontinua a ser instalada. O@tanstack/ai-react0.31.0 lista-o emdependenciescomo^0.0.13, e um caret em0.0.xsó aceita essa versão exata, pelo que o gestor de pacotes a mantém ao lado da nossa1.x(ADR-0021). Nunca a importamos: os hooks da entrada raiz não a carregam, só o@tanstack/ai-react/uio faz (verificado no pacote 0.31.0), e a regra acima mantém essa entrada fora do nosso código. A cópia conta na mesma para o lockfile e para as auditorias de dependências, e manter o/uide fora é uma regra de revisão. Verificar combun why @tanstack/markdown: esperam-se duas versões, e um bundle de produção que contenha a antiga significa que o/uiou oTextPartfoi importado. Reavaliar quando o@tanstack/ai-reactpassar para o@tanstack/markdown1.x - Servidor e browser ficam acoplados pelo formato do protocolo do stream. Depois de uma atualização que o altere, os separadores abertos antes do deploy podem falhar até serem recarregados, e os deploys graduais executam brevemente duas versões (ADR-0010). Mostrar erros com o ID do pedido e uma indicação para recarregar
- O SDK não autentica, não limita pedidos, não remove dados nem sanitiza — essas regras são nossas, aplicadas pela revisão e pelos testes de guarda. Rever alterações de IA como alterações de segurança
- O controlo de custos é nosso: o limitador conta pedidos, não tokens, pelo que os tetos de ciclo, de tokens, de chamadas a tools e de prazo vivem no nosso código
- A documentação e os exemplos precisam de tradução. A documentação no GitHub acompanha o
main; o pacote publicado incluisrc/eskills/para agentes, nãodocs/. O seu exemplo de server function chama.inputValidator(...), que os tipos decreateServerFndo Start 1.170 não têm (o nosso é.validator()), e não valida nada em runtime - Os adapters trazem o SDK do fornecedor com o seu próprio intervalo: o
@tanstack/ai-anthropic0.22.1 depende de@anthropic-ai/sdk^0.97.1(2026-05-19), enquanto a última no npm é a 0.137.0. Uma correção no SDK do fornecedor pode ficar à espera de uma versão do adapter - As convenções GenAI do OpenTelemetry ainda estão em estado Development, e o middleware emite
gen_ai.systemonde a convenção atual dizgen_ai.provider.name. Construir dashboards sobre o que vemos no Tempo e voltar a verificar em cada atualização - Uma comunidade mais pequena do que a do AI SDK, e menos fornecedores oficiais; as lacunas passam pelo OpenRouter ou pelo
openaiCompatible, ao preço de um gateway no caminho dos dados
Do que abdicamos: da major estável v7 e do ecossistema maior do AI SDK, e do minimalismo de chamar diretamente o SDK de um fornecedor. Aceite — um SDK tipado e agnóstico do fornecedor, da nossa própria stack TanStack e com a captura de prompts desligada por predefinição, pesa mais do que a maturidade numa predefinição da empresa; os produtos com outras necessidades escrevem o seu próprio ADR. Reabrir esta decisão quando a 1.0 sair, se o projeto estagnar ou se um produto precisar de um fornecedor que este SDK não alcança. Reavaliar a regra do Markdown quando o @tanstack/ai-react passar para o @tanstack/markdown 1.x: só então o seu renderizador pode partilhar uma só cópia e uma só política com o nosso.

