vibezcheck 0.5.5 β†’ 0.5.7

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.
Files changed (40) hide show
  1. package/README.md +180 -50
  2. package/dist/ai-sdk/index.d.mts +41 -1
  3. package/dist/ai-sdk/index.d.ts +41 -1
  4. package/dist/ai-sdk/index.js +157 -2
  5. package/dist/ai-sdk/index.js.map +1 -1
  6. package/dist/ai-sdk/index.mjs +156 -2
  7. package/dist/ai-sdk/index.mjs.map +1 -1
  8. package/dist/ai-sdk/middleware.js +17 -1
  9. package/dist/ai-sdk/middleware.js.map +1 -1
  10. package/dist/ai-sdk/middleware.mjs +17 -1
  11. package/dist/ai-sdk/middleware.mjs.map +1 -1
  12. package/dist/billing/index.js +17 -1
  13. package/dist/billing/index.js.map +1 -1
  14. package/dist/billing/index.mjs +17 -1
  15. package/dist/billing/index.mjs.map +1 -1
  16. package/dist/compat/stripe-meter.js +17 -1
  17. package/dist/compat/stripe-meter.js.map +1 -1
  18. package/dist/compat/stripe-meter.mjs +17 -1
  19. package/dist/compat/stripe-meter.mjs.map +1 -1
  20. package/dist/compat/stripe-provider.js +155 -2
  21. package/dist/compat/stripe-provider.js.map +1 -1
  22. package/dist/compat/stripe-provider.mjs +155 -2
  23. package/dist/compat/stripe-provider.mjs.map +1 -1
  24. package/dist/index.d.mts +4 -3
  25. package/dist/index.d.ts +4 -3
  26. package/dist/index.js +158 -2
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +157 -2
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/react/index.d.mts +32 -9
  31. package/dist/react/index.d.ts +32 -9
  32. package/dist/react/index.js +851 -242
  33. package/dist/react/index.js.map +1 -1
  34. package/dist/react/index.mjs +852 -243
  35. package/dist/react/index.mjs.map +1 -1
  36. package/examples/nextjs-app-router-openai/README.md +8 -6
  37. package/examples/nextjs-app-router-openai/app/api/chat/route.ts +14 -6
  38. package/examples/nextjs-app-router-openai/app/page.tsx +68 -22
  39. package/examples/nextjs-app-router-openai/package.json +1 -1
  40. package/package.json +189 -188
package/README.md CHANGED
@@ -1,37 +1,49 @@
1
1
  # ✦ VibezCheck
2
2
 
3
3
  > **The 1-Line Token Meter & Real-Time Billing Engine for AI.**
4
- > Track tokens, compute real-time dollar costs, set profit margins, and bill customers with **0ms added latency**.
4
+ > Track tokens, compute real-time micro-costs, set profit margins, and bill customers with **0ms added latency** across Vercel AI SDK (v4, v5, v6, v7) and native LLM SDKs.
5
5
 
6
- [![npm version](https://img.shields.io/npm/v/vibezcheck.svg)](https://npmjs.org/package/vibezcheck)
6
+ [![npm version](https://img.shields.io/npm/v/vibezcheck.svg?color=cb3837)](https://npmjs.org/package/vibezcheck)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
8
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-Strict-blue.svg)](https://www.typescriptlang.org/)
9
- [![Tests](https://img.shields.io/badge/Tests-62%20Passed-brightgreen.svg)]()
9
+ [![Tests](https://img.shields.io/badge/Tests-94%20Passed-brightgreen.svg)]()
10
+ [![Latency](https://img.shields.io/badge/Latency-0ms%20Added-orange.svg)]()
10
11
 
11
12
  ---
12
13
 
13
14
  ### The 2-Line Promise
14
15
 
15
16
  ```typescript
16
- // 1. Server: Wrap your model with automated billing
17
- const model = vibezcheck('openai/gpt-4o', { customer: 'cus_alex' });
17
+ // 1. Server: Wrap your model with billing & inject telemetry into stream
18
+ const model = vibezcheck('openai/gpt-4o-mini', { customer: 'cus_alex', pricing: { margin: 1.3 } });
19
+ return vibezcheck.toResponse(result);
18
20
 
19
- // 2. Client: Drop in the financial HUD
21
+ // 2. Client: Drop in zero-prop financial HUD & micro-receipts
22
+ <VibezReceipt message={message} />
20
23
  <VibezCheck messages={messages} />
21
24
  ```
22
25
 
23
26
  ---
24
27
 
25
- ## πŸš€ Features
28
+ ## ⚑ What's New in v0.5.6
26
29
 
27
- * **⚑ 0ms Added Latency**: Direct transparent proxy stream; no external server redirects or buffering.
28
- * **πŸ›‘οΈ Built-in $0.50 Circuit Breaker**: Prevents infinite loops and runaway bills automatically.
29
- * **πŸ›Ÿ In-Flight Abort Trapper**: Catches and bills tokens even if the customer closes their tab mid-stream.
30
- * **πŸͺ™ BigInt Nano-USD Precision**: Sub-cent financial math ($1 = $10^9$ Nano-USD) eliminating IEEE 754 float drift.
31
- * **πŸ’° 1-Line Profit Margins**: Turn wholesale API costs into retail profits with `pricing: { margin: 1.5 }` (+50% profit).
30
+ * **πŸͺ„ Zero-Prop Client UIs**: `<VibezReceipt message={message} />` and `<VibezCheck messages={messages} />` auto-derive model slugs, exact tokens, dollar micro-costs, profit margins (`+30% Margin`), and latency straight from the telemetry stream. No manual `model`, `margin`, or `cost` props required.
31
+ * **πŸ“‘ Universal Stream Injector (`vibezcheck.toResponse`)**: Intercepts AI SDK stream completion chunks to attach verified telemetry (`data-vibezcheck` & `message-metadata` SSE for v5/v6/v7, or `2:[{...}]` annotations for v4) with **0ms TTFB overhead**.
32
+ * **πŸ›‘οΈ Zero Context Window Pollution**: Telemetry travels out-of-band. When AI SDK's `convertToModelMessages(messages)` prepares future conversation turns, metadata parts are automatically omittedβ€”keeping your LLM context pure and preventing runaway token loops.
33
+ * **🎨 Scoped Zero-Config Styling**: Built-in inline fallback styles guarantee that receipts and HUD components look polished out-of-the-box, even if your Tailwind configuration ignores `node_modules`.
34
+
35
+ ---
36
+
37
+ ## πŸš€ Key Features
38
+
39
+ * **⚑ 0ms Added Latency**: Direct transparent proxy stream; no external server redirects, no pre-buffering, and zero impact on first-token response time.
40
+ * **πŸ›‘οΈ Built-in $0.50 Circuit Breaker**: Auto-trips if an agent or query exceeds the safety fuse box, preventing infinite loops and surprise $1,000 bills.
41
+ * **πŸ›Ÿ In-Flight Abort Trapper**: Accurately meters and debits tokens even if the client closes their laptop lid or navigates away mid-stream.
42
+ * **πŸͺ™ BigInt Nano-USD Precision**: Sub-cent financial math ($1 = $10^9$ Nano-USD) eliminating IEEE 754 floating-point drift.
43
+ * **πŸ’° 1-Line Profit Margins**: Turn wholesale API costs into retail revenue with `pricing: { margin: 1.3 }` (+30% markup).
32
44
  * **πŸ†“ Zero-DB Dev Mode**: Run locally on `localhost:3000` with NO database and NO Stripe keys required.
33
- * **πŸ’³ Pluggable SaaS Billing**: Top-ups, checkout sessions, customer portal, and user API keys (`vz_live_...`).
34
- * **πŸ€– Multi-Tool Agent Sessions**: Cumulative session budgets across models, Python sandboxes, and web scrapers.
45
+ * **πŸ’³ Pluggable SaaS Billing**: Credit wallets, top-ups, checkout sessions, customer portal, and user API keys (`vz_live_...`).
46
+ * **πŸ€– Multi-Tool Agent Sessions**: Cumulative session budgets across multiple LLM steps, Python sandboxes, and web scrapers.
35
47
  * **πŸ”„ 100% Drop-In Polyfills**: Direct replacement for `@stripe/ai-sdk/meter`, `@stripe/ai-sdk/provider`, and `@stripe/token-meter`.
36
48
 
37
49
  ---
@@ -44,93 +56,211 @@ npm install vibezcheck ai @ai-sdk/openai @ai-sdk/react stripe
44
56
 
45
57
  ---
46
58
 
47
- ## ⚑ Quickstart: Zero-DB Dev Mode to Production
59
+ ## ⚑ 1-Minute Quickstart: Next.js App Router
60
+
61
+ ### 1. Server Route (`app/api/chat/route.ts`)
48
62
 
49
- ### 1. Next.js 15 API Route (`app/api/chat/route.ts`)
63
+ Wrap any model with `vibezcheck()`, and return the response using `vibezcheck.toResponse()`:
50
64
 
51
65
  ```typescript
52
66
  import { streamText } from 'ai';
53
67
  import { openai } from '@ai-sdk/openai';
54
68
  import { vibezcheck } from 'vibezcheck';
55
69
 
70
+ export const maxDuration = 30;
71
+
56
72
  export async function POST(req: Request) {
57
73
  const { messages } = await req.json();
58
74
 
59
75
  const result = streamText({
60
- // Wrap any AI SDK model or use string identifier
76
+ // Wrap any provider (OpenAI, Anthropic, Google, DeepSeek, etc.)
61
77
  model: vibezcheck(openai('gpt-4o-mini'), {
62
- customer: 'user_alex@example.com', // Stripe customer ID or user email
63
- pricing: { margin: 1.3 }, // +30% profit margin
64
- maxCostPerCallUSD: 0.50, // Safety fuse box
78
+ customer: 'cus_alex', // Stripe customer ID or user email
79
+ pricing: { margin: 1.3 }, // +30% profit margin
80
+ maxCostPerCallUSD: 0.50, // Safety fuse box ($0.50 cap)
65
81
  }),
66
82
  messages,
67
83
  });
68
84
 
69
- return result.toDataStreamResponse();
85
+ // ✦ Universal telemetry stream injector (works with v4, v5, v6, and v7)
86
+ return vibezcheck.toResponse(result);
70
87
  }
71
88
  ```
72
89
 
73
- ### 2. Frontend React Client (`app/page.tsx`)
90
+ ### 2. Frontend Client (`app/page.tsx`)
91
+
92
+ Render message bubbles with `<VibezReceipt />` and float the aggregated `<VibezCheck />` HUD:
74
93
 
75
94
  ```tsx
76
95
  'use client';
77
96
 
78
97
  import { useChat } from '@ai-sdk/react';
79
98
  import { useState } from 'react';
80
- import { VibezCheck } from 'vibezcheck/ui';
99
+ import { VibezReceipt, VibezCheck } from 'vibezcheck/ui';
81
100
 
82
- export default function Chat() {
101
+ export default function ChatPage() {
83
102
  const [input, setInput] = useState('');
84
103
  const { messages, sendMessage } = useChat();
85
104
 
86
105
  return (
87
- <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
88
- {messages.map(message => (
89
- <div key={message.id} className="whitespace-pre-wrap">
90
- {message.role === 'user' ? 'User: ' : 'AI: '}
91
- {message.parts.map((part, i) => {
92
- switch (part.type) {
93
- case 'text':
94
- return <div key={`${message.id}-${i}`}>{part.text}</div>;
95
- }
96
- })}
97
- </div>
98
- ))}
106
+ <main className="flex flex-col w-full max-w-lg py-20 mx-auto px-4 min-h-screen">
107
+ <div className="flex-1 space-y-4 mb-24">
108
+ {messages.map((message) => (
109
+ <div key={message.id} className="p-3 rounded-xl border border-zinc-200 dark:border-zinc-800">
110
+ <div className="text-xs font-semibold uppercase tracking-wider text-zinc-400 mb-1">
111
+ {message.role === 'user' ? 'You' : 'Assistant'}
112
+ </div>
113
+
114
+ {/* Display message content */}
115
+ <div className="text-sm whitespace-pre-wrap">
116
+ {message.parts
117
+ ? message.parts.map((part, i) => (part.type === 'text' ? part.text : null))
118
+ : message.content}
119
+ </div>
120
+
121
+ {/* ✦ Zero-Prop Micro-Receipt: auto-derives tokens, micro-cost, model, & latency */}
122
+ {message.role === 'assistant' && (
123
+ <div className="mt-2 flex justify-end">
124
+ <VibezReceipt message={message} />
125
+ </div>
126
+ )}
127
+ </div>
128
+ ))}
129
+ </div>
99
130
 
100
131
  <form
101
- onSubmit={e => {
132
+ onSubmit={(e) => {
102
133
  e.preventDefault();
134
+ if (!input.trim()) return;
103
135
  sendMessage({ text: input });
104
136
  setInput('');
105
137
  }}
138
+ className="fixed bottom-0 left-0 right-0 p-4 bg-background/80 backdrop-blur-md"
106
139
  >
107
- <input
108
- className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
109
- value={input}
110
- placeholder="Say something..."
111
- onChange={e => setInput(e.currentTarget.value)}
112
- />
140
+ <div className="max-w-lg mx-auto">
141
+ <input
142
+ className="w-full p-3 rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 shadow-sm"
143
+ value={input}
144
+ placeholder="Ask something..."
145
+ onChange={(e) => setInput(e.target.value)}
146
+ />
147
+ </div>
113
148
  </form>
114
149
 
115
- {/* ✦ 1-Line Token Meter & Financial HUD: Works with zero database & zero Stripe in Dev Mode */}
150
+ {/* ✦ Zero-Prop Financial HUD: aggregated conversation tokens, cost, & active model */}
116
151
  <VibezCheck messages={messages} />
117
- </div>
152
+ </main>
118
153
  );
119
154
  }
120
155
  ```
121
156
 
122
157
  ---
123
158
 
124
- ## πŸ’³ Commercial SaaS Use Cases
159
+ ## 🧩 Zero-Prop Client Components
160
+
161
+ Import from either `vibezcheck/ui` or `vibezcheck/react`:
162
+
163
+ ### 1. `<VibezReceipt />` (Per-Message Micro-Badge)
164
+
165
+ Displays verified tokens, cost in USD, model slug, and latency under individual assistant messages.
166
+
167
+ ```tsx
168
+ <VibezReceipt message={message} />
169
+ ```
170
+
171
+ * **Zero Required Props**: Reads telemetry from `message.parts` (`data-vibezcheck`), `message.annotations`, and `message.metadata`.
172
+ * **Variants**: `variant="pill"` (default), `variant="minimal"`, or `variant="card"`.
173
+ * **Scoped Styling**: Ships with inline fallback styling so it never breaks even without Tailwind CSS.
174
+
175
+ ### 2. `<VibezCheck />` (Global Conversation HUD)
176
+
177
+ Floating pill and expandable popover showing real-time conversation metrics.
178
+
179
+ ```tsx
180
+ <VibezCheck messages={messages} />
181
+ ```
182
+
183
+ * **Aggregated Metrics**: Automatically sums input, output, and reasoning tokens across all messages.
184
+ * **Auto-Derived Model & Margin**: Displays the active model badge (e.g. `gpt-4o-mini`) and configured markup (e.g. `+30% Margin`).
185
+ * **Optional Top-Up**: Pass `onTopUp={() => openBillingModal()}` to display an integrated credit top-up button.
186
+
187
+ ---
188
+
189
+ ## πŸ”„ Multi-Version Vercel AI SDK Compatibility
190
+
191
+ `vibezcheck` provides seamless compatibility across every major generation of the Vercel AI SDK:
192
+
193
+ ### AI SDK v5, v6 & v7 (UI Message Stream)
194
+
195
+ ```typescript
196
+ import { createUIMessageStreamResponse, toUIMessageStream, streamText } from 'ai';
197
+ import { openai } from '@ai-sdk/openai';
198
+ import { vibezcheck } from 'vibezcheck';
199
+
200
+ export async function POST(req: Request) {
201
+ const { messages } = await req.json();
202
+
203
+ const result = streamText({
204
+ model: vibezcheck(openai('gpt-4o-mini'), { customer: 'cus_123' }),
205
+ messages,
206
+ });
207
+
208
+ // Recommended: pass result directly
209
+ return vibezcheck.toResponse(result);
210
+
211
+ // Or wrap custom UI message stream responses:
212
+ // return vibezcheck.toResponse(
213
+ // createUIMessageStreamResponse({
214
+ // stream: toUIMessageStream({ stream: result.stream }),
215
+ // })
216
+ // );
217
+ }
218
+ ```
219
+
220
+ ### AI SDK v4 (Data Stream Protocol)
221
+
222
+ ```typescript
223
+ import { streamText } from 'ai';
224
+ import { openai } from '@ai-sdk/openai';
225
+ import { vibezcheck } from 'vibezcheck';
226
+
227
+ export async function POST(req: Request) {
228
+ const { messages } = await req.json();
229
+
230
+ const result = streamText({
231
+ model: vibezcheck(openai('gpt-4o'), { customer: 'cus_123' }),
232
+ messages,
233
+ });
234
+
235
+ // Automatically emits 2:[{"type":"vibezcheck", ...}] data stream annotations
236
+ return vibezcheck.toResponse(result);
237
+ }
238
+ ```
239
+
240
+ ---
241
+
242
+ ## πŸ›‘οΈ Context Window Hygiene: How It Works
243
+
244
+ A frequent hazard in LLM streaming is "telemetry leakage"β€”where usage data sent to the client gets accidentally included in subsequent conversation turns, causing runaway context growth and unnecessary token costs.
245
+
246
+ VibezCheck prevents this by design:
247
+ 1. **Server Injection**: `vibezcheck.toResponse` injects telemetry out-of-band right before stream completion (`data-vibezcheck` and `message-metadata` SSE events).
248
+ 2. **Client Ingestion**: `<VibezReceipt />` and `<VibezCheck />` extract telemetry from message parts without mutating textual content.
249
+ 3. **Next Request**: When `convertToModelMessages(messages)` runs on subsequent requests, the AI SDK automatically discards transient metadata and data parts. **Zero telemetry tokens are sent to the LLM.**
250
+
251
+ ---
252
+
253
+ ## πŸ’³ Commercial SaaS Billing
254
+
255
+ ### 1. Stripe Top-Up Sessions (`BillingHelper`)
125
256
 
126
- ### 1. Credit Wallet Top-Ups & Checkout (`BillingHelper`)
257
+ Allow users to purchase pre-paid AI credits with Stripe Checkout:
127
258
 
128
259
  ```typescript
129
260
  import { BillingHelper } from 'vibezcheck';
130
261
 
131
262
  const billing = new BillingHelper({ apiKey: process.env.STRIPE_SECRET_KEY });
132
263
 
133
- // Customer purchases $20 in AI usage credits
134
264
  export async function POST(req: Request) {
135
265
  const { customerId } = await req.json();
136
266
 
@@ -158,7 +288,7 @@ const auth = new ApiKeyAuth(stripe);
158
288
  // 1. Generate key for customer
159
289
  const { apiKey } = await auth.createKey({
160
290
  customerId: 'cus_123',
161
- name: 'Production Worker Key',
291
+ name: 'Production Key',
162
292
  });
163
293
  // => "vz_live_9f83b2..."
164
294
 
@@ -236,7 +366,7 @@ npx vibezcheck audit --ci
236
366
 
237
367
  ## πŸ“‚ Examples
238
368
 
239
- * **[Next.js App Router + OpenAI](examples/nextjs-app-router-openai)**: Direct implementation of the official [AI SDK Next.js App Router Tutorial](https://ai-sdk.dev/docs/getting-started/nextjs-app-router) with 1-line VibezCheck token metering.
369
+ * **[Next.js App Router + OpenAI](examples/nextjs-app-router-openai)**: Modern AI SDK App Router implementation with 1-line token metering and `<VibezCheck />`.
240
370
  * **[Next.js 15 AI SaaS Starter](examples/nextjs-saas-starter)**: Complete SaaS template with Stripe Checkout, Top-Up Modal, and `<VibezReceipt />`.
241
371
  * **[Local Cost Meter](examples/01-local-cost-meter.ts)**: Offline micro-transaction calculations with BigInt Nano-USD precision.
242
372
  * **[Native OpenAI Streaming](examples/03-native-openai-stream.ts)**: Zero-latency stream wrapper for the official `openai` Node SDK.
@@ -4,6 +4,45 @@ import { C as CustomerParam } from '../types-Btf2OhIr.mjs';
4
4
  import { V as VibezMeter } from '../client-BzwEHQlC.mjs';
5
5
  import 'stripe';
6
6
 
7
+ /**
8
+ * ✦ VibezCheck Universal Telemetry Response Stream Injector
9
+ *
10
+ * Transmits real-time verified token counts, exact dollar costs, profit margins,
11
+ * and model telemetry from the server into AI SDK useChat() across all versions:
12
+ * - AI SDK v5/v6/v7 (UI Message Stream): Injects `data-vibezcheck` and `message-metadata` SSE events
13
+ * - AI SDK v4 (Data Stream Protocol): Injects `2:[{"type":"vibezcheck", ...}]` message annotation lines
14
+ * - Custom / Text Streams: Injects clean telemetry without breaking stream parsers
15
+ */
16
+ interface ToResponseOptions {
17
+ /** Custom ResponseInit (status, statusText, headers) */
18
+ init?: ResponseInit;
19
+ /** Additional HTTP headers */
20
+ headers?: HeadersInit;
21
+ /** Explicit telemetry payload override */
22
+ telemetry?: any;
23
+ }
24
+ /**
25
+ * Transforms an AI SDK stream result (or standard Response) into a client-ready HTTP Response
26
+ * that transmits both AI completion chunks and verified VibezCheck telemetry.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * import { streamText } from 'ai';
31
+ * import { openai } from '@ai-sdk/openai';
32
+ * import { vibezcheck } from 'vibezcheck';
33
+ *
34
+ * export async function POST(req: Request) {
35
+ * const { messages } = await req.json();
36
+ * const result = streamText({
37
+ * model: vibezcheck(openai('gpt-4o'), { customer: 'cus_123' }),
38
+ * messages,
39
+ * });
40
+ * return vibezcheck.toResponse(result);
41
+ * }
42
+ * ```
43
+ */
44
+ declare function toResponse(result: any, options?: ToResponseOptions): Response;
45
+
7
46
  interface VibezCheckModelOptions extends WithBillingOptions {
8
47
  /** OpenAI / Anthropic / AI Gateway API Key override */
9
48
  apiKey?: string;
@@ -69,6 +108,7 @@ declare function createVibezModel(modelOrId: any, options?: VibezCheckModelOptio
69
108
  declare function createVibezSession(sessionOptions?: VibezSessionOptions): VibezSession;
70
109
  declare const vibezcheck: typeof createVibezModel & {
71
110
  session: typeof createVibezSession;
111
+ toResponse: typeof toResponse;
72
112
  };
73
113
 
74
- export { type VibezCheckModelOptions, type VibezSession, type VibezSessionOptions, WithBillingOptions, createVibezModel, createVibezSession, vibezcheck };
114
+ export { type ToResponseOptions, type VibezCheckModelOptions, type VibezSession, type VibezSessionOptions, WithBillingOptions, createVibezModel, createVibezSession, toResponse, vibezcheck };
@@ -4,6 +4,45 @@ import { C as CustomerParam } from '../types-Btf2OhIr.js';
4
4
  import { V as VibezMeter } from '../client-CbO6vLUe.js';
5
5
  import 'stripe';
6
6
 
7
+ /**
8
+ * ✦ VibezCheck Universal Telemetry Response Stream Injector
9
+ *
10
+ * Transmits real-time verified token counts, exact dollar costs, profit margins,
11
+ * and model telemetry from the server into AI SDK useChat() across all versions:
12
+ * - AI SDK v5/v6/v7 (UI Message Stream): Injects `data-vibezcheck` and `message-metadata` SSE events
13
+ * - AI SDK v4 (Data Stream Protocol): Injects `2:[{"type":"vibezcheck", ...}]` message annotation lines
14
+ * - Custom / Text Streams: Injects clean telemetry without breaking stream parsers
15
+ */
16
+ interface ToResponseOptions {
17
+ /** Custom ResponseInit (status, statusText, headers) */
18
+ init?: ResponseInit;
19
+ /** Additional HTTP headers */
20
+ headers?: HeadersInit;
21
+ /** Explicit telemetry payload override */
22
+ telemetry?: any;
23
+ }
24
+ /**
25
+ * Transforms an AI SDK stream result (or standard Response) into a client-ready HTTP Response
26
+ * that transmits both AI completion chunks and verified VibezCheck telemetry.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * import { streamText } from 'ai';
31
+ * import { openai } from '@ai-sdk/openai';
32
+ * import { vibezcheck } from 'vibezcheck';
33
+ *
34
+ * export async function POST(req: Request) {
35
+ * const { messages } = await req.json();
36
+ * const result = streamText({
37
+ * model: vibezcheck(openai('gpt-4o'), { customer: 'cus_123' }),
38
+ * messages,
39
+ * });
40
+ * return vibezcheck.toResponse(result);
41
+ * }
42
+ * ```
43
+ */
44
+ declare function toResponse(result: any, options?: ToResponseOptions): Response;
45
+
7
46
  interface VibezCheckModelOptions extends WithBillingOptions {
8
47
  /** OpenAI / Anthropic / AI Gateway API Key override */
9
48
  apiKey?: string;
@@ -69,6 +108,7 @@ declare function createVibezModel(modelOrId: any, options?: VibezCheckModelOptio
69
108
  declare function createVibezSession(sessionOptions?: VibezSessionOptions): VibezSession;
70
109
  declare const vibezcheck: typeof createVibezModel & {
71
110
  session: typeof createVibezSession;
111
+ toResponse: typeof toResponse;
72
112
  };
73
113
 
74
- export { type VibezCheckModelOptions, type VibezSession, type VibezSessionOptions, WithBillingOptions, createVibezModel, createVibezSession, vibezcheck };
114
+ export { type ToResponseOptions, type VibezCheckModelOptions, type VibezSession, type VibezSessionOptions, WithBillingOptions, createVibezModel, createVibezSession, toResponse, vibezcheck };
@@ -33,6 +33,7 @@ __export(ai_sdk_exports, {
33
33
  createVibezModel: () => createVibezModel,
34
34
  createVibezSession: () => createVibezSession,
35
35
  meteredModel: () => meteredModel,
36
+ toResponse: () => toResponse,
36
37
  vibezcheck: () => vibezcheck,
37
38
  withBilling: () => withBilling
38
39
  });
@@ -1417,6 +1418,7 @@ function withBilling(model, optionsOrCustomer, extraOptions = {}) {
1417
1418
  }
1418
1419
  }
1419
1420
  scheduleFlush();
1421
+ return event;
1420
1422
  };
1421
1423
  const handler = {
1422
1424
  get(target, prop, receiver) {
@@ -1479,7 +1481,22 @@ function withBilling(model, optionsOrCustomer, extraOptions = {}) {
1479
1481
  const chunkUsage = (chunk.type === "finish" ? chunk.usage : null) || chunk.usage || chunk.token_usage || chunk.usageMetadata;
1480
1482
  if (chunkUsage) {
1481
1483
  streamCompleted = true;
1482
- handleUsage(chunkUsage);
1484
+ const event = handleUsage(chunkUsage);
1485
+ if (chunk.type === "finish" && event) {
1486
+ chunk.providerMetadata = {
1487
+ ...chunk.providerMetadata,
1488
+ vibezcheck: {
1489
+ type: "vibezcheck",
1490
+ cost: event.cost,
1491
+ usage: event.usage,
1492
+ model: modelId,
1493
+ provider,
1494
+ customerId,
1495
+ customerEmail,
1496
+ timestamp: event.timestamp
1497
+ }
1498
+ };
1499
+ }
1483
1500
  }
1484
1501
  },
1485
1502
  flush() {
@@ -1534,6 +1551,142 @@ function withBilling(model, optionsOrCustomer, extraOptions = {}) {
1534
1551
  }
1535
1552
  var meteredModel = withBilling;
1536
1553
 
1554
+ // src/ai-sdk/to-response.ts
1555
+ function toResponse(result, options) {
1556
+ let baseResponse;
1557
+ if (typeof result?.toUIMessageStreamResponse === "function") {
1558
+ baseResponse = result.toUIMessageStreamResponse(options?.init);
1559
+ } else if (typeof result?.toDataStreamResponse === "function") {
1560
+ baseResponse = result.toDataStreamResponse(options?.init);
1561
+ } else if (typeof result?.toTextStreamResponse === "function") {
1562
+ baseResponse = result.toTextStreamResponse(options?.init);
1563
+ } else if (result instanceof Response) {
1564
+ baseResponse = result;
1565
+ } else if (result && typeof result === "object" && result.body && result.headers) {
1566
+ baseResponse = result;
1567
+ } else {
1568
+ baseResponse = new Response(result, options?.init);
1569
+ }
1570
+ if (!baseResponse.body) {
1571
+ return baseResponse;
1572
+ }
1573
+ const contentType = baseResponse.headers.get("content-type") || "";
1574
+ const isSSE = contentType.includes("text/event-stream") || baseResponse.headers.has("x-vercel-ai-ui-message-stream");
1575
+ const isDataStream = baseResponse.headers.has("x-vercel-ai-data-stream");
1576
+ const encoder = new TextEncoder();
1577
+ const decoder = new TextDecoder();
1578
+ let injected = false;
1579
+ const resolveTelemetry = async () => {
1580
+ if (options?.telemetry) return options.telemetry;
1581
+ if (result?.providerMetadata) {
1582
+ try {
1583
+ const meta = await Promise.race([
1584
+ result.providerMetadata,
1585
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 800))
1586
+ ]);
1587
+ if (meta?.vibezcheck) {
1588
+ return meta.vibezcheck;
1589
+ }
1590
+ } catch {
1591
+ }
1592
+ }
1593
+ if (result?.usage) {
1594
+ try {
1595
+ const u = await Promise.race([
1596
+ result.usage,
1597
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 800))
1598
+ ]);
1599
+ if (u) {
1600
+ const inputTokens = u.promptTokens ?? u.inputTokens ?? 0;
1601
+ const outputTokens = u.completionTokens ?? u.outputTokens ?? 0;
1602
+ const reasoningTokens = u.reasoningTokens ?? u.completionTokensDetails?.reasoningTokens;
1603
+ return {
1604
+ type: "vibezcheck",
1605
+ usage: {
1606
+ inputTokens,
1607
+ outputTokens,
1608
+ totalTokens: inputTokens + outputTokens,
1609
+ reasoningTokens
1610
+ }
1611
+ };
1612
+ }
1613
+ } catch {
1614
+ }
1615
+ }
1616
+ return null;
1617
+ };
1618
+ const transformStream = new TransformStream({
1619
+ async transform(chunk, controller) {
1620
+ const text = decoder.decode(chunk, { stream: true });
1621
+ if (isSSE && text.includes("[DONE]") && !injected) {
1622
+ injected = true;
1623
+ const telemetry = await resolveTelemetry();
1624
+ if (telemetry) {
1625
+ const ssePayload = `data: ${JSON.stringify({ type: "data-vibezcheck", data: telemetry })}
1626
+
1627
+ data: ${JSON.stringify({ type: "message-metadata", messageMetadata: { vibezcheck: telemetry } })}
1628
+
1629
+ `;
1630
+ const doneIndex = text.indexOf("data: [DONE]");
1631
+ if (doneIndex !== -1) {
1632
+ const before = text.slice(0, doneIndex);
1633
+ const after = text.slice(doneIndex);
1634
+ if (before) controller.enqueue(encoder.encode(before));
1635
+ controller.enqueue(encoder.encode(ssePayload));
1636
+ controller.enqueue(encoder.encode(after));
1637
+ return;
1638
+ } else {
1639
+ controller.enqueue(encoder.encode(ssePayload));
1640
+ }
1641
+ }
1642
+ } else if (isDataStream && (text.includes("d:{") || text.includes("d: {")) && !injected) {
1643
+ injected = true;
1644
+ const telemetry = await resolveTelemetry();
1645
+ if (telemetry) {
1646
+ const annotationLine = `2:[${JSON.stringify(telemetry)}]
1647
+ `;
1648
+ controller.enqueue(encoder.encode(annotationLine));
1649
+ }
1650
+ }
1651
+ controller.enqueue(chunk);
1652
+ },
1653
+ async flush(controller) {
1654
+ if (!injected) {
1655
+ injected = true;
1656
+ const telemetry = await resolveTelemetry();
1657
+ if (telemetry) {
1658
+ if (isSSE) {
1659
+ const ssePayload = `data: ${JSON.stringify({ type: "data-vibezcheck", data: telemetry })}
1660
+
1661
+ data: ${JSON.stringify({ type: "message-metadata", messageMetadata: { vibezcheck: telemetry } })}
1662
+
1663
+ `;
1664
+ controller.enqueue(encoder.encode(ssePayload));
1665
+ } else {
1666
+ const annotationLine = `2:[${JSON.stringify(telemetry)}]
1667
+ `;
1668
+ controller.enqueue(encoder.encode(annotationLine));
1669
+ }
1670
+ }
1671
+ }
1672
+ }
1673
+ });
1674
+ const mergedHeaders = new Headers(baseResponse.headers);
1675
+ if (options?.headers) {
1676
+ const extraHeaders = new Headers(options.headers);
1677
+ extraHeaders.forEach((value, key) => {
1678
+ mergedHeaders.set(key, value);
1679
+ });
1680
+ }
1681
+ const status = options?.init?.status ?? baseResponse.status;
1682
+ const statusText = options?.init?.statusText ?? baseResponse.statusText;
1683
+ return new Response(baseResponse.body.pipeThrough(transformStream), {
1684
+ status,
1685
+ statusText,
1686
+ headers: mergedHeaders
1687
+ });
1688
+ }
1689
+
1537
1690
  // src/ai-sdk/declarative.ts
1538
1691
  function createVibezModel(modelOrId, options = {}) {
1539
1692
  if (typeof modelOrId === "object" && modelOrId !== null) {
@@ -1708,13 +1861,15 @@ function createVibezSession(sessionOptions = {}) {
1708
1861
  };
1709
1862
  }
1710
1863
  var vibezcheck = Object.assign(createVibezModel, {
1711
- session: createVibezSession
1864
+ session: createVibezSession,
1865
+ toResponse
1712
1866
  });
1713
1867
  // Annotate the CommonJS export names for ESM import in node:
1714
1868
  0 && (module.exports = {
1715
1869
  createVibezModel,
1716
1870
  createVibezSession,
1717
1871
  meteredModel,
1872
+ toResponse,
1718
1873
  vibezcheck,
1719
1874
  withBilling
1720
1875
  });