cloudbroker-sdk
v0.7.0
Published
CloudBroker SDK — Token Intelligence & Operational Financial Intelligence
Maintainers
Readme
cloudbroker-sdk
Token Intelligence & Operational Financial Intelligence para Node.js.
Instalação
npm install cloudbroker-sdkUso com OpenAI
import OpenAI from "openai";
import { CloudBrokerAI } from "cloudbroker-sdk";
const openai = CloudBrokerAI.wrap(new OpenAI(), {
apiKey: "cb-key-xxxx", // sua chave CloudBroker
product: "checkout", // nome do produto
team: "mobile", // nome do squad (opcional)
});
// Todas as chamadas seguem iguais — nada muda
const res = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Olá!" }],
});Uso com Anthropic
import Anthropic from "@anthropic-ai/sdk";
import { CloudBrokerAI } from "cloudbroker-sdk";
const anthropic = CloudBrokerAI.wrap(new Anthropic(), {
apiKey: "cb-key-xxxx",
product: "checkout",
team: "mobile",
});
const res = await anthropic.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Olá!" }],
});Uso com Gemini
import { GoogleGenAI } from "@google/genai";
import { CloudBrokerAI } from "cloudbroker-sdk";
const gemini = CloudBrokerAI.wrap(new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }), {
apiKey: "cb-key-xxxx",
product: "checkout",
team: "mobile",
});
const res = await gemini.models.generateContent({
model: "gemini-2.5-flash",
contents: "Olá!",
});O cbLabel pode ir no próprio params (a API do Gemini é de argumento único) ou
no segundo argumento, como nos outros wrappers. Nos dois casos ele é removido
antes de a chamada seguir pro Google:
await gemini.models.generateContent({
model: "gemini-2.5-flash", contents: "…", cbLabel: "checkout:resumo-pedido",
});Por que o custo do Gemini não é input × preço + output × preço
A conta do Gemini tem três particularidades. Ignorar qualquer uma delas erra o valor de forma relevante, então o SDK trata as três:
| Particularidade | O que acontece se ignorar |
|---|---|
| promptTokenCount já inclui os tokens servidos de cache, que custam ~10x menos | superfatura a parcela cacheada (1,65x a mais num prompt 80% cacheado) |
| thoughtsTokenCount é separado de candidatesTokenCount e é cobrado como output | subfatura — 33x a menos num caso com 2000 thinking tokens |
| 2.5 Pro dobra de preço acima de 200k tokens de prompt; áudio na entrada custa mais que texto | subfatura prompts longos e entrada de áudio |
Limitações declaradas
- O custo é estimado por tabela de preço local, não lido de uma fatura. O
Gemini não expõe API de uso ou custo por chave — só o dashboard do AI Studio
e o console do Cloud Billing. Os preços são os do paid tier publicados em
ai.google.dev/gemini-api/docs/pricing, conferidos em 2026-08-02, e não foram calibrados contra fatura real. - Free tier sai com custo > 0. A tabela é a do paid tier; quem está no free tier vê o valor que pagaria, não o que paga.
- Modelo fora da tabela não recebe preço de outro modelo. O evento vai com
costUSD: nulleunpriced: true, preservando os tokens. Variante com sufixo (-preview-…) é precificada pelo modelo base e marcadaapproxPricing. - Armazenamento de contexto em cache não é cobrado aqui. O Google cobra por
hora de cache ativo (US$ 1,00/h na maioria dos modelos); isso não aparece no
usageMetadatade uma chamada e não é modelado. - Batch API não é modelada — o Google cobra metade nesse modo.
Streaming
Chamadas com stream: true também são rastreadas — o SDK observa os chunks
conforme você os consome e registra o usage ao final, sem alterar o fluxo:
const stream = await openai.chat.completions.create({
model: "gpt-4o",
stream: true,
messages: [{ role: "user", content: "Olá!" }],
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content || "");
}
// tokens/custo são registrados automaticamente quando o stream terminaPara OpenAI, o SDK injeta stream_options: { include_usage: true } para receber
o usage no chunk final. Para Anthropic, o usage vem dos eventos
message_start / message_delta. O objeto de stream original é preservado
(.tee(), .controller, etc.).
Rastreamento de Deploy
import { CloudBrokerAI } from "cloudbroker-sdk";
// Opção 1: Via wrapper (se você está usando OpenAI/Anthropic)
const openai = CloudBrokerAI.wrap(new OpenAI(), {
apiKey: "cb-key-xxxx",
product: "checkout",
});
openai.trackDeploy({
repo: "myapp",
branch: "main",
prTitle: "feat: add new feature",
prUrl: "https://github.com/org/myapp/pull/42",
author: "john.doe",
prNumber: 42,
filesChanged: 5,
linesAdded: 120,
linesRemoved: 30,
});
// Opção 2: Direto (sem OpenAI/Anthropic)
CloudBrokerAI.trackDeploy("cb-key-xxxx", {
repo: "myapp",
branch: "main",
prTitle: "feat: add new feature",
prUrl: "https://github.com/org/myapp/pull/42",
author: "john.doe",
prNumber: 42,
filesChanged: 5,
linesAdded: 120,
linesRemoved: 30,
});Cost per Feature — Rastreie Custo por Feature
Atribua custo de AWS e Azure a cada feature/módulo sua. O SDK envolve um client do provedor e, a cada chamada, estima o custo (requests + transferência) e registra um evento — mesmo padrão fire-and-forget do wrapper de IA.
👉 Guia Completo — Setup (5 min), exemplos, FAQ.
Escopo atual — AWS: S3 e DynamoDB (
us-east-2); Azure: Blob Storage e Cosmos DB (eastus2). Armazenamento GB-mês e atribuição automática (stack-trace) estão fora do MVP.
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { CloudBrokerCloud } from "cloudbroker-sdk";
const s3 = CloudBrokerCloud.wrapAWS(new S3Client({ region: "us-east-2" }), {
apiKey: "cb-key-xxxx",
product: "checkout",
team: "mobile",
});
// label opcional do trecho, no 2º argumento do .send()
await s3.send(new PutObjectCommand({ Bucket: "b", Key: "nf.pdf", Body: buf }), {
cbLabel: "upload-nota-fiscal",
});Funciona igual com DynamoDB:
import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";
const ddb = CloudBrokerCloud.wrapAWS(new DynamoDBClient({ region: "us-east-2" }), {
apiKey: "cb-key-xxxx", product: "checkout",
});
await ddb.send(new PutItemCommand({ ... }), { cbLabel: "gravar-pedido" });Cada chamada gera um evento POST /api/cloud/events com service, operation,
product, team, label, requests, bytesIn/bytesOut, costUSD estimado e
latencyMs. O cbLabel nunca é repassado ao AWS SDK.
Azure — Blob Storage e Cosmos DB
O Azure SDK não tem um .send() único como o AWS SDK v3: cada sub-client tem
métodos próprios. Por isso o wrapAzure intercepta a cadeia de sub-clients —
as fábricas (getContainerClient, database/container) devolvem sub-clients já
embrulhados, e só os métodos de data-plane conhecidos são rastreados. Qualquer
método fora dessa lista passa intocado.
import { BlobServiceClient } from "@azure/storage-blob";
import { CloudBrokerCloud } from "cloudbroker-sdk";
const blobSvc = CloudBrokerCloud.wrapAzure(
BlobServiceClient.fromConnectionString(process.env.AZURE_STORAGE_CONNECTION),
{ apiKey: "cb-key-xxxx", product: "checkout", team: "mobile" },
);
// label opcional dentro do objeto de options nativo do método
await blobSvc
.getContainerClient("docs")
.getBlockBlobClient("nf.pdf")
.uploadData(buf, { cbLabel: "upload-nota-fiscal" });Cosmos DB usa o requestCharge (RUs) real que o servidor devolve em cada
resposta — custo por unidade cobrada de verdade, não aproximação por request:
import { CosmosClient } from "@azure/cosmos";
const cosmos = CloudBrokerCloud.wrapAzure(new CosmosClient(conn), {
apiKey: "cb-key-xxxx", product: "checkout",
});
const orders = cosmos.database("loja").container("pedidos");
await orders.items.create({ id: "123" }, { cbLabel: "criar-pedido" });
await orders.items.query("SELECT * FROM c").fetchAll(); // RUs do query também contamO expressMiddleware (abaixo) funciona igual pros dois provedores — mesma
prioridade de label/product/team, sem precisar de cbLabel manual.
Precisão e premissas — leia antes de tratar o número como fechado:
- Cosmos: as RUs são as reais devolvidas pelo servidor em cada resposta (
requestCharge). O preço por RU assume o modelo serverless. Em throughput provisionado (manual ou autoscale) a Azure cobra RU/s reservada por hora, independentemente do consumo — esse modelo ainda não é representado, então o custo calculado não corresponde à fatura nesse caso.- Blob: estimado por classe de operação (write/read/list), assumindo tier Hot e redundância LRS. Cool/Archive e GRS/ZRS têm outro preço.
- Transferência de saída: cobrada sobre todo
bytesOut. A Azure não cobra saída intra-região e concede franquia mensal — para um app rodando na mesma região do Storage, este termo superestima.- Região: só
eastus2tem tabela de preço. Em outra região o evento é registrado comcostUSD: nullebillingUnit: "*-region-unpriced"— a operação aparece no relatório, mas sem custo inventado.- Reconciliação: a tela compara o estimado contra o custo real do Cost Management e mostra o desvio; o ajuste da tabela é manual (não há recalibração automática).
- Listagem:
listBlobsFlat/listContainersdevolvem iterator preguiçoso — o evento só é emitido quando uma página é de fato buscada. Percorrendo comfor await, contamos 1 operação mesmo que o SDK pagine internamente; use.byPage()para contagem por página.- Cosmos
for awaitdireto (getAsyncIterator) não é instrumentado — usefetchAll()/fetchNext().- Delete no tier Hot é grátis: sai com custo
0ebillingUnit: "blob-free", sem sumir do relatório.⚠️ Ainda não calibrado contra uma fatura Azure real. A estrutura é exercitada contra os SDKs oficiais (
@azure/storage-blob,@azure/cosmos) na suíte de testes, mas os valores da tabela de preço Azure não foram conferidos contra a página oficial nem contra uma fatura. Trate o custo Azure como ordem de grandeza até rodar a reconciliação na sua conta.
Atribuição automática por requisição (Express)
Em vez de passar cbLabel em cada .send(), use o middleware: todas as chamadas
de nuvem feitas durante a requisição herdam um label de negócio automaticamente
(via AsyncLocalStorage). Ótimo pra agrupar custo por módulo ou rota.
Cada campo (label, product, team) pode ser fixo (string) ou uma função
(req) => string. Isso permite a hierarquia Grupo (team) → App (product) →
Trecho (label) → Serviço na visão de custo.
import express from "express";
import { CloudBrokerCloud } from "cloudbroker-sdk";
const app = express();
app.use(CloudBrokerCloud.expressMiddleware({
team: (req) => (req.path.startsWith("/api/admin") ? "Plataforma" : "Produto"), // Grupo
product: (req) => req.path.split("/")[2] || "core", // App
label: (req) => `${req.method} ${req.path}`, // Trecho
}));
// daqui pra frente, qualquer s3.send(...)/ddb.send(...) dentro da request
// é atribuído ao grupo/app/trecho — sem cbLabel manual.Prioridade de cada campo: explícito no .send() (cbLabel/cbProduct/cbTeam) >
contexto do middleware > valor estático do wrapAWS > null.
Garantias
- Fire-and-forget: se o CloudBroker estiver fora, sua aplicação não é afetada
- Zero latência adicional: o tracking acontece em background
- Retry automático: eventos com falha são reenviados em 30s
- Fila local: até 500 eventos em memória se o servidor estiver indisponível
API Key CloudBroker
Gere sua chave em app.cloudbroker.app.br → Configurações → API Keys.
