cloudbroker-sdk 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudbroker-sdk",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "CloudBroker SDK — Token Intelligence & Operational Financial Intelligence",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/client.js CHANGED
@@ -37,7 +37,7 @@ export class CloudBrokerClient {
37
37
  try {
38
38
  const payload = {
39
39
  ...event,
40
- sdkVersion: "0.4.0",
40
+ sdkVersion: "0.5.0",
41
41
  sentAt: new Date().toISOString(),
42
42
  };
43
43
  if (this.#cloudQueue.length < MAX_QUEUE_SIZE) {
@@ -93,7 +93,7 @@ export class CloudBrokerClient {
93
93
  try {
94
94
  const payload = {
95
95
  ...event,
96
- sdkVersion: "0.4.0",
96
+ sdkVersion: "0.5.0",
97
97
  sentAt: new Date().toISOString(),
98
98
  };
99
99
 
package/src/test.js CHANGED
@@ -173,5 +173,57 @@ assert.strictEqual(ctxEvent.product, "Billing", `product do contexto esperad
173
173
  assert.strictEqual(ctxEvent.team, "Plataforma", `team do contexto esperado, veio ${ctxEvent.team}`);
174
174
  console.log("✅ expressMiddleware: label/product/team do contexto aplicados sem cb* explícito");
175
175
 
176
+ // 4) CloudBrokerAI.wrap — label de código nas chamadas de IA (mesma
177
+ // convenção do wrapAWS: explícito na chamada > contexto cbStore > nenhum),
178
+ // pra agregar custo de IA junto com custo de nuvem na visão "Custo por Código".
179
+ const capturedAi = [];
180
+ globalThis.fetch = async (url, opts) => {
181
+ if (String(url).includes("/api/ai/events")) {
182
+ capturedAi.push(JSON.parse(opts.body));
183
+ }
184
+ return { ok: true, status: 200, json: async () => ({}) };
185
+ };
186
+
187
+ let sawOpenAiOptions;
188
+ const mockOpenAI2 = {
189
+ chat: {
190
+ completions: {
191
+ create: async (params, options) => {
192
+ sawOpenAiOptions = options;
193
+ return {
194
+ model: params.model,
195
+ choices: [{ message: { role: "assistant", content: "ok" } }],
196
+ usage: { prompt_tokens: 10, completion_tokens: 5 },
197
+ };
198
+ },
199
+ },
200
+ },
201
+ };
202
+ const wrappedAi = CloudBrokerAI.wrap(mockOpenAI2, {
203
+ apiKey: "cb-test-key", product: "FinOps Advisor", team: "platform", debug: true,
204
+ });
205
+
206
+ await wrappedAi.chat.completions.create(
207
+ { model: "gpt-4o", messages: [{ role: "user", content: "x" }] },
208
+ { cbLabel: "finops-advisor:intent-classify" }
209
+ );
210
+ assert.strictEqual(sawOpenAiOptions, undefined, "cbLabel não pode vazar pro SDK do provider");
211
+ await new Promise((r) => setTimeout(r, 50));
212
+ assert.strictEqual(capturedAi.length, 1, `esperado 1 evento de IA, veio ${capturedAi.length}`);
213
+ assert.strictEqual(capturedAi[0].events[0].label, "finops-advisor:intent-classify");
214
+ console.log("✅ CloudBrokerAI.wrap: label explícito (cbLabel) capturado, sem vazar pro provider");
215
+
216
+ // label via contexto (cbStore, mesmo ALS de CloudBrokerCloud.expressMiddleware)
217
+ const mw2 = CloudBrokerCloud.expressMiddleware({ label: "finops-advisor:root-cause-agent" });
218
+ await new Promise((resolve) => {
219
+ mw2({ method: "POST", path: "/api/chat/response" }, {}, async () => {
220
+ await wrappedAi.chat.completions.create({ model: "gpt-4o", messages: [{ role: "user", content: "y" }] });
221
+ resolve();
222
+ });
223
+ });
224
+ await new Promise((r) => setTimeout(r, 50));
225
+ assert.strictEqual(capturedAi[1].events[0].label, "finops-advisor:root-cause-agent");
226
+ console.log("✅ CloudBrokerAI.wrap: label via contexto cbStore (sem cbLabel explícito)");
227
+
176
228
  globalThis.fetch = realFetch;
177
229
  console.log("\n🎉 Todos os testes passaram.");
package/src/wrapper.js CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { CloudBrokerClient } from "./client.js";
7
7
  import { PRICING } from "./pricing.js";
8
+ import { cbStore } from "./context.js";
8
9
 
9
10
  export class CloudBrokerAI {
10
11
  /**
@@ -17,6 +18,13 @@ export class CloudBrokerAI {
17
18
  * @param {string} options.team — nome do squad (ex: "mobile")
18
19
  * @param {string} options.endpoint — endpoint CloudBroker (opcional)
19
20
  * @param {boolean} options.debug — logs de debug (opcional)
21
+ *
22
+ * Label opcional do trecho de código, pra agregar custo de IA junto com
23
+ * custo de nuvem na mesma visão "Custo por Código" (mesma convenção de
24
+ * CloudBrokerCloud.wrapAWS):
25
+ * await client.chat.completions.create(params, { cbLabel: "checkout:resumo-pedido" });
26
+ * await client.messages.create(params, { cbLabel: "checkout:resumo-pedido" });
27
+ * Ou via CloudBrokerCloud.expressMiddleware() (contexto automático por requisição).
20
28
  */
21
29
  static wrap(client, options = {}) {
22
30
  const { apiKey, product, team, endpoint, debug = false } = options;
@@ -77,6 +85,11 @@ export class CloudBrokerAI {
77
85
 
78
86
  client.chat.completions.create = async (params, options) => {
79
87
  const start = Date.now();
88
+ // Mesma precedência de cloud-wrapper.js: explícito na chamada > contexto
89
+ // cbStore (expressMiddleware) > nenhum. Nunca vaza cbLabel pro SDK do provider.
90
+ const cbLabel = options?.cbLabel || cbStore.getStore()?.label || null;
91
+ const { cbLabel: _l, ...providerOptions } = options || {};
92
+ const fwdOptions = Object.keys(providerOptions).length ? providerOptions : undefined;
80
93
 
81
94
  // ── Streaming: pede usage no chunk final e instrumenta o iterador ────────
82
95
  if (params?.stream) {
@@ -84,7 +97,7 @@ export class CloudBrokerAI {
84
97
  ...params,
85
98
  stream_options: { include_usage: true, ...(params.stream_options || {}) },
86
99
  };
87
- const stream = await originalCreate(streamParams, options);
100
+ const stream = await originalCreate(streamParams, fwdOptions);
88
101
 
89
102
  let model = params.model || "unknown";
90
103
  let inputTokens = 0, outputTokens = 0;
@@ -104,6 +117,7 @@ export class CloudBrokerAI {
104
117
  model,
105
118
  product: product || null,
106
119
  team: team || null,
120
+ label: cbLabel,
107
121
  inputTokens,
108
122
  outputTokens,
109
123
  totalTokens: inputTokens + outputTokens,
@@ -121,7 +135,7 @@ export class CloudBrokerAI {
121
135
  }
122
136
 
123
137
  // ── Não-streaming ───────────────────────────────────────────────────────
124
- const res = await originalCreate(params, options);
138
+ const res = await originalCreate(params, fwdOptions);
125
139
  const ms = Date.now() - start;
126
140
 
127
141
  // Fire-and-forget
@@ -137,6 +151,7 @@ export class CloudBrokerAI {
137
151
  model,
138
152
  product: product || null,
139
153
  team: team || null,
154
+ label: cbLabel,
140
155
  inputTokens,
141
156
  outputTokens,
142
157
  totalTokens: inputTokens + outputTokens,
@@ -159,10 +174,13 @@ export class CloudBrokerAI {
159
174
 
160
175
  client.messages.create = async (params, options) => {
161
176
  const start = Date.now();
177
+ const cbLabel = options?.cbLabel || cbStore.getStore()?.label || null;
178
+ const { cbLabel: _l, ...providerOptions } = options || {};
179
+ const fwdOptions = Object.keys(providerOptions).length ? providerOptions : undefined;
162
180
 
163
181
  // ── Streaming: acumula usage dos eventos message_start / message_delta ───
164
182
  if (params?.stream) {
165
- const stream = await originalCreate(params, options);
183
+ const stream = await originalCreate(params, fwdOptions);
166
184
 
167
185
  let model = params.model || "unknown";
168
186
  let inputTokens = 0, outputTokens = 0;
@@ -185,6 +203,7 @@ export class CloudBrokerAI {
185
203
  model,
186
204
  product: product || null,
187
205
  team: team || null,
206
+ label: cbLabel,
188
207
  inputTokens,
189
208
  outputTokens,
190
209
  totalTokens: inputTokens + outputTokens,
@@ -202,7 +221,7 @@ export class CloudBrokerAI {
202
221
  }
203
222
 
204
223
  // ── Não-streaming ───────────────────────────────────────────────────────
205
- const res = await originalCreate(params, options);
224
+ const res = await originalCreate(params, fwdOptions);
206
225
  const ms = Date.now() - start;
207
226
 
208
227
  try {
@@ -217,6 +236,7 @@ export class CloudBrokerAI {
217
236
  model,
218
237
  product: product || null,
219
238
  team: team || null,
239
+ label: cbLabel,
220
240
  inputTokens,
221
241
  outputTokens,
222
242
  totalTokens: inputTokens + outputTokens,